From 5f1d146f44a190e0e5a558c8853aaaf2db0fde81 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Sep 2020 16:26:57 +0200 Subject: [PATCH 001/566] #111 Convert to mono repo and add dev example (#83) * #111 Convert to mono repo and add dev example -Convert to lerna repository - Extract protocol into separate package - Add workflow standalone example as dev example Part of eclipse-glsp/glsp/issues/111 * Address review feedback --- examples/workflow-sprotty/LICENSE | 277 ++++++ examples/workflow-sprotty/README.md | 3 + examples/workflow-sprotty/css/diagram.css | 198 ++++ examples/workflow-sprotty/package.json | 54 ++ examples/workflow-sprotty/src/di.config.ts | 131 +++ .../src/direct-task-editing/di.config.ts | 24 + .../direct-task-editing/direct-task-editor.ts | 156 ++++ examples/workflow-sprotty/src/index.ts | 19 + examples/workflow-sprotty/src/model-schema.ts | 43 + examples/workflow-sprotty/src/model.ts | 94 ++ .../workflow-sprotty/src/workflow-views.tsx | 114 +++ examples/workflow-sprotty/tsconfig.json | 13 + examples/workflow-standalone/app/diagram.html | 38 + examples/workflow-standalone/app/example1.wf | 884 ++++++++++++++++++ examples/workflow-standalone/css/diagram.css | 45 + examples/workflow-standalone/package.json | 67 ++ examples/workflow-standalone/src/di.config.ts | 29 + examples/workflow-standalone/src/main.ts | 61 ++ .../workflow-standalone/src/url-parameters.ts | 35 + examples/workflow-standalone/tsconfig.json | 15 + .../workflow-standalone/webpack.config.js | 72 ++ packages/client/.nycrc | 16 + packages/client/README.md | 12 + packages/client/css/command-palette.css | 58 ++ packages/client/css/decoration.css | 37 + packages/client/css/glsp-sprotty.css | 98 ++ packages/client/css/tool-palette.css | 138 +++ packages/client/package.json | 78 ++ packages/client/src/base/action-dispatcher.ts | 103 ++ .../src/base/actions/context-actions.ts | 41 + .../src/base/actions/edit-mode-action.ts | 35 + .../base/actions/edit-validation-actions.ts | 79 ++ .../src/base/actions/protocol-actions.ts | 51 + packages/client/src/base/args.ts | 16 + .../auto-complete/auto-complete-actions.ts | 24 + .../auto-complete/auto-complete-widget.ts | 304 ++++++ .../auto-complete/validation-decorator.ts | 122 +++ packages/client/src/base/command-stack.ts | 36 + packages/client/src/base/di.config.ts | 64 ++ .../src/base/drag-aware-mouse-listener.ts | 69 ++ packages/client/src/base/editor-context.ts | 103 ++ .../base/model-initialization-constraint.ts | 90 ++ .../src/base/model/update-model-command.ts | 112 +++ .../client/src/base/operations/operation.ts | 128 +++ .../base/selection-clearing-mouse-listener.ts | 42 + packages/client/src/base/source-uri-aware.ts | 22 + .../base/tool-manager/glsp-tool-manager.ts | 93 ++ packages/client/src/base/types.ts | 37 + .../src/features/change-bounds/model.ts | 76 ++ .../change-bounds/movement-restrictor.ts | 87 ++ .../client/src/features/change-bounds/snap.ts | 35 + .../command-palette/command-palette-tool.ts | 44 + .../src/features/command-palette/di.config.ts | 34 + .../server-command-palette-provider.ts | 48 + .../delete-element-context-menu.ts | 41 + .../src/features/context-menu/di.config.ts | 39 + ...rvice-aware-context-menu-mouse-listener.ts | 54 ++ .../server-context-menu-provider.ts | 46 + .../features/copy-paste/copy-paste-actions.ts | 55 ++ .../copy-paste/copy-paste-context-menu.ts | 95 ++ .../features/copy-paste/copy-paste-handler.ts | 142 +++ .../src/features/copy-paste/di.config.ts | 45 + .../features/decoration/decoration-placer.ts | 45 + .../src/features/decoration/di.config.ts | 30 + .../client/src/features/decoration/view.tsx | 54 ++ .../src/features/edit-label/di.config.ts | 30 + .../features/edit-label/edit-label-tool.ts | 51 + .../edit-label/edit-label-validator.ts | 103 ++ .../client/src/features/execute/di.config.ts | 25 + .../src/features/execute/execute-command.ts | 36 + packages/client/src/features/execute/model.ts | 26 + .../client/src/features/hints/di.config.ts | 30 + packages/client/src/features/hints/model.ts | 32 + .../hints/request-type-hints-action.ts | 35 + .../client/src/features/hints/type-hints.ts | 256 +++++ .../client/src/features/hover/di.config.ts | 60 ++ packages/client/src/features/hover/hover.ts | 86 ++ .../client/src/features/layout/di.config.ts | 26 + .../features/layout/layout-commands.spec.ts | 365 ++++++++ .../src/features/layout/layout-commands.ts | 415 ++++++++ .../src/features/mouse-tool/di.config.ts | 28 + .../src/features/mouse-tool/mouse-tool.ts | 135 +++ .../src/features/navigation/di.config.ts | 33 + .../external-navigate-to-target-handler.ts | 23 + .../navigation/navigation-action-handler.ts | 247 +++++ .../navigation-target-resolver.spec.ts | 60 ++ .../navigation/navigation-target-resolver.ts | 143 +++ packages/client/src/features/rank/model.ts | 29 + .../client/src/features/reconnect/model.ts | 76 ++ .../client/src/features/save/di.config.ts | 30 + packages/client/src/features/save/model.ts | 26 + packages/client/src/features/save/save.ts | 32 + .../client/src/features/select/di.config.ts | 35 + .../features/select/select-feedback-action.ts | 89 ++ .../features/select/select-mouse-listener.ts | 26 + .../features/select/selection-service.spec.ts | 371 ++++++++ .../src/features/select/selection-service.ts | 240 +++++ .../change-bounds-tool-feedback.ts | 243 +++++ .../tool-feedback/creation-tool-feedback.ts | 170 ++++ .../features/tool-feedback/css-feedback.ts | 92 ++ .../src/features/tool-feedback/di.config.ts | 65 ++ .../tool-feedback/edge-edit-tool-feedback.ts | 306 ++++++ .../feedback-action-dispatcher.ts | 103 ++ .../src/features/tool-feedback/model.ts | 31 + .../src/features/tool-feedback/view.tsx | 70 ++ .../src/features/tool-palette/di.config.ts | 30 + .../src/features/tool-palette/palette-item.ts | 40 + .../src/features/tool-palette/tool-palette.ts | 348 +++++++ .../src/features/tools/base-glsp-tool.ts | 53 ++ .../src/features/tools/change-bounds-tool.ts | 442 +++++++++ .../client/src/features/tools/delete-tool.ts | 111 +++ .../client/src/features/tools/di.config.ts | 44 + .../src/features/tools/edge-creation-tool.ts | 168 ++++ .../src/features/tools/edge-edit-tool.ts | 315 +++++++ .../src/features/tools/node-creation-tool.ts | 117 +++ .../client/src/features/undo-redo/model.ts | 26 + .../src/features/validation/di.config.ts | 51 + .../src/features/validation/issue-marker.ts | 93 ++ .../validation/marker-navigator.spec.ts | 206 ++++ .../features/validation/marker-navigator.ts | 245 +++++ .../src/features/validation/validate.ts | 281 ++++++ packages/client/src/index.ts | 107 +++ packages/client/src/lib/model.ts | 27 + .../src/model-source/glsp-diagram-server.ts | 173 ++++ .../src/model-source/glsp-server-status.ts | 38 + packages/client/src/utils/array-utils.ts | 34 + packages/client/src/utils/html-utils.ts | 25 + packages/client/src/utils/layout-utils.ts | 95 ++ packages/client/src/utils/marker.ts | 51 + packages/client/src/utils/smodel-util.ts | 147 +++ packages/client/src/utils/viewpoint-util.ts | 100 ++ packages/client/tsconfig.json | 12 + packages/protocol/README.md | 12 + packages/protocol/package.json | 68 ++ packages/protocol/src/glsp-client.ts | 93 ++ packages/protocol/src/index.ts | 18 + .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 145 +++ .../src/jsonrpc/glsp-jsonrpc-client.ts | 59 ++ packages/protocol/tsconfig.json | 12 + 139 files changed, 13415 insertions(+) create mode 100644 examples/workflow-sprotty/LICENSE create mode 100644 examples/workflow-sprotty/README.md create mode 100644 examples/workflow-sprotty/css/diagram.css create mode 100644 examples/workflow-sprotty/package.json create mode 100644 examples/workflow-sprotty/src/di.config.ts create mode 100644 examples/workflow-sprotty/src/direct-task-editing/di.config.ts create mode 100644 examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts create mode 100644 examples/workflow-sprotty/src/index.ts create mode 100644 examples/workflow-sprotty/src/model-schema.ts create mode 100644 examples/workflow-sprotty/src/model.ts create mode 100644 examples/workflow-sprotty/src/workflow-views.tsx create mode 100644 examples/workflow-sprotty/tsconfig.json create mode 100644 examples/workflow-standalone/app/diagram.html create mode 100644 examples/workflow-standalone/app/example1.wf create mode 100644 examples/workflow-standalone/css/diagram.css create mode 100644 examples/workflow-standalone/package.json create mode 100644 examples/workflow-standalone/src/di.config.ts create mode 100644 examples/workflow-standalone/src/main.ts create mode 100644 examples/workflow-standalone/src/url-parameters.ts create mode 100644 examples/workflow-standalone/tsconfig.json create mode 100644 examples/workflow-standalone/webpack.config.js create mode 100644 packages/client/.nycrc create mode 100644 packages/client/README.md create mode 100644 packages/client/css/command-palette.css create mode 100644 packages/client/css/decoration.css create mode 100644 packages/client/css/glsp-sprotty.css create mode 100644 packages/client/css/tool-palette.css create mode 100644 packages/client/package.json create mode 100644 packages/client/src/base/action-dispatcher.ts create mode 100644 packages/client/src/base/actions/context-actions.ts create mode 100644 packages/client/src/base/actions/edit-mode-action.ts create mode 100644 packages/client/src/base/actions/edit-validation-actions.ts create mode 100644 packages/client/src/base/actions/protocol-actions.ts create mode 100644 packages/client/src/base/args.ts create mode 100644 packages/client/src/base/auto-complete/auto-complete-actions.ts create mode 100644 packages/client/src/base/auto-complete/auto-complete-widget.ts create mode 100644 packages/client/src/base/auto-complete/validation-decorator.ts create mode 100644 packages/client/src/base/command-stack.ts create mode 100644 packages/client/src/base/di.config.ts create mode 100644 packages/client/src/base/drag-aware-mouse-listener.ts create mode 100644 packages/client/src/base/editor-context.ts create mode 100644 packages/client/src/base/model-initialization-constraint.ts create mode 100644 packages/client/src/base/model/update-model-command.ts create mode 100644 packages/client/src/base/operations/operation.ts create mode 100644 packages/client/src/base/selection-clearing-mouse-listener.ts create mode 100644 packages/client/src/base/source-uri-aware.ts create mode 100644 packages/client/src/base/tool-manager/glsp-tool-manager.ts create mode 100644 packages/client/src/base/types.ts create mode 100644 packages/client/src/features/change-bounds/model.ts create mode 100644 packages/client/src/features/change-bounds/movement-restrictor.ts create mode 100644 packages/client/src/features/change-bounds/snap.ts create mode 100644 packages/client/src/features/command-palette/command-palette-tool.ts create mode 100644 packages/client/src/features/command-palette/di.config.ts create mode 100644 packages/client/src/features/command-palette/server-command-palette-provider.ts create mode 100644 packages/client/src/features/context-menu/delete-element-context-menu.ts create mode 100644 packages/client/src/features/context-menu/di.config.ts create mode 100644 packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts create mode 100644 packages/client/src/features/context-menu/server-context-menu-provider.ts create mode 100644 packages/client/src/features/copy-paste/copy-paste-actions.ts create mode 100644 packages/client/src/features/copy-paste/copy-paste-context-menu.ts create mode 100644 packages/client/src/features/copy-paste/copy-paste-handler.ts create mode 100644 packages/client/src/features/copy-paste/di.config.ts create mode 100644 packages/client/src/features/decoration/decoration-placer.ts create mode 100644 packages/client/src/features/decoration/di.config.ts create mode 100644 packages/client/src/features/decoration/view.tsx create mode 100644 packages/client/src/features/edit-label/di.config.ts create mode 100644 packages/client/src/features/edit-label/edit-label-tool.ts create mode 100644 packages/client/src/features/edit-label/edit-label-validator.ts create mode 100644 packages/client/src/features/execute/di.config.ts create mode 100644 packages/client/src/features/execute/execute-command.ts create mode 100644 packages/client/src/features/execute/model.ts create mode 100644 packages/client/src/features/hints/di.config.ts create mode 100644 packages/client/src/features/hints/model.ts create mode 100644 packages/client/src/features/hints/request-type-hints-action.ts create mode 100644 packages/client/src/features/hints/type-hints.ts create mode 100644 packages/client/src/features/hover/di.config.ts create mode 100644 packages/client/src/features/hover/hover.ts create mode 100644 packages/client/src/features/layout/di.config.ts create mode 100644 packages/client/src/features/layout/layout-commands.spec.ts create mode 100644 packages/client/src/features/layout/layout-commands.ts create mode 100644 packages/client/src/features/mouse-tool/di.config.ts create mode 100644 packages/client/src/features/mouse-tool/mouse-tool.ts create mode 100644 packages/client/src/features/navigation/di.config.ts create mode 100644 packages/client/src/features/navigation/external-navigate-to-target-handler.ts create mode 100644 packages/client/src/features/navigation/navigation-action-handler.ts create mode 100644 packages/client/src/features/navigation/navigation-target-resolver.spec.ts create mode 100644 packages/client/src/features/navigation/navigation-target-resolver.ts create mode 100644 packages/client/src/features/rank/model.ts create mode 100644 packages/client/src/features/reconnect/model.ts create mode 100644 packages/client/src/features/save/di.config.ts create mode 100644 packages/client/src/features/save/model.ts create mode 100644 packages/client/src/features/save/save.ts create mode 100644 packages/client/src/features/select/di.config.ts create mode 100644 packages/client/src/features/select/select-feedback-action.ts create mode 100644 packages/client/src/features/select/select-mouse-listener.ts create mode 100644 packages/client/src/features/select/selection-service.spec.ts create mode 100644 packages/client/src/features/select/selection-service.ts create mode 100644 packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts create mode 100644 packages/client/src/features/tool-feedback/creation-tool-feedback.ts create mode 100644 packages/client/src/features/tool-feedback/css-feedback.ts create mode 100644 packages/client/src/features/tool-feedback/di.config.ts create mode 100644 packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts create mode 100644 packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts create mode 100644 packages/client/src/features/tool-feedback/model.ts create mode 100644 packages/client/src/features/tool-feedback/view.tsx create mode 100644 packages/client/src/features/tool-palette/di.config.ts create mode 100644 packages/client/src/features/tool-palette/palette-item.ts create mode 100644 packages/client/src/features/tool-palette/tool-palette.ts create mode 100644 packages/client/src/features/tools/base-glsp-tool.ts create mode 100644 packages/client/src/features/tools/change-bounds-tool.ts create mode 100644 packages/client/src/features/tools/delete-tool.ts create mode 100644 packages/client/src/features/tools/di.config.ts create mode 100644 packages/client/src/features/tools/edge-creation-tool.ts create mode 100644 packages/client/src/features/tools/edge-edit-tool.ts create mode 100644 packages/client/src/features/tools/node-creation-tool.ts create mode 100644 packages/client/src/features/undo-redo/model.ts create mode 100644 packages/client/src/features/validation/di.config.ts create mode 100644 packages/client/src/features/validation/issue-marker.ts create mode 100644 packages/client/src/features/validation/marker-navigator.spec.ts create mode 100644 packages/client/src/features/validation/marker-navigator.ts create mode 100644 packages/client/src/features/validation/validate.ts create mode 100644 packages/client/src/index.ts create mode 100644 packages/client/src/lib/model.ts create mode 100644 packages/client/src/model-source/glsp-diagram-server.ts create mode 100644 packages/client/src/model-source/glsp-server-status.ts create mode 100644 packages/client/src/utils/array-utils.ts create mode 100644 packages/client/src/utils/html-utils.ts create mode 100644 packages/client/src/utils/layout-utils.ts create mode 100644 packages/client/src/utils/marker.ts create mode 100644 packages/client/src/utils/smodel-util.ts create mode 100644 packages/client/src/utils/viewpoint-util.ts create mode 100644 packages/client/tsconfig.json create mode 100644 packages/protocol/README.md create mode 100644 packages/protocol/package.json create mode 100644 packages/protocol/src/glsp-client.ts create mode 100644 packages/protocol/src/index.ts create mode 100644 packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts create mode 100644 packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts create mode 100644 packages/protocol/tsconfig.json diff --git a/examples/workflow-sprotty/LICENSE b/examples/workflow-sprotty/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/examples/workflow-sprotty/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/examples/workflow-sprotty/README.md b/examples/workflow-sprotty/README.md new file mode 100644 index 0000000..5cd92ef --- /dev/null +++ b/examples/workflow-sprotty/README.md @@ -0,0 +1,3 @@ +# workflow-sprotty + +This package contains the configuration for the sprotty-glsp diagrams of the workflow example language. diff --git a/examples/workflow-sprotty/css/diagram.css b/examples/workflow-sprotty/css/diagram.css new file mode 100644 index 0000000..9b3a88e --- /dev/null +++ b/examples/workflow-sprotty/css/diagram.css @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.sprotty-graph { + font-size: 15pt; + border: none; + height: 100%; +} + +.sprotty-popup { + z-index: 1; +} + +.sprotty-text { + font-size: 12pt; + text-anchor: middle +} + +.sprotty-node { + stroke-width: 0; + font-weight: bold; +} + +.sprotty-node.mouseover:not(.selected) { + stroke-width: 3; +} + +.sprotty-comp { + line-height: 3px; + font-weight: normal; + text-align: left; +} + +.sprotty-label { + stroke-width: 0; + fill: #000; + font-weight: inherit; + text-align: inherit; + font-size: 100%; +} + +.sprotty-label.heading { + font-weight: bold; +} + +.sprotty-icon { + fill: black; + stroke-width: 0; +} + +.sprotty-label.icon { + fill: #fff; +} + +.sprotty-button { + fill: #152; + stroke-width: 0; +} + +.sprotty-edge { + fill: none; + stroke-width: 2px; +} + +.sprotty-edge.selected { + stroke-width: 4px; +} + +.sprotty-edge.mouseover:not(.selected) { + stroke-width: 3px; +} + +.sprotty-edge>.sprotty-routing-handle { + r: 7px; + fill: #884; + stroke: none; + z-index: 1000; +} + +.sprotty-edge>.sprotty-routing-handle[data-kind='line'] { + opacity: 0.35; +} + +.sprotty-edge>.sprotty-routing-handle.selected { + fill: #66a; +} + +.sprotty-edge>.sprotty-routing-handle.mouseover { + stroke: #112; + stroke-width: 1; +} + +.sprotty-missing { + stroke-width: 1; + stroke: #f00; + fill: #f00; + font-size: 14pt; + text-anchor: middle; +} + +.sprotty-node:not(.selected) { + stroke-width: 1px; +} + +.sprotty-node.task.automated { + fill: gray; +} + +.sprotty-node.task.manual { + fill: lightblue; +} + +text { + stroke-width: 0; + stroke: #000; + fill: #000; + font-family: sans-serif; + font-size: 8pt; + text-anchor: middle; +} + +.sprotty-edge.weighted.low, .sprotty-edge.weighted.low .arrow { + stroke: lightblue +} + +.sprotty-edge.weighted.low .arrow { + fill: lightblue; +} + +.sprotty-edge.weighted, .sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium, .sprotty-edge.weighted.medium .arrow { + stroke: rgb(53, 53, 226); +} + +.sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium .arrow { + fill: rgb(53, 53, 226); +} + +.sprotty-edge.weighted.high, .sprotty-edge.weighted.high .arrow { + stroke: darkblue +} + +.sprotty-edge.weighted.high .arrow { + fill: darkblue; +} + +polygon.sprotty-node { + fill: white; + stroke-width: 1pt; +} + +.sprotty-node.selected { + stroke-width: 4; +} + +svg { + border-style: solid; + border-width: 1px; + border-color: #bbb; +} + +.label-edit input { + margin-top: -1px; + margin-left: -5px; +} + +g .movement-not-allowed>.sprotty-node { + stroke: var(--glsp-error-foreground)!important; +} + +.sprotty-resize-handle.movement-not-allowed { + stroke: var(--glsp-error-foreground)!important; + fill: var(--glsp-error-foreground)!important; +} + +.error>.sprotty-node { + stroke: var(--glsp-error-foreground)!important; +} + +.warning>.sprotty-node { + stroke: var(--glsp-warning-foreground)!important; +} + +.info>.sprotty-node { + stroke: var(--glsp-info-foreground)!important; +} diff --git a/examples/workflow-sprotty/package.json b/examples/workflow-sprotty/package.json new file mode 100644 index 0000000..e41f702 --- /dev/null +++ b/examples/workflow-sprotty/package.json @@ -0,0 +1,54 @@ +{ + "name": "@eclipse-glsp-examples/workflow-sprotty", + "version": "0.7.0", + "description": "GLSP sprotty diagrams for the Workflow DSL", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "author": "Eclipse GLSP", + "contributors": [ + { + "name": "Philip Langer", + "email": "planger@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Tobias Ortmayr", + "email": "tortmayr@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Martin Fleck", + "email": "mfleck@eclipsesource.com", + "url": "https://www.eclipsesource.com" + } + ], + "keywords": [ + "sprotty", + "workflow", + "diagram", + "example" + ], + "dependencies": { + "@eclipse-glsp/client": "0.7.1", + "balloon-css": "^0.5.0" + }, + "devDependencies": { + "rimraf": "^2.6.1", + "tslint": "^5.5.0", + "typescript": "^3.9.2" + }, + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "rimraf lib", + "build": "tsc && yarn run lint", + "lint": "tslint -c ../../configs/tslint.json --project ./tsconfig.json", + "watch": "tsc -w" + }, + "files": [ + "lib", + "src", + "build", + "css" + ], + "main": "lib/index", + "types": "lib/index" +} diff --git a/examples/workflow-sprotty/src/di.config.ts b/examples/workflow-sprotty/src/di.config.ts new file mode 100644 index 0000000..59b7027 --- /dev/null +++ b/examples/workflow-sprotty/src/di.config.ts @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../css/diagram.css"; +import "balloon-css/balloon.min.css"; +import "sprotty/css/edit-label.css"; + +import { + boundsModule, + buttonModule, + configureModelElement, + ConsoleLogger, + defaultGLSPModule, + defaultModule, + DeleteElementContextMenuItemProvider, + DiamondNodeView, + edgeLayoutModule, + editLabelFeature, + executeCommandModule, + ExpandButtonView, + expandModule, + exportModule, + fadeModule, + GLSP_TYPES, + glspCommandPaletteModule, + glspContextMenuModule, + glspDecorationModule, + glspEditLabelModule, + GLSPGraph, + glspHoverModule, + glspMouseToolModule, + glspSelectModule, + glspServerCopyPasteModule, + GridSnapper, + HtmlRoot, + HtmlRootView, + labelEditUiModule, + layoutCommandsModule, + LogLevel, + markerNavigatorModule, + modelHintsModule, + modelSourceModule, + navigationModule, + NoOverlapMovmentRestrictor, + openModule, + overrideViewerOptions, + paletteModule, + PreRenderedElement, + PreRenderedView, + RevealNamedElementActionProvider, + routingModule, + SButton, + SCompartment, + SCompartmentView, + SEdge, + SGraphView, + SLabel, + SLabelView, + SRoutingHandle, + SRoutingHandleView, + toolFeedbackModule, + toolsModule, + TYPES, + validationModule, + viewportModule, + zorderModule +} from "@eclipse-glsp/client"; +import { Container, ContainerModule } from "inversify"; + +import { directTaskEditor } from "./direct-task-editing/di.config"; +import { ActivityNode, Icon, TaskNode, WeightedEdge } from "./model"; +import { ForkOrJoinNodeView, IconView, TaskNodeView, WeightedEdgeView, WorkflowEdgeView } from "./workflow-views"; + +const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { + rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); + bind(GLSP_TYPES.IMovementRestrictor).to(NoOverlapMovmentRestrictor).inSingletonScope(); + bind(TYPES.ISnapper).to(GridSnapper); + bind(TYPES.ICommandPaletteActionProvider).to(RevealNamedElementActionProvider); + bind(TYPES.IContextMenuItemProvider).to(DeleteElementContextMenuItemProvider); + const context = { bind, unbind, isBound, rebind }; + configureModelElement(context, 'graph', GLSPGraph, SGraphView); + configureModelElement(context, 'task:automated', TaskNode, TaskNodeView); + configureModelElement(context, 'task:manual', TaskNode, TaskNodeView); + configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); + configureModelElement(context, 'comp:comp', SCompartment, SCompartmentView); + configureModelElement(context, 'comp:header', SCompartment, SCompartmentView); + configureModelElement(context, 'label:icon', SLabel, SLabelView); + configureModelElement(context, 'html', HtmlRoot, HtmlRootView); + configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); + configureModelElement(context, 'button:expand', SButton, ExpandButtonView); + configureModelElement(context, 'routing-point', SRoutingHandle, SRoutingHandleView); + configureModelElement(context, 'volatile-routing-point', SRoutingHandle, SRoutingHandleView); + configureModelElement(context, 'edge', SEdge, WorkflowEdgeView); + configureModelElement(context, 'edge:weighted', WeightedEdge, WeightedEdgeView); + configureModelElement(context, 'icon', Icon, IconView); + configureModelElement(context, 'activityNode:merge', ActivityNode, DiamondNodeView); + configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); + configureModelElement(context, 'activityNode:fork', ActivityNode, ForkOrJoinNodeView); + configureModelElement(context, 'activityNode:join', ActivityNode, ForkOrJoinNodeView); +}); + +export default function createContainer(widgetId: string): Container { + const container = new Container(); + + container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, toolsModule, + glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, + workflowDiagramModule, executeCommandModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, + glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, + layoutCommandsModule, directTaskEditor, navigationModule, markerNavigatorModule); + + overrideViewerOptions(container, { + baseDiv: widgetId, + hiddenDiv: widgetId + "_hidden", + needsClientLayout: true + }); + + return container; +} diff --git a/examples/workflow-sprotty/src/direct-task-editing/di.config.ts b/examples/workflow-sprotty/src/direct-task-editing/di.config.ts new file mode 100644 index 0000000..cb2e5df --- /dev/null +++ b/examples/workflow-sprotty/src/direct-task-editing/di.config.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { TYPES } from "sprotty"; + +import { TaskEditor } from "./direct-task-editor"; + +export const directTaskEditor = new ContainerModule((bind, _unbind, isBound) => { + bind(TaskEditor).toSelf().inSingletonScope(); + bind(TYPES.IUIExtension).toService(TaskEditor); +}); diff --git a/examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts new file mode 100644 index 0000000..c48a263 --- /dev/null +++ b/examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts @@ -0,0 +1,156 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + AbstractUIExtension, + Action, + AutoCompleteWidget, + EditorContextService, + getAbsoluteClientBounds, + GLSPActionDispatcher, + ILogger, + isSetContextActionsAction, + isSetEditValidationResultAction, + LabeledAction, + RequestContextActions, + RequestEditValidationAction, + SModelElement, + SModelRoot, + toActionArray, + ValidationDecorator, + ValidationStatus, + ViewerOptions +} from "@eclipse-glsp/client"; +import { inject, injectable } from "inversify"; +import { TYPES } from "sprotty/lib"; +import { DOMHelper } from "sprotty/lib/base/views/dom-helper"; + +import { isTaskNode, TaskNode } from "../model"; + +export class ApplyTaskEditOperation implements Action { + static readonly KIND = 'applyTaskEdit'; + readonly kind = ApplyTaskEditOperation.KIND; + constructor(readonly taskId: string, readonly expression: string) { } +} + +@injectable() +export class TaskEditor extends AbstractUIExtension { + + static readonly ID = 'task-editor'; + readonly autoSuggestionSettings = { + noSuggestionsMessage: 'No suggestions available', + suggestionsClass: 'command-palette-suggestions', + debounceWaitMs: 50, + showOnFocus: true + }; + + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + @inject(EditorContextService) + protected editorContextService: EditorContextService; + + @inject(TYPES.ViewerOptions) + protected viewerOptions: ViewerOptions; + + @inject(TYPES.DOMHelper) + protected domHelper: DOMHelper; + + @inject(TYPES.ILogger) + protected logger: ILogger; + + protected task: TaskNode; + protected autoSuggestion: AutoCompleteWidget; + + id() { return TaskEditor.ID; } + containerClass() { return 'command-palette'; } + + protected initializeContents(containerElement: HTMLElement): void { + this.autoSuggestion = new AutoCompleteWidget( + this.autoSuggestionSettings, + { provideSuggestions: input => this.retrieveSuggestions(input) }, + { executeFromSuggestion: input => this.executeFromSuggestion(input) }, + () => this.hide(), this.logger); + this.autoSuggestion.configureValidation( + { validate: input => this.validateInput(input) }, + new ValidationDecorator(containerElement) + ); + this.autoSuggestion.configureTextSubmitHandler( + { executeFromTextOnlyInput: (input: string) => this.executeFromTextOnlyInput(input) } + ); + this.autoSuggestion.initialize(containerElement); + } + + show(root: Readonly, ...contextElementIds: string[]) { + super.show(root, ...contextElementIds); + this.autoSuggestion.open(root); + } + + protected onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]) { + this.task = getTask(contextElementIds, root)[0]; + this.autoSuggestion.inputField.value = ''; + this.setPosition(containerElement); + } + + protected setPosition(containerElement: HTMLElement) { + let x = 0; + let y = 0; + + if (this.task) { + const bounds = getAbsoluteClientBounds(this.task, this.domHelper, this.viewerOptions); + x = bounds.x + 5; + y = bounds.y + 5; + } + + containerElement.style.left = `${x}px`; + containerElement.style.top = `${y}px`; + containerElement.style.width = '200px'; + } + + protected async retrieveSuggestions(input: string): Promise { + const response = await this.actionDispatcher.request(new RequestContextActions(TaskEditor.ID, + this.editorContextService.get({ ['text']: input }))); + if (isSetContextActionsAction(response)) { + return response.actions; + } + return Promise.reject(); + } + + protected async validateInput(input: string): Promise { + const response = await this.actionDispatcher.request(new RequestEditValidationAction(TaskEditor.ID, this.task.id, input)); + if (isSetEditValidationResultAction(response)) { + return response.status; + } + return Promise.reject(); + } + + protected executeFromSuggestion(input: LabeledAction | Action[] | Action) { + this.actionDispatcher.dispatchAll(toActionArray(input)); + } + + protected executeFromTextOnlyInput(input: string) { + const action = new ApplyTaskEditOperation(this.task.id, input); + this.actionDispatcher.dispatch(action); + } + + hide() { + this.autoSuggestion.dispose(); + super.hide(); + } +} + +function getTask(ids: string[], element: SModelElement): TaskNode[] { + return ids.map(id => element.index.getById(id)).filter(isTaskNode); +} diff --git a/examples/workflow-sprotty/src/index.ts b/examples/workflow-sprotty/src/index.ts new file mode 100644 index 0000000..a94b0e3 --- /dev/null +++ b/examples/workflow-sprotty/src/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import createWorkflowDiagramContainer from "./di.config"; + +export { createWorkflowDiagramContainer }; + diff --git a/examples/workflow-sprotty/src/model-schema.ts b/examples/workflow-sprotty/src/model-schema.ts new file mode 100644 index 0000000..531ac59 --- /dev/null +++ b/examples/workflow-sprotty/src/model-schema.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SEdgeSchema, SNodeSchema } from "@eclipse-glsp/client"; + +export namespace ActivityNodeSchema { + export namespace Type { + export const INITIAL = 'initalNode'; + export const FINAL = 'finalNode'; + export const DECISION = 'decisionNode'; + export const MERGE = 'mergeNode'; + export const JOIN = 'joinNode'; + export const FORK = 'forkNode'; + export const UNDEFINED = "undefined"; + } +} + +export interface TaskNodeSchema extends SNodeSchema { + name?: string + duration?: number + taskType?: string + reference?: string +} + +export interface WeightedEdgeSchema extends SEdgeSchema { + probability?: string +} + +export interface ActivityNodeSchema extends SNodeSchema { + nodeType: string +} diff --git a/examples/workflow-sprotty/src/model.ts b/examples/workflow-sprotty/src/model.ts new file mode 100644 index 0000000..edd8c9e --- /dev/null +++ b/examples/workflow-sprotty/src/model.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Bounds, + boundsFeature, + CommandExecutor, + connectableFeature, + deletableFeature, + DiamondNode, + executeCommandFeature, + fadeFeature, + hoverFeedbackFeature, + isEditableLabel, + layoutableChildFeature, + LayoutContainer, + layoutContainerFeature, + moveFeature, + Nameable, + nameFeature, + popupFeature, + RectangularNode, + SEdge, + selectFeature, + SModelElement, + SShapeElement, + WithEditableLabel, + withEditLabelFeature +} from "@eclipse-glsp/client"; + +import { ActivityNodeSchema } from "./model-schema"; + +export class TaskNode extends RectangularNode implements Nameable, WithEditableLabel { + static readonly DEFAULT_FEATURES = [connectableFeature, deletableFeature, selectFeature, boundsFeature, + moveFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature, nameFeature, withEditLabelFeature]; + name: string = ""; + duration?: number; + taskType?: string; + reference?: string; + + get editableLabel() { + const headerComp = this.children.find(element => element.type === 'comp:header'); + if (headerComp) { + const label = headerComp.children.find(element => element.type === 'label:heading'); + if (label && isEditableLabel(label)) { + return label; + } + } + return undefined; + } +} + +export function isTaskNode(element: SModelElement): element is TaskNode { + return element instanceof TaskNode || false; +} + +export class WeightedEdge extends SEdge { + probability?: string; +} + +export class ActivityNode extends DiamondNode { + nodeType: string = ActivityNodeSchema.Type.UNDEFINED; + size = { + width: 32, + height: 32 + }; + strokeWidth = 1; +} + + +export class Icon extends SShapeElement implements LayoutContainer, CommandExecutor { + static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature, executeCommandFeature]; + + commandId: string; + layout: string; + layoutOptions?: { [key: string]: string | number | boolean; }; + bounds: Bounds; + size = { + width: 32, + height: 32 + }; +} diff --git a/examples/workflow-sprotty/src/workflow-views.tsx b/examples/workflow-sprotty/src/workflow-views.tsx new file mode 100644 index 0000000..a9d83e1 --- /dev/null +++ b/examples/workflow-sprotty/src/workflow-views.tsx @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + angleOfPoint, + IView, + Point, + PolylineEdgeView, + RectangularNodeView, + RenderingContext, + SEdge, + SShapeElement, + toDegrees +} from "@eclipse-glsp/client"; +import { injectable } from "inversify"; +import * as snabbdom from "snabbdom-jsx"; +import { VNode } from "snabbdom/vnode"; + +import { ActivityNode, Icon, TaskNode, WeightedEdge } from "./model"; + +const JSX = { createElement: snabbdom.svg }; + +@injectable() +export class TaskNodeView extends RectangularNodeView { + render(node: TaskNode, context: RenderingContext): VNode { + const rcr = this.getRoundedCornerRadius(node); + const graph = + + {context.renderChildren(node)} + ; + return graph; + } + + protected getRoundedCornerRadius(node: SShapeElement): number { + return 5; + } +} + +@injectable() +export class ForkOrJoinNodeView extends RectangularNodeView { + render(node: ActivityNode, context: RenderingContext): VNode { + const graph = + + ; + return graph; + } +} + +@injectable() +export class WorkflowEdgeView extends PolylineEdgeView { + protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { + const p1 = segments[segments.length - 2]; + const p2 = segments[segments.length - 1]; + return [ + + ]; + } +} + +@injectable() +export class WeightedEdgeView extends WorkflowEdgeView { + render(edge: Readonly, context: RenderingContext): VNode { + const router = this.edgeRouterRegistry.get(edge.routerKind); + const route = router.route(edge); + if (route.length === 0) + return this.renderDanglingEdge("Cannot compute route", edge, context); + + return + {this.renderLine(edge, route, context)} + {this.renderAdditionals(edge, route, context)} + {context.renderChildren(edge, { route })} + ; + } +} + +@injectable() +export class IconView implements IView { + render(element: Icon, context: RenderingContext): VNode { + const radius = this.getRadius(); + return + + {context.renderChildren(element)} + ; + } + + getRadius() { + return 16; + } +} diff --git a/examples/workflow-sprotty/tsconfig.json b/examples/workflow-sprotty/tsconfig.json new file mode 100644 index 0000000..e940dd2 --- /dev/null +++ b/examples/workflow-sprotty/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "baseUrl": ".", + "reactNamespace": "JSX", + "skipLibCheck": true + }, + "include": [ + "src" + ], +} diff --git a/examples/workflow-standalone/app/diagram.html b/examples/workflow-standalone/app/diagram.html new file mode 100644 index 0000000..9a0199d --- /dev/null +++ b/examples/workflow-standalone/app/diagram.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + +
+ + + + diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf new file mode 100644 index 0000000..d9730ea --- /dev/null +++ b/examples/workflow-standalone/app/example1.wf @@ -0,0 +1,884 @@ +{ + "id": "sprotty", + "children": [ + { + "name": "Push", + "taskType": "manual", + "id": "task0", + "children": [ + { + "id": "task0_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task0_icon", + "children": [ + { + "id": "task0_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task0_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 48.90625, + "height": 22.40625 + }, + "text": "Push", + "alignment": { + "x": 24.453125, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 91.90625, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 470.0, + "y": 250.0 + }, + "size": { + "width": 101.90625, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "ManualTask1", + "taskType": "manual", + "id": "task1", + "children": [ + { + "id": "task1_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task1_icon", + "children": [ + { + "id": "task1_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task1_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 53.5625, + "height": 22.40625 + }, + "text": "RflWt", + "alignment": { + "x": 26.109375, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 96.5625, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 930.0, + "y": 110.0 + }, + "size": { + "width": 106.5625, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "ManualTask2", + "taskType": "manual", + "id": "task2", + "children": [ + { + "id": "task2_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task2_icon", + "children": [ + { + "id": "task2_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task2_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 62.8125, + "height": 22.40625 + }, + "text": "ChkTp", + "alignment": { + "x": 31.109375, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 105.8125, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 670.0, + "y": 350.0 + }, + "size": { + "width": 115.8125, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "ManualTask3", + "taskType": "manual", + "id": "task3", + "children": [ + { + "id": "task3_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task3_icon", + "children": [ + { + "id": "task3_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task3_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 76.953125, + "height": 22.40625 + }, + "text": "PreHeat", + "alignment": { + "x": 37.8125, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 119.953125, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 920.0, + "y": 390.0 + }, + "size": { + "width": 129.953125, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "ManualTask4", + "taskType": "manual", + "id": "task4", + "children": [ + { + "id": "task4_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task4_icon", + "children": [ + { + "id": "task4_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task4_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 50.15625, + "height": 22.40625 + }, + "text": "Brew", + "alignment": { + "x": 24.453125, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 93.15625, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 1280.0, + "y": 260.0 + }, + "size": { + "width": 103.15625, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "ManualTask6", + "taskType": "manual", + "id": "task6", + "children": [ + { + "id": "task6_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task6_icon", + "children": [ + { + "id": "task6_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 4.796875 + }, + "size": { + "width": 16.6875, + "height": 22.40625 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task6_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 73.9375, + "height": 22.40625 + }, + "text": "KeepTp", + "alignment": { + "x": 36.671875, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 116.9375, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 920.0, + "y": 300.0 + }, + "size": { + "width": 126.9375, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "AutomatedTask8", + "taskType": "automated", + "id": "task8", + "children": [ + { + "id": "task8_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task8_icon", + "children": [ + { + "id": "task8_ticon", + "type": "label:icon", + "position": { + "x": 7.9921875, + "y": 4.796875 + }, + "size": { + "width": 16.015625, + "height": 22.40625 + }, + "text": "A", + "alignment": { + "x": 8.28125, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task8_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 64.671875, + "height": 22.40625 + }, + "text": "ChkWt", + "alignment": { + "x": 31.671875, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 107.671875, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:automated", + "position": { + "x": 680.0, + "y": 160.0 + }, + "size": { + "width": 117.671875, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "name": "AutomatedTask9", + "taskType": "automated", + "id": "task9", + "children": [ + { + "id": "task9_header", + "children": [ + { + "commandId": "simulate-command", + "id": "task9_icon", + "children": [ + { + "id": "task9_ticon", + "type": "label:icon", + "position": { + "x": 7.9921875, + "y": 4.796875 + }, + "size": { + "width": 16.015625, + "height": 22.40625 + }, + "text": "A", + "alignment": { + "x": 8.28125, + "y": 18.40625 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task9_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 9.796875 + }, + "size": { + "width": 57.109375, + "height": 22.40625 + }, + "text": "WtOK", + "alignment": { + "x": 28.578125, + "y": 18.40625 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 100.109375, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:automated", + "position": { + "x": 930.0, + "y": 190.0 + }, + "size": { + "width": 110.109375, + "height": 52.0 + }, + "layout": "vbox" + }, + { + "nodeType": "mergeNode", + "id": "activityNode1", + "type": "activityNode:merge", + "position": { + "x": 1100.0, + "y": 170.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "id": "edge4", + "type": "edge", + "sourceId": "task1", + "targetId": "activityNode1" + }, + { + "id": "edge5", + "type": "edge", + "sourceId": "task9", + "targetId": "activityNode1" + }, + { + "nodeType": "decisionNode", + "id": "activityNode2", + "type": "activityNode:decision", + "position": { + "x": 840.0, + "y": 360.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "id": "edge7", + "type": "edge", + "sourceId": "task2", + "targetId": "activityNode2" + }, + { + "nodeType": "mergeNode", + "id": "activityNode4", + "type": "activityNode:merge", + "position": { + "x": 1100.0, + "y": 350.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "id": "edge11", + "type": "edge", + "sourceId": "task6", + "targetId": "activityNode4" + }, + { + "id": "edge12", + "type": "edge", + "sourceId": "task3", + "targetId": "activityNode4" + }, + { + "nodeType": "decisionNode", + "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "type": "activityNode:decision", + "position": { + "x": 840.0, + "y": 170.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "id": "69d7d698-c5ce-4e85-be06-d9bd9357322d", + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "task1" + }, + { + "id": "9c6a2ca0-e178-4b38-abea-33b07aecad54", + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "task9" + }, + { + "nodeType": "forkNode", + "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "type": "activityNode:fork", + "position": { + "x": 590.0, + "y": 250.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", + "type": "edge", + "sourceId": "task0", + "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" + }, + { + "id": "ce77c929-f358-4a13-97c4-62c6792a644c", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task8" + }, + { + "id": "e346b1f6-dec9-40a5-a838-22dae399b8e9", + "type": "edge", + "sourceId": "task8", + "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" + }, + { + "id": "5eeaa218-ad56-4052-8b28-bbc08d82ed34", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task2" + }, + { + "nodeType": "joinNode", + "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "type": "activityNode:join", + "position": { + "x": 1220.0, + "y": 260.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "2f57af4e-2960-496e-992d-03c8b021d81a", + "type": "edge:weighted", + "sourceId": "activityNode2", + "targetId": "task6" + }, + { + "id": "2c7427b0-9743-4bfb-b04c-078ba98f8d7e", + "type": "edge:weighted", + "sourceId": "activityNode2", + "targetId": "task3" + }, + { + "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", + "type": "edge", + "sourceId": "activityNode1", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "id": "0471cae4-c754-4e89-8337-96ed1546dd02", + "type": "edge", + "sourceId": "activityNode4", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "id": "2c3ec34b-291c-43ef-bdf9-035afc4764e6", + "type": "edge", + "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "targetId": "task4" + } + ], + "type": "graph" +} diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css new file mode 100644 index 0000000..ed3f49a --- /dev/null +++ b/examples/workflow-standalone/css/diagram.css @@ -0,0 +1,45 @@ +.sprotty-graph { + background: rgb(179, 196, 202); +} + +.sprotty-node { + fill: #cdc; + stroke: rgb(0, 0, 0); +} + +.sprotty-edge { + stroke: black; +} + +.sprotty-edge.arrow { + fill: black; +} + +.sprotty-edge.selected { + stroke: #844; +} + +.sprotty-edge.selected>.arrow { + fill: #844; + stroke: #844; +} + +.sprotty-node:not(.selected) { + stroke: black; +} + +.sprotty-node.forkOrJoin { + fill: black; +} + +.sprotty-node.forkOrJoin.selected { + stroke: rgb(87, 87, 214); +} + +polygon.sprotty-node { + stroke: black; +} + +.sprotty-node.selected { + stroke: rgb(87, 87, 214); +} diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json new file mode 100644 index 0000000..ece0653 --- /dev/null +++ b/examples/workflow-standalone/package.json @@ -0,0 +1,67 @@ +{ + "private": true, + "name": "workflow-standalone", + "version": "0.7.0", + "description": "Standalone browser-app for the Workflow example", + "dependencies": { + "@eclipse-glsp-examples/workflow-sprotty": "0.7.0", + "@eclipse-glsp/client": "0.7.1" + }, + "author": "Eclipse GLSP", + "contributors": [ + { + "name": "Philip Langer", + "email": "planger@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Tobias Ortmayr", + "email": "tortmayr@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Martin Fleck", + "email": "mfleck@eclipsesource.com", + "url": "https://www.eclipsesource.com" + } + ], + "devDependencies": { + "@types/chai": "^4.0.1", + "@types/file-saver": "^0.0.1", + "@types/mocha": "^2.2.41", + "@types/node": "10.14.18", + "chai": "^4.1.0", + "circular-dependency-plugin": "^2.0.0", + "core-js": "^2.5.1", + "css-loader": "^2.1.0", + "http-server": "^0.11.1", + "jenkins-mocha": "^6.0.0", + "jsdom": "9.12.0", + "jsdom-global": "2.1.1", + "mocha": "^3.4.2", + "nyc": "^11.0.3", + "reflect-metadata": "^0.1.10", + "rimraf": "^2.6.1", + "semver": "5.6.0", + "snabbdom-to-html": "^3.1.1", + "source-map-loader": "^0.2.3", + "style-loader": "^0.23.1", + "ts-loader": "^4.4.2", + "tslint": "^5.5.0", + "tslint-loader": "^3.4.3", + "typescript": "^3.9.2", + "typings": "^2.1.0", + "webpack": "^4.15.1", + "webpack-cli": "^3.0.8" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepare": "yarn run clean && yarn build", + "clean": "rimraf lib app/bundle.js app/bundle.js.map app/css", + "lint": "tslint -c ../../configs/tslint.json --project ./tsconfig.json", + "build": "tsc && yarn lint && webpack", + "watch": "tsc -w -p ./tsconfig.json" + } +} diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts new file mode 100644 index 0000000..dc10713 --- /dev/null +++ b/examples/workflow-standalone/src/di.config.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../css/diagram.css"; + +import { createWorkflowDiagramContainer } from "@eclipse-glsp-examples/workflow-sprotty/lib"; +import { GLSPDiagramServer } from "@eclipse-glsp/client"; +import { Container } from "inversify"; +import { ConsoleLogger, LogLevel, TYPES } from "sprotty"; + +export default function createContainer(): Container { + const container = createWorkflowDiagramContainer('sprotty'); + container.bind(TYPES.ModelSource).to(GLSPDiagramServer).inSingletonScope(); + container.rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + container.rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); + return container; +} diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/main.ts new file mode 100644 index 0000000..03ac7ca --- /dev/null +++ b/examples/workflow-standalone/src/main.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "reflect-metadata"; + +import { + EnableToolPaletteAction, + GLSPDiagramServer, + InitializeClientSessionAction, + RequestTypeHintsAction +} from "@eclipse-glsp/client"; +import { ApplicationIdProvider, BaseJsonrpcGLSPClient, JsonrpcGLSPClient } from "@eclipse-glsp/protocol"; +import { join, resolve } from "path"; +import { IActionDispatcher, RequestModelAction, TYPES } from "sprotty"; + +import createContainer from "./di.config"; + +const port = 8081; +const id = "workflow"; +const name = "workflow"; +const websocket = new WebSocket(`ws://localhost:${port}/${id}`); +const container = createContainer(); + +const loc = window.location.pathname; +const currentDir = loc.substring(0, loc.lastIndexOf('/')); +const examplePath = resolve(join(currentDir, '..', 'app', 'example1.wf')); + +const diagramServer = container.get(TYPES.ModelSource); +diagramServer.clientId = ApplicationIdProvider.get() + "_" + examplePath; + +const actionDispatcher = container.get(TYPES.IActionDispatcher); + +websocket.onopen = () => { + const connectionProvider = JsonrpcGLSPClient.createWebsocketConnectionProvider(websocket); + const glspClient = new BaseJsonrpcGLSPClient({ id, name, connectionProvider }); + diagramServer.connect(glspClient).then(client => { + client.initializeServer({ applicationId: ApplicationIdProvider.get() }); + actionDispatcher.dispatch(new InitializeClientSessionAction(diagramServer.clientId)); + actionDispatcher.dispatch(new RequestModelAction({ + sourceUri: `file://${examplePath}`, + diagramType: "workflow-diagram", + })); + actionDispatcher.dispatch(new RequestTypeHintsAction("workflow-diagram")); + actionDispatcher.dispatch(new EnableToolPaletteAction()); + }); +}; + +websocket.onerror = (ev) => alert("Connection to server errored. Please make sure that the server is running"); + diff --git a/examples/workflow-standalone/src/url-parameters.ts b/examples/workflow-standalone/src/url-parameters.ts new file mode 100644 index 0000000..59025a3 --- /dev/null +++ b/examples/workflow-standalone/src/url-parameters.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export function getParameters(): { [key: string]: string } { + let search = window.location.search.substring(1); + const result = {}; + while (search.length > 0) { + const nextParamIndex = search.indexOf('&'); + let param: string; + if (nextParamIndex < 0) { + param = search; + search = ''; + } else { + param = search.substring(0, nextParamIndex); + search = search.substring(nextParamIndex + 1); + } + const valueIndex = param.indexOf('='); + if (valueIndex > 0 && valueIndex < param.length - 1) { + result[param.substring(0, valueIndex)] = decodeURIComponent(param.substring(valueIndex + 1)); + } + } + return result; +} diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json new file mode 100644 index 0000000..2c83567 --- /dev/null +++ b/examples/workflow-standalone/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "baseUrl": ".", + "noImplicitAny": false + }, + "include": [ + "src", + "lib", + "css", + "app" + ] +} diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js new file mode 100644 index 0000000..f5ff8a1 --- /dev/null +++ b/examples/workflow-standalone/webpack.config.js @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2017 TypeFox & others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +const webpack = require('webpack'); +const path = require('path'); + +const buildRoot = path.resolve(__dirname, 'lib'); +const appRoot = path.resolve(__dirname, 'app'); +var CircularDependencyPlugin = require('circular-dependency-plugin'); + + +module.exports = { + entry: [ + 'core-js/es6/map', + 'core-js/es6/promise', + 'core-js/es6/string', + 'core-js/es6/symbol', + path.resolve(buildRoot, 'main') + ], + output: { + filename: 'bundle.js', + path: appRoot + }, + mode: 'development', + devtool: 'source-map', + resolve: { + // Add `.ts` and `.tsx` as a resolvable extension. + extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js'] + }, + module: { + rules: [ + // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` + { + test: /\.tsx?$/, + use: [{ + loader: 'ts-loader', + options: { + configFile: path.resolve(__dirname, 'examples.tsconfig.json') + } + }] + }, + { + test: /\.css$/, + exclude: /\.useable\.css$/, + loader: 'style-loader!css-loader' + } + ] + }, + node : { fs: 'empty', net: 'empty' }, + plugins: [ + new CircularDependencyPlugin({ + exclude: /(node_modules|examples)\/./, + failOnError: false + }), + new webpack.WatchIgnorePlugin([ + /\.js$/, + /\.d\.ts$/ + ]) + ] +}; diff --git a/packages/client/.nycrc b/packages/client/.nycrc new file mode 100644 index 0000000..7f5c6c6 --- /dev/null +++ b/packages/client/.nycrc @@ -0,0 +1,16 @@ +{ + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "typings", + "**/*.spec.ts" + ], + "reporter": [ + "html" + ], + "extension": [ + ".ts" + ], + "report-dir": "./artifacts/coverage" +} diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000..41e7593 --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,12 @@ +# Eclipse GLSP - Client [![build-status](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.eclipse.org%2Fglsp%2Fjob%2Feclipse-glsp%2Fjob%2Fglsp-client%2Fjob%2Fmaster%2F)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) [![build-status-server](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/deploy-npm-glsp-client/&label=publish)](https://ci.eclipse.org/glsp/job/deploy-npm-glsp-client/) + +A web-based diagram client framework for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) based on [Eclipse Sprotty](https://github.com/eclipse/sprotty). + + +## Building +This project is built with `yarn` and is available from npm via [@eclipse-glsp/client](https://www.npmjs.com/package/@eclipse-glsp/client). + + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). + +![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/client/css/command-palette.css b/packages/client/css/command-palette.css new file mode 100644 index 0000000..3498d66 --- /dev/null +++ b/packages/client/css/command-palette.css @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.command-palette { + transition: opacity 0.3s linear; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.command-palette-suggestions { + background: white; + z-index: 1000; + overflow: auto; + box-sizing: border-box; + border: 1px solid rgba(60, 60, 60, 0.6); + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.command-palette-suggestions .icon { + padding-right: 0.3em; +} + +.command-palette-suggestions em { + font-weight: bold; + font-style: normal; +} + +.command-palette-suggestions>div { + padding: 0 4px; +} + +.command-palette-suggestions .group { + background: #eee; +} + +.command-palette-suggestions>div:hover:not(.group), .command-palette-suggestions>div.selected { + cursor: pointer; +} + +.command-palette-suggestions>div:hover:not(.group) { + background: #e0e0e0; +} + +.command-palette-suggestions>div.selected { + background: #bbdefb; +} diff --git a/packages/client/css/decoration.css b/packages/client/css/decoration.css new file mode 100644 index 0000000..d127923 --- /dev/null +++ b/packages/client/css/decoration.css @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +:root { + --glsp-error-foreground: red; + --glsp-warning-foreground: yellow; + --glsp-info-foreground: lightblue; +} + +.sprotty-issue-background { + fill: none; +} + +.sprotty-issue.sprotty-error { + fill: var(--glsp-error-foreground); +} + +.sprotty-issue.sprotty-warning { + fill: var(--glsp-warning-foreground); +} + +.sprotty-issue.sprotty-info { + fill: var(--glsp-info-foreground); +} diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css new file mode 100644 index 0000000..bfc4c3e --- /dev/null +++ b/packages/client/css/glsp-sprotty.css @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.sprotty { + padding: 0px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.sprotty svg text::selection { + background: none; +} + +.sprotty-hidden { + display: block; + position: absolute; + width: 0px; + height: 0px; +} + +.sprotty-popup { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + position: absolute; + background: white; + border-radius: 5px; + border: 1px solid; + max-width: 400px; + min-width: 100px; +} + +.sprotty-popup>div { + margin: 10px; +} + +.sprotty-popup-closed { + display: none; +} + +.sprotty-resize-handle { + /* radius does not work in Firefox */ + r: 7px; + fill: #884; + stroke: none; + z-index: 1000; +} + +.sprotty-resize-handle.selected { + fill: #66a; +} + +.sprotty-resize-handle.active { + fill: #0074e8; +} + +.sprotty-resize-handle.mouseover { + stroke: #112; + stroke-width: 1; +} + +.node-creation-mode { + cursor: copy; +} + +.overlap-forbidden-mode { + cursor: not-allowed; +} + +.default-mode { + cursor: default; +} + +.edge-modification-not-allowed-mode { + cursor: no-drop; +} + +.edge-creation-select-source-mode { + cursor: pointer; +} + +.edge-creation-select-target-mode, .edge-reconnect-select-target-mode { + cursor: crosshair; +} + +.resize-mode { + cursor: none; +} diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css new file mode 100644 index 0000000..c3a870b --- /dev/null +++ b/packages/client/css/tool-palette.css @@ -0,0 +1,138 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +@import url("https://use.fontawesome.com/releases/v5.6.3/css/all.css"); +/* Css for main container */ + +.tool-palette { + position: absolute; + right: 25px; + top: 25px; + text-align: center; + width: 180px; + display: block; + z-index: 1000; + border-style: solid; + border-width: 0px; + border-color: #bbb; + border-radius: 5px; + color: black; + user-select: none; + /* supported by Chrome and Opera */ + -webkit-user-select: none; + /* Safari */ + -khtml-user-select: none; + /* Konqueror HTML */ + -moz-user-select: none; + /* Firefox */ + -ms-user-select: none; + /* Internet Explorer/Edge */ +} + +/* Css for header compartment */ + +.palette-header { + padding: 0.4em; + text-align: left; + font-size: 1.15em; + font-weight: bold; + background: #CCCCCC; + border: 1px solid rgba(60, 60, 60, 0.6); + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.header-icon i { + margin-right: 0.2em +} + +.header-tools i { + border: 1px solid #CCCCCC; + padding: 0.2em; + margin-right: 0.2em; +} + +.header-tools i:hover { + background: #DFDFDF; +} + +.header-tools .clicked { + background: #BDDAEF; + border: 1px solid rgba(60, 60, 60, 0.6); +} + +/*Css for palette body compartment */ + +.palette-body { + background: rgba(100, 100, 100, 0.2); + border: 1px solid rgba(60, 60, 60, 0.6); + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.tool-group { + text-align: left; + background: #EDEDEE; +} + +.group-header { + font-weight: bold; + background: #CCCCCC; +} + +.group-header:hover { + background: #AAAAAA; +} + +.group-header i { + padding: 0.4em; +} + +.tool-button { + background: #EDEDEE; + padding: 0.4em; +} + +.tool-button:hover { + background: #DFDFDF; +} + +.tool-button.clicked { + background: #BDDAEF; +} + +.tool-button.collapsed { + display: none; +} + +.collapsible-palette { + overflow: auto; + transition: max-height 0.2s ease-out; +} + +.minimize-palette-button { + position: absolute; + right: 12px; + top: 25px; + z-index: 1000; +} + +.search-input { + background: #DFDFDF; + color: black; + border: v#BDDAEF; + padding-left: 3px; + width: 150px; + margin-left: 5px; +} diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..520785b --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,78 @@ +{ + "name": "@eclipse-glsp/client", + "version": "0.7.1", + "description": "A sprotty-based client for GLSP", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "homepage": "https://www.eclipse.org/glsp/", + "bugs": "https://github.com/eclipse-glsp/glsp-client/issues", + "files": [ + "lib", + "src", + "css" + ], + "author": { + "name": "Eclipse GLSP" + }, + "contributors": [ + { + "name": "Philip Langer", + "email": "planger@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Tobias Ortmayr", + "email": "tortmayr@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Martin Fleck", + "email": "mfleck@eclipsesource.com", + "url": "https://www.eclipsesource.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "dependencies": { + "autocompleter": "5.1.0", + "sprotty": "0.9.0", + "@eclipse-glsp/protocol": "0.7.0" + }, + "devDependencies": { + "@types/node": "10.14.18", + "@types/mocha": "^5.2.7", + "@babel/runtime": "^7.11.2", + "@types/chai": "4.1.3", + "mocha": "^6.2.0", + "jenkins-mocha": "^8.0.0", + "chai": "^4.2.0", + "ts-node": "^8.3.0", + "reflect-metadata": "^0.1.13", + "rimraf": "^2.6.1", + "tslint": "^5.5.0", + "typescript": "^3.9.2" + }, + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "rimraf lib", + "build": "tsc && yarn run lint", + "lint": "tslint -c ../../tslint.json --project ./tsconfig.json", + "watch": "tsc -w", + "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts new file mode 100644 index 0000000..249f349 --- /dev/null +++ b/packages/client/src/base/action-dispatcher.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject } from "inversify"; +import { Action, ActionDispatcher, isResponseAction, RequestAction, ResponseAction } from "sprotty"; + +import { ModelInitializationConstraint } from "./model-initialization-constraint"; + +export class GLSPActionDispatcher extends ActionDispatcher { + + protected readonly timeouts: Map = new Map(); + + @inject(ModelInitializationConstraint) protected initiailizationConstraint: ModelInitializationConstraint; + protected onModelInitialized: Promise = Promise.resolve(); + + initialize(): Promise { + return super.initialize().then(() => this.startModelInitialization()); + } + + startModelInitialization() { + this.logger.log(this, 'Starting model initialization mode'); + this.onModelInitialized = this.initiailizationConstraint.onInitialized(); + this.onModelInitialized.then(() => this.logger.log(this, 'Model initialization completed')); + } + + onceModelInitialized(): Promise { + return this.onModelInitialized; + } + + dispatch(action: Action): Promise { + const result = super.dispatch(action); + this.initiailizationConstraint.notifyDispatched(action); + return result; + } + + protected handleAction(action: Action): Promise { + if (isResponseAction(action)) { + // clear timeout + const timeout = this.timeouts.get(action.responseId); + if (timeout !== undefined) { + clearTimeout(timeout); + this.timeouts.delete(action.responseId); + } + + // we might have reached a timeout, so we simply drop the response + const deferred = this.requests.get(action.responseId); + if (deferred === undefined) { + this.logger.log(this, 'No matching request for response', action); + return Promise.resolve(); + } + } + return super.handleAction(action); + } + + /** + * Dispatch a request and waits for a response until the timeout given in `timeoutMs` has + * been reached. The returned promise is resolved when a response with matching identifier + * is dispatched or when the timeout has been reached. That response is _not_ passed to the + * registered action handlers. Instead, it is the responsibility of the caller of this method + * to handle the response properly. For example, it can be sent to the registered handlers by + * passing it again to the `dispatch` method. + * If `rejectOnTimeout` is set to false (default) the returned promise will be resolved with + * no value, otherwise it will be rejected. + */ + requestUntil(action: RequestAction, timeoutMs: number = 2000, rejectOnTimeout: boolean = false): Promise { + if (!action.requestId) { + return Promise.reject(new Error('Request without requestId')); + } + + const requestId = action.requestId; + const timeout = setTimeout(() => { + const deferred = this.requests.get(requestId); + if (deferred !== undefined) { + // cleanup + clearTimeout(timeout); + this.requests.delete(requestId); + + const notification = 'Request ' + requestId + ' (' + action + ') time out after ' + timeoutMs + 'ms.'; + if (rejectOnTimeout) { + deferred.reject(notification); + } else { + this.logger.info(this, notification); + deferred.resolve(); + } + } + }, timeoutMs); + this.timeouts.set(requestId, timeout); + + return super.request(action); + } +} diff --git a/packages/client/src/base/actions/context-actions.ts b/packages/client/src/base/actions/context-actions.ts new file mode 100644 index 0000000..4416cce --- /dev/null +++ b/packages/client/src/base/actions/context-actions.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, generateRequestId, LabeledAction, RequestAction, ResponseAction } from "sprotty"; + +import { Args } from "../args"; +import { EditorContext } from "../editor-context"; + +export class RequestContextActions implements RequestAction { + static readonly KIND = "requestContextActions"; + kind = RequestContextActions.KIND; + constructor( + public readonly contextId: string, + public readonly editorContext: EditorContext, + public readonly requestId: string = generateRequestId()) { } +} + +export class SetContextActions implements ResponseAction { + static readonly KIND = "setContextActions"; + kind = SetContextActions.KIND; + constructor(public readonly actions: LabeledAction[], + public readonly responseId: string = '', + readonly args?: Args) { } +} + +export function isSetContextActionsAction(action: Action): action is SetContextActions { + return action !== undefined && (action.kind === SetContextActions.KIND) + && (action).actions !== undefined; +} diff --git a/packages/client/src/base/actions/edit-mode-action.ts b/packages/client/src/base/actions/edit-mode-action.ts new file mode 100644 index 0000000..26e2005 --- /dev/null +++ b/packages/client/src/base/actions/edit-mode-action.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from "sprotty"; +import { injectable } from "inversify"; + +@injectable() +export class SetEditModeAction implements Action { + static readonly KIND = "setEditMode"; + kind = SetEditModeAction.KIND; + + constructor(public readonly editMode: string = EditMode.EDITABLE) { } +} + +export function isSetEditModeAction(action: Action): action is SetEditModeAction { + return action !== undefined && (action.kind === SetEditModeAction.KIND) + && "editMode" in action && typeof action["editMode"] === "string"; +} + +export namespace EditMode { + export const READONLY: string = "readonly"; + export const EDITABLE: string = "editable"; +} diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts new file mode 100644 index 0000000..3a4627f --- /dev/null +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, generateRequestId, RequestAction, ResponseAction } from "sprotty"; + +import { Args } from "../args"; + +export class RequestEditValidationAction implements RequestAction { + static readonly KIND = "requestEditValidation"; + kind = RequestEditValidationAction.KIND; + constructor( + public readonly contextId: string, + public readonly modelElementId: string, + public readonly text: string, + public readonly requestId: string = generateRequestId()) { } +} + +export class SetEditValidationResultAction implements ResponseAction { + static readonly KIND = "setEditValidationResult"; + kind = SetEditValidationResultAction.KIND; + constructor(public readonly status: ValidationStatus, + public readonly responseId: string = '', + readonly args?: Args) { } +} + +export function isSetEditValidationResultAction(action: Action): action is SetEditValidationResultAction { + return action !== undefined && (action.kind === SetEditValidationResultAction.KIND) + && (action).status !== undefined; +} + +export interface ValidationStatus { + readonly severity: ValidationStatus.Severity; + readonly message: string; + readonly error: ResponseError; +} + +export interface ResponseError { + readonly code: number; + readonly message: string; + readonly data: Object; +} + +export namespace ValidationStatus { + + export enum Severity { + FATAL, ERROR, WARNING, INFO, OK, NONE + } + + export const NONE: ValidationStatus = { + severity: Severity.NONE, message: '', error: { code: -1, message: '', data: {} } + }; + + export function isOk(validationStatus: ValidationStatus): boolean { + return validationStatus.severity === Severity.OK + || validationStatus.severity === Severity.INFO + || validationStatus.severity === Severity.NONE; + } + + export function isWarning(validationStatus: ValidationStatus): boolean { + return validationStatus.severity === Severity.WARNING; + } + + export function isError(validationStatus: ValidationStatus): boolean { + return validationStatus.severity === Severity.ERROR + || validationStatus.severity === Severity.FATAL; + } +} diff --git a/packages/client/src/base/actions/protocol-actions.ts b/packages/client/src/base/actions/protocol-actions.ts new file mode 100644 index 0000000..5dd8e14 --- /dev/null +++ b/packages/client/src/base/actions/protocol-actions.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { Action } from "sprotty"; + +/** + * Initializes the graphical representation (diagram) for a specific client session. + * Each individual diagram on the client side counts as one session and has to provide + * a unique clientId. + */ +@injectable() +export class InitializeClientSessionAction implements Action { + static readonly KIND = "initializeClientSession"; + readonly kind = InitializeClientSessionAction.KIND; + + constructor(public readonly clientId: string) { } + +} + +export function initializeClientSessionAction(action: Action): action is InitializeClientSessionAction { + return action.kind === InitializeClientSessionAction.KIND; +} + +/** + * Sent to the server if the graphical representation (diagram) for a specific client session + * is no longer needed. e.g. the tab containing the diagram widget has been closed. + */ +@injectable() +export class DisposeClientSessionAction implements Action { + static readonly KIND = "disposeClientSession"; + readonly kind = DisposeClientSessionAction.KIND; + + constructor(public readonly clientId: string) { } +} + +export function isDisposeClientSessionAction(action: Action): action is DisposeClientSessionAction { + return action.kind === DisposeClientSessionAction.KIND; +} diff --git a/packages/client/src/base/args.ts b/packages/client/src/base/args.ts new file mode 100644 index 0000000..0abdeed --- /dev/null +++ b/packages/client/src/base/args.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export type Args = { [key: string]: string | number | boolean }; diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts new file mode 100644 index 0000000..9d57dd7 --- /dev/null +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LabeledAction } from "sprotty/lib"; + +export interface SetAutoCompleteValueAction extends LabeledAction { + readonly text: string; +} + +export function isSetAutoCompleteValueAction(action: LabeledAction): action is SetAutoCompleteValueAction { + return action !== undefined && (action).text !== undefined; +} diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts new file mode 100644 index 0000000..3d2b466 --- /dev/null +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -0,0 +1,304 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { AutocompleteResult, AutocompleteSettings } from "autocompleter"; +import { Action, ILogger, isAction, isLabeledAction, LabeledAction, SModelRoot } from "sprotty/lib"; +import { toArray } from "sprotty/lib/utils/iterable"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +import { ValidationStatus } from "../actions/edit-validation-actions"; +import { isSetAutoCompleteValueAction } from "./auto-complete-actions"; +import { IValidationDecorator } from "./validation-decorator"; + +export interface AutoCompleteSettings { + readonly noSuggestionsMessage: string; + readonly suggestionsClass: string; + readonly debounceWaitMs: number; + readonly showOnFocus: boolean; +} + +export interface InputValidator { + validate(input: string): Promise; +} + +export interface SuggestionProvider { + provideSuggestions(input: string): Promise; +} + +export interface InputValueInitializer { + initializeValue(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): string; +} + +export interface SuggestionSubmitHandler { + executeFromSuggestion(input: LabeledAction | Action | Action[]): void; +} + +export interface TextSubmitHandler { + executeFromTextOnlyInput(input: string): void; +} + +const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); + +export class AutoCompleteWidget { + + loadingIndicatorClasses = ['loading', 'fa', 'fa-spinner', 'fa-pulse', 'fa-3x', 'fa-fw']; + + protected containerElement: HTMLElement; + protected inputElement: HTMLInputElement; + protected loadingIndicator: HTMLSpanElement; + protected autoCompleteResult: AutocompleteResult; + protected contextActions?: LabeledAction[]; + protected previousContent?: string; + + protected inputValidator?: InputValidator; + protected validationDecorator: IValidationDecorator = IValidationDecorator.NO_DECORATION; + + protected textSubmitHandler?: TextSubmitHandler; + + constructor( + protected autoSuggestionSettings: AutoCompleteSettings, + protected suggestionProvider: SuggestionProvider, + protected suggestionSubmitHandler: SuggestionSubmitHandler, + protected notifyClose: () => void = () => { }, + protected logger?: ILogger + ) { } + + configureValidation(inputValidator: InputValidator, + validationDecorator: IValidationDecorator = IValidationDecorator.NO_DECORATION): void { + this.inputValidator = inputValidator; + this.validationDecorator = validationDecorator; + } + + configureTextSubmitHandler(textSubmitHandler: TextSubmitHandler): void { + this.textSubmitHandler = textSubmitHandler; + } + + initialize(containerElement: HTMLElement): void { + this.containerElement = containerElement; + this.inputElement = document.createElement('input'); + this.inputElement.style.position = 'absolute'; + this.inputElement.spellcheck = false; + this.inputElement.autocapitalize = 'false'; + this.inputElement.autocomplete = 'false'; + this.inputElement.style.width = '100%'; + this.inputElement.addEventListener('keydown', event => this.handleKeyDown(event)); + this.inputElement.addEventListener('blur', () => window.setTimeout(() => this.notifyClose(), 200)); + this.containerElement.appendChild(this.inputElement); + this.containerElement.style.position = 'absolute'; + } + + protected handleKeyDown(event: KeyboardEvent) { + if (matchesKeystroke(event, 'Escape')) { + this.notifyClose(); + return; + } + if (matchesKeystroke(event, 'Enter') && !this.isInputElementChanged() && this.isSuggestionAvailable()) { + return; + } + if (this.isInputElementChanged()) { + this.invalidateValidationResultAndContextActions(); + } + if (!matchesKeystroke(event, 'Enter') || this.isSuggestionAvailable()) { + return; + } + if (!this.validationDecorator.isValidatedOk()) { + event.stopImmediatePropagation(); + return; + } + if (this.textSubmitHandler) { + this.executeFromTextOnlyInput(); + this.notifyClose(); + } + } + + protected isInputElementChanged() { + return this.inputElement.value !== this.previousContent; + } + + protected invalidateValidationResultAndContextActions() { + this.contextActions = undefined; + this.validationDecorator.invalidate(); + } + + open(root: Readonly, ...contextElementIds: string[]) { + this.contextActions = undefined; + this.autoCompleteResult = configureAutocomplete(this.autocompleteSettings(root)); + this.previousContent = this.inputElement.value; + this.inputElement.setSelectionRange(0, this.inputElement.value.length); + this.inputElement.focus(); + } + + protected autocompleteSettings(root: Readonly): AutocompleteSettings { + return { + input: this.inputElement, + emptyMsg: this.autoSuggestionSettings.noSuggestionsMessage, + className: this.autoSuggestionSettings.suggestionsClass, + showOnFocus: this.autoSuggestionSettings.showOnFocus, + debounceWaitMs: this.autoSuggestionSettings.debounceWaitMs, + minLength: -1, + fetch: (text: string, update: (items: LabeledAction[]) => void) => + this.updateSuggestions(update, text, root), + onSelect: (item: LabeledAction) => this.onSelect(item), + render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => + this.renderSuggestions(item, currentValue), + customize: (input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) => { + this.customizeInputElement(input, inputRect, container, maxHeight); + } + }; + } + + protected customizeInputElement(input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) { + // move container into our UIExtension container as this is already positioned correctly + if (this.containerElement) { + this.containerElement.appendChild(container); + } + } + + protected updateSuggestions(update: (items: LabeledAction[]) => void, text: string, root: Readonly, ...contextElementIds: string[]) { + this.onLoading(); + this.doUpdateSuggestions(text, root) + .then(actions => { + this.contextActions = this.filterActions(text, actions); + update(this.contextActions); + this.onLoaded('success'); + }) + .catch(reason => { + if (this.logger) { this.logger.error(this, 'Failed to obtain suggestions', reason); } + this.onLoaded('error'); + }); + } + + protected onLoading() { + if (this.loadingIndicator && this.containerElement.contains(this.loadingIndicator)) { + return; + } + this.loadingIndicator = document.createElement('span'); + this.loadingIndicator.classList.add(...this.loadingIndicatorClasses); + this.containerElement.appendChild(this.loadingIndicator); + } + + protected doUpdateSuggestions(text: string, root: Readonly, ...contextElementIds: string[]): Promise { + return this.suggestionProvider.provideSuggestions(text); + } + + protected onLoaded(success: 'success' | 'error') { + if (this.containerElement.contains(this.loadingIndicator)) { + this.containerElement.removeChild(this.loadingIndicator); + } + this.validationDecorator.invalidate(); + this.validateInputIfNoContextActions(); + this.previousContent = this.inputElement.value; + } + + protected renderSuggestions(item: LabeledAction, value: string) { + const itemElement = document.createElement('div'); + const wordMatcher = this.espaceForRegExp(value).split(' ').join('|'); + const regex = new RegExp(wordMatcher, 'gi'); + if (item.icon) { + this.renderIcon(itemElement, item.icon); + } + itemElement.innerHTML += item.label.replace(regex, match => '' + match + ''); + return itemElement; + } + + protected espaceForRegExp(value: string) { + return value.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + protected renderIcon(itemElement: HTMLDivElement, icon: string) { + itemElement.innerHTML += ``; + } + + protected filterActions(filterText: string, actions: LabeledAction[]): LabeledAction[] { + return toArray(actions.filter(action => { + const label = action.label.toLowerCase(); + const searchWords = filterText.split(' '); + return searchWords.every(word => label.indexOf(word.toLowerCase()) !== -1); + })); + } + + protected onSelect(item: LabeledAction) { + if (isSetAutoCompleteValueAction(item)) { + this.inputElement.value = item.text; + // trigger update of suggestions with an keyup event + window.setTimeout(() => this.inputElement.dispatchEvent(new Event('keyup'))); + } else { + this.executeFromSuggestion(item); + this.notifyClose(); + } + } + + protected validateInputIfNoContextActions() { + if (this.isNoOrExactlyOneMatchingContextAction()) { + this.validateInput(); + } else { + this.validationDecorator.dispose(); + } + } + + private isNoOrExactlyOneMatchingContextAction() { + return !this.isSuggestionAvailable() + || (this.contextActions + && this.contextActions.length === 1 + && this.inputElement.value.endsWith(this.contextActions[0].label)); + } + + protected isSuggestionAvailable() { + return this.contextActions && this.contextActions.length > 0; + } + + validateInput() { + if (this.inputValidator) { + this.inputValidator.validate(this.inputElement.value) + .then(result => this.validationDecorator.decorateValidationResult(result)) + .catch(error => this.handleErrorDuringValidation(error)); + } + } + + protected handleErrorDuringValidation(error: Error) { + if (this.logger) { this.logger.error(this, 'Failed to validate input', error); } + this.validationDecorator.dispose(); + } + + protected executeFromSuggestion(input: LabeledAction | Action[] | Action): void { + this.suggestionSubmitHandler.executeFromSuggestion(input); + } + + protected executeFromTextOnlyInput(): void { + if (this.textSubmitHandler) { + this.textSubmitHandler.executeFromTextOnlyInput(this.inputElement.value); + } + } + + get inputField(): HTMLInputElement { + return this.inputElement; + } + + dispose() { + this.validationDecorator.dispose(); + if (this.autoCompleteResult) { + this.autoCompleteResult.destroy(); + } + } +} + +export function toActionArray(input: LabeledAction | Action[] | Action): Action[] { + if (isLabeledAction(input)) { + return input.actions; + } else if (isAction(input)) { + return [input]; + } + return []; +} diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts new file mode 100644 index 0000000..bce8d80 --- /dev/null +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -0,0 +1,122 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ValidationStatus } from "../actions/edit-validation-actions"; + +export interface IValidationDecorator { + decorateValidationResult(status: ValidationStatus): void; + isValidatedOk(): boolean; + invalidate(): void; + dispose(): void; +} + +export namespace IValidationDecorator { + export const NO_DECORATION: IValidationDecorator = { + decorateValidationResult(status: ValidationStatus) { }, + isValidatedOk(): boolean { return false; }, + invalidate() { }, + dispose() { } + }; +} + +export class ValidationDecorator implements IValidationDecorator { + + warningClasses = ['warning']; + warningIconClasses = ['fa', 'fa-question-circle']; + errorClasses = ['error']; + errorIconClasses = ['fa', 'fa-exclamation-circle']; + + isValidated: boolean = false; + hasValidationError: boolean = false; + + protected decorationDiv?: HTMLDivElement; + + constructor(protected containerElement: HTMLElement) { } + + decorateValidationResult(status: ValidationStatus): void { + if (ValidationStatus.isError(status)) { + this.hasValidationError = true; + this.decorateError(status.message ? status.message : 'Error'); + } else if (ValidationStatus.isWarning(status)) { + this.hasValidationError = false; + this.decorateWarning(status.message ? status.message : 'Warning'); + } else { + this.hasValidationError = false; + this.dispose(); + } + this.isValidated = true; + } + + protected decorateError(message: string): void { + this.switchCssClasses(this.containerElement, this.errorClasses); + const div = this.createDecorationDiv(); + this.switchCssClasses(div, this.errorClasses); + div.innerHTML = ` ${message}`; + this.adjustPosition(); + } + + protected decorateWarning(message: string): void { + this.switchCssClasses(this.containerElement, this.warningClasses); + const div = this.createDecorationDiv(); + this.switchCssClasses(div, this.warningClasses); + div.innerHTML = ` ${message}`; + this.adjustPosition(); + } + + protected switchCssClasses(element: HTMLElement, cssClasses: string[]) { + element.classList.remove(...this.errorClasses, ...this.warningClasses); + element.classList.add(...cssClasses); + } + + protected createDecorationDiv(): HTMLDivElement { + if (!this.decorationDiv) { + this.containerElement.classList.add('validation'); + this.decorationDiv = document.createElement('div'); + this.decorationDiv.style.width = `${this.decorationContainerWidth()}px`; + this.decorationDiv.classList.add('validation-decorator'); + this.containerElement.appendChild(this.decorationDiv); + } + return this.decorationDiv; + } + + protected decorationContainerWidth(): number { + return this.containerElement.clientWidth - 22; + } + + protected adjustPosition(): void { + if (this.decorationDiv) { + const height = this.decorationDiv.clientHeight + 2; + this.decorationDiv.style.top = `-${height}px`; + } + } + + isValidatedOk(): boolean { + return this.isValidated && !this.hasValidationError; + } + + invalidate(): void { + this.isValidated = false; + } + + dispose(): void { + this.hasValidationError = false; + this.isValidated = false; + if (this.decorationDiv && this.containerElement && this.containerElement.contains(this.decorationDiv)) { + this.containerElement.removeChild(this.decorationDiv); + this.switchCssClasses(this.containerElement, []); + this.decorationDiv = undefined; + } + } +} diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts new file mode 100644 index 0000000..1721e09 --- /dev/null +++ b/packages/client/src/base/command-stack.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from "sprotty"; + +import { GlspRedoAction, GlspUndoAction } from "../features/undo-redo/model"; + +@injectable() +export class GLSPCommandStack extends CommandStack { + + @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; + + undo(): Promise { + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new GlspUndoAction())); + return this.thenUpdate(); + } + + redo(): Promise { + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new GlspRedoAction())); + return this.thenUpdate(); + } +} + diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts new file mode 100644 index 0000000..324c210 --- /dev/null +++ b/packages/client/src/base/di.config.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../../css/glsp-sprotty.css"; + +import { ContainerModule } from "inversify"; +import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from "sprotty"; + +import { GLSPActionDispatcher } from "./action-dispatcher"; +import { SetEditModeAction } from "./actions/edit-mode-action"; +import { GLSPCommandStack } from "./command-stack"; +import { EditorContextService } from "./editor-context"; +import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from "./model-initialization-constraint"; +import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from "./model/update-model-command"; +import { SelectionClearingMouseListener } from "./selection-clearing-mouse-listener"; +import { GLSPToolManager } from "./tool-manager/glsp-tool-manager"; +import { GLSP_TYPES } from "./types"; + +const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, _unbind, isBound, rebind }; + bind(EditorContextService).toSelf().inSingletonScope(); + bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider(ctx => { + return () => { + return new Promise((resolve, reject) => { + if (ctx.container.isBound(EditorContextService)) { + resolve(ctx.container.get(EditorContextService)); + } else { + reject(); + } + }); + }; + }); + + configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); + + // Model update initialization ------------------------------------ + configureCommand(context, FeedbackAwareUpdateModelCommand); + configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); + + bind(TYPES.MouseListener).to(SelectionClearingMouseListener); + + rebind(TYPES.ICommandStack).to(GLSPCommandStack); + bind(GLSPToolManager).toSelf().inSingletonScope(); + bind(GLSP_TYPES.IGLSPToolManager).toService(GLSPToolManager); + rebind(TYPES.IToolManager).toService(GLSPToolManager); + bind(GLSPActionDispatcher).toSelf().inSingletonScope(); + rebind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + + bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); +}); + +export default defaultGLSPModule; diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts new file mode 100644 index 0000000..af10e32 --- /dev/null +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MouseListener, SModelElement } from "sprotty"; + +/** + * A mouse listener that is aware of prior mouse dragging. + * + * Therefore, this listener distinguishes between mouse up events after dragging and + * mouse up events without prior dragging. Subclasses may override the methods + * `draggingMouseUp` and/or `nonDraggingMouseUp` to react to only these specific kinds + * of mouse up events. + */ +export class DragAwareMouseListener extends MouseListener { + + private _isMouseDown: boolean = false; + private _isMouseDrag: boolean = false; + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + this._isMouseDown = true; + return []; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + if (this._isMouseDown) { + this._isMouseDrag = true; + } + return []; + } + + mouseUp(element: SModelElement, event: MouseEvent): Action[] { + this._isMouseDown = false; + if (this._isMouseDrag) { + this._isMouseDrag = false; + return this.draggingMouseUp(element, event); + } + + return this.nonDraggingMouseUp(element, event); + } + + nonDraggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + return []; + } + + draggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + return []; + } + + get isMouseDrag() { + return this._isMouseDrag; + } + + get isMouseDown() { + return this._isMouseDown; + } + +} diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context.ts new file mode 100644 index 0000000..493bc89 --- /dev/null +++ b/packages/client/src/base/editor-context.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from "inversify"; +import { Action, IActionHandler, ModelSource, MousePositionTracker, Point, SModelElement, SModelRoot, TYPES } from "sprotty"; + +import { SelectionService } from "../features/select/selection-service"; +import { distinctAdd, remove } from "../utils/array-utils"; +import { EditMode, isSetEditModeAction } from "./actions/edit-mode-action"; +import { Args } from "./args"; +import { isSourceUriAware } from "./source-uri-aware"; +import { GLSP_TYPES } from "./types"; + +export interface EditorContext { + readonly selectedElementIds: string[]; + readonly lastMousePosition?: Point; + readonly args?: Args; +} + +export interface EditModeListener { + editModeChanged(newValue: string, oldvalue: string): void; +} +@injectable() +export class EditorContextService implements IActionHandler { + + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; + @inject(TYPES.ModelSourceProvider) protected modelSource: () => Promise; + editMode: string; + + constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) { } + + register(editModeListener: EditModeListener) { + distinctAdd(this.editModeListeners, editModeListener); + } + + deregister(editModeListener: EditModeListener) { + remove(this.editModeListeners, editModeListener); + } + + get(args?: Args): EditorContext { + return { + selectedElementIds: Array.from(this.selectionService.getSelectedElementIDs()), + lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram, + args + }; + } + + getWithSelection(selectedElementIds: string[], args?: Args): EditorContext { + return { + selectedElementIds, + lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram, + args + }; + } + + handle(action: Action) { + if (isSetEditModeAction(action)) { + const oldValue = this.editMode; + this.editMode = action.editMode; + this.notifiyEditModeListeners(oldValue); + } + } + + protected notifiyEditModeListeners(oldValue: string) { + this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); + } + + async getSourceUri() { + const modelSource = await this.modelSource(); + if (isSourceUriAware(modelSource)) { + return modelSource.getSourceURI(); + } + return undefined; + } + + get modelRoot(): Readonly { + return this.selectionService.getModelRoot(); + } + + get selectedElements(): Readonly[] { + return this.selectionService.getSelectedElements(); + } + + get isReadonly(): boolean { + return this.editMode === EditMode.READONLY; + } +} + +export type EditorContextServiceProvider = () => Promise; + diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts new file mode 100644 index 0000000..684816a --- /dev/null +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -0,0 +1,90 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { Action, InitializeCanvasBoundsAction, UpdateModelAction } from "sprotty"; +import { Deferred } from "sprotty/lib/utils/async"; + +/** + * The constraint defining when the initialization of the GLSP model is completed. + * + * Many actions, such as the `CenterAction`, can only be successfully processed if + * the GLSP model initialization is completed, that is, the model has been set, + * its bounds have been computed, the canvas bounds are available, etc. + * + * An injectable implementation of this constraint will be used by the + * `GLSPActionDispatcher` to determine when the initialization is completed. + * The action dispatcher therefore provides a promise via `onceInitialized()` + * to trigger clients that want to dispatch an action, once the initialization + * is done. + * + * For most of the cases `DefaultInitializationConstraint` can be used. In fact, + * it is bound by default. However, custom implementations can rebind other + * implementations of this constraint to, for instance, delay further before the + * `onceInitialized()` promise is fulfilled by the `GLSPActionDispatcher`. + */ +@injectable() +export abstract class ModelInitializationConstraint { + protected completion: Deferred = new Deferred(); + protected completed: boolean = false; + + get isCompleted(): boolean { + return this.completed; + } + + protected setCompleted(isCompleted: boolean) { + this.completed = isCompleted; + if (isCompleted) { this.completion.resolve(); } + } + + onInitialized(): Promise { + return this.completion.promise; + } + + notifyDispatched(action: Action) { + if (this.isCompleted) { return; } + if (this.isInitializedAfter(action)) { + this.setCompleted(true); + } + } + + abstract isInitializedAfter(action: Action): boolean; +} + +/** + * Default initialization constraint triggers after a non-empty `UpdateModelAction` + * and a subsequent `InitializeCanvasBoundsAction`. + */ +@injectable() +export class DefaultModelInitializationConstraint extends ModelInitializationConstraint { + protected seenNonEmptyUpdateModel: boolean = false; + + isInitializedAfter(action: Action) { + if (this.isNonEmptyUpdateModel(action)) { + this.seenNonEmptyUpdateModel = true; + } else if (this.seenNonEmptyUpdateModel && action.kind === InitializeCanvasBoundsAction.KIND) { + return true; + } + return false; + } + + protected isNonEmptyUpdateModel(action: Action): boolean { + if (action && action.kind === UpdateModelAction.KIND) { + const updateModelAction = action as UpdateModelAction; + return updateModelAction.newRoot !== undefined && updateModelAction.newRoot.type !== 'NONE'; + } + return false; + } +} diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts new file mode 100644 index 0000000..df606c8 --- /dev/null +++ b/packages/client/src/base/model/update-model-command.ts @@ -0,0 +1,112 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from "inversify"; +import { + Action, + ActionHandlerRegistry, + Command, + CommandActionHandler, + CommandExecutionContext, + CommandReturn, + IActionHandler, + ILogger, + SetModelAction, + SetModelCommand, + SModelRoot, + TYPES +} from "sprotty"; +import { UpdateModelAction, UpdateModelCommand } from "sprotty/lib/features/update/update-model"; + +import { IFeedbackActionDispatcher } from "../../features/tool-feedback/feedback-action-dispatcher"; +import { FeedbackCommand } from "../../features/tool-feedback/model"; +import { GLSP_TYPES } from "../types"; + +/* ActionHandler that transforms a SetModelAction into an (feedback-aware) UpdateModelAction. This can be done because in sprotty +* UpdateModel behaves the same as SetModel if no model is present yet.*/ +@injectable() +export class SetModelActionHandler implements IActionHandler { + handle(action: Action): Action | void { + if (isSetModelAction(action)) { + return new UpdateModelAction(action.newRoot, false); + } + } +} + +export function isSetModelAction(action: Action): action is SetModelAction { + return action !== undefined && (action.kind === SetModelCommand.KIND) + && (action).newRoot !== undefined; +} + +export interface SModelRootListener { + modelRootChanged(root: Readonly): void +} + +/** + * A special`UpdateModelCommand` that retrieves all registered `actions` from the `IFeedbackActionDispatcher` (if present) and applies their feedback + * to the `newRoot` before performing the update + */ +@injectable() +export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { + protected actionHandlerRegistry?: ActionHandlerRegistry; + constructor(@inject(TYPES.Action) action: UpdateModelAction, + @inject(TYPES.ILogger) protected logger: ILogger, + @inject(GLSP_TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher, + @inject(TYPES.ActionHandlerRegistryProvider) actionHandlerRegistryProvider: () => Promise, + @multiInject(GLSP_TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []) { + super(action); + actionHandlerRegistryProvider().then(registry => this.actionHandlerRegistry = registry); + } + + protected performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { + if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { + // Create a temporary context wich defines the `newRoot` as `root` + // This way we do not corrupt the redo/undo behavior of the super class + const tempContext: CommandExecutionContext = { + root: newRoot, + duration: context.duration, + logger: context.logger, + modelChanged: context.modelChanged, + modelFactory: context.modelFactory, + syncer: context.syncer + }; + + const feedbackCommands = this.getFeedbackCommands(this.actionHandlerRegistry); + feedbackCommands.forEach(command => command.execute(tempContext)); + } + + this.modelRootListeners.forEach(listener => listener.modelRootChanged(newRoot)); + return super.performUpdate(oldRoot, newRoot, context); + } + + private getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { + const result: Command[] = []; + this.feedbackActionDispatcher.getRegisteredFeedback().forEach(action => { + const handler = registry.get(action.kind).find(h => h instanceof CommandActionHandler); + if (handler instanceof CommandActionHandler) { + result.push(handler.handle(action)); + } + }); + // sort commands descanding by priority + return result.sort((a, b) => getPriority(b) - getPriority(a)); + } +} + +function getPriority(command: Command): number { + if (command instanceof FeedbackCommand) { + return command.priority; + } + return 0; +} diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts new file mode 100644 index 0000000..c1a9313 --- /dev/null +++ b/packages/client/src/base/operations/operation.ts @@ -0,0 +1,128 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, ElementAndBounds, isAction, Point } from "sprotty"; + +import { Args } from "../args"; + +export interface Operation extends Action { } + +export interface CreateOperation extends Operation { + elementTypeId: string; + args?: Args; +} + +export function isCreateOperation(object?: any): object is CreateOperation { + return isAction(object) && "elementTypeId" in object; +} + +export class CreateNodeOperation implements CreateOperation { + static readonly KIND = "createNode"; + readonly kind = CreateNodeOperation.KIND; + + constructor(public readonly elementTypeId: string, + public location?: Point, + public containerId?: string, + public args?: Args) { } +} + +export function isCreateNodeOperation(object?: any): object is CreateNodeOperation { + return isCreateOperation() && object.kind === CreateNodeOperation.KIND; +} + +export class CreateEdgeOperation implements CreateOperation { + static readonly KIND = "createEdge"; + readonly kind = CreateEdgeOperation.KIND; + + constructor(public readonly elementTypeId: string, + public sourceElementId?: string, + public targetElementId?: string, + public args?: Args) { } +} + +export function isCreateConnectionOperation(object?: any): object is CreateEdgeOperation { + return isCreateOperation() && object.kind === CreateEdgeOperation.KIND; +} + +export class DeleteElementOperation implements Operation { + static readonly KIND = "deleteElement"; + kind = DeleteElementOperation.KIND; + constructor(readonly elementIds: string[]) { } +} + +export class ChangeBoundsOperation implements Operation { + static readonly KIND = "changeBounds"; + readonly kind = ChangeBoundsOperation.KIND; + constructor(public newBounds: ElementAndBounds[]) { } +} + +export class ChangeContainerOperation implements Operation { + static readonly KIND = "changeContainer"; + readonly kind = ChangeContainerOperation.KIND; + constructor(public readonly elementId: string, + public readonly targetContainerId: string, + public readonly location?: string) { } +} + +export class ReconnectEdgeOperation implements Operation { + static readonly KIND = "reconnectEdge"; + readonly kind = ReconnectEdgeOperation.KIND; + constructor(public readonly connectionElementId: string, + public readonly sourceElementId: string, + public readonly targetElementId: string) { } +} + +export class ChangeRoutingPointsOperation implements Operation { + static readonly KIND = "changeRoutingPoints"; + readonly kind = ChangeRoutingPointsOperation.KIND; + constructor(public newRoutingPoints: ElementAndRoutingPoints[]) { } +} +export class CompoundOperation implements Operation { + static readonly KIND = "compound"; + readonly kind = CompoundOperation.KIND; + + constructor(public operationList: Operation[]) { } +} +export interface ElementAndRoutingPoints { + elementId: string + newRoutingPoints?: Point[]; +} + +export abstract class TriggerElementCreationAction implements Action { + abstract readonly kind: string; + constructor(public readonly elementTypeId: string, readonly args?: Args) { } +} + +export class TriggerNodeCreationAction extends TriggerElementCreationAction { + static readonly KIND = "triggerNodeCreation"; + kind = TriggerNodeCreationAction.KIND; +} + +export class TriggerEdgeCreationAction extends TriggerElementCreationAction { + static readonly KIND = "triggerEdgeCreation"; + kind = TriggerEdgeCreationAction.KIND; +} + +export function isTriggerElementTypeCreationAction(object?: any): object is TriggerElementCreationAction { + return isAction(object) && "elementTypeId" in object; +} + +export function isTriggerNodeCreationAction(object?: any): object is TriggerNodeCreationAction { + return isTriggerElementTypeCreationAction(object) && object.kind === TriggerNodeCreationAction.KIND; +} + +export function isTriggerEdgeCreationAction(object?: any): object is TriggerEdgeCreationAction { + return isTriggerElementTypeCreationAction(object) && object.kind === TriggerEdgeCreationAction.KIND; +} diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts new file mode 100644 index 0000000..7ea110f --- /dev/null +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { MouseListener, SModelElement } from "sprotty"; + +/** + * A mouse listener that clears the document selection on click + * + * Clicking on SVG elements doesn't update the document selection. In the context of diagramming + * with GLSP, this is unnatural, as the user would expect that clicking in the diagram (e.g. to + * select a diagram element) would clear any other selection state in the DOM. From a technical + * point of view, an active selection in the document after clicking into the diagram may also + * lead to bogus target elements in certain browser events, such as clipboard events, etc. + * + * This listener helps to overcome this problem by actively clearing any existing selection in + * the document, if the user clicks into the diagram. + */ +@injectable() +export class SelectionClearingMouseListener extends MouseListener { + mouseDown(target: SModelElement, event: MouseEvent) { + const selection = document.getSelection(); + if (selection === null) { + return []; + } + selection.removeAllRanges(); + selection.addRange(document.createRange()); + return []; + } +} diff --git a/packages/client/src/base/source-uri-aware.ts b/packages/client/src/base/source-uri-aware.ts new file mode 100644 index 0000000..c7a28b9 --- /dev/null +++ b/packages/client/src/base/source-uri-aware.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export abstract class SourceUriAware { + abstract getSourceURI(): string; +} + +export function isSourceUriAware(obj: any): obj is SourceUriAware { + return obj !== undefined && typeof obj.getSourceURI === 'function'; +} diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts new file mode 100644 index 0000000..80615c9 --- /dev/null +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from "inversify"; +import { Tool, ToolManager } from "sprotty"; + +import { distinctAdd } from "../../utils/array-utils"; +import { EditMode } from "../actions/edit-mode-action"; +import { EditModeListener, EditorContextService, EditorContextServiceProvider } from "../editor-context"; +import { GLSP_TYPES } from "../types"; + +export interface IGLSPToolManager extends ToolManager { + /* Disables all actives tools and activates only default tool with non-edit function*/ + disableEditTools(): void; +} +@injectable() +export class GLSPToolManager extends ToolManager implements IGLSPToolManager, EditModeListener { + protected editorContext?: EditorContextService; + constructor(@multiInject(GLSP_TYPES.ITool) @optional() tools: Tool[], + @multiInject(GLSP_TYPES.IDefaultTool) @optional() defaultTools: Tool[], + @inject(GLSP_TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider) { + super(); + this.registerTools(...tools); + this.registerDefaultTools(...defaultTools); + this.enableDefaultTools(); + contextServiceProvider().then(editorContext => { + editorContext.register(this); + this.editorContext = editorContext; + }); + } + + registerDefaultTools(...tools: Tool[]) { + for (const tool of tools) { + distinctAdd(this.defaultTools, tool); + } + } + + registerTools(...tools: Tool[]) { + for (const tool of tools) { + distinctAdd(this.tools, tool); + } + } + + enable(toolIds: string[]) { + this.disableActiveTools(); + let tools = toolIds.map(id => this.tool(id)); + if (this.editorContext && this.editorContext.isReadonly) { + tools = tools.filter(tool => tool && (!isGLSPTool(tool) || tool.isEditTool === false)); + } + tools.forEach(tool => { + if (tool !== undefined) { + tool.enable(); + this.actives.push(tool); + } + }); + } + + disableEditTools() { + this.disableActiveTools(); + this.enable(this.defaultTools.filter(tool => !isGLSPTool(tool) || tool.isEditTool === false).map(tool => tool.id)); + } + + editModeChanged(oldValue: string, newValue: string) { + if (oldValue === newValue) { + return; + } + if (newValue === EditMode.READONLY) { + this.disableEditTools(); + } else if (newValue === EditMode.EDITABLE) { + this.enableDefaultTools(); + } + } +} + +export interface GLSPTool extends Tool { + isEditTool: boolean; +} + +export function isGLSPTool(tool: Tool): tool is GLSPTool { + return "isEditTool" in tool && typeof tool["isEditTool"] === "boolean"; +} diff --git a/packages/client/src/base/types.ts b/packages/client/src/base/types.ts new file mode 100644 index 0000000..169ab77 --- /dev/null +++ b/packages/client/src/base/types.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export const GLSP_TYPES = { + IAsyncClipboardService: Symbol.for("IAsyncClipboardService"), + ICommandPaletteActionProviderRegistry: Symbol.for("ICommandPaletteActionProviderRegistry"), + IEditorContextServiceProvider: Symbol.for("IEditorContextProvider"), + IFeedbackActionDispatcher: Symbol.for("IFeedbackActionDispatcher"), + IToolFactory: Symbol.for("Factory"), + ITypeHintProvider: Symbol.for("ITypeHintProvider"), + IMovementRestrictor: Symbol.for("IMovmementRestrictor"), + SelectionService: Symbol.for("SelectionService"), + SelectionListener: Symbol.for("SelectionListener"), + SModelRootListener: Symbol.for("SModelRootListener"), + MouseTool: Symbol.for("MouseTool"), + IContextMenuService: Symbol.for("IContextMenuService"), + IContextMenuServiceProvider: Symbol.for("IContextMenuServiceProvider"), + IContextMenuProviderRegistry: Symbol.for("IContextMenuProviderRegistry"), + IContextMenuProvider: Symbol.for("IContextMenuProvider"), + ICopyPasteHandler: Symbol.for("ICopyPasteHandler"), + IGLSPToolManager: Symbol.for("IGLSPToolManager"), + ITool: Symbol.for("ITool"), + IDefaultTool: Symbol.for("IDefaultTool"), + IEditModeListener: Symbol.for("IEditModeListener") +}; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts new file mode 100644 index 0000000..1261060 --- /dev/null +++ b/packages/client/src/features/change-bounds/model.ts @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + BoundsAware, + Hoverable, + hoverFeedbackFeature, + isBoundsAware, + isMoveable, + isSelectable, + Locateable, + SChildElement, + Selectable, + SModelElement, + SParentElement +} from "sprotty"; + +export const resizeFeature = Symbol("resizeFeature"); + +export interface Resizable extends BoundsAware, Selectable { +} + +export function isResizable(element: SModelElement): element is SParentElement & Resizable { + return isBoundsAware(element) && isSelectable(element) && element instanceof SParentElement && element.hasFeature(resizeFeature); +} + +export enum ResizeHandleLocation { + TopLeft = "top-left", + TopRight = "top-right", + BottomLeft = "bottom-left", + BottomRight = "bottom-right" +} + +export function isBoundsAwareMoveable(element: SModelElement): element is SModelElement & Locateable & BoundsAware { + return isMoveable(element) && isBoundsAware(element); +} + +export class SResizeHandle extends SChildElement implements Hoverable { + static readonly TYPE = 'resize-handle'; + type: string = SResizeHandle.TYPE; + hoverFeedback: boolean = false; + location?: ResizeHandleLocation; + + constructor(location?: ResizeHandleLocation) { + super(); + this.location = location; + } + + hasFeature(feature: symbol): boolean { + return feature === hoverFeedbackFeature; + } +} + +export function addResizeHandles(element: SParentElement) { + removeResizeHandles(element); + element.add(new SResizeHandle(ResizeHandleLocation.TopLeft)); + element.add(new SResizeHandle(ResizeHandleLocation.TopRight)); + element.add(new SResizeHandle(ResizeHandleLocation.BottomLeft)); + element.add(new SResizeHandle(ResizeHandleLocation.BottomRight)); +} + +export function removeResizeHandles(element: SParentElement) { + element.removeAll(child => child instanceof SResizeHandle); +} diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts new file mode 100644 index 0000000..0b43400 --- /dev/null +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { BoundsAware, Point, SModelElement, SNode, SParentElement } from "sprotty"; + +import { toAbsoluteBounds } from "../../utils/viewpoint-util"; +import { ModifyCSSFeedbackAction } from "../tool-feedback/css-feedback"; +import { isBoundsAwareMoveable, SResizeHandle } from "./model"; + +export interface IMovementRestrictor { + validate(newLocation: Point, element: SModelElement): boolean; + cssClasses?: string[]; +} + +export function createMovementRestrictionFeedback(element: SModelElement, movementRestrictor: IMovementRestrictor): ModifyCSSFeedbackAction { + const elements: SModelElement[] = [element]; + if (element instanceof SParentElement) { + element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); + } + return new ModifyCSSFeedbackAction(elements, movementRestrictor.cssClasses); + +} + +export function removeMovementRestrictionFeedback(element: SModelElement, movementRestrictor: IMovementRestrictor): ModifyCSSFeedbackAction { + const elements: SModelElement[] = [element]; + if (element instanceof SParentElement) { + element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); + } + + return new ModifyCSSFeedbackAction(elements, undefined, movementRestrictor.cssClasses); +} + +@injectable() +export class NoOverlapMovmentRestrictor implements IMovementRestrictor { + validate(newLocation: Point, element: SModelElement): boolean { + if (!isBoundsAwareMoveable(element)) { + return false; + } + // Create ghost element at the newLocation + const ghostElement = Object.create(element) as SModelElement & BoundsAware; + ghostElement.bounds = { + x: newLocation.x, + y: newLocation.y, + width: element.bounds.width, + height: element.bounds.height + }; + ghostElement.type = "Ghost"; + ghostElement.id = element.id; + return !Array.from(element.root.index.all().filter(e => e.id !== ghostElement.id && e !== ghostElement.root && (e instanceof SNode)) + .map(e => e as SModelElement & BoundsAware)).some(e => areOverlapping(e, ghostElement)); + } + + cssClasses = ["movement-not-allowed"]; +} + +export function areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware) { + const b1 = toAbsoluteBounds(element1); + const b2 = toAbsoluteBounds(element2); + const r1TopLeft: Point = b1; + const r1BottomRight = { x: b1.x + b1.width, y: b1.y + b1.height }; + const r2TopLeft: Point = b2; + const r2BottomRight = { x: b2.x + b2.width, y: b2.y + b2.height }; + + // If one rectangle is on left side of other + if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) + return false; + + // If one rectangle is above other + if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) + return false; + + return true; + +} diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts new file mode 100644 index 0000000..d3f7c0d --- /dev/null +++ b/packages/client/src/features/change-bounds/snap.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { ISnapper, Point, SModelElement } from "sprotty"; + +@injectable() +export class GridSnapper implements ISnapper { + get gridX() { + return 10; + } + + get gridY() { + return 10; + } + + snap(position: Point, element: SModelElement): Point { + return { + x: Math.round(position.x / this.gridX) * this.gridX, + y: Math.round(position.y / this.gridY) * this.gridY + }; + } +} diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts new file mode 100644 index 0000000..fc5738b --- /dev/null +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, postConstruct } from "inversify"; +import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from "sprotty"; + +@injectable() +export class CommandPaletteTool implements Tool { + static ID = "glsp.command-palette-tool"; + + protected commandPaletteKeyListener: KeyListener; + @inject(KeyTool) protected keyTool: KeyTool; + + @postConstruct() + protected postConstruct() { + this.commandPaletteKeyListener = this.createCommandPaleteKeyListener(); + } + + get id(): string { + return CommandPaletteTool.ID; + } + enable(): void { + this.keyTool.register(this.commandPaletteKeyListener); + } + disable(): void { + this.keyTool.deregister(this.commandPaletteKeyListener); + } + + protected createCommandPaleteKeyListener(): KeyListener { + return new CommandPaletteKeyListener(); + } +} diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/di.config.ts new file mode 100644 index 0000000..29e9c3c --- /dev/null +++ b/packages/client/src/features/command-palette/di.config.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../../../css/command-palette.css"; + +import { ContainerModule } from "inversify"; +import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { CommandPaletteTool } from "./command-palette-tool"; +import { ServerCommandPaletteActionProvider } from "./server-command-palette-provider"; + +const glspCommandPaletteModule = new ContainerModule((bind) => { + bind(CommandPalette).toSelf().inSingletonScope(); + bind(TYPES.IUIExtension).toService(CommandPalette); + bind(CommandPaletteActionProviderRegistry).toSelf().inSingletonScope(); + bind(TYPES.ICommandPaletteActionProviderRegistry).toService(CommandPaletteActionProviderRegistry); + bind(TYPES.ICommandPaletteActionProvider).to(ServerCommandPaletteActionProvider); + bind(GLSP_TYPES.IDefaultTool).to(CommandPaletteTool); +}); + +export default glspCommandPaletteModule; diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts new file mode 100644 index 0000000..803c90d --- /dev/null +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, ICommandPaletteActionProvider, LabeledAction, Point, SModelElement, TYPES } from "sprotty"; + +import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { isSetContextActionsAction, RequestContextActions } from "../../base/actions/context-actions"; +import { EditorContextService } from "../../base/editor-context"; + +export namespace ServerCommandPalette { + export const CONTEXT_ID = "command-palette"; + export const TEXT = "text"; + export const INDEX = "index"; +} + +@injectable() +export class ServerCommandPaletteActionProvider implements ICommandPaletteActionProvider { + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(EditorContextService) protected editorContext: EditorContextService; + + getActions(root: Readonly, text: string, lastMousePosition?: Point, index?: number): Promise { + const requestAction = new RequestContextActions(ServerCommandPalette.CONTEXT_ID, this.editorContext.get({ + [ServerCommandPalette.TEXT]: text, + [ServerCommandPalette.INDEX]: index ? index : 0 + })); + return this.actionDispatcher.requestUntil(requestAction).then(response => this.getPaletteActionsFromResponse(response)); + } + + getPaletteActionsFromResponse(action: Action): LabeledAction[] { + if (isSetContextActionsAction(action)) { + return action.actions; + } + return []; + } +} diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts new file mode 100644 index 0000000..b2514e8 --- /dev/null +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from "sprotty"; + +import { EditorContextServiceProvider } from "../../base/editor-context"; +import { DeleteElementOperation } from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; + +@injectable() +export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { + @inject(GLSP_TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; + + async getItems(root: Readonly, lastMousePosition?: Point): Promise { + const editorContextService = await this.editorContextServiceProvider(); + + return [ + { + id: "delete", + label: "Delete", + sortString: "d", + group: "edit", + actions: [new DeleteElementOperation(editorContextService.selectedElements.map(e => e.id))], + isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 + } + ]; + } +} diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts new file mode 100644 index 0000000..0cbbc97 --- /dev/null +++ b/packages/client/src/features/context-menu/di.config.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { ContextMenuProviderRegistry, IContextMenuService, TYPES } from "sprotty"; + +import { SelectionServiceAwareContextMenuMouseListener } from "./selection-service-aware-context-menu-mouse-listener"; +import { ServerContextMenuItemProvider } from "./server-context-menu-provider"; + +const glspContextMenuModule = new ContainerModule(bind => { + bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => { + return () => { + return new Promise((resolve, reject) => { + if (ctx.container.isBound(TYPES.IContextMenuService)) { + resolve(ctx.container.get(TYPES.IContextMenuService)); + } else { + reject(); + } + }); + }; + }); + bind(TYPES.MouseListener).to(SelectionServiceAwareContextMenuMouseListener); + bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); + bind(TYPES.IContextMenuItemProvider).to(ServerContextMenuItemProvider); +}); + +export default glspContextMenuModule; diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts new file mode 100644 index 0000000..affc5ad --- /dev/null +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + ContextMenuProviderRegistry, + findParentByFeature, + IContextMenuServiceProvider, + isSelectable, + MouseListener, + SModelElement, + TYPES +} from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { SelectionService } from "../select/selection-service"; + +@injectable() +export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { + + constructor( + @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService: IContextMenuServiceProvider, + @inject(TYPES.IContextMenuProviderRegistry) @optional() protected readonly menuProvider: ContextMenuProviderRegistry, + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService) { + super(); + } + + mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + if (event.button === 2 && this.contextMenuService && this.menuProvider) { + const mousePosition = { x: event.x, y: event.y }; + const selectableTarget = findParentByFeature(target, isSelectable); + if (selectableTarget) { + selectableTarget.selected = true; + this.selectionService.updateSelection(target.root, [selectableTarget.id], []); + } + Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]) + .then(([menuService, menuItems]) => menuService.show(menuItems, mousePosition)); + } + return []; + } +} diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts new file mode 100644 index 0000000..451bac4 --- /dev/null +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, IContextMenuItemProvider, isSelected, LabeledAction, Point, SModelElement, TYPES } from "sprotty"; + +import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { isSetContextActionsAction, RequestContextActions } from "../../base/actions/context-actions"; +import { EditorContextService } from "../../base/editor-context"; + +export namespace ServerContextMenu { + export const CONTEXT_ID = "context-menu"; +} + +@injectable() +export class ServerContextMenuItemProvider implements IContextMenuItemProvider { + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(EditorContextService) protected editorContext: EditorContextService; + + getItems(root: Readonly, lastMousePosition?: Point): Promise { + const selectedElementIds = Array.from(root.index.all().filter(isSelected).map(e => e.id)); + const context = this.editorContext.getWithSelection(selectedElementIds); + const requestAction = new RequestContextActions(ServerContextMenu.CONTEXT_ID, context); + return this.actionDispatcher.requestUntil(requestAction).then(response => this.getContextActionsFromResponse(response)); + } + + getContextActionsFromResponse(action: Action): LabeledAction[] { + if (isSetContextActionsAction(action)) { + return action.actions; + } + return []; + } +} diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/client/src/features/copy-paste/copy-paste-actions.ts new file mode 100644 index 0000000..d748194 --- /dev/null +++ b/packages/client/src/features/copy-paste/copy-paste-actions.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, generateRequestId, RequestAction, ResponseAction } from "sprotty"; + +import { EditorContext } from "../../base/editor-context"; + +export class CutOperationAction implements Action { + static readonly KIND = "cut"; + kind = CutOperationAction.KIND; + constructor(public readonly editorContext: EditorContext) { } +} + +export class PasteOperationAction implements Action { + static readonly KIND = "paste"; + kind = PasteOperationAction.KIND; + constructor( + public readonly clipboardData: ClipboardData, + public readonly editorContext: EditorContext) { } +} + +export class RequestClipboardDataAction implements RequestAction { + static readonly KIND = "requestClipboardData"; + kind = RequestClipboardDataAction.KIND; + + constructor( + public readonly editorContext: EditorContext, + public readonly requestId: string = generateRequestId()) { } + + static create(editorContext: EditorContext): RequestAction { + return new RequestClipboardDataAction(editorContext); + } +} + +export type ClipboardData = { [format: string]: string }; + +export class SetClipboardDataAction implements ResponseAction { + static readonly KIND = "setClipboardData"; + kind = SetClipboardDataAction.KIND; + constructor( + public readonly clipboardData: ClipboardData, + public readonly responseId: string = '') { } +} diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts new file mode 100644 index 0000000..d70aa50 --- /dev/null +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + IActionDispatcher, + IActionHandler, + IContextMenuItemProvider, + isSelected, + MenuItem, + Point, + SModelRoot, + TYPES +} from "sprotty/lib"; + +import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; + +export class InvokeCopyAction implements Action { + static readonly KIND = "invoke-copy"; + kind = InvokeCopyAction.KIND; +} + +export class InvokePasteAction implements Action { + static readonly KIND = "invoke-paste"; + kind = InvokePasteAction.KIND; +} + +export class InvokeCutAction implements Action { + static readonly KIND = "invoke-cut"; + kind = InvokeCutAction.KIND; +} + +@injectable() +export class InvokeCopyPasteActionHandler implements IActionHandler { + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + handle(action: Action) { + switch (action.kind) { + case InvokeCopyAction.KIND: + document.execCommand('copy'); + break; + case InvokePasteAction.KIND: + // in a browser without additional permission we can't invoke the paste command + // the user needs to invoke it from the browser, so notify the user about it + this.notifyUserToUseShortcut(); + break; + case InvokeCutAction.KIND: + document.execCommand('cut'); + break; + } + } + + protected notifyUserToUseShortcut() { + const message = 'Please use the browser\'s paste command or shortcut.'; + const timeout = 10000; + const severity = 'WARNING'; + this.dispatcher.dispatchAll([ + { kind: GLSPServerStatusAction.KIND, severity, timeout, message }, + { kind: ServerMessageAction.KIND, severity, timeout, message } + ]); + } +} + +@injectable() +export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvider { + getItems(root: Readonly, lastMousePosition?: Point): Promise { + const hasSelectedElements = Array.from(root.index.all().filter(isSelected)).length > 0; + return Promise.resolve([ + { + id: "copy", label: "Copy", group: "copy-paste", + actions: [new InvokeCopyAction()], isEnabled: () => hasSelectedElements + }, + { + id: "cut", label: "Cut", group: "copy-paste", + actions: [new InvokeCutAction()], isEnabled: () => hasSelectedElements + }, + { + id: "paste", label: "Paste", group: "copy-paste", + actions: [new InvokePasteAction()], isEnabled: () => true + } + ]); + } +} diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts new file mode 100644 index 0000000..50d3e70 --- /dev/null +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -0,0 +1,142 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { TYPES, ViewerOptions } from "sprotty"; +import { v4 as uuid } from "uuid"; + +import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { EditorContextService } from "../../base/editor-context"; +import { GLSP_TYPES } from "../../base/types"; +import { ClipboardData, CutOperationAction, PasteOperationAction, RequestClipboardDataAction } from "./copy-paste-actions"; + +export interface ICopyPasteHandler { + handleCopy(e: ClipboardEvent): void; + handleCut(e: ClipboardEvent): void; + handlePaste(e: ClipboardEvent): void; +} + +export interface IAsyncClipboardService { + clear(): void; + put(data: ClipboardData, id?: string): void; + get(id?: string): ClipboardData | undefined; +} + +/** + * A local implementation of the async clipboard interface. + * + * This implementation just stores the clipboard data in memory, but not in the clipboard. + * This implementation can be used if you don't need to support cross-widget/browser/application + * data transfer and you would like to avoid to require the permission of the user for accessing the + * system clipboard asynchronously. + * + * In order to detect whether the user copied something else since we recorded the clipboard data + * we put a uuid into the system clipboard synchronously. If on paste this ID has changed or is not + * available anymore, we know that the user copied in another application or context, so we shouldn't + * paste what we have stored locally and just return undefined. + * + * Real async clipboard service implementations can just ignore the ID that is passed and rely on the + * system clipboard's content instead. + */ +@injectable() +export class LocalClipboardService implements IAsyncClipboardService { + protected currentId?: string; + protected data?: ClipboardData; + + clear() { + this.currentId = undefined; + this.data = undefined; + } + + put(data: ClipboardData, id: string) { + this.currentId = id; + this.data = data; + } + + get(id?: string): ClipboardData | undefined { + if (id !== this.currentId) { + return undefined; + } + return this.data; + } +} + +interface ClipboardId { + readonly clipboardId: string; +} + +function toClipboardId(clipboardId: string): string { + return JSON.stringify({ clipboardId }); +} + +function isClipboardId(jsonData: any): jsonData is ClipboardId { + return jsonData !== undefined && 'clipboardId' in jsonData; +} + +function getClipboardIdFromDataTransfer(dataTransfer: DataTransfer): string | undefined { + const jsonString = dataTransfer.getData(CLIPBOARD_DATA_FORMAT); + const jsonObject = jsonString ? JSON.parse(jsonString) : undefined; + return isClipboardId(jsonObject) ? jsonObject.clipboardId : undefined; +} + +const CLIPBOARD_DATA_FORMAT = "application/json"; + +@injectable() +export class ServerCopyPasteHandler implements ICopyPasteHandler { + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; + @inject(GLSP_TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; + @inject(EditorContextService) protected editorContext: EditorContextService; + + handleCopy(e: ClipboardEvent) { + if (e.clipboardData && this.shouldCopy(e)) { + const clipboardId = uuid(); + e.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); + this.actionDispatcher + .request(RequestClipboardDataAction.create(this.editorContext.get())) + .then(action => this.clipboadService.put(action.clipboardData, clipboardId)); + e.preventDefault(); + } else { + if (e.clipboardData) { e.clipboardData.clearData(); } + this.clipboadService.clear(); + } + } + + handleCut(e: ClipboardEvent): void { + if (e.clipboardData && this.shouldCopy(e)) { + this.handleCopy(e); + this.actionDispatcher.dispatch(new CutOperationAction(this.editorContext.get())); + e.preventDefault(); + } + } + + handlePaste(e: ClipboardEvent): void { + if (e.clipboardData) { + const clipboardId = getClipboardIdFromDataTransfer(e.clipboardData); + const clipboardData = this.clipboadService.get(clipboardId); + if (clipboardData) { + this.actionDispatcher.dispatch(new PasteOperationAction(clipboardData, this.editorContext.get())); + } + e.preventDefault(); + } + } + + protected shouldCopy(e: ClipboardEvent) { + return this.editorContext.get().selectedElementIds.length > 0 && document.activeElement instanceof SVGElement + && document.activeElement.parentElement && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; + } + +} diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/di.config.ts new file mode 100644 index 0000000..ccf2c8f --- /dev/null +++ b/packages/client/src/features/copy-paste/di.config.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureActionHandler } from "sprotty/lib"; + +import { GLSP_TYPES } from "../../base/types"; +import { + CopyPasteContextMenuItemProvider, + InvokeCopyAction, + InvokeCopyPasteActionHandler, + InvokeCutAction, + InvokePasteAction +} from "./copy-paste-context-menu"; +import { LocalClipboardService, ServerCopyPasteHandler } from "./copy-paste-handler"; + +export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { + bind(GLSP_TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); + bind(GLSP_TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); +}); + +/** + * This module is not required if the diagram is deployed in Theia but only intended to be used + * in a standalone deployment of GLSP. If the GLSP diagram in Theia use the Theia-native + * `CopyPasteMenuContribution` in `glsp-theia-integration` instead. + */ +export const copyPasteContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { + bind(GLSP_TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); + bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); + configureActionHandler({ bind, isBound }, InvokeCopyAction.KIND, InvokeCopyPasteActionHandler); + configureActionHandler({ bind, isBound }, InvokeCutAction.KIND, InvokeCopyPasteActionHandler); + configureActionHandler({ bind, isBound }, InvokePasteAction.KIND, InvokeCopyPasteActionHandler); +}); diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts new file mode 100644 index 0000000..7b25603 --- /dev/null +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { + Decoration, + DecorationPlacer, + isSizeable, + ORIGIN_POINT, + Point, + SChildElement, + SModelElement, + SRoutableElement +} from "sprotty"; + +@injectable() +export class GlspDecorationPlacer extends DecorationPlacer { + + protected static readonly DECORATION_OFFSET: Point = { x: 12, y: 10 }; + + protected getPosition(element: SModelElement & Decoration): Point { + if (element instanceof SChildElement && element.parent instanceof SRoutableElement) { + return super.getPosition(element); + } + if (isSizeable(element)) + return { + x: GlspDecorationPlacer.DECORATION_OFFSET.x * element.bounds.width, + y: GlspDecorationPlacer.DECORATION_OFFSET.y * element.bounds.height + }; + return ORIGIN_POINT; + } + +} diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts new file mode 100644 index 0000000..e64aefb --- /dev/null +++ b/packages/client/src/features/decoration/di.config.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../../../css/decoration.css"; + +import { ContainerModule } from "inversify"; +import { configureModelElement, SIssueMarker, TYPES } from "sprotty"; + +import { GlspDecorationPlacer } from "./decoration-placer"; +import { GlspIssueMarkerView } from "./view"; + +const glspDecorationModule = new ContainerModule((bind, _unbind, isBound) => { + configureModelElement({ bind, isBound }, 'marker', SIssueMarker, GlspIssueMarkerView); + bind(GlspDecorationPlacer).toSelf().inSingletonScope(); + bind(TYPES.IVNodePostprocessor).toService(GlspDecorationPlacer); +}); + +export default glspDecorationModule; diff --git a/packages/client/src/features/decoration/view.tsx b/packages/client/src/features/decoration/view.tsx new file mode 100644 index 0000000..4b5e46c --- /dev/null +++ b/packages/client/src/features/decoration/view.tsx @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import * as snabbdom from "snabbdom-jsx"; +import { VNode } from "snabbdom/vnode"; +import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity } from "sprotty"; + +const JSX = { createElement: snabbdom.svg }; + +@injectable() +export class GlspIssueMarkerView extends IssueMarkerView { + + render(marker: SIssueMarker, context: RenderingContext): VNode { + const maxSeverity = super.getMaxSeverity(marker); + const group = + + + + + ; + setClass(group, 'sprotty-' + maxSeverity, true); + return group; + } + + protected get radius(): number { + return 8; // var(--theia-icon-size)=16px => 16/2=8 + } + + protected getGlspIssueMarkerPath(severity: SIssueSeverity): string { + switch (severity) { + // paths used here are svg versions of FontAwesome icons, resized to var(--theia-icon-size) 16px + case 'error': // 'fa-times-circle' + return 'M 8,0 C 3.5806452,0 0,3.58065 0,8 c 0,4.41935 3.5806452,8 8,8 4.419355,0 8,-3.58065 8,-8 C 16,3.58065 12.419355,0 8,0 Z m 3.922581,10.1 c 0.151613,0.15161 0.151613,0.39677 0,0.54839 l -1.27742,1.27419 c -0.151613,0.15161 -0.396774,0.15161 -0.548387,0 L 8,9.80645 5.9,11.92258 c -0.1516129,0.15161 -0.3967742,0.15161 -0.5483871,0 L 4.0774194,10.64516 c -0.1516129,-0.15161 -0.1516129,-0.39677 0,-0.54839 L 6.1935484,8 4.0774194,5.9 c -0.1516129,-0.15161 -0.1516129,-0.39677 0,-0.54839 L 5.3548387,4.07419 c 0.1516129,-0.15161 0.3967742,-0.15161 0.5483871,0 L 8,6.19355 10.1,4.07742 c 0.151613,-0.15161 0.396774,-0.15161 0.548387,0 l 1.277419,1.27742 c 0.151613,0.15161 0.151613,0.39677 0,0.54839 L 9.8064516,8 Z'; + case 'warning': // 'fa-exclamation-circle' + return 'm 16,8 c 0,4.41926 -3.582032,8 -8,8 C 3.5820323,16 0,12.41926 0,8 0,3.58332 3.5820323,0 8,0 c 4.417968,0 8,3.58332 8,8 z M 8,9.6129 c -0.8195161,0 -1.483871,0.66436 -1.483871,1.48387 0,0.81952 0.6643549,1.48388 1.483871,1.48388 0.8195161,0 1.483871,-0.66436 1.483871,-1.48388 C 9.483871,10.27726 8.8195161,9.6129 8,9.6129 Z M 6.5911935,4.27916 6.8304839,8.66626 c 0.011194,0.20529 0.1809355,0.366 0.3865161,0.366 h 1.566 c 0.2055806,0 0.3753226,-0.16071 0.3865161,-0.366 L 9.4088065,4.27916 C 9.4209035,4.05742 9.2443548,3.87097 9.0222903,3.87097 H 6.9776774 c -0.2220645,0 -0.3985806,0.18645 -0.3864839,0.40819 z'; + case 'info': // 'fa-info-circle information' + return 'M 8.0000004,0 C 3.5820324,0 0,3.58332 0,8 0,12.41926 3.5820324,16 8.0000004,16 12.417968,16 16,12.41926 16,8 16,3.58332 12.417968,0 8.0000004,0 Z m 0,3.54839 c 0.748258,0 1.354839,0.60658 1.354839,1.35484 0,0.74825 -0.606581,1.35483 -1.354839,1.35483 -0.748258,0 -1.354839,-0.60658 -1.354839,-1.35483 0,-0.74826 0.606581,-1.35484 1.354839,-1.35484 z m 1.806452,8.19355 c 0,0.21377 -0.173323,0.38709 -0.387097,0.38709 h -2.83871 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.38709 v -0.7742 c 0,-0.21377 0.173323,-0.38709 0.387097,-0.38709 h 0.387097 V 8.51613 h -0.387097 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.3871 V 7.35484 c 0,-0.21378 0.173323,-0.3871 0.387097,-0.3871 h 2.064516 c 0.213774,0 0.387097,0.17332 0.387097,0.3871 v 3.22581 h 0.387097 c 0.213774,0 0.387097,0.17332 0.387097,0.38709 z'; + } + } + +} diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/edit-label/di.config.ts new file mode 100644 index 0000000..f9f9d1f --- /dev/null +++ b/packages/client/src/features/edit-label/di.config.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { ApplyLabelEditCommand, configureCommand, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { DirectLabelEditTool } from "./edit-label-tool"; +import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from "./edit-label-validator"; + +const glspEditLabelModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { + bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); + bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); + bind(GLSP_TYPES.IDefaultTool).to(DirectLabelEditTool); + configureCommand({ bind, isBound }, ApplyLabelEditCommand); +}); + +export default glspEditLabelModule; diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/edit-label/edit-label-tool.ts new file mode 100644 index 0000000..840f00d --- /dev/null +++ b/packages/client/src/features/edit-label/edit-label-tool.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from "sprotty"; + +import { BaseGLSPTool } from "../tools/base-glsp-tool"; + +@injectable() +export class DirectLabelEditTool extends BaseGLSPTool { + static readonly ID = "glsp.direct-label-edit-tool"; + + protected editLabelMouseListener: MouseListener; + protected editLabelKeyListener: KeyListener; + + get id(): string { + return DirectLabelEditTool.ID; + } + + protected createEditLabelMouseListener(): MouseListener { + return new EditLabelMouseListener(); + } + + protected createEditLabelKeyListener(): KeyListener { + return new EditLabelKeyListener(); + } + + enable(): void { + this.editLabelKeyListener = this.createEditLabelKeyListener(); + this.editLabelMouseListener = this.createEditLabelMouseListener(); + this.mouseTool.register(this.editLabelMouseListener); + this.keyTool.register(this.editLabelKeyListener); + } + + disable(): void { + this.keyTool.deregister(this.editLabelKeyListener); + this.mouseTool.deregister(this.editLabelMouseListener); + } +} diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts new file mode 100644 index 0000000..57529e7 --- /dev/null +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + EditableLabel, + EditLabelValidationResult, + IEditLabelValidationDecorator, + IEditLabelValidator, + Severity, + SModelElement, + TYPES +} from "sprotty"; + +import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { + isSetEditValidationResultAction, + RequestEditValidationAction, + ValidationStatus +} from "../../base/actions/edit-validation-actions"; + +export namespace LabelEditValidation { + export const CONTEXT_ID = 'label-edit'; + export function toEditLabelValidationResult(status: ValidationStatus): EditLabelValidationResult { + const message = status.message; + let severity = 'ok'; + if (ValidationStatus.isError(status)) { + severity = 'error'; + } else if (ValidationStatus.isWarning(status)) { + severity = 'warning'; + } + return { message, severity }; + } +} + +export class ValidateLabelEditAction extends RequestEditValidationAction { + constructor(value: string, labelId: string) { + super(LabelEditValidation.CONTEXT_ID, labelId, value); + } +} + +@injectable() +export class ServerEditLabelValidator implements IEditLabelValidator { + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + + validate(value: string, label: EditableLabel & SModelElement): Promise { + const action = new ValidateLabelEditAction(value, label.id); + return this.actionDispatcher.requestUntil(action).then(response => this.getValidationResultFromResponse(response)); + } + + getValidationResultFromResponse(action: Action): EditLabelValidationResult { + if (isSetEditValidationResultAction(action)) { + return LabelEditValidation.toEditLabelValidationResult(action.status); + } + return { severity: 'ok' }; + } + +} + +@injectable() +export class BalloonLabelValidationDecorator implements IEditLabelValidationDecorator { + + decorate(input: HTMLInputElement, result: EditLabelValidationResult): void { + const containerElement = input.parentElement; + if (!containerElement) { + return; + } + if (result.message) { + containerElement.setAttribute('data-balloon', result.message); + containerElement.setAttribute('data-balloon-pos', 'up-left'); + containerElement.setAttribute('data-balloon-visible', 'true'); + } + switch (result.severity) { + case 'ok': containerElement.classList.add('validation-ok'); break; + case 'warning': containerElement.classList.add('validation-warning'); break; + case 'error': containerElement.classList.add('validation-error'); break; + } + } + + dispose(input: HTMLInputElement): void { + const containerElement = input.parentElement; + if (containerElement) { + containerElement.removeAttribute('data-balloon'); + containerElement.removeAttribute('data-balloon-pos'); + containerElement.removeAttribute('data-balloon-visible'); + containerElement.classList.remove('validation-ok', 'validation-warning', 'validation-error'); + } + } +} diff --git a/packages/client/src/features/execute/di.config.ts b/packages/client/src/features/execute/di.config.ts new file mode 100644 index 0000000..2692e5a --- /dev/null +++ b/packages/client/src/features/execute/di.config.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { TYPES } from "sprotty"; + +import { ExecuteCommandMouseListener } from "./execute-command"; + +const executeCommandModule = new ContainerModule(bind => { + bind(TYPES.MouseListener).to(ExecuteCommandMouseListener); +}); + +export default executeCommandModule; diff --git a/packages/client/src/features/execute/execute-command.ts b/packages/client/src/features/execute/execute-command.ts new file mode 100644 index 0000000..a3cbe2e --- /dev/null +++ b/packages/client/src/features/execute/execute-command.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, findParentByFeature, MouseListener, SModelElement } from "sprotty"; + +import { isCommandExecutor } from "./model"; + +export class ExecuteServerCommandAction implements Action { + static readonly KIND = "executeServerCommand"; + kind = ExecuteServerCommandAction.KIND; + constructor(public readonly commandId: String, public readonly options?: { [key: string]: string }) { } +} + +export class ExecuteCommandMouseListener extends MouseListener { + doubleClick(target: SModelElement, event: WheelEvent): (Action | Promise)[] { + const result: Action[] = []; + const commandExecutorTarget = findParentByFeature(target, isCommandExecutor); + if (commandExecutorTarget) { + result.push(new ExecuteServerCommandAction(commandExecutorTarget.commandId, { invokerId: commandExecutorTarget.id })); + } + + return result; + } +} diff --git a/packages/client/src/features/execute/model.ts b/packages/client/src/features/execute/model.ts new file mode 100644 index 0000000..cb114b9 --- /dev/null +++ b/packages/client/src/features/execute/model.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SModelElement, SModelExtension } from "sprotty"; + +export const executeCommandFeature = Symbol('executeFeature'); + +export interface CommandExecutor extends SModelExtension { + commandId: string +} + +export function isCommandExecutor(element: SModelElement): element is SModelElement & CommandExecutor { + return element.hasFeature(executeCommandFeature); +} diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/di.config.ts new file mode 100644 index 0000000..1dd3407 --- /dev/null +++ b/packages/client/src/features/hints/di.config.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureActionHandler, configureCommand } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { SetTypeHintsAction } from "./request-type-hints-action"; +import { ApplyTypeHintsCommand, TypeHintProvider } from "./type-hints"; + +const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { + bind(TypeHintProvider).toSelf().inSingletonScope(); + bind(GLSP_TYPES.ITypeHintProvider).toService(TypeHintProvider); + configureActionHandler({ bind, isBound }, SetTypeHintsAction.KIND, TypeHintProvider); + configureCommand({ bind, isBound }, ApplyTypeHintsCommand); +}); + +export default modelHintsModule; diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts new file mode 100644 index 0000000..0fc677f --- /dev/null +++ b/packages/client/src/features/hints/model.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SModelElement, SModelElementSchema, SModelExtension } from "sprotty"; + +export const containerFeature = Symbol("containable"); +export interface Containable extends SModelExtension { + isContainableElement(input: SModelElement | SModelElementSchema | string): boolean +} + +export function isContainable(element: SModelElement): element is SModelElement & Containable { + return element.hasFeature(containerFeature); +} + +export const reparentFeature = Symbol("reparentFeature"); +export interface Reparentable extends SModelExtension { } + +export function isReparentable(element: SModelElement): element is SModelElement & Reparentable { + return element.hasFeature(reparentFeature); +} diff --git a/packages/client/src/features/hints/request-type-hints-action.ts b/packages/client/src/features/hints/request-type-hints-action.ts new file mode 100644 index 0000000..d38d475 --- /dev/null +++ b/packages/client/src/features/hints/request-type-hints-action.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from "sprotty"; + +import { EdgeTypeHint, ShapeTypeHint } from "./type-hints"; + +export class RequestTypeHintsAction implements Action { + static readonly KIND = "requestTypeHints"; + kind = RequestTypeHintsAction.KIND; + constructor(public readonly diagramType?: string) { } +} + +export class SetTypeHintsAction implements Action { + static readonly KIND = "setTypeHints"; + kind = SetTypeHintsAction.KIND; + constructor(public readonly shapeHints: ShapeTypeHint[], public readonly edgeHints: EdgeTypeHint[]) { } +} + +export function isSetTypeHintsAction(action: Action): action is SetTypeHintsAction { + return action !== undefined && (action.kind === SetTypeHintsAction.KIND) + && (action).shapeHints !== undefined && (action).edgeHints !== undefined; +} diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts new file mode 100644 index 0000000..87277f9 --- /dev/null +++ b/packages/client/src/features/hints/type-hints.ts @@ -0,0 +1,256 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + CommandExecutionContext, + CommandReturn, + Connectable, + connectableFeature, + deletableFeature, + editFeature, + FeatureSet, + IActionHandler, + ICommand, + moveFeature, + SEdge, + SModelElement, + SModelElementSchema, + SModelRoot, + SShapeElement, + TYPES +} from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { getElementTypeId, hasCompatibleType } from "../../utils/smodel-util"; +import { resizeFeature } from "../change-bounds/model"; +import { reconnectFeature } from "../reconnect/model"; +import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; +import { FeedbackCommand } from "../tool-feedback/model"; +import { Containable, containerFeature, reparentFeature } from "./model"; +import { isSetTypeHintsAction } from "./request-type-hints-action"; + +export abstract class TypeHint { + /** + The id of the element. + */ + readonly elementTypeId: string; + + /** + * Specifies whether the element can be relocated. + */ + readonly repositionable: boolean; + + /** + * Specifices wheter the element can be deleted + */ + readonly deletable: boolean; +} + +export class ShapeTypeHint extends TypeHint { + /** + * Specifies whether the element can be resized. + */ + readonly resizable: boolean; + + /** + * Specifies whether the element can be moved to another parent + */ + readonly reparentable: boolean; + + /** + * The types of elements that can be contained by this element (if any) + */ + readonly containableElementTypeIds?: string[]; +} + +export class EdgeTypeHint extends TypeHint { + /** + * Specifies whether the routing of this element can be changed. + */ + readonly routable: boolean; + + /** + * Allowed source element types for this edge type + */ + readonly sourceElementTypeIds: string[]; + + /** + * Allowed targe element types for this edge type + */ + readonly targetElementTypeIds: string[]; + + isAllowedSource(input: SModelElement | SModelElementSchema | string): boolean { + const type = getElementTypeId(input); + return this.sourceElementTypeIds.includes(type); + + } + isAllowedTarget(input: SModelElement | SModelElementSchema | string): boolean { + const type = getElementTypeId(input); + return this.targetElementTypeIds.includes(type); + } +} + +@injectable() +export class ApplyTypeHintsAction implements Action { + readonly kind = ApplyTypeHintsCommand.KIND; + constructor() { } +} + +@injectable() +export class ApplyTypeHintsCommand extends FeedbackCommand { + + static KIND = "applyTypeHints"; + readonly priority = 10; + + constructor(@inject(TYPES.Action) protected action: ApplyTypeHintsAction, + @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + context.root.index.all() + .forEach(element => { + if (element instanceof SShapeElement || element instanceof SModelRoot) { + this.applyShapeTypeHint(element); + + } else if (element instanceof SEdge) { + return this.applyEdgeTypeHint(element); + } + }); + return context.root; + } + + protected applyEdgeTypeHint(element: SModelElement) { + const hint = this.typeHintProvider.getEdgeTypeHint(element); + if (hint && isModifiableFetureSet(element.features)) { + addOrRemove(element.features, deletableFeature, hint.deletable); + addOrRemove(element.features, editFeature, hint.routable); + addOrRemove(element.features, reconnectFeature, hint.repositionable); + } + } + + protected applyShapeTypeHint(element: SModelElement) { + const hint = this.typeHintProvider.getShapeTypeHint(element); + if (hint && isModifiableFetureSet(element.features)) { + addOrRemove(element.features, deletableFeature, hint.deletable); + addOrRemove(element.features, moveFeature, hint.repositionable); + addOrRemove(element.features, resizeFeature, hint.resizable); + addOrRemove(element.features, reparentFeature, hint.reparentable); + + addOrRemove(element.features, containerFeature, true); + const containable = createContainable(hint); + Object.assign(element, containable); + + addOrRemove(element.features, connectableFeature, true); + const validSourceEdges = this.typeHintProvider.getValidEdgeElementTypes(element, "source"); + const validTargetEdges = this.typeHintProvider.getValidEdgeElementTypes(element, "target"); + const connectable = createConnectable(validSourceEdges, validTargetEdges); + Object.assign(element, connectable); + } + } +} + +function createConnectable(validSourceEdges: string[], validTargetEdges: string[]): Connectable { + return { + canConnect: (routable, role) => + role === "source" ? + validSourceEdges.includes(routable.type) : + validTargetEdges.includes(routable.type) + }; +} + +function createContainable(hint: ShapeTypeHint): Containable { + return { + isContainableElement: (element) => + hint.containableElementTypeIds ? + hint.containableElementTypeIds.includes(getElementTypeId(element)) : + false + }; +} + +function addOrRemove(features: Set, feature: symbol, add: boolean) { + if (add && !features.has(feature)) { + features.add(feature); + } else if (!add && features.has(feature)) { + features.delete(feature); + } +} +function isModifiableFetureSet(featureSet?: FeatureSet): featureSet is FeatureSet & Set { + return featureSet !== undefined && featureSet instanceof Set; +} + +export interface ITypeHintProvider { + getShapeTypeHint(input: SModelElement | SModelElement | string): ShapeTypeHint | undefined; + getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined; + getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: "source" | "target"): string[]; +} + +@injectable() +export class TypeHintProvider implements IActionHandler, ITypeHintProvider { + + @inject(GLSP_TYPES.IFeedbackActionDispatcher) + protected feedbackActionDispatcher: IFeedbackActionDispatcher; + + protected shapeHints: Map = new Map; + protected edgeHints: Map = new Map; + + handle(action: Action): ICommand | Action | void { + if (isSetTypeHintsAction(action)) { + action.shapeHints.forEach(hint => this.shapeHints.set(hint.elementTypeId, hint)); + action.edgeHints.forEach(hint => this.edgeHints.set(hint.elementTypeId, hint)); + this.feedbackActionDispatcher.registerFeedback(this, [new ApplyTypeHintsAction()]); + } + } + + getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: "source" | "target"): string[] { + const elementTypeId = getElementTypeId(input); + if (role === "source") { + return Array.from( + Array.from(this.edgeHints.values()) + .filter(hint => hint.sourceElementTypeIds.some(sourceElementTypeId => hasCompatibleType(elementTypeId, sourceElementTypeId))) + .map(hint => hint.elementTypeId)); + } else { + return Array.from( + Array.from(this.edgeHints.values()) + .filter(hint => hint.targetElementTypeIds.some(targetElementTypeId => hasCompatibleType(elementTypeId, targetElementTypeId))) + .map(hint => hint.elementTypeId)); + } + } + + getShapeTypeHint(input: SModelElement | SModelElement | string) { + return getTypeHint(input, this.shapeHints); + } + + getEdgeTypeHint(input: SModelElement | SModelElement | string) { + return getTypeHint(input, this.edgeHints); + } +} + +function getTypeHint(input: SModelElement | SModelElement | string, hints: Map): T | undefined { + const type = getElementTypeId(input); + let hint = hints.get(type); + // Check subtypes + if (hint === undefined) { + const subtypes = type.split(":"); + while (hint === undefined && subtypes.length > 0) { + subtypes.pop(); + hint = hints.get(subtypes.join(":")); + } + } + return hint; +} + diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts new file mode 100644 index 0000000..e2b0936 --- /dev/null +++ b/packages/client/src/features/hover/di.config.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { + CenterCommand, + ClosePopupActionHandler, + configureActionHandler, + configureCommand, + FitToScreenCommand, + HoverFeedbackCommand, + HoverKeyListener, + HoverState, + MoveCommand, + PopupHoverMouseListener, + SetPopupModelCommand, + SetViewportCommand, + TYPES +} from "sprotty"; +import { PopupPositionUpdater } from "sprotty/lib/features/hover/popup-position-updater"; + +import { GlspHoverMouseListener } from "./hover"; + + +const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { + bind(TYPES.PopupVNodePostprocessor).to(PopupPositionUpdater).inSingletonScope(); + bind(TYPES.MouseListener).to(GlspHoverMouseListener); + bind(TYPES.PopupMouseListener).to(PopupHoverMouseListener); + bind(TYPES.KeyListener).to(HoverKeyListener); + bind(TYPES.HoverState).toConstantValue({ + mouseOverTimer: undefined, + mouseOutTimer: undefined, + popupOpen: false, + previousPopupElement: undefined + }); + bind(ClosePopupActionHandler).toSelf().inSingletonScope(); + + const context = { bind, isBound }; + configureCommand(context, HoverFeedbackCommand); + configureCommand(context, SetPopupModelCommand); + configureActionHandler(context, SetPopupModelCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, FitToScreenCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, CenterCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, SetViewportCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, MoveCommand.KIND, ClosePopupActionHandler); +}); + +export default glspHoverModule; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts new file mode 100644 index 0000000..e96981d --- /dev/null +++ b/packages/client/src/features/hover/hover.ts @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import { + Action, + Bounds, + EMPTY_ROOT, + HoverMouseListener, + PreRenderedElementSchema, + RequestPopupModelAction, + SetPopupModelAction, + SIssueMarker, + SIssueSeverity, + SModelElement, + SModelRootSchema +} from "sprotty"; + +import { GIssueMarker } from "../validation/issue-marker"; + +@injectable() +export class GlspHoverMouseListener extends HoverMouseListener { + + protected startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { + this.stopMouseOverTimer(); + return new Promise(resolve => { + this.state.mouseOverTimer = window.setTimeout(() => { + const popupBounds = this.computePopupBounds(target, { x: event.pageX, y: event.pageY }); + if (target instanceof GIssueMarker) { + resolve(new SetPopupModelAction(this.createPopupModel(target as GIssueMarker, popupBounds))); + } else { + resolve(new RequestPopupModelAction(target.id, popupBounds)); + } + + this.state.popupOpen = true; + this.state.previousPopupElement = target; + }, this.options.popupOpenDelay); + }); + } + + protected createPopupModel(marker: GIssueMarker, bounds: Bounds): SModelRootSchema { + if (marker.issues !== undefined && marker.issues.length > 0) { + const message = '
    ' + marker.issues.map(i => '
  • ' + i.severity.toUpperCase() + ': ' + i.message + '
  • ').join('') + '
'; + return { + type: 'html', + id: 'sprotty-popup', + children: [ + { + type: 'pre-rendered', + id: 'popup-title', + code: `
${message}
` + } + ], + canvasBounds: this.modifyBounds(bounds) + }; + } + return { type: EMPTY_ROOT.type, id: EMPTY_ROOT.id }; + } + + protected modifyBounds(bounds: Bounds): Bounds { + return bounds; + } +} + +export function getSeverity(marker: SIssueMarker): SIssueSeverity { + let currentSeverity: SIssueSeverity = 'info'; + for (const severity of marker.issues.map(s => s.severity)) { + if (severity === 'error') + return severity; + if (severity === 'warning' && currentSeverity === 'info') + currentSeverity = severity; + } + return currentSeverity; +} diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts new file mode 100644 index 0000000..5c7045a --- /dev/null +++ b/packages/client/src/features/layout/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureCommand } from "sprotty"; + +import { AlignElementsCommand, ResizeElementsCommand } from "./layout-commands"; + +const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound) => { + configureCommand({ bind, isBound }, ResizeElementsCommand); + configureCommand({ bind, isBound }, AlignElementsCommand); +}); + +export default layoutCommandsModule; diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-commands.spec.ts new file mode 100644 index 0000000..2d4f675 --- /dev/null +++ b/packages/client/src/features/layout/layout-commands.spec.ts @@ -0,0 +1,365 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "mocha"; +import "reflect-metadata"; + +import { expect } from "chai"; +import { Container } from "inversify"; +import { + Action, + AnimationFrameSyncer, + Bounds, + CommandExecutionContext, + ConsoleLogger, + defaultModule, + ElementAndBounds, + ElementMove, + FeatureSet, + IActionDispatcher, + MoveAction, + MoveCommand, + RequestAction, + ResponseAction, + SChildElement, + SetBoundsAction, + SetBoundsCommand, + SGraphFactory, + SModelRoot, + TYPES +} from "sprotty"; + +import { ChangeBoundsOperation } from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; +import { resizeFeature } from "../change-bounds/model"; +import { SelectionService } from "../select/selection-service"; +import { FeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; +import { + AlignElementsAction, + AlignElementsCommand, + Alignment, + Reduce, + ResizeDimension, + ResizeElementsAction, + ResizeElementsCommand +} from "./layout-commands"; + +class MockActionDispatcher implements IActionDispatcher { + constructor(public dispatchedActions: Action[] = []) { } + dispatch(action: Action): Promise { + this.dispatchedActions.push(action); + return Promise.resolve(); + } + dispatchAll(actions: Action[]): Promise { + actions.forEach(action => this.dispatchedActions.push(action)); + return Promise.resolve(); + } + request(action: RequestAction): Promise { + throw new Error("Method not implemented."); + } +} + +const container = new Container(); +container.load(defaultModule); +container.bind(GLSP_TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); +container.bind(SelectionService).toSelf().inSingletonScope(); +container.bind(GLSP_TYPES.SelectionService).toService(SelectionService); +container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); +const graphFactory = container.get(TYPES.IModelFactory); +const selectionService = container.get(GLSP_TYPES.SelectionService); + +const actionDispatcher = new MockActionDispatcher(); + +const node1 = { + id: 'node1', type: 'node:circle', + selected: true +}; +const node2 = { + id: 'node2', type: 'node:circle', + selected: true +}; +const node3 = { + id: 'node3', type: 'node:circle', + selected: true +}; +const model = createModel(); + +function createModel() { + const root = graphFactory.createRoot({ + id: 'model1', + type: 'graph', + children: [node1, node2, node3] + }); + root.children.forEach(child => applyFeature(child, resizeFeature)); + return root; +} + +function applyFeature(element: SChildElement, feature: symbol) { + (element.features as FeatureSet & Set).add(feature); +} + +const context: CommandExecutionContext = { + root: model, + modelFactory: graphFactory, + duration: 0, + modelChanged: undefined!, + logger: new ConsoleLogger(), + syncer: new AnimationFrameSyncer() +}; + +const defaultSize = { height: 10, width: 10 }; + +describe('AlignElementsCommand', () => { + + it('should align all elements left', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Left); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + + it('should align all elements right', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Right); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + + it('should align all elements center', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Center); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + + it('should align all elements top', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Top); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + + it('should align all elements bottom', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Bottom); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + + it('should align all elements middle', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, + { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, + { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } + ]); + const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Middle); + const command = new AlignElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + assertAllBounds(new Map([ + ["node1", { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ["node2", { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ["node3", { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }] + ])); + }); + +}); + +describe('ResizeElementsCommand', () => { + + it('should make same width as last', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, + { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, + { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } + ]); + const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Width, Reduce.last); + const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + // resize is keeping the center, so the X moves by diff / 2 + assertAllBoundsInChangeBounds(new Map([ + ["node1", { x: 90, y: 100, height: 10, width: 30 }], + ["node2", { x: 95, y: 200, height: 20, width: 30 }], + ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ])); + }); + + it('should make same height as last', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, + { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, + { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } + ]); + const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Height, Reduce.last); + const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + // resize is keeping the center, so the Y moves by diff / 2 + assertAllBoundsInChangeBounds(new Map([ + ["node1", { x: 100, y: 90, height: 30, width: 10 }], + ["node2", { x: 100, y: 195, height: 30, width: 20 }], + ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ])); + }); + + it('should make same width and height as last', () => { + actionDispatcher.dispatchedActions = []; + const newModel = initModel([ + { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, + { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, + { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } + ]); + const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Width_And_Height, Reduce.last); + const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); + command.execute(newContext(newModel)); + + // resize is keeping the center, so the Y moves by diff / 2 + assertAllBoundsInChangeBounds(new Map([ + ["node1", { x: 90, y: 90, height: 30, width: 30 }], + ["node2", { x: 95, y: 195, height: 30, width: 30 }], + ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ])); + }); + +}); + +function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { + const mySetBoundsAction = new SetBoundsAction(elementAndBounds); + const setBoundsCommand = new SetBoundsCommand(mySetBoundsAction); + return setBoundsCommand.execute(context) as SModelRoot; +} + +function newContext(root: SModelRoot) { + return { + root: root, + modelFactory: graphFactory, + duration: 0, + modelChanged: undefined!, + logger: new ConsoleLogger(), + syncer: new AnimationFrameSyncer() + }; +} + +function assertAllBounds(allBounds: Map) { + allBounds.forEach((bounds, nodeId) => assertBounds(nodeId, bounds)); +} + +function assertAllBoundsInChangeBounds(allBounds: Map) { + allBounds.forEach((bounds, nodeId) => assertBoundsInChangeBoundsActions(nodeId, bounds)); +} + +function assertBounds(nodeId: string, bounds: Bounds) { + assertBoundsInMoves(nodeId, bounds); + assertBoundsInChangeBoundsActions(nodeId, bounds); +} + +function assertBoundsInMoves(nodeId: string, bounds: Bounds) { + const moves = dispatchedElementMoves(); + const move = getMoveById(nodeId, moves); + expect(move.toPosition.x).to.be.equal(bounds.x); + expect(move.toPosition.y).to.be.equal(bounds.y); +} + +function assertBoundsInChangeBoundsActions(nodeId: string, bounds: Bounds) { + const allChangeBounds = dispatchedChangeBounds(); + const changeBounds = getElementAndBoundsById(nodeId, allChangeBounds); + expect(changeBounds.newPosition!.x).to.be.equal(bounds.x); + expect(changeBounds.newPosition!.y).to.be.equal(bounds.y); + expect(changeBounds.newSize!.height).to.be.equal(bounds.height); + expect(changeBounds.newSize!.width).to.be.equal(bounds.width); +} + +function getMoveById(id: string, moves: ElementMove[]) { + return moves.filter(m => m.elementId === id)[0]; +} + +function getElementAndBoundsById(id: string, elementAndBounds: ElementAndBounds[]) { + return elementAndBounds.filter(m => m.elementId === id)[0]; +} + +function dispatchedElementMoves() { + return actionDispatcher.dispatchedActions.filter(isMoveAction).map(a => a.moves).reduce((acc, val) => acc.concat(val), []); +} + +function dispatchedChangeBounds() { + return actionDispatcher.dispatchedActions.filter(isChangeBounds).map(a => a.newBounds).reduce((acc, val) => acc.concat(val), []); +} + +function isMoveAction(action: Action): action is MoveAction { + return action.kind === MoveCommand.KIND; +} + +function isChangeBounds(action: Action): action is ChangeBoundsOperation { + return action.kind === ChangeBoundsOperation.KIND; +} diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts new file mode 100644 index 0000000..b162afd --- /dev/null +++ b/packages/client/src/features/layout/layout-commands.ts @@ -0,0 +1,415 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + Command, + CommandExecutionContext, + CommandReturn, + ElementAndBounds, + ElementMove, + IActionDispatcher, + MoveAction, + SetBoundsAction, + SModelElement, + TYPES +} from "sprotty"; + +import { ChangeBoundsOperation } from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; +import { + toValidElementAndBounds, + toValidElementMove, + WriteableElementAndBounds, + WriteableElementMove +} from "../../utils/layout-utils"; +import { BoundsAwareModelElement } from "../../utils/smodel-util"; +import { isBoundsAwareMoveable, isResizable } from "../change-bounds/model"; +import { IMovementRestrictor } from "../change-bounds/movement-restrictor"; +import { SelectionService } from "../select/selection-service"; + +export enum ResizeDimension { + Width, + Height, + Width_And_Height +} + +export namespace Reduce { + export function min(...values: number[]): number { + return Math.min(...values); + } + + export function max(...values: number[]): number { + return Math.max(...values); + } + + export function avg(...values: number[]): number { + return values.reduce((a, b) => a + b, 0) / values.length; + } + + export function first(...values: number[]): number { + return values[0]; + } + + export function last(...values: number[]): number { + return values[values.length - 1]; + } +} + +export class ResizeElementsAction implements Action { + readonly kind = ResizeElementsCommand.KIND; + + constructor( + /** + * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized. + */ + public readonly elementIds: string[] = [], + /** + * Resize dimension. + */ + public readonly dimension: ResizeDimension = ResizeDimension.Width, + /** + * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. + */ + public readonly reductionFunction: (...values: number[]) => number) { + } +} + +export enum Alignment { + Left, + Center, + Right, + Top, + Middle, + Bottom +} + +export namespace Select { + export function all(elements: BoundsAwareModelElement[]) { + return elements; + } + + export function first(elements: BoundsAwareModelElement[]) { + return [elements[0]]; + } + + export function last(elements: BoundsAwareModelElement[]) { + return [elements[elements.length - 1]]; + } +} + + +export class AlignElementsAction implements Action { + readonly kind = AlignElementsCommand.KIND; + + constructor( + /** + * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. + */ + public readonly elementIds: string[] = [], + /** + * Alignment direction. + */ + public readonly alignment: Alignment = Alignment.Left, + /** + * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. + */ + public readonly selectionFunction: (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[] = Select.all) { + } +} + +@injectable() +abstract class LayoutElementsCommand extends Command { + constructor(@inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction, + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + super(); + } + + getActionElements(context: CommandExecutionContext): BoundsAwareModelElement[] { + const model = context.root; + const elementIDs = this.action.elementIds; + if (elementIDs.length === 0) { + // collect the selected elements from the selection service (selection order is kept by service) + this.selectionService.getSelectedElementIDs().forEach(elementID => elementIDs.push(elementID)); + } + const boundsAwareElements: BoundsAwareModelElement[] = []; + elementIDs.forEach(id => { + const element = model.index.getById(id); + if (element && this.isActionElement(element)) { + boundsAwareElements.push(element); + } + }); + return boundsAwareElements; + } + + protected abstract isActionElement(element: SModelElement): element is BoundsAwareModelElement; + + dispatchAction(action: Action) { + this.actionDispatcher.dispatch(action); + } + + dispatchActions(actions: Action[]) { + this.actionDispatcher.dispatchAll(actions); + } +} + +@injectable() +export class ResizeElementsCommand extends LayoutElementsCommand { + static readonly KIND = 'layout:resize'; + + constructor(@inject(TYPES.Action) protected action: ResizeElementsAction, + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + super(action, actionDispatcher, selectionService, movementRestrictor); + } + + protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + return isResizable(element); + } + + execute(context: CommandExecutionContext): CommandReturn { + const elements = this.getActionElements(context); + if (elements.length > 1) { + switch (this.action.dimension) { + case ResizeDimension.Width: + this.resizeWidth(elements); + break; + case ResizeDimension.Height: + this.resizeHeight(elements); + break; + case ResizeDimension.Width_And_Height: + this.resizeWidthAndHeight(elements); + break; + } + } + return context.root; + } + + resizeWidth(elements: BoundsAwareModelElement[]) { + const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); + this.dispatchResizeActions(elements, (element, bounds) => { + // resize around center + const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width); + bounds.newPosition.x = element.bounds.x - halfDiffWidth; + bounds.newSize.width = targetWidth; + }); + } + + resizeHeight(elements: BoundsAwareModelElement[]) { + const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); + this.dispatchResizeActions(elements, (element, bounds) => { + // resize around middle + const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height); + bounds.newPosition.y = element.bounds.y - halfDiffHeight; + bounds.newSize.height = targetHeight; + }); + } + + resizeWidthAndHeight(elements: BoundsAwareModelElement[]) { + const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); + const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); + this.dispatchResizeActions(elements, (element, bounds) => { + // resize around center and middle + const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width); + const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height); + bounds.newPosition.x = element.bounds.x - halfDiffWidth; + bounds.newPosition.y = element.bounds.y - halfDiffHeight; + bounds.newSize.width = targetWidth; + bounds.newSize.height = targetHeight; + }); + } + + dispatchResizeActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void) { + const elementAndBounds: ElementAndBounds[] = []; // client- and server-side resize + elements.forEach(element => { + const elementChange = this.createElementAndBounds(element, change); + if (elementChange) { + // simply skip invalid changes + elementAndBounds.push(elementChange); + } + }); + this.dispatchActions([new SetBoundsAction(elementAndBounds), new ChangeBoundsOperation(elementAndBounds)]); + } + + createElementAndBounds(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void) { + const bounds: WriteableElementAndBounds = { + elementId: element.id, + newPosition: { + x: element.bounds.x, + y: element.bounds.y + }, + newSize: { + width: element.bounds.width, + height: element.bounds.height + } + }; + change(element, bounds); + return toValidElementAndBounds(element, bounds, this.movementRestrictor); + } + + undo(context: CommandExecutionContext): CommandReturn { + // we dispatch another action which can be undone, so no explicit implementation necessary + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + // we dispatch another action which can be redone, so no explicit implementation necessary + return context.root; + } +} + +@injectable() +export class AlignElementsCommand extends LayoutElementsCommand { + static readonly KIND = 'layout:align'; + + constructor(@inject(TYPES.Action) protected action: AlignElementsAction, + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + super(action, actionDispatcher, selectionService, movementRestrictor); + } + + protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + return isBoundsAwareMoveable(element); + } + + execute(context: CommandExecutionContext): CommandReturn { + const elements = this.getActionElements(context); + if (elements.length > 1) { + switch (this.action.alignment) { + case Alignment.Left: + this.alignLeft(elements); + break; + case Alignment.Center: + this.alignCenter(elements); + break; + case Alignment.Right: + this.alignRight(elements); + break; + case Alignment.Top: + this.alignTop(elements); + break; + case Alignment.Middle: + this.alignMiddle(elements); + break; + case Alignment.Bottom: + this.alignBottom(elements); + break; + } + } + return context.root; + } + + alignLeft(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); + this.dispatchAlignActions(elements, (_, move) => move.toPosition.x = minX); + } + + alignCenter(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); + const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); + const diffX = maxX - minX; + const centerX = minX + 0.5 * diffX; + this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = centerX - 0.5 * element.bounds.width); + } + + alignRight(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); + this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = maxX - element.bounds.width); + } + + alignTop(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); + this.dispatchAlignActions(elements, (_, move) => move.toPosition.y = minY); + } + + alignMiddle(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); + const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); + const diffY = maxY - minY; + const middleY = minY + 0.5 * diffY; + this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = middleY - 0.5 * element.bounds.height); + } + + alignBottom(elements: BoundsAwareModelElement[]) { + const calculationElements = this.action.selectionFunction(elements); + const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); + this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = maxY - element.bounds.height); + } + + dispatchAlignActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void) { + const moves: ElementMove[] = []; // client-side move + const elementAndBounds: ElementAndBounds[] = []; // server-side move + elements.forEach(element => { + const move = this.createElementMove(element, change); + if (move) { + // simply skip invalid changes + moves.push(move); + const elementAndBound = this.createElementAndBounds(element, move); + elementAndBounds.push(elementAndBound); + } + }); + this.dispatchActions([new MoveAction(moves), new ChangeBoundsOperation(elementAndBounds)]); + } + + createElementMove(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void) { + const move: WriteableElementMove = { + elementId: element.id, + fromPosition: { + x: element.bounds.x, + y: element.bounds.y + }, + toPosition: { + x: element.bounds.x, + y: element.bounds.y + } + }; + change(element, move); + return toValidElementMove(element, move, this.movementRestrictor); + } + + createElementAndBounds(element: BoundsAwareModelElement, move: ElementMove): ElementAndBounds { + return { + elementId: element.id, + newPosition: { + x: move.toPosition.x, + y: move.toPosition.y + }, + newSize: { + width: element.bounds.width, + height: element.bounds.height + } + }; + } + + undo(context: CommandExecutionContext): CommandReturn { + // we dispatch another action which can be undone, so no explicit implementation necessary + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + // we dispatch another action which can be redone, so no explicit implementation necessary + return context.root; + } +} diff --git a/packages/client/src/features/mouse-tool/di.config.ts b/packages/client/src/features/mouse-tool/di.config.ts new file mode 100644 index 0000000..7034bd9 --- /dev/null +++ b/packages/client/src/features/mouse-tool/di.config.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { MouseTool } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { RankingMouseTool } from "./mouse-tool"; + +const glspMouseToolModule = new ContainerModule((bind, _unbind, _isBound, rebind) => { + bind(RankingMouseTool).toSelf().inSingletonScope(); + bind(GLSP_TYPES.MouseTool).toService(RankingMouseTool); + rebind(MouseTool).toService(RankingMouseTool); +}); + +export default glspMouseToolModule; diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts new file mode 100644 index 0000000..59f40d1 --- /dev/null +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -0,0 +1,135 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable, multiInject, optional } from "inversify"; +import { VNode } from "snabbdom/vnode"; +import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from "sprotty"; + +import { getRank } from "../rank/model"; + +export interface IMouseTool { + register(mouseListener: MouseListener): void; + deregister(mouseListener: MouseListener): void; +} + +@injectable() +export class RankingMouseTool extends MouseTool implements IMouseTool { + protected rankedMouseListeners: Map; + + constructor(@multiInject(TYPES.MouseListener) @optional() protected mouseListeners: MouseListener[] = []) { + super(mouseListeners); + this.rankedMouseListeners = groupBy(mouseListeners, listener => getRank(listener)); + } + + register(mouseListener: MouseListener) { + super.register(mouseListener); + this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); + } + + deregister(mouseListener: MouseListener) { + super.deregister(mouseListener); + this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); + } + + decorate(vnode: VNode, element: SModelElement) { + // we need to overwrite the existing event handlers registered by the original mouse tool + if (element instanceof SModelRoot) { + overwriteOn(vnode, 'mouseover', this.mouseOver.bind(this), element); + overwriteOn(vnode, 'mouseout', this.mouseOut.bind(this), element); + overwriteOn(vnode, 'mouseenter', this.mouseEnter.bind(this), element); + overwriteOn(vnode, 'mouseleave', this.mouseLeave.bind(this), element); + overwriteOn(vnode, 'mousedown', this.mouseDown.bind(this), element); + overwriteOn(vnode, 'mouseup', this.mouseUp.bind(this), element); + overwriteOn(vnode, 'mousemove', this.mouseMove.bind(this), element); + overwriteOn(vnode, 'wheel', this.wheel.bind(this), element); + overwriteOn(vnode, 'contextmenu', (target: SModelElement, event: any) => { + event.preventDefault(); + }, element); + overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this), element); + } + vnode = this.mouseListeners.reduce( + (n: VNode, listener: MouseListener) => listener.decorate(n, element), + vnode); + return vnode; + } + + protected handleEvent(methodName: K, model: SModelRoot, event: MouseEvent) { + this.focusOnMouseEvent(methodName, model); + const element = this.getTargetElement(model, event); + if (!element) { + return; + } + this.notifyListenersByRank(element, methodName, model, event); + } + + async notifyListenersByRank(element: SModelElement, methodName: K, model: SModelRoot, event: MouseEvent) { + for (const rank of this.rankedMouseListeners) { + await this.dispatchActions(rank[1], methodName, element, event); + } + } + + async dispatchActions(mouseListeners: MouseListener[], methodName: K, element: SModelElement, event: MouseEvent): Promise { + const actions = mouseListeners + .map(listener => listener[methodName].apply(listener, [element, event])) + .reduce((a, b) => a.concat(b)); + if (actions.length > 0) { + event.preventDefault(); + for (const actionOrPromise of actions) { + if (isAction(actionOrPromise)) { + await this.actionDispatcher.dispatch(actionOrPromise); + } else { + actionOrPromise.then((action: Action) => { + this.actionDispatcher.dispatch(action); + }); + } + } + } + } +} + +function groupBy(array: Array, keyFunction: (x: T) => K): Map { + const unsortedMap = array.reduce((result: Map, item: T) => { + const key = keyFunction(item); + if (!result.has(key)) { + result.set(key, [item]); + } else { + const entries = result.get(key); + if (entries) { + entries.push(item); + } + } + return result; + }, new Map()); + return new Map([...unsortedMap.entries()].sort()); +} + +function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, event: Event) => void, element: SModelElement) { + const val = getOn(vnode); + // ignore any previous val[event] registrations + (val as any)[event] = [listener, element]; +} + +function getOn(vnode: VNode) { + const data = getData(vnode); + if (!data.on) + data.on = {}; + return data.on; +} + +function getData(vnode: VNode) { + if (!vnode.data) + vnode.data = {}; + return vnode.data; +} diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts new file mode 100644 index 0000000..b9a3e05 --- /dev/null +++ b/packages/client/src/features/navigation/di.config.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureActionHandler } from "sprotty/lib"; + +import { + NavigateAction, + NavigateToTargetAction, + NavigationActionHandler, + ProcessNavigationArgumentsAction +} from "./navigation-action-handler"; +import { NavigationTargetResolver } from "./navigation-target-resolver"; + +export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { + bind(NavigationTargetResolver).toSelf().inSingletonScope(); + bind(NavigationActionHandler).toSelf().inSingletonScope(); + configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, NavigateToTargetAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); +}); diff --git a/packages/client/src/features/navigation/external-navigate-to-target-handler.ts b/packages/client/src/features/navigation/external-navigate-to-target-handler.ts new file mode 100644 index 0000000..16403bf --- /dev/null +++ b/packages/client/src/features/navigation/external-navigate-to-target-handler.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; + +import { Args } from "../../base/args"; + +@injectable() +export abstract class ExternalNavigateToTargetHandler { + abstract navigateTo(uri: string, args?: Args): Promise; +} diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts new file mode 100644 index 0000000..76674ef --- /dev/null +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -0,0 +1,247 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + CenterAction, + generateRequestId, + ILogger, + RequestAction, + ResponseAction, + SelectAction, + SelectAllAction +} from "sprotty"; +import { Action, IActionDispatcher, IActionHandler, ICommand, TYPES } from "sprotty/lib"; + +import { Args } from "../../base/args"; +import { EditorContext, EditorContextServiceProvider } from "../../base/editor-context"; +import { GLSP_TYPES } from "../../base/types"; +import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; +import { ExternalNavigateToTargetHandler } from "./external-navigate-to-target-handler"; +import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTargetAction } from "./navigation-target-resolver"; + +/** + * Action for triggering a navigation of a certain target type. + * + * Examples for target types could be `documentation`, `implementation`, etc. + * but this is up to the domain-specific diagram implementation to decide. + * Such an action will eventually trigger a `RequestNavigationTargetsAction` + * (see `NavigationActionHandler`) in order to request the navigation targets + * from the server. + * + * This action is typically triggered by a user action. + */ +export class NavigateAction implements Action { + static readonly KIND = 'navigate'; + readonly kind = NavigateAction.KIND; + constructor(readonly targetTypeId: string, readonly args?: Args) { } +} + +export function isNavigateAction(action: Action): action is NavigateAction { + return action !== undefined && (action.kind === NavigateAction.KIND) + && (action).targetTypeId !== undefined; +} + +/** Action that is usually sent to the server to request navigation targets for a navigation type. */ +export class RequestNavigationTargetsAction implements RequestAction { + static readonly KIND = "requestNavigationTargets"; + kind = RequestNavigationTargetsAction.KIND; + constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, + readonly requestId: string = generateRequestId()) { } +} + +/** Action that is usually sent from the server to the client as a repsonse to a `RequestNavigationTargetsAction`. */ +export class SetNavigationTargetsAction implements ResponseAction { + static readonly KIND = "setNavigationTargets"; + kind = SetNavigationTargetsAction.KIND; + constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) { } +} + +export function isSetNavigationTargetsAction(action: Action): action is SetNavigationTargetsAction { + return action !== undefined && (action.kind === SetNavigationTargetsAction.KIND) + && (action).targets !== undefined; +} + +/** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ +export class NavigateToTargetAction implements Action { + static readonly KIND = 'navigateToTarget'; + readonly kind = NavigateToTargetAction.KIND; + constructor(readonly target: NavigationTarget) { } +} + +export function isNavigateToTargetAction(action: Action): action is NavigateToTargetAction { + return action !== undefined && (action.kind === NavigateToTargetAction.KIND) + && (action).target !== undefined; +} + +/** Action to trigger the processing of additional navigation arguments. + * + * The resolution of a `NavigationTarget` may entail additional arguments. In this case, this action is + * triggered allowing the client to react to those arguments. The default `NavigationActionHandler` will + * only process the arguments' keys `info`, `warning`, and `error` to present them to the user. + * Customizations, however, may add domain-specific arguments and register custom handler to also process + * other arguments and trigger some additional behavior (e.g. update other views, etc.). + */ +export class ProcessNavigationArgumentsAction implements Action { + static readonly KIND = 'processNavigationArguments'; + readonly kind = ProcessNavigationArgumentsAction.KIND; + constructor(readonly args: Args) { } +} + +export function isProcessNavigationArgumentsAction(action: Action): action is ProcessNavigationArgumentsAction { + return action !== undefined && (action.kind === ProcessNavigationArgumentsAction.KIND) + && (action).args !== undefined; +} + +/** + * Default handler for all actions that are related to the navigation. + * + * For a `NavigateAction` this handler triggers a `RequestNavigationTargetAction` to obtain the actual + * navigation targets for the navigation type that is specified in the `NavigateAction`. + * Once the navigation targets are available, it will trigger a `NavigateToTargetAction` to actually + * perform the navigation. + * + * In other scenarios, clients may also trigger the `NavigateToTargetAction` directly, e.g. when opening + * the diagram. + * + * Depending on the URI and arguments of the navigation target we may encounter three cases: + * *(a)* the navigation target already specifies element IDs, in which case this action handler navigates + * to the specified elements directly, by the selecting them and centering them in the viewport. + * *(b)* the arguments of the navigation targets don't contain element IDs, but other arguments, the + * navigation target needs to be resolved into actual elment IDs by the `NavigationTargetResolver`. + * This can for instance be useful, if the navigation deals with queries or some other more complex + * logic that can't be dealt with on the client. + * *(c)* the target isn't resolved by the `NavigationTargetResolver`, e.g. because the `uri` doesn't match + * the URI of the current diagram. In this case, the navigation request is forwarded to the + * `ExternalNavigateToTargetHandler`, if it exists. + */ +@injectable() +export class NavigationActionHandler implements IActionHandler { + + readonly notificationTimeout = 5000; + + @inject(TYPES.ILogger) protected logger: ILogger; + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; + @inject(NavigationTargetResolver) protected resolver: NavigationTargetResolver; + @inject(ExternalNavigateToTargetHandler) @optional() protected externalHandler: ExternalNavigateToTargetHandler; + + handle(action: Action): ICommand | Action | void { + if (isNavigateAction(action)) { + this.handleNavigateAction(action); + } else if (isNavigateToTargetAction(action)) { + this.handleNavigateToTarget(action); + } else if (isProcessNavigationArgumentsAction(action)) { + this.processNavigationArguments(action.args); + } + } + + protected async handleNavigateAction(action: NavigateAction) { + try { + const editorContextService = await this.editorContextService(); + const context = editorContextService.get(action.args); + const response = await this.dispatcher.request(new RequestNavigationTargetsAction(action.targetTypeId, context)); + if (isSetNavigationTargetsAction(response) && response.targets && response.targets.length === 1) { + if (response.targets.length > 1) { + this.logger.warn(this, 'Processing of multiple targets is not supported yet. ' + + 'Only the first is being processed.', response.targets); + } + return this.dispatcher.dispatch(new NavigateToTargetAction(response.targets[0])); + } + this.warnAboutFailedNavigation('No valid navigation target found'); + } catch (reason) { + this.logger.error(this, 'Failed to obtain navigation target', reason, action); + } + } + + protected async handleNavigateToTarget(action: NavigateToTargetAction) { + try { + const resolvedElements = await this.resolveElements(action); + if (this.containsElementIdsOrArguments(resolvedElements)) { + this.navigateTo(resolvedElements); + this.handleResolutionArguments(resolvedElements); + return; + } else if (this.externalHandler) { + this.navigateToExternal(action.target); + return; + } + this.warnAboutFailedNavigation('Could not resolve or navigate to target', action.target); + } catch (reason) { + this.logger.error(this, 'Failed to navigate', reason, action); + } + } + + protected resolveElements(action: NavigateToTargetAction): Promise { + return this.resolver.resolve(action.target); + } + + protected containsElementIdsOrArguments(target: SetResolvedNavigationTargetAction | undefined): target is SetResolvedNavigationTargetAction { + return target !== undefined && (this.containsElementIds(target.elementIds) || this.containsArguments(target.args)); + } + + protected containsElementIds(elementIds: string[] | undefined): elementIds is string[] { + return elementIds !== undefined && elementIds.length > 0; + } + + protected containsArguments(args: Args | undefined): args is Args { + return args !== undefined && args !== undefined && Object.keys(args).length > 0; + } + + protected navigateTo(target: SetResolvedNavigationTargetAction) { + const elementIds = target.elementIds; + if (!this.containsElementIds(elementIds)) { + return; + } + this.dispatcher.dispatchAll([new SelectAllAction(false), new SelectAction(elementIds), new CenterAction(elementIds)]); + } + + protected handleResolutionArguments(target: SetResolvedNavigationTargetAction) { + const args = target.args; + if (!this.containsArguments(args)) { + return; + } + this.dispatcher.dispatch(new ProcessNavigationArgumentsAction(args)); + } + + protected navigateToExternal(target: NavigationTarget): Promise { + return this.externalHandler.navigateTo(target.uri, target.args); + } + + protected processNavigationArguments(args: Args) { + if (args.info && args.info.toString().length > 0) { + this.notify('INFO', args.info.toString()); + } + if (args.warning && args.warning.toString().length > 0) { + this.notify('WARNING', args.warning.toString()); + } + if (args.error && args.error.toString().length > 0) { + this.notify('ERROR', args.error.toString()); + } + } + + protected warnAboutFailedNavigation(msg: string, target?: NavigationTarget) { + const message = `${msg}` + (target ? `: '${target.uri}' (arguments: ${JSON.stringify(target.args)})` : ''); + this.logger.warn(this, msg, target); + this.notify('WARNING', message); + } + + private notify(severity: string, message: string) { + const timeout = this.notificationTimeout; + this.dispatcher.dispatchAll([ + { kind: GLSPServerStatusAction.KIND, severity, timeout, message }, + { kind: ServerMessageAction.KIND, severity, timeout, message } + ]); + } +} diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts new file mode 100644 index 0000000..e3ef08a --- /dev/null +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "mocha"; +import "reflect-metadata"; + +import { expect } from "chai"; + +import { NavigationTarget } from "./navigation-target-resolver"; + +describe('NavigationTarget', () => { + + it('should be able to set and get element IDs', () => { + const navigationTarget: NavigationTarget = { uri: 'uri' }; + NavigationTarget.setElementIds(navigationTarget, ['id1', 'id2']); + expect(NavigationTarget.getElementIds(navigationTarget)).to.be.eql(['id1', 'id2']); + }); + + it('should be able to set and get textual positions', () => { + const navigationTarget: NavigationTarget = { uri: 'uri' }; + NavigationTarget.setTextPosition(navigationTarget, { line: 1, character: 2 }); + expect(NavigationTarget.getTextPosition(navigationTarget)).to.be.eql({ line: 1, character: 2 }); + }); + + it('should be able to set and get custom query arguments', () => { + const navigationTarget: NavigationTarget = { uri: 'uri' }; + NavigationTarget.addArgument(navigationTarget, 'name', 'test'); + expect(navigationTarget.args!.name).to.be.eql('test'); + }); + + it('should specify whether it has arguments', () => { + let navigationTarget: NavigationTarget = { uri: 'uri' }; + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.false; + NavigationTarget.addArgument(navigationTarget, 'name', 'test'); + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.true; + + navigationTarget = { uri: 'uri' }; + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.false; + NavigationTarget.setElementIds(navigationTarget, ['id1', 'id2']); + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.true; + + navigationTarget = { uri: 'uri' }; + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.false; + NavigationTarget.setTextPosition(navigationTarget, { line: 1, character: 2 }); + expect(NavigationTarget.hasArguments(navigationTarget)).to.be.true; + }); + +}); diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts new file mode 100644 index 0000000..342578f --- /dev/null +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -0,0 +1,143 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, generateRequestId, IActionDispatcher, RequestAction, ResponseAction, TYPES } from "sprotty"; + +import { Args } from "../../base/args"; +import { EditorContextServiceProvider } from "../../base/editor-context"; +import { GLSP_TYPES } from "../../base/types"; + +export interface NavigationTarget { + uri: string; + label?: string; + args?: Args; +} + +export namespace NavigationTarget { + + export const ELEMENT_IDS = 'elementIds'; + export const ELEMENT_IDS_SEPARATOR = '&'; + export const TEXT_LINE = 'line'; + export const TEXT_COLUMN = 'column'; + + export function hasArguments(target: NavigationTarget): boolean { + return target.args !== undefined && Object.keys(target.args).length > 0; + } + + export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean) { + if (target.args === undefined) { + target.args = {}; + } + target.args[key] = value; + } + + export function getElementIds(target: NavigationTarget): string[] { + if (target.args === undefined || target.args[NavigationTarget.ELEMENT_IDS] === undefined) { + return []; + } + const elementIdsValue = target.args[NavigationTarget.ELEMENT_IDS].toString(); + return elementIdsValue.split(NavigationTarget.ELEMENT_IDS_SEPARATOR); + } + + export function setElementIds(target: NavigationTarget, elementIds: string[]) { + if (target.args === undefined) { + target.args = {}; + } + return target.args[NavigationTarget.ELEMENT_IDS] = elementIds.join(NavigationTarget.ELEMENT_IDS_SEPARATOR); + } + + export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined) { + if (position) { + if (target.args === undefined) { + target.args = {}; + } + target.args[NavigationTarget.TEXT_LINE] = position.line; + target.args[NavigationTarget.TEXT_COLUMN] = position.character; + } + } + + export function getTextPosition(target: NavigationTarget): TextPosition | undefined { + if (target.args === undefined + || target.args[NavigationTarget.TEXT_LINE] === undefined + || target.args[NavigationTarget.TEXT_COLUMN] === undefined) { + return undefined; + } + return { + line: Number(target.args[NavigationTarget.TEXT_LINE]), + character: Number(target.args[NavigationTarget.TEXT_COLUMN]) + }; + } +} + +export interface TextPosition { + line: number; + character: number; +} + +export class ResolveNavigationTargetAction implements RequestAction { + static readonly KIND = 'resolveNavigationTarget'; + kind = ResolveNavigationTargetAction.KIND; + constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) { } +} + +export class SetResolvedNavigationTargetAction implements ResponseAction { + static readonly KIND = 'setResolvedNavigationTarget'; + kind = SetResolvedNavigationTargetAction.KIND; + constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') { } +} + +export function isSetResolvedNavigationTargets(action: Action): action is SetResolvedNavigationTargetAction { + return action !== undefined && (action.kind === SetResolvedNavigationTargetAction.KIND); +} + +/** + * Resolves `NavigationTargets` to element ids. + * + * If the `NavigationTarget` doesn't have element ids itself, this resolver queries the server via a + * `ResolveNavigationTargetAction` for element ids. + */ +@injectable() +export class NavigationTargetResolver { + + @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + + async resolve(navigationTarget: NavigationTarget): Promise { + const contextService = await this.editorContextService(); + const sourceUri = await contextService.getSourceUri(); + return this.resolveWithSourceUri(sourceUri, navigationTarget); + } + + async resolveWithSourceUri(sourceUri: string | undefined, target: NavigationTarget): Promise { + if (sourceUri && sourceUri !== target.uri && `file://${sourceUri}` !== target.uri) { + // different URI, so we can't resolve it locally + return undefined; + } + if (NavigationTarget.getElementIds(target).length > 0) { + return new SetResolvedNavigationTargetAction(NavigationTarget.getElementIds(target)); + } + const response = await this.requestResolution(target); + if (isSetResolvedNavigationTargets(response)) { + return response; + } + return undefined; + } + + protected requestResolution(target: NavigationTarget) { + return this.dispatcher.request(new ResolveNavigationTargetAction(target)); + } +} + diff --git a/packages/client/src/features/rank/model.ts b/packages/client/src/features/rank/model.ts new file mode 100644 index 0000000..cab7dd5 --- /dev/null +++ b/packages/client/src/features/rank/model.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export const DEFAULT_RANK: number = 0; + +export interface Ranked { + rank: number; +} + +export function isRanked(arg: any): arg is Ranked { + return arg !== undefined && arg.rank !== undefined && typeof (arg.rank) === 'number'; +} + +export function getRank(arg: any): number { + return isRanked(arg) ? arg.rank : DEFAULT_RANK; +} diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts new file mode 100644 index 0000000..2a5b12a --- /dev/null +++ b/packages/client/src/features/reconnect/model.ts @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + edgeInProgressID, + edgeInProgressTargetHandleID, + RoutingHandleKind, + selectFeature, + SModelElement, + SModelExtension, + SRoutableElement, + SRoutingHandle +} from "sprotty"; + +export const reconnectFeature = Symbol("reconnectFeature"); +export interface Reconnectable extends SModelExtension { +} + +export function isReconnectable(element: SModelElement): element is SRoutableElement & Reconnectable { + return element instanceof SRoutableElement && element.hasFeature(reconnectFeature); +} + +const ROUTING_HANDLE_SOURCE_INDEX: number = -2; + +export function isReconnectHandle(element: SModelElement | undefined): element is SReconnectHandle { + return element !== undefined && element instanceof SReconnectHandle; +} + + +export function addReconnectHandles(element: SRoutableElement) { + removeReconnectHandles(element); + createReconnectHandle(element, 'source', ROUTING_HANDLE_SOURCE_INDEX); + createReconnectHandle(element, 'target', element.routingPoints.length); +} + +export function removeReconnectHandles(element: SRoutableElement) { + element.removeAll(child => child instanceof SReconnectHandle); +} + +export function isSourceRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle) { + return routingHandle.pointIndex === ROUTING_HANDLE_SOURCE_INDEX; +} + +export function isTargetRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle) { + return routingHandle.pointIndex === edge.routingPoints.length; +} + +export function createReconnectHandle(edge: SRoutableElement, kind: RoutingHandleKind, routingPointIndex: number): SReconnectHandle { + const handle = new SReconnectHandle(); + handle.kind = kind; + handle.pointIndex = routingPointIndex; + handle.type = 'routing-point'; + if (kind === 'target' && edge.id === edgeInProgressID) { + handle.id = edgeInProgressTargetHandleID; + } + edge.add(handle); + return handle; +} + +export class SReconnectHandle extends SRoutingHandle { + hasFeature(feature: symbol): boolean { + return feature !== selectFeature && super.hasFeature(feature); + } +} diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts new file mode 100644 index 0000000..bab4c64 --- /dev/null +++ b/packages/client/src/features/save/di.config.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { TYPES } from "sprotty"; + +import { SaveModelKeyboardListener } from "./save"; + +/** + * This module is not required the diagram is deployed in Theia with the `GLSPDiagramWidget` + * but only intended to be used in a standalone deployment of GLSP. + */ +const saveModule = new ContainerModule(bind => { + bind(TYPES.KeyListener).to(SaveModelKeyboardListener); + +}); + +export default saveModule; diff --git a/packages/client/src/features/save/model.ts b/packages/client/src/features/save/model.ts new file mode 100644 index 0000000..9b68181 --- /dev/null +++ b/packages/client/src/features/save/model.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SModelElement, SModelExtension, SModelRoot } from "sprotty"; + +export const saveFeature = Symbol.for('saveFeature'); + +export interface Saveable extends SModelExtension { + dirty: boolean; +} + +export function isSaveable(element: SModelElement): element is SModelRoot & Saveable { + return element.hasFeature(saveFeature); +} diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save.ts new file mode 100644 index 0000000..d9a63ac --- /dev/null +++ b/packages/client/src/features/save/save.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, KeyListener, SModelRoot } from "sprotty"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +export class SaveModelAction implements Action { + static readonly KIND = "saveModel"; + readonly kind = SaveModelAction.KIND; + constructor() { } +} + +export class SaveModelKeyboardListener extends KeyListener { + keyDown(element: SModelRoot, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { + return [new SaveModelAction()]; + } + return []; + } +} diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts new file mode 100644 index 0000000..51cb096 --- /dev/null +++ b/packages/client/src/features/select/di.config.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureCommand, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { SelectAllFeedbackCommand, SelectFeedbackCommand } from "./select-feedback-action"; +import { RankedSelectMouseListener } from "./select-mouse-listener"; +import { SelectAllCommand, SelectCommand, SelectionService } from "./selection-service"; + +const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { + bind(SelectionService).toSelf().inSingletonScope(); + bind(GLSP_TYPES.SelectionService).toService(SelectionService); + configureCommand({ bind, isBound }, SelectCommand); + configureCommand({ bind, isBound }, SelectAllCommand); + configureCommand({ bind, isBound }, SelectFeedbackCommand); + configureCommand({ bind, isBound }, SelectAllFeedbackCommand); + bind(TYPES.MouseListener).to(RankedSelectMouseListener); + bind(GLSP_TYPES.SModelRootListener).toService(SelectionService); +}); + +export default glspSelectModule; diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts new file mode 100644 index 0000000..691c10a --- /dev/null +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Command, + CommandExecutionContext, + SelectAllCommand as SprottySelectAllCommand, + SelectCommand as SprottySelectCommand, + SModelRoot, + TYPES +} from "sprotty"; + +export class SelectFeedbackAction { + kind = SelectFeedbackCommand.KIND; + + constructor(public readonly selectedElementsIDs: string[] = [], + public readonly deselectedElementsIDs: string[] = []) { + } +} + +export class SelectAllFeedbackAction { + kind = SelectAllFeedbackCommand.KIND; + + /** + * If `select` is true, all elements are selected, othewise they are deselected. + */ + constructor(public readonly select: boolean = true) { + } +} + +@injectable() +export class SelectFeedbackCommand extends Command { + static readonly KIND = 'elementSelectedFeedback'; + private sprottySelectCommand: SprottySelectCommand; + + constructor(@inject(TYPES.Action) public action: SelectFeedbackAction) { + super(); + this.sprottySelectCommand = new SprottySelectCommand(action); + } + + execute(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectCommand.execute(context); + } + + undo(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectCommand.undo(context); + } + + redo(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectCommand.redo(context); + } +} + +@injectable() +export class SelectAllFeedbackCommand extends Command { + static readonly KIND = 'allSelectedFeedback'; + private sprottySelectAllCommand: SprottySelectAllCommand; + + constructor(@inject(TYPES.Action) public action: SelectAllFeedbackAction) { + super(); + this.sprottySelectAllCommand = new SprottySelectAllCommand(action); + } + + execute(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectAllCommand.execute(context); + } + + undo(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectAllCommand.undo(context); + } + + redo(context: CommandExecutionContext): SModelRoot { + return this.sprottySelectAllCommand.redo(context); + } +} + diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts new file mode 100644 index 0000000..55c2ebb --- /dev/null +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SelectMouseListener } from "sprotty"; + +import { DEFAULT_RANK, Ranked } from "../rank/model"; + +/** + * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. + * This ensures that default mouse listeners are working on a model that has selection changes already applied. + */ +export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { + rank: number = DEFAULT_RANK - 1; /* we want to be executed before all default mouse listeners */ +} diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts new file mode 100644 index 0000000..1a078d8 --- /dev/null +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -0,0 +1,371 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { AssertionError, expect } from "chai"; +import { Container, injectable } from "inversify"; +import { Action, defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; +import { SelectFeedbackAction } from "./select-feedback-action"; +import { SelectionListener, SelectionService } from "./selection-service"; + +@injectable() +class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { + protected feedbackEmitters: Map = new Map; + + registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + this.feedbackEmitters.set(feedbackEmitter, actions); + } + + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + this.feedbackEmitters.delete(feedbackEmitter); + } + + getRegisteredFeedback(): Action[] { + const result: Action[] = []; + this.feedbackEmitters.forEach((value, key) => result.push(...value)); + return result; + } + + getSingleFeedbackAction(): SelectFeedbackAction | undefined { + const actions = this.getRegisteredFeedback(); + return actions.length === 1 ? actions[0] as SelectFeedbackAction : undefined; + } +} + +class MockSelectionListener implements SelectionListener { + private called: number = 0; + private root: SModelRoot; + private selectedElements: string[] = []; + + selectionChanged(newRoot: Readonly, selectedElements: string[]): void { + this.called++; + this.root = newRoot; + this.selectedElements = selectedElements; + } + + getRoot() { + return this.root; + } + + getSelectedElements() { + return this.selectedElements; + } + + getCalled() { + return this.called; + } +} + +function createContainer(): Container { + const container = new Container(); + container.load(defaultModule); + container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); + container.bind(GLSP_TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); + container.bind(SelectionService).toSelf().inSingletonScope(); + container.bind(GLSP_TYPES.SelectionService).toService(SelectionService); + return container; +} + +describe('SelectionService', () => { + let graphFactory: SGraphFactory; + let root: SModelRoot; + let selectionService: SelectionService; + let feedbackDispatcher: MockFeedbackActionDispatcher; + + beforeEach(() => { + const container = createContainer(); + graphFactory = container.get(TYPES.IModelFactory); + root = createRoot('node1', 'node2', 'node3', 'node4', 'node5'); + selectionService = container.get(GLSP_TYPES.SelectionService); + feedbackDispatcher = container.get(GLSP_TYPES.IFeedbackActionDispatcher); + }); + + describe('Initial State', () => { + it('On creation nothing should be selected and no feedback should be dispatched.', () => { + assertSelectionAndFeedback([], []); + }); + }); + describe('Single Selection', () => { + it('Selecting a single element should be tracked correctly and trigger feedback.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1'], []); + assertSelectionAndFeedback(['node1'], []); + }); + it('Selecting the same element twice in one operation should not make a difference.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node1'], []); + assertSelectionAndFeedback(['node1'], []); + }); + it('Selecting and then deselecting the same element should result in an empty selection.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1'], []); + selectionService.updateSelection(root, [], ['node1']); + assertSelectionAndFeedback([], ['node1']); + }); + it('Selecting and deselecting the same element in the same operation should have no effect.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1'], ['node1']); + assertSelectionAndFeedback([], []); + }); + it('Selecting and deselecting not-existing nodes should have no effect.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['not-existing'], []); + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, [], ['not-existing']); + assertSelectionAndFeedback([], []); + }); + }); + describe('Multi Selection', () => { + it('Selecting multiple elements should be tracked correctly and trigger feedback.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2'], []); + assertSelectionAndFeedback(['node1', 'node2'], []); + }); + it('Selecting multiple elements should have the selection order in the dispatched feedback.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node2', 'node1'], []); + expect(() => assertSelectionAndFeedback(['node1', 'node2'], [])).to.throw(AssertionError, 'ordered members'); + }); + it('Selecting the same elements twice in one operation should not make a difference.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node1', 'node2', 'node2'], []); + assertSelectionAndFeedback(['node1', 'node2'], []); + }); + it('Selecting and then deselecting the same elements should result in an empty selection.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2'], []); + selectionService.updateSelection(root, [], ['node1', 'node2']); + assertSelectionAndFeedback([], ['node1', 'node2']); + }); + it('Selecting and deselecting the same elements in one operation should have no effect.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2'], ['node1', 'node2']); + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2', 'node3'], ['node1', 'node2']); + assertSelectionAndFeedback(['node3'], []); + }); + it('Selecting three elements and deselecting one should result in two selected and one deselected element.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); + assertSelectionAndFeedback(['node1', 'node2', 'node3'], []); + selectionService.updateSelection(root, [], ['node2']); + assertSelectionAndFeedback(['node1', 'node3'], ['node2']); + }); + it('A series of selection and deselection operations should be tracked correctly.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); + assertSelectionAndFeedback(['node1', 'node2', 'node3'], []); + selectionService.updateSelection(root, ['node4'], ['node2']); + assertSelectionAndFeedback(['node1', 'node3', 'node4'], ['node2']); + selectionService.updateSelection(root, ['node3', 'node1'], ['node2', 'node4']); + assertSelectionAndFeedback(['node1', 'node3'], ['node4']); + selectionService.updateSelection(root, ['node3'], ['node3']); + assertSelectionAndFeedback(['node1', 'node3'], ['node4']); + }); + }); + describe('Changing Root', () => { + it('Changing root deselects all selected elements if there are no matching elements.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1'], []); + assertSelectionAndFeedback(['node1'], []); + + const newRoot = createRoot('newNode1', 'newNode2', 'newNode3'); + selectionService.modelRootChanged(newRoot); + assertSelectionAndFeedback([], ['node1']); + }); + it('Changing root keeps selected elements if there are matching elements.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2'], []); + assertSelectionAndFeedback(['node1', 'node2'], []); + + const newRoot = createRoot('node1', 'newNode2', 'newNode3'); + selectionService.modelRootChanged(newRoot); + assertSelectionAndFeedback(['node1'], ['node2']); + }); + it('Changing root with new selection correctly selects matchting elements and deselects not matching elements.', () => { + assertSelectionAndFeedback([], []); + selectionService.updateSelection(root, ['node1', 'node2'], []); + assertSelectionAndFeedback(['node1', 'node2'], []); + + const newRoot = createRoot('newNode1', 'newNode2', 'newNode3'); + selectionService.updateSelection(newRoot, ['newNode1'], []); + assertSelectionAndFeedback(['newNode1'], ['node1', 'node2']); + }); + }); + describe('Listeners', () => { + it('A registered listener should be notified of a single selection change.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1', 'node1'], []); + assertListener(listener, root, ['node1'], 1); + }); + it('A registered listener should be notified of a multi-selection change.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); + assertListener(listener, root, ['node1', 'node2', 'node3'], 1); + }); + it('A registered listener should be notified of series of selection changes.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + selectionService.updateSelection(root, ['node2'], ['node1']); + assertListener(listener, root, ['node2'], 2); + selectionService.updateSelection(root, ['node3', 'node4'], []); + assertListener(listener, root, ['node2', 'node3', 'node4'], 3); + selectionService.updateSelection(root, [], ['node4']); + assertListener(listener, root, ['node2', 'node3'], 4); + }); + it('A registered listener should receive the selected elements in the right order.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node2', 'node1'], []); + expect(() => assertListener(listener, root, ['node1', 'node2'], 1)).to.throw(AssertionError, 'ordered members'); + }); + it('A registered listener should be notified of root changes.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, [], []); + assertListener(listener, root, [], 1); + + const newRoot = createRoot('node1', 'newNode2', 'newNode3'); + selectionService.updateSelection(newRoot, [], []); + assertListener(listener, newRoot, [], 2); + }); + it('Registering an already registered listener should have no effect.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + selectionService.register(listener); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); + assertListener(listener, root, ['node1', 'node2', 'node3'], 1); + selectionService.register(listener); + selectionService.updateSelection(root, [], ['node2']); + assertListener(listener, root, ['node1', 'node3'], 2); + }); + it('Selecting the same elements consecutively should not trigger a listener update.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + }); + it('Selecting a not-existing elements should not trigger a listener update.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + selectionService.updateSelection(root, ['not-existing'], []); + assertListener(listener, root, ['node1'], 1); + }); + it('All registered listeners should be called on selection changes.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + const listener2: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + selectionService.register(listener2); + assertListener(listener, undefined, [], 0); + assertListener(listener2, undefined, [], 0); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + assertListener(listener2, root, ['node1'], 1); + selectionService.updateSelection(root, ['node2'], ['node1']); + assertListener(listener, root, ['node2'], 2); + assertListener(listener2, root, ['node2'], 2); + selectionService.updateSelection(root, ['node3', 'node4'], []); + assertListener(listener, root, ['node2', 'node3', 'node4'], 3); + assertListener(listener2, root, ['node2', 'node3', 'node4'], 3); + selectionService.updateSelection(root, [], ['node4']); + assertListener(listener, root, ['node2', 'node3'], 4); + assertListener(listener2, root, ['node2', 'node3'], 4); + }); + it('A deregistered listener should not receive further updates.', () => { + const listener: MockSelectionListener = new MockSelectionListener(); + selectionService.register(listener); + assertListener(listener, undefined, [], 0); + selectionService.updateSelection(root, ['node1'], []); + assertListener(listener, root, ['node1'], 1); + selectionService.deregister(listener); + selectionService.updateSelection(root, ['node2'], ['node1']); + assertListener(listener, root, ['node1'], 1); + selectionService.updateSelection(root, ['node3', 'node4'], []); + assertListener(listener, root, ['node1'], 1); + selectionService.updateSelection(root, [], ['node4']); + assertListener(listener, root, ['node1'], 1); + + const newRoot = createRoot('node1', 'newNode2', 'newNode3'); + selectionService.updateSelection(newRoot, [], []); + assertListener(listener, root, ['node1'], 1); + }); + }); + + function createRoot(...nodes: string[]): SModelRoot { + const children: SModelElementSchema[] = []; + nodes.forEach(node => children.push({ id: node, type: 'node:circle' })); + return graphFactory.createRoot({ + id: 'selection-service-spec', + type: 'graph', + children: children + }); + } + + function assertSelectionAndFeedback(expectedSelection: string[], expectedDeselection: string[]) { + assertSelectionService(expectedSelection); + assertDispatchedFeedback(expectedSelection, expectedDeselection); + } + + function assertSelectionService(expectedSelection: string[]) { + expect(selectionService.isSingleSelection()).to.equal(expectedSelection.length === 1); + expect(selectionService.isMultiSelection()).to.equal(expectedSelection.length > 1); + expect(selectionService.hasSelectedElements()).to.equal(expectedSelection.length > 0); + expect(selectionService.getSelectedElementIDs()).to.have.lengthOf(expectedSelection.length); + if (expectedSelection.length > 0) { + expect(selectionService.getSelectedElementIDs()).to.have.all.keys(...expectedSelection); + } + } + + function assertDispatchedFeedback(expectedSelection: string[], expectedDeselection: string[]) { + // a single feedback action reflects aggregated selection/deselection + const hasFeedback = expectedSelection.length > 0 || expectedDeselection.length > 0; + if (hasFeedback) { + expect(feedbackDispatcher.getRegisteredFeedback()).to.have.lengthOf(1); + expect(feedbackDispatcher.getSingleFeedbackAction()!.selectedElementsIDs).to.have.lengthOf(expectedSelection.length); + expect(feedbackDispatcher.getSingleFeedbackAction()!.selectedElementsIDs).to.have.ordered.members(expectedSelection); + expect(feedbackDispatcher.getSingleFeedbackAction()!.deselectedElementsIDs).to.have.lengthOf(expectedDeselection.length); + expect(feedbackDispatcher.getSingleFeedbackAction()!.deselectedElementsIDs).to.have.ordered.members(expectedDeselection); + } else { + expect(feedbackDispatcher.getRegisteredFeedback()).to.have.lengthOf(0); + expect(feedbackDispatcher.getSingleFeedbackAction()).to.be.undefined; + } + } + + function assertListener(listener: MockSelectionListener, expectedRoot: SModelRoot | undefined, expectedSelection: string[], expectedCalled: number) { + expect(listener.getRoot()).to.equal(expectedRoot); + expect(listener.getSelectedElements()).to.have.lengthOf(expectedSelection.length); + expect(listener.getSelectedElements()).to.have.ordered.members(expectedSelection); + expect(listener.getCalled()).to.equal(expectedCalled); + } +}); diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts new file mode 100644 index 0000000..f959741 --- /dev/null +++ b/packages/client/src/features/select/selection-service.ts @@ -0,0 +1,240 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from "inversify"; +import { + Action, + Command, + CommandExecutionContext, + ILogger, + isSelectable, + isSelected, + SChildElement, + SelectAction, + SelectAllAction, + SelectAllCommand as SprottySelectAllCommand, + SelectCommand as SprottySelectCommand, + SModelElement, + SModelRoot, + SParentElement, + TYPES +} from "sprotty"; + +import { SModelRootListener } from "../../base/model/update-model-command"; +import { GLSP_TYPES } from "../../base/types"; +import { distinctAdd, remove } from "../../utils/array-utils"; +import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; +import { SelectFeedbackAction } from "./select-feedback-action"; + +export interface SelectionListener { + selectionChanged(root: Readonly, selectedElements: string[]): void; +} + +@injectable() +export class SelectionService implements SModelRootListener { + private root: Readonly; + private selectedElementIDs: Set = new Set(); + + @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.ILogger) protected logger: ILogger; + + constructor(@multiInject(GLSP_TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) { } + + register(selectionListener: SelectionListener) { + distinctAdd(this.selectionListeners, selectionListener); + } + + deregister(selectionListener: SelectionListener) { + remove(this.selectionListeners, selectionListener); + } + + modelRootChanged(root: Readonly): void { + this.updateSelection(root, [], []); + } + + updateSelection(root: Readonly, select: string[], deselect: string[]) { + if (root === undefined && select.length === 0 && deselect.length === 0) { + return; + } + const prevRoot = this.root; + const prevSelectedElementIDs = new Set(this.selectedElementIDs); + + // update root + this.root = root; + + // update selected element IDs and collect deselected elements + // - select all elements that are not deselected at the same time (no-op) + // - deselect all elements that are not selected at the same time (no-op) but was selected + const toSelect = [...select].filter(selectId => deselect.indexOf(selectId) === -1); + const toDeselect = [...deselect].filter(deselectId => select.indexOf(deselectId) === -1 && this.selectedElementIDs.has(deselectId)); + for (const id of toDeselect) { + this.selectedElementIDs.delete(id); + } + for (const id of toSelect) { + this.selectedElementIDs.add(id); + } + + const deselectedElementIDs = new Set(toDeselect); + // see if selected elements still exist in the updated root + for (const id of this.selectedElementIDs) { + const element = root.index.getById(id); + if (element === undefined) { + this.selectedElementIDs.delete(id); + if (prevRoot !== undefined && prevRoot.index.getById(id)) { + deselectedElementIDs.add(id); + } + } + } + + // only send out changes if there actually are changes, i.e., the root or the selected elements changed + const selectionChanged = prevSelectedElementIDs.size !== this.selectedElementIDs.size || ![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value)); + if (selectionChanged) { + // aggregate to feedback action handling all elements as only the last feedback is restored + this.dispatchFeedback([new SelectFeedbackAction([...this.selectedElementIDs], [...deselectedElementIDs])]); + } + + const rootChanged = prevRoot !== root; + if (rootChanged || selectionChanged) { + // notify listeners after the feedback action + this.notifyListeners(this.root, this.selectedElementIDs); + } + } + + dispatchFeedback(actions: Action[]) { + this.feedbackDispatcher.registerFeedback(this, actions); + } + + notifyListeners(root: SModelRoot, selectedElementIDs: Set) { + this.selectionListeners.forEach(listener => listener.selectionChanged(root, Array.from(selectedElementIDs))); + } + + getModelRoot(): Readonly { + return this.root; + } + + getSelectedElements(): Readonly[] { + return Array.from(this.root.index.all().filter(isSelected)); + } + + /** + * QUERY METHODS + */ + + getSelectedElementIDs(): Set { + return this.selectedElementIDs; + } + + hasSelectedElements(): boolean { + return this.selectedElementIDs.size > 0; + } + + isSingleSelection(): boolean { + return this.selectedElementIDs.size === 1; + } + + isMultiSelection(): boolean { + return this.selectedElementIDs.size > 1; + } +} + + +@injectable() +export class SelectCommand extends Command { + static readonly KIND = SprottySelectCommand.KIND; + + protected selected: SModelElement[] = []; + protected deselected: SModelElement[] = []; + + constructor(@inject(TYPES.Action) public action: SelectAction, @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService) { + super(); + } + + execute(context: CommandExecutionContext): SModelRoot { + const model = context.root; + this.action.selectedElementsIDs.forEach(id => { + const element = model.index.getById(id); + if (element instanceof SChildElement && isSelectable(element)) { + this.selected.push(element); + } + }); + this.action.deselectedElementsIDs.forEach(id => { + const element = model.index.getById(id); + if (element instanceof SChildElement && isSelectable(element)) { + this.deselected.push(element); + } + }); + return this.redo(context); + } + + undo(context: CommandExecutionContext): SModelRoot { + const select = this.deselected.map(element => element.id); + const deselect = this.selected.map(element => element.id); + this.selectionService.updateSelection(context.root, select, deselect); + return context.root; + } + + redo(context: CommandExecutionContext): SModelRoot { + const select = this.selected.map(element => element.id); + const deselect = this.deselected.map(element => element.id); + this.selectionService.updateSelection(context.root, select, deselect); + return context.root; + } +} + +@injectable() +export class SelectAllCommand extends Command { + static readonly KIND = SprottySelectAllCommand.KIND; + protected previousSelection: Map = new Map(); + + constructor(@inject(TYPES.Action) public action: SelectAllAction, @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService) { + super(); + } + + execute(context: CommandExecutionContext): SModelRoot { + return this.redo(context); + } + + undo(context: CommandExecutionContext): SModelRoot { + const index = context.root.index; + for (const previousState of this.previousSelection) { + const element = index.getById(previousState[0]); + if (element !== undefined && isSelectable(element)) { + element.selected = previousState[1]; + } + } + return context.root; + } + + redo(context: CommandExecutionContext): SModelRoot { + const selectables: string[] = []; + this.selectAll(context.root, this.action.select, selectables); + if (this.action.select) { + this.selectionService.updateSelection(context.root, selectables, []); + } else { + this.selectionService.updateSelection(context.root, [], selectables); + } + return context.root; + } + + protected selectAll(element: SParentElement, newState: boolean, selected: string[]): void { + if (isSelectable(element)) { + this.previousSelection.set(element.id, element.selected); + selected.push(element.id); + } + for (const child of element.children) { + this.selectAll(child, newState, selected); + } + } +} diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts new file mode 100644 index 0000000..ba88a70 --- /dev/null +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -0,0 +1,243 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { VNode } from "snabbdom/vnode"; +import { + Action, + CommandExecutionContext, + CommandReturn, + ElementMove, + findParentByFeature, + isMoveable, + isSelectable, + isViewport, + MouseListener, + MoveAction, + Point, + SModelElement, + SModelRoot, + TYPES +} from "sprotty"; + +import { isNotUndefined } from "../../utils/smodel-util"; +import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from "../change-bounds/model"; +import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from "../change-bounds/movement-restrictor"; +import { ChangeBoundsTool } from "../tools/change-bounds-tool"; +import { FeedbackCommand } from "./model"; + +export class ShowChangeBoundsToolResizeFeedbackAction implements Action { + kind = ShowChangeBoundsToolResizeFeedbackCommand.KIND; + constructor(readonly elementId?: string) { } +} + +export class HideChangeBoundsToolResizeFeedbackAction implements Action { + kind = HideChangeBoundsToolResizeFeedbackCommand.KIND; + constructor() { } +} + +@injectable() +export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { + static readonly KIND = "showChangeBoundsToolResizeFeedback"; + + constructor( + @inject(TYPES.Action) + protected action: ShowChangeBoundsToolResizeFeedbackAction + ) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const index = context.root.index; + index + .all() + .filter(isResizable) + .forEach(removeResizeHandles); + + if (isNotUndefined(this.action.elementId)) { + const resizeElement = index.getById(this.action.elementId); + if (isNotUndefined(resizeElement) && isResizable(resizeElement)) { + addResizeHandles(resizeElement); + } + } + return context.root; + } +} + +@injectable() +export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { + static readonly KIND = "hideChangeBoundsToolResizeFeedback"; + + constructor( + @inject(TYPES.Action) + protected action: HideChangeBoundsToolResizeFeedbackAction + ) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const index = context.root.index; + index + .all() + .filter(isResizable) + .forEach(removeResizeHandles); + return context.root; + } +} + +/** + * This mouse listener provides visual feedback for moving by sending client-side + * `MoveAction`s while elements are selected and dragged. This will also update + * their bounds, which is important, as it is not only required for rendering + * the visual feedback but also the basis for sending the change to the server + * (see also `tools/MoveTool`). + */ +export class FeedbackMoveMouseListener extends MouseListener { + hasDragged = false; + startDragPosition: Point | undefined; + elementId2startPos = new Map(); + + constructor(protected tool: ChangeBoundsTool) { + super(); + } + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + if (event.button === 0 && !(target instanceof SResizeHandle)) { + const moveable = findParentByFeature(target, isMoveable); + if (moveable !== undefined) { + this.startDragPosition = { x: event.pageX, y: event.pageY }; + } else { + this.startDragPosition = undefined; + } + this.hasDragged = false; + } + return []; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (event.buttons === 0) this.mouseUp(target, event); + else if (this.startDragPosition) { + if (this.elementId2startPos.size === 0) { + this.collectStartPositions(target.root); + } + this.hasDragged = true; + const moveAction = this.getElementMoves(target, event, false); + if (moveAction) result.push(moveAction); + } + return result; + } + + protected collectStartPositions(root: SModelRoot) { + root.index + .all() + .filter(element => isSelectable(element) && element.selected) + .forEach(element => { + if (isMoveable(element)) { + this.elementId2startPos.set(element.id, element.position); + } + }); + } + + protected getElementMoves(target: SModelElement, event: MouseEvent, isFinished: boolean): MoveAction | undefined { + if (!this.startDragPosition) return undefined; + const elementMoves: ElementMove[] = []; + const viewport = findParentByFeature(target, isViewport); + const zoom = viewport ? viewport.zoom : 1; + const delta = { + x: (event.pageX - this.startDragPosition.x) / zoom, + y: (event.pageY - this.startDragPosition.y) / zoom + }; + this.elementId2startPos.forEach((startPosition, elementId) => { + const element = target.root.index.getById(elementId); + if (element) { + let toPosition = this.snap( + { + x: startPosition.x + delta.x, + y: startPosition.y + delta.y + }, + element, + !event.shiftKey + ); + + if (isMoveable(element)) { + toPosition = this.validateMove(startPosition, toPosition, element, isFinished); + elementMoves.push({ + elementId: element.id, + fromPosition: { + x: element.position.x, + y: element.position.y + }, + toPosition + }); + } + } + }); + if (elementMoves.length > 0) + return new MoveAction(elementMoves, false, isFinished); + else return undefined; + } + + protected validateMove(startPostion: Point, toPosition: Point, element: SModelElement, isFinished: boolean) { + let newPosition = toPosition; + if (this.tool.movementRestrictor) { + const valid = this.tool.movementRestrictor.validate(toPosition, element); + let action; + if (!valid) { + action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); + + if (isFinished) { + newPosition = startPostion; + } + } else { + action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); + } + + this.tool.dispatchFeedback([action], this); + } + return newPosition; + } + protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + if (isSnap && this.tool.snapper) return this.tool.snapper.snap(position, element); + else return position; + } + + mouseEnter(target: SModelElement, event: MouseEvent): Action[] { + if (target instanceof SModelRoot && event.buttons === 0) + this.mouseUp(target, event); + return []; + } + + mouseUp(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (this.startDragPosition) { + const moveAction = this.getElementMoves(target, event, true); + if (moveAction) { + result.push(moveAction); + } + if (this.tool.movementRestrictor) { + this.tool.deregisterFeedback([removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)], this); + } + + } + this.hasDragged = false; + this.startDragPosition = undefined; + this.elementId2startPos.clear(); + return result; + } + + decorate(vnode: VNode, element: SModelElement): VNode { + return vnode; + } +} diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts new file mode 100644 index 0000000..3b54948 --- /dev/null +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -0,0 +1,170 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + AnchorComputerRegistry, + center, + CommandExecutionContext, + CommandReturn, + euclideanDistance, + findChildrenAtPosition, + findParentByFeature, + isBoundsAware, + isConnectable, + MouseListener, + MoveAction, + PolylineEdgeRouter, + SChildElement, + SConnectableElement, + SDanglingAnchor, + SEdgeSchema, + SModelElement, + SModelRoot, + SRoutableElement, + TYPES +} from "sprotty"; + +import { isRoutable } from "../../utils/smodel-util"; +import { getAbsolutePosition, toAbsolutePosition } from "../../utils/viewpoint-util"; +import { FeedbackCommand } from "./model"; + +export class DrawFeedbackEdgeAction implements Action { + kind = DrawFeedbackEdgeCommand.KIND; + constructor(readonly elementTypeId: string, readonly sourceId: string, readonly edgeSchema?: SEdgeSchema) { } +} + +@injectable() +export class DrawFeedbackEdgeCommand extends FeedbackCommand { + static readonly KIND = 'drawFeedbackEdge'; + + constructor(@inject(TYPES.Action) protected action: DrawFeedbackEdgeAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const feedbackEdgeSchema = this.action.edgeSchema ? this.action.edgeSchema : defaultFeedbackEdgeSchema; + drawFeedbackEdge(context, this.action.sourceId, this.action.elementTypeId, feedbackEdgeSchema); + return context.root; + } +} + +export class RemoveFeedbackEdgeAction implements Action { + kind = RemoveFeedbackEdgeCommand.KIND; + constructor() { } +} + +@injectable() +export class RemoveFeedbackEdgeCommand extends FeedbackCommand { + static readonly KIND = 'removeFeedbackEdgeCommand'; + + execute(context: CommandExecutionContext): CommandReturn { + removeFeedbackEdge(context.root); + return context.root; + } +} + +export class FeedbackEdgeEnd extends SDanglingAnchor { + static readonly TYPE = 'feedback-edge-end'; + type = FeedbackEdgeEnd.TYPE; + constructor(readonly sourceId: string, + readonly elementTypeId: string, + public feedbackEdge: SRoutableElement | undefined = undefined) { + super(); + } +} + +export class FeedbackEdgeEndMovingMouseListener extends MouseListener { + constructor(protected anchorRegistry: AnchorComputerRegistry) { + super(); + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const root = target.root; + const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); + if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { + return []; + } + + const edge = edgeEnd.feedbackEdge; + const position = getAbsolutePosition(edgeEnd, event); + const endAtMousePosition = findChildrenAtPosition(target.root, position) + .find(e => isConnectable(e) && e.canConnect(edge, 'target')); + + if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { + const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, endAtMousePosition.anchorKind); + const anchor = anchorComputer.getAnchor(endAtMousePosition, center(edge.source.bounds)); + if (euclideanDistance(anchor, edgeEnd.position) > 1) { + return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; + } + } else { + return [new MoveAction([{ elementId: edgeEnd.id, toPosition: position }], false)]; + } + + return []; + } +} + +export function feedbackEdgeId(root: SModelRoot): string { + return root.id + '_feedback_edge'; +} + +export function feedbackEdgeEndId(root: SModelRoot): string { + return root.id + '_feedback_anchor'; +} + +export const defaultFeedbackEdgeSchema: SEdgeSchema = { + cssClasses: ["feedback-edge"], + opacity: 0.3 +}; + +export function drawFeedbackEdge(context: CommandExecutionContext, sourceId: string, elementTypeId: string, feedbackEdgeSchema: SEdgeSchema) { + const root = context.root; + const sourceChild = root.index.getById(sourceId); + if (!sourceChild) { + return; + } + + const source = findParentByFeature(sourceChild, isConnectable); + if (!source || !isBoundsAware(source)) { + return; + } + + const edgeEnd = new FeedbackEdgeEnd(source.id, elementTypeId); + edgeEnd.id = feedbackEdgeEndId(root); + edgeEnd.position = toAbsolutePosition(source); + + feedbackEdgeSchema.id = feedbackEdgeId(root); + feedbackEdgeSchema.type = elementTypeId; + feedbackEdgeSchema.sourceId = source.id; + feedbackEdgeSchema.targetId = edgeEnd.id; + + const feedbackEdge = context.modelFactory.createElement(feedbackEdgeSchema); + if (isRoutable(feedbackEdge)) { + edgeEnd.feedbackEdge = feedbackEdge; + root.add(edgeEnd); + root.add(feedbackEdge); + } +} + +export function removeFeedbackEdge(root: SModelRoot) { + const feedbackEdge = root.index.getById(feedbackEdgeId(root)); + const feedbackEdgeEnd = root.index.getById(feedbackEdgeEndId(root)); + if (feedbackEdge instanceof SChildElement) + root.remove(feedbackEdge); + if (feedbackEdgeEnd instanceof SChildElement) + root.remove(feedbackEdgeEnd); +} diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts new file mode 100644 index 0000000..af06da1 --- /dev/null +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from "sprotty"; + +import { addCssClasses, removeCssClasses } from "../../utils/smodel-util"; +import { FeedbackCommand } from "./model"; + +export class ModifyCSSFeedbackAction implements Action { + kind = ModifyCssFeedbackCommand.KIND; + readonly elementIds?: string[]; + constructor(elements?: SModelElement[], readonly addClasses?: string[], readonly removeClasses?: string[]) { + if (elements) { + this.elementIds = elements.map(elt => elt.id); + } + } +} + +@injectable() +export class ModifyCssFeedbackCommand extends FeedbackCommand { + static readonly KIND = 'modifyCSSFeedback'; + + constructor(@inject(TYPES.Action) readonly action: ModifyCSSFeedbackAction) { + super(); + } + + execute(context: CommandExecutionContext): SModelRoot { + const elements: SModelElement[] = []; + if (this.action.elementIds) { + elements.push(...this.action.elementIds.map(elementId => context.root.index.getById(elementId)).filter(exists)); + } else { + elements.push(context.root); + } + + elements.forEach(e => { + if (this.action.removeClasses) { + removeCssClasses(e, this.action.removeClasses); + } + + if (this.action.addClasses) { + addCssClasses(e, this.action.addClasses); + } + }); + + return context.root; + } +} + +function exists(elt?: SModelElement): elt is SModelElement { + return elt !== undefined; +} + +export enum CursorCSS { + DEFAULT = 'default-mode', + OVERLAP_FORBIDDEN = 'overlap-forbidden-mode', + NODE_CREATION = 'node-creation-mode', + EDGE_CREATION_SOURCE = 'edge-creation-select-source-mode', + EDGE_CREATION_TARGET = 'edge-creation-select-target-mode', + EDGE_RECONNECT = 'edge-reconnect-select-target-mode', + OPERATION_NOT_ALLOWED = 'edge-modification-not-allowed-mode', + ELEMENT_DELETION = "element-deletion-mode", + RESIZE = "resize-mode" +} + +export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAction { + const addCss = []; + if (cursorCss) { + addCss.push(cursorCss); + } + return new ModifyCSSFeedbackAction(undefined, addCss, Object.values(CursorCSS)); +} + +export function applyCssClasses(element: SModelElement, ...classes: string[]) { + return new ModifyCSSFeedbackAction([element], classes, []); +} + +export function deleteCssClasses(element: SModelElement, ...classes: string[]) { + return new ModifyCSSFeedbackAction([element], [], classes); +} diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts new file mode 100644 index 0000000..990c07d --- /dev/null +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { SResizeHandle } from "../change-bounds/model"; +import { + HideChangeBoundsToolResizeFeedbackCommand, + ShowChangeBoundsToolResizeFeedbackCommand +} from "./change-bounds-tool-feedback"; +import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from "./creation-tool-feedback"; +import { ModifyCssFeedbackCommand } from "./css-feedback"; +import { + DrawFeedbackEdgeSourceCommand, + HideEdgeReconnectHandlesFeedbackCommand, + ShowEdgeReconnectHandlesFeedbackCommand, + SwitchRoutingModeCommand +} from "./edge-edit-tool-feedback"; +import { FeedbackActionDispatcher } from "./feedback-action-dispatcher"; +import { FeedbackEdgeEndView, SResizeHandleView } from "./view"; + +const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { + bind(GLSP_TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); + + configureCommand({ bind, isBound }, ModifyCssFeedbackCommand); + + // create node and edge tool feedback + configureCommand({ bind, isBound }, DrawFeedbackEdgeCommand); + configureCommand({ bind, isBound }, RemoveFeedbackEdgeCommand); + + configureView({ bind, isBound }, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); + // move tool feedback: we use sprotties MoveCommand as client-side visual feedback + configureCommand({ bind, isBound }, MoveCommand); + + // resize tool feedback + configureCommand({ bind, isBound }, ShowChangeBoundsToolResizeFeedbackCommand); + configureCommand({ bind, isBound }, HideChangeBoundsToolResizeFeedbackCommand); + configureView({ bind, isBound }, SResizeHandle.TYPE, SResizeHandleView); + + // reconnect edge tool feedback + configureCommand({ bind, isBound }, ShowEdgeReconnectHandlesFeedbackCommand); + configureCommand({ bind, isBound }, HideEdgeReconnectHandlesFeedbackCommand); + configureCommand({ bind, isBound }, DrawFeedbackEdgeSourceCommand); + + configureCommand({ bind, isBound }, SwitchRoutingModeCommand); + + bind(TYPES.IVNodePostprocessor).to(LocationPostprocessor); + bind(TYPES.HiddenVNodePostprocessor).to(LocationPostprocessor); +}); + +export default toolFeedbackModule; diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts new file mode 100644 index 0000000..617ba29 --- /dev/null +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -0,0 +1,306 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { VNode } from "snabbdom/vnode"; +import { + Action, + AnchorComputerRegistry, + center, + CommandExecutionContext, + CommandReturn, + EdgeRouterRegistry, + ElementMove, + euclideanDistance, + findChildrenAtPosition, + findParentByFeature, + isBoundsAware, + isConnectable, + isSelected, + isViewport, + MouseListener, + MoveAction, + Point, + PolylineEdgeRouter, + SConnectableElement, + SModelElement, + SModelRoot, + SRoutingHandle, + SwitchEditModeAction, + SwitchEditModeCommand, + TYPES +} from "sprotty"; + +import { isNotUndefined, isRoutable, isRoutingHandle } from "../../utils/smodel-util"; +import { getAbsolutePosition } from "../../utils/viewpoint-util"; +import { addReconnectHandles, removeReconnectHandles } from "../reconnect/model"; +import { + FeedbackEdgeEnd, + feedbackEdgeEndId, + FeedbackEdgeEndMovingMouseListener, + feedbackEdgeId +} from "./creation-tool-feedback"; +import { FeedbackCommand } from "./model"; + +/** + * RECONNECT HANDLES FEEDBACK + */ + +export class ShowEdgeReconnectHandlesFeedbackAction implements Action { + kind = ShowEdgeReconnectHandlesFeedbackCommand.KIND; + constructor(readonly elementId?: string) { } +} + +export class HideEdgeReconnectHandlesFeedbackAction implements Action { + kind = HideEdgeReconnectHandlesFeedbackCommand.KIND; + constructor() { } +} + +@injectable() +export class ShowEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { + static readonly KIND = 'showReconnectHandlesFeedback'; + + constructor(@inject(TYPES.Action) protected action: ShowEdgeReconnectHandlesFeedbackAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const index = context.root.index; + index.all().filter(isRoutable).forEach(removeReconnectHandles); + + if (isNotUndefined(this.action.elementId)) { + const routableElement = index.getById(this.action.elementId); + if (isNotUndefined(routableElement) && isRoutable(routableElement)) { + addReconnectHandles(routableElement); + } + } + return context.root; + } +} + +@injectable() +export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { + static readonly KIND = 'hideReconnectHandlesFeedback'; + + constructor(@inject(TYPES.Action) protected action: HideEdgeReconnectHandlesFeedbackAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const index = context.root.index; + index.all().filter(isRoutable).forEach(removeReconnectHandles); + return context.root; + } +} +/** + * ROUTING FEEDBACK + */ + +export class SwitchRoutingModeAction extends SwitchEditModeAction { + readonly kind = SwitchRoutingModeCommand.KIND; +} +@injectable() +export class SwitchRoutingModeCommand extends SwitchEditModeCommand { + static KIND = "switchRoutingMode"; + constructor(@inject(TYPES.Action) action: SwitchRoutingModeAction) { super(action); } +} + +/** + * SOURCE AND TARGET EDGE FEEDBACK + */ + +export class DrawFeedbackEdgeSourceAction implements Action { + kind = DrawFeedbackEdgeSourceCommand.KIND; + constructor(readonly elementTypeId: string, readonly targetId: string) { } +} + + +@injectable() +export class DrawFeedbackEdgeSourceCommand extends FeedbackCommand { + static readonly KIND = 'drawFeedbackEdgeSource'; + + constructor(@inject(TYPES.Action) protected action: DrawFeedbackEdgeSourceAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + drawFeedbackEdgeSource(context, this.action.targetId, this.action.elementTypeId); + return context.root; + } +} + +/** + * SOURCE AND TARGET MOUSE MOVE LISTENER + */ + +export class FeedbackEdgeTargetMovingMouseListener extends FeedbackEdgeEndMovingMouseListener { + constructor(protected anchorRegistry: AnchorComputerRegistry) { + super(anchorRegistry); + } +} + +export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { + constructor(protected anchorRegistry: AnchorComputerRegistry) { + super(); + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const root = target.root; + const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); + if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { + return []; + } + + const edge = edgeEnd.feedbackEdge; + const position = getAbsolutePosition(edgeEnd, event); + const endAtMousePosition = findChildrenAtPosition(target.root, position) + .find(e => isConnectable(e) && e.canConnect(edge, 'source')); + + if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { + const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, endAtMousePosition.anchorKind); + const anchor = anchorComputer.getAnchor(endAtMousePosition, center(edge.target.bounds)); + if (euclideanDistance(anchor, edgeEnd.position) > 1) { + return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; + } + } else { + return [new MoveAction([{ elementId: edgeEnd.id, toPosition: position }], false)]; + } + + return []; + } +} + +export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { + hasDragged = false; + lastDragPosition: Point | undefined; + + constructor(protected edgeRouterRegistry?: EdgeRouterRegistry) { + super(); + } + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (event.button === 0) { + const routingHandle = findParentByFeature(target, isRoutingHandle); + if (routingHandle !== undefined) { + result.push(new SwitchRoutingModeAction([target.id], [])); + this.lastDragPosition = { x: event.pageX, y: event.pageY }; + } else { + this.lastDragPosition = undefined; + } + this.hasDragged = false; + } + return result; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (event.buttons === 0) + this.mouseUp(target, event); + else if (this.lastDragPosition) { + const viewport = findParentByFeature(target, isViewport); + this.hasDragged = true; + const zoom = viewport ? viewport.zoom : 1; + const dx = (event.pageX - this.lastDragPosition.x) / zoom; + const dy = (event.pageY - this.lastDragPosition.y) / zoom; + const handleMoves: ElementMove[] = []; + target.root.index.all() + .filter(element => isSelected(element)) + .forEach(element => { + if (isRoutingHandle(element)) { + const point = this.getHandlePosition(element); + if (point !== undefined) { + handleMoves.push({ + elementId: element.id, + fromPosition: point, + toPosition: { + x: point.x + dx, + y: point.y + dy + } + }); + } + } + }); + this.lastDragPosition = { x: event.pageX, y: event.pageY }; + if (handleMoves.length > 0) + result.push(new MoveAction(handleMoves, false)); + } + return result; + } + + protected getHandlePosition(handle: SRoutingHandle): Point | undefined { + if (this.edgeRouterRegistry) { + const parent = handle.parent; + if (!isRoutable(parent)) + return undefined; + const router = this.edgeRouterRegistry.get(parent.routerKind); + const route = router.route(parent); + return router.getHandlePosition(parent, route, handle); + } + return undefined; + } + + mouseEnter(target: SModelElement, event: MouseEvent): Action[] { + if (target instanceof SModelRoot && event.buttons === 0) + this.mouseUp(target, event); + return []; + } + + mouseUp(target: SModelElement, event: MouseEvent): Action[] { + this.hasDragged = false; + this.lastDragPosition = undefined; + return []; + } + + decorate(vnode: VNode, element: SModelElement): VNode { + return vnode; + } +} + +/** + * UTILITY FUNCTIONS + */ + +function drawFeedbackEdgeSource(context: CommandExecutionContext, targetId: string, elementTypeId: string) { + const root = context.root; + const targetChild = root.index.getById(targetId); + if (!targetChild) { + return; + } + + const target = findParentByFeature(targetChild, isConnectable); + if (!target || !isBoundsAware(target)) { + return; + } + + const edgeEnd = new FeedbackEdgeEnd(target.id, elementTypeId); + edgeEnd.id = feedbackEdgeEndId(root); + edgeEnd.position = { x: target.bounds.x, y: target.bounds.y }; + + const feedbackEdgeSchema = { + type: 'edge', + id: feedbackEdgeId(root), + sourceId: edgeEnd.id, + targetId: target.id, + opacity: 0.3 + }; + + const feedbackEdge = context.modelFactory.createElement(feedbackEdgeSchema); + if (isRoutable(feedbackEdge)) { + edgeEnd.feedbackEdge = feedbackEdge; + root.add(edgeEnd); + root.add(feedbackEdge); + } +} diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts new file mode 100644 index 0000000..cf3ff83 --- /dev/null +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, IActionDispatcher, ILogger, TYPES } from "sprotty"; + +export interface IFeedbackEmitter { } + +/** + * Dispatcher for actions that are meant to show visual feedback on + * the diagram that is not part of the diagram sent from the server + * after a model update. + * + * The purpose of this dispatcher is to re-establish the feedback + * after the model has been updated or reset by the server, as this would + * overwrite the already established feedback, in case it is drawn by + * extending the `SModelRoot`. Therefore, tools can register themselves + * as feedback emitters with actions they want to place for showing + * feedback. This dispatcher will then re-establish all feedback actions + * of the registered tools, whenever the `SModelRoot` has been set or updated. + */ +export interface IFeedbackActionDispatcher { + /** + * Registers `actions` to be sent out by a `feedbackEmitter`. + * @param feedbackEmitter the emitter sending out feedback actions. + * @param actions the actions to be sent out. + */ + registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void; + + /** + * Deregisters a `feedbackEmitter` from this dispatcher and thereafter + * dispatches the provided `actions`. + * @param feedbackEmitter the emitter to be deregistered. + * @param actions the actions to be dispatched right after the deregistration. + * These actions do not have to be related to the actions sent out by the + * deregistered `feedbackEmitter`. The purpose of these actions typically is + * to reset the normal state of the diagram without the feedback (e.g., reset a + * CSS class that was set by a feedbackEmitter). + */ + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void; + + /** + * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. + */ + getRegisteredFeedback(): Action[] +} + +@injectable() +export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { + protected feedbackEmitters: Map = new Map; + + constructor( + @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise, + @inject(TYPES.ILogger) protected logger: ILogger) { + } + + registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + this.feedbackEmitters.set(feedbackEmitter, actions); + this.dispatch(actions, feedbackEmitter); + } + + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + this.feedbackEmitters.delete(feedbackEmitter); + this.dispatch(actions, feedbackEmitter); + } + + private dispatch(actions: Action[], feedbackEmitter: IFeedbackEmitter) { + this.actionDispatcher() + .then(dispatcher => dispatcher.dispatchAll(actions)) + .then(() => this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`)) + .catch(reason => this.logger.error(this, 'Failed to dispatch feedback actions', reason)); + } + + getRegisteredFeedback() { + const result: Action[] = []; + this.feedbackEmitters.forEach((value, key) => result.push(...value)); + return result; + } + + getRegisteredFeedbackEmitters(action: Action) { + const result: IFeedbackEmitter[] = []; + this.feedbackEmitters.forEach((value, key) => { + if (value.find(a => a === action)) { + result.push(key); + } + } + ); + return result; + } + +} diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts new file mode 100644 index 0000000..5b6080c --- /dev/null +++ b/packages/client/src/features/tool-feedback/model.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Command, CommandExecutionContext, CommandReturn } from "sprotty"; + +export abstract class FeedbackCommand extends Command { + // used by the `FeedbackAwareUpdateModelCommand` + readonly priority: number = 0; + + abstract execute(context: CommandExecutionContext): CommandReturn; + undo(context: CommandExecutionContext): CommandReturn { + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + return context.root; + } +} + diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx new file mode 100644 index 0000000..733c1c3 --- /dev/null +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from "inversify"; +import * as snabbdom from "snabbdom-jsx"; +import { VNode } from "snabbdom/vnode"; +import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement } from "sprotty"; + +import { isResizable, ResizeHandleLocation, SResizeHandle } from "../change-bounds/model"; + +const JSX = { createElement: snabbdom.svg }; + +/** +* This view is used for the invisible end of the feedback edge. +* A feedback edge is shown as a visual feedback when creating edges. +*/ +@injectable() +export class FeedbackEdgeEndView implements IView { + render(model: Readonly, context: RenderingContext): VNode { + const position: Point = (model as any).position || ORIGIN_POINT; + return ; + } +} + +@injectable() +export class SResizeHandleView implements IView { + render(handle: SResizeHandle, context: RenderingContext): VNode { + const position = this.getPosition(handle); + if (position !== undefined) { + const node = ; + setAttr(node, 'data-kind', handle.location); + return node; + } + // Fallback: Create an empty group + return ; + } + + protected getPosition(handle: SResizeHandle): Point | undefined { + const parent = handle.parent; + if (isResizable(parent)) { + if (handle.location === ResizeHandleLocation.TopLeft) { + return { x: 0, y: 0 }; + } else if (handle.location === ResizeHandleLocation.TopRight) { + return { x: parent.bounds.width, y: 0 }; + } else if (handle.location === ResizeHandleLocation.BottomLeft) { + return { x: 0, y: parent.bounds.height }; + } else if (handle.location === ResizeHandleLocation.BottomRight) { + return { x: parent.bounds.width, y: parent.bounds.height }; + } + } + return undefined; + } + + getRadius(): number { + return 7; + } +} diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts new file mode 100644 index 0000000..51d1dfd --- /dev/null +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "../../../css/tool-palette.css"; + +import { ContainerModule } from "inversify"; +import { configureActionHandler, EnableDefaultToolsAction, TYPES } from "sprotty"; + +import { EnableToolPaletteAction, ToolPalette } from "./tool-palette"; + +const toolPaletteModule = new ContainerModule((bind, _unbind, isBound) => { + bind(ToolPalette).toSelf().inSingletonScope(); + bind(TYPES.IUIExtension).toService(ToolPalette); + configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); + configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); +}); + +export default toolPaletteModule; diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts new file mode 100644 index 0000000..eb4c2a7 --- /dev/null +++ b/packages/client/src/features/tool-palette/palette-item.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { isLabeledAction, LabeledAction } from "sprotty"; + +import { isTriggerElementTypeCreationAction, TriggerElementCreationAction } from "../../base/operations/operation"; + +export interface PaletteItem extends LabeledAction { + readonly id: string; + readonly sortString: string; + readonly children?: PaletteItem[]; +} + +export function isPaletteItem(object?: any): object is PaletteItem { + return isLabeledAction(object) && "id" in object && "sortString" in object; +} + +export namespace PaletteItem { + export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { + if (item) { + const initiAction = item.actions.filter(a => isTriggerElementTypeCreationAction(a)) + .map(a => a as TriggerElementCreationAction); + return initiAction.length > 0 ? initiAction[0] : undefined; + } + return undefined; + } +} + diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts new file mode 100644 index 0000000..ea4251a --- /dev/null +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -0,0 +1,348 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, postConstruct } from "inversify"; +import { + AbstractUIExtension, + Action, + EnableDefaultToolsAction, + EnableToolsAction, + IActionHandler, + ICommand, + IToolManager, + SetUIExtensionVisibilityAction, + SModelRoot, + TYPES +} from "sprotty"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { isSetContextActionsAction, RequestContextActions, SetContextActions } from "../../base/actions/context-actions"; +import { EditModeListener, EditorContextService } from "../../base/editor-context"; +import { MouseDeleteTool } from "../tools/delete-tool"; +import { RequestMarkersAction } from "../validation/validate"; +import { PaletteItem } from "./palette-item"; + +const CLICKED_CSS_CLASS = "clicked"; +const SEARCH_ICON = "fa-search"; +const PALETTE_ICON = "fa-palette"; +const CHEVRON_DOWN = "fa-chevron-down"; +const PALETTE_HEIGHT = "500px"; + +@injectable() +export class EnableToolPaletteAction implements Action { + static readonly KIND = "enableToolPalette"; + readonly kind = EnableToolPaletteAction.KIND; +} + +@injectable() +export class ToolPalette extends AbstractUIExtension implements IActionHandler, EditModeListener { + + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IToolManager) protected readonly toolManager: IToolManager; + @inject(EditorContextService) protected readonly editorContext: EditorContextService; + + protected paletteItems: PaletteItem[]; + protected paletteItemsCopy: PaletteItem[] = []; + protected bodyDiv?: HTMLElement; + protected lastActivebutton?: HTMLElement; + protected defaultToolsButton: HTMLElement; + protected searchField: HTMLInputElement; + modelRootId: string; + + id() { return ToolPalette.ID; } + containerClass() { return ToolPalette.ID; } + + static readonly ID = "tool-palette"; + + @postConstruct() + postConstruct() { + this.editorContext.register(this); + } + + initialize() { + if (!this.paletteItems) { + return false; + } + return super.initialize(); + } + + protected initializeContents(containerElement: HTMLElement): void { + this.createHeader(); + this.createBody(); + } + + protected onBeforeShow(containerElement: HTMLElement, root: Readonly) { + this.modelRootId = root.id; + this.containerElement.style.maxHeight = PALETTE_HEIGHT; + } + + protected addMinimizePaletteButton(): void { + const baseDiv = document.getElementById(this.options.baseDiv); + const minPaletteDiv = document.createElement("div"); + minPaletteDiv.classList.add("minimize-palette-button"); + this.containerElement.classList.add("collapsible-palette"); + if (baseDiv) { + const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); + const minimizeIcon = createIcon(["fas", CHEVRON_DOWN]); + minimizeIcon.onclick = (ev) => { + if (this.containerElement.style.maxHeight !== "0px") { + this.containerElement.style.maxHeight = "0px"; + } else { + this.containerElement.style.maxHeight = PALETTE_HEIGHT; + } + changeCSSClass(minimizeIcon, PALETTE_ICON); + changeCSSClass(minimizeIcon, "fa"); + changeCSSClass(minimizeIcon, "fas"); + changeCSSClass(minimizeIcon, CHEVRON_DOWN); + }; + insertedDiv.appendChild(minimizeIcon); + } + } + + protected createBody(): void { + const bodyDiv = document.createElement("div"); + bodyDiv.classList.add("palette-body"); + this.paletteItems.sort(compare) + .forEach(item => { + if (item.children) { + const group = createToolGroup(item); + item.children.sort(compare).forEach(child => group.appendChild(this.createToolButton(child))); + bodyDiv.appendChild(group); + } else { + bodyDiv.appendChild(this.createToolButton(item)); + } + }); + if (this.paletteItems.length === 0) { + const noResultsDiv = document.createElement("div"); + noResultsDiv.innerText = "No results found."; + noResultsDiv.classList.add("tool-button"); + bodyDiv.appendChild(noResultsDiv); + } + // Remove existing body to refresh filtered entries + if (this.bodyDiv) { + this.containerElement.removeChild(this.bodyDiv); + } + this.containerElement.appendChild(bodyDiv); + this.bodyDiv = bodyDiv; + } + + protected createHeader(): void { + this.addMinimizePaletteButton(); + const headerCompartment = document.createElement("div"); + headerCompartment.classList.add("palette-header"); + + // Title header + const header = document.createElement("div"); + header.classList.add("header-icon"); + header.appendChild(createIcon(["fa", "fa-palette"])); + header.insertAdjacentText("beforeend", "Palette"); + headerCompartment.append(header); + // Header Tools Compartment + const headerTools = document.createElement("div"); + headerTools.classList.add("header-tools"); + + // Create button for DefaultTools + this.defaultToolsButton = createIcon(["fas", "fa-mouse-pointer", "fa-xs", "clicked"]); + this.defaultToolsButton.id = "btn_default_tools"; + this.defaultToolsButton.onclick = this.onClickStaticToolButton(this.defaultToolsButton); + headerTools.appendChild(this.defaultToolsButton); + this.lastActivebutton = this.defaultToolsButton; + + // Create button for MouseDeleteTool + const deleteToolButton = createIcon(["fas", "fa-eraser", "fa-xs"]); + deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); + headerTools.appendChild(deleteToolButton); + + // Create button for ValidationTool + const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); + validateActionButton.onclick = (ev: MouseEvent) => { + const modelIds: string[] = [this.modelRootId]; + this.actionDispatcher.dispatch(new RequestMarkersAction(modelIds)); + }; + headerTools.appendChild(validateActionButton); + + // Create button for Search + const searchIcon = createIcon(["fas", SEARCH_ICON, "state-icon", "fa-xs"]); + searchIcon.onclick = (ev) => { + const searchField = document.getElementById(this.containerElement.id + "_search_field"); + if (searchField) { + if (searchField.style.display === "inline") { + searchField.style.display = "none"; + } else { + searchField.style.display = "inline"; + searchField.focus(); + } + } + }; + searchIcon.classList.add("search-icon"); + this.searchField = document.createElement("input"); + this.searchField.classList.add("search-input"); + this.searchField.id = this.containerElement.id + "_search_field"; + this.searchField.type = "text"; + this.searchField.placeholder = " Search..."; + this.searchField.style.display = "none"; + this.searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); + this.searchField.onkeydown = (ev) => this.clearOnEspace(ev); + + headerTools.appendChild(searchIcon); + headerCompartment.appendChild(headerTools); + headerCompartment.appendChild(this.searchField); + this.containerElement.appendChild(headerCompartment); + } + + protected createToolButton(item: PaletteItem): HTMLElement { + const button = document.createElement("div"); + button.classList.add("tool-button"); + button.innerHTML = item.label; + button.onclick = this.onClickCreateToolButton(button, item); + return button; + } + + protected onClickCreateToolButton(button: HTMLElement, item: PaletteItem) { + return (ev: MouseEvent) => { + if (!this.editorContext.isReadonly) { + this.actionDispatcher.dispatchAll(item.actions); + this.changeActiveButton(button); + this.restoreFocus(); + } + }; + } + + protected onClickStaticToolButton(button: HTMLElement, toolId?: string) { + return (ev: MouseEvent) => { + if (!this.editorContext.isReadonly) { + const action = toolId ? new EnableToolsAction([toolId]) : new EnableDefaultToolsAction(); + this.actionDispatcher.dispatch(action); + this.changeActiveButton(button); + this.restoreFocus(); + } + }; + } + + changeActiveButton(button?: HTMLElement) { + if (this.lastActivebutton) { + this.lastActivebutton.classList.remove(CLICKED_CSS_CLASS); + } + if (button) { + button.classList.add(CLICKED_CSS_CLASS); + this.lastActivebutton = button; + } else { + this.defaultToolsButton.classList.add(CLICKED_CSS_CLASS); + this.lastActivebutton = this.defaultToolsButton; + } + } + + handle(action: Action): ICommand | Action | void { + if (action.kind === EnableToolPaletteAction.KIND) { + const requestAction = new RequestContextActions(ToolPalette.ID, { + selectedElementIds: [] + }); + this.actionDispatcher.requestUntil(requestAction).then(response => { + if (isSetContextActionsAction(response)) { + this.paletteItems = response.actions.map(e => e as PaletteItem); + this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); + } + }); + } else if (action instanceof EnableDefaultToolsAction) { + this.changeActiveButton(); + } + } + + editModeChanged(oldValue: string, newValue: string) { + this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); + } + + protected clearOnEspace(event: KeyboardEvent) { + if (matchesKeystroke(event, 'Escape')) { + this.searchField.value = ''; + this.requestFilterUpdate(''); + } + } + + protected handleSetContextActions(action: SetContextActions) { + this.paletteItems = action.actions.map(e => e as PaletteItem); + this.createBody(); + } + + protected requestFilterUpdate(filter: string): void { + // Initialize the copy if it's empty + if (this.paletteItemsCopy.length === 0) { + // Creating deep copy + this.paletteItemsCopy = JSON.parse(JSON.stringify(this.paletteItems)); + } + + // Reset the paletteItems before searching + this.paletteItems = JSON.parse(JSON.stringify(this.paletteItemsCopy)); + // Filter the entries + const filteredPaletteItems: PaletteItem[] = []; + for (const itemGroup of this.paletteItems) { + if (itemGroup.children) { + // Fetch the labels according to the filter + const matchingChildren = itemGroup.children.filter(child => child.label.toLowerCase().includes(filter.toLowerCase())); + // Add the itemgroup containing the correct entries + if (matchingChildren.length > 0) { + // Clear existing children + itemGroup.children.splice(0, itemGroup.children.length); + // Push the matching children + matchingChildren.forEach(child => itemGroup.children!.push(child)); + filteredPaletteItems.push(itemGroup); + } + } + } + this.paletteItems = filteredPaletteItems; + this.createBody(); + } +} + +export function compare(a: PaletteItem, b: PaletteItem): number { + const sortStringBased = a.sortString.localeCompare(b.sortString); + if (sortStringBased !== 0) { + return sortStringBased; + } + return a.label.localeCompare(b.label); +} + +export function createIcon(cssClasses: string[]) { + const icon = document.createElement("i"); + icon.classList.add(...cssClasses); + return icon; +} + +export function createToolGroup(item: PaletteItem): HTMLElement { + const group = document.createElement("div"); + group.classList.add("tool-group"); + group.id = item.id; + const header = document.createElement("div"); + header.classList.add("group-header"); + if (item.icon) { + header.appendChild(createIcon(["fas", item.icon])); + } + header.insertAdjacentText('beforeend', item.label); + header.ondblclick = (ev) => { + const css = "collapsed"; + changeCSSClass(group, css); + Array.from(group.children).forEach(child => changeCSSClass(child, css)); + window!.getSelection()!.removeAllRanges(); + }; + + group.appendChild(header); + return group; +} + +export function changeCSSClass(element: Element, css: string) { + element.classList.contains(css) ? element.classList.remove(css) : + element.classList.add(css); +} diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts new file mode 100644 index 0000000..086e439 --- /dev/null +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, IActionDispatcher, KeyTool, TYPES } from "sprotty"; + +import { EditorContextService } from "../../base/editor-context"; +import { GLSPTool } from "../../base/tool-manager/glsp-tool-manager"; +import { GLSP_TYPES } from "../../base/types"; +import { IMouseTool } from "../mouse-tool/mouse-tool"; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; + +@injectable() +export abstract class BaseGLSPTool implements GLSPTool { + @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; + @inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(KeyTool) protected keyTool: KeyTool; + @inject(EditorContextService) protected readonly editorContext: EditorContextService; + + abstract enable(): void; + abstract disable(): void; + + abstract id: string; + + get isEditTool(): boolean { + return true; + } + + dispatchFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter) { + this.feedbackDispatcher.registerFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); + } + + dispatchActions(actions: Action[]) { + this.actionDispatcher.dispatchAll(actions); + } + + deregisterFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter) { + this.feedbackDispatcher.deregisterFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); + } +} diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts new file mode 100644 index 0000000..31611d3 --- /dev/null +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -0,0 +1,442 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + Bounds, + BoundsAware, + Dimension, + EdgeRouterRegistry, + ElementAndBounds, + findParentByFeature, + ISnapper, + isSelected, + isViewport, + MouseListener, + Point, + SConnectableElement, + SetBoundsAction, + SModelElement, + SModelRoot, + SParentElement, + TYPES +} from "sprotty"; + +import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { + ChangeBoundsOperation, + ChangeRoutingPointsOperation, + CompoundOperation, + ElementAndRoutingPoints, + Operation +} from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; +import { isValidMove, isValidSize, WriteablePoint } from "../../utils/layout-utils"; +import { + forEachElement, + isNonRoutableSelectedMovableBoundsAware, + toElementAndBounds, + toElementAndRoutingPoints +} from "../../utils/smodel-util"; +import { isBoundsAwareMoveable, isResizable, Resizable, ResizeHandleLocation, SResizeHandle } from "../change-bounds/model"; +import { + createMovementRestrictionFeedback, + IMovementRestrictor, + removeMovementRestrictionFeedback +} from "../change-bounds/movement-restrictor"; +import { SelectionListener, SelectionService } from "../select/selection-service"; +import { + FeedbackMoveMouseListener, + HideChangeBoundsToolResizeFeedbackAction, + ShowChangeBoundsToolResizeFeedbackAction +} from "../tool-feedback/change-bounds-tool-feedback"; +import { applyCssClasses, CursorCSS, cursorFeedbackAction, deleteCssClasses } from "../tool-feedback/css-feedback"; +import { BaseGLSPTool } from "./base-glsp-tool"; + +/** + * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. + * In contrast to Sprotty's implementation this tool only sends a `ChangeBoundsOperationAction` when an operation has finished and does not + * provide client-side live updates to improve performance. + * + * | Operation | Client Update | Server Update + * +-----------+------------------+---------------------------- + * | Move | MoveAction | ChangeBoundsOperationAction + * | Resize | SetBoundsAction | ChangeBoundsOperationAction + * + * To provide a visual client updates during move we install the `FeedbackMoveMouseListener` and to provide visual client updates during resize + * and send the server updates we install the `ChangeBoundsListener`. + */ +@injectable() +export class ChangeBoundsTool extends BaseGLSPTool { + static ID = "glsp.change-bounds-tool"; + readonly id = ChangeBoundsTool.ID; + + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; + protected feedbackMoveMouseListener: MouseListener; + protected changeBoundsListener: MouseListener & SelectionListener; + + enable() { + // install feedback move mouse listener for client-side move updates + this.feedbackMoveMouseListener = this.createMoveMouseListener(); + this.mouseTool.register(this.feedbackMoveMouseListener); + + // install change bounds listener for client-side resize updates and server-side updates + this.changeBoundsListener = this.createChangeBoundsListener(); + this.mouseTool.register(this.changeBoundsListener); + this.selectionService.register(this.changeBoundsListener); + } + + protected createMoveMouseListener(): MouseListener { + return new FeedbackMoveMouseListener(this); + } + + protected createChangeBoundsListener(): MouseListener & SelectionListener { + return new ChangeBoundsListener(this); + } + + disable() { + this.mouseTool.deregister(this.changeBoundsListener); + this.selectionService.deregister(this.changeBoundsListener); + this.mouseTool.deregister(this.feedbackMoveMouseListener); + this.deregisterFeedback([], this.feedbackMoveMouseListener); + this.deregisterFeedback([new HideChangeBoundsToolResizeFeedbackAction], this.changeBoundsListener); + } +} + +export class ChangeBoundsListener extends DragAwareMouseListener implements SelectionListener { + static readonly CSS_CLASS_ACTIVE = 'active'; + + // members for calculating the correct position change + protected initialBounds: Bounds | undefined; + protected lastDragPosition?: Point; + protected positionDelta: WriteablePoint = { x: 0, y: 0 }; + + // members for resize mode + protected activeResizeElement?: SModelElement; + protected activeResizeHandle?: SResizeHandle; + + constructor(protected tool: ChangeBoundsTool) { + super(); + } + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + super.mouseDown(target, event); + if (event.button !== 0) { + return []; + } + // check if we have a resize handle (only single-selection) + if (this.activeResizeElement && target instanceof SResizeHandle) { + this.activeResizeHandle = target; + } else { + this.setActiveResizeElement(target); + } + if (this.activeResizeElement) { + this.initPosition(event); + } else { + this.reset(); + } + return []; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + super.mouseMove(target, event); + if (this.isMouseDrag && this.activeResizeHandle) { + // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements + // consider resize handles ourselves + const actions: Action[] = [cursorFeedbackAction(CursorCSS.RESIZE), applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE)]; + const positionUpdate = this.updatePosition(target, event); + if (positionUpdate) { + const resizeActions = this.handleResizeOnClient(positionUpdate); + actions.push(...resizeActions); + } + return actions; + } + return []; + } + + draggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { + if (this.lastDragPosition === undefined) { + this.resetPosition(); + return []; + } + const actions: Action[] = []; + + if (this.activeResizeHandle) { + // Resize + actions.push(...this.handleResize(this.activeResizeHandle)); + } else { + // Move + actions.push(...this.handleMoveOnServer(target)); + } + this.resetPosition(); + return actions; + } + + protected handleMoveOnServer(target: SModelElement): Action[] { + const operations: Operation[] = []; + + operations.push(...this.handleMoveElementsOnServer(target)); + operations.push(...this.handleMoveRoutingPointsOnServer(target)); + if (operations.length > 0) { + return [new CompoundOperation(operations)]; + } + return operations; + } + + protected handleMoveElementsOnServer(target: SModelElement): Action[] { + const result: Operation[] = []; + const newBounds: ElementAndBounds[] = []; + forEachElement(target, isNonRoutableSelectedMovableBoundsAware, element => { + this.createElementAndBounds(element).forEach(bounds => newBounds.push(bounds)); + }); + if (newBounds.length > 0) { + result.push(new ChangeBoundsOperation(newBounds)); + } + return result; + } + + protected handleMoveRoutingPointsOnServer(target: SModelElement): Action[] { + const result: Operation[] = []; + const newRoutingPoints: ElementAndRoutingPoints[] = []; + forEachElement(target, isNonRoutableSelectedMovableBoundsAware, element => { + // If client routing is enabled -> delegate routingpoints of connected edges to server + if (this.tool.edgeRouterRegistry && element instanceof SConnectableElement) { + element.incomingEdges.map(toElementAndRoutingPoints).forEach(ear => newRoutingPoints.push(ear)); + element.outgoingEdges.map(toElementAndRoutingPoints).forEach(ear => newRoutingPoints.push(ear)); + } + }); + if (newRoutingPoints.length > 0) { + result.push(new ChangeRoutingPointsOperation(newRoutingPoints)); + } + return result; + } + + protected handleResize(activeResizeHandle: SResizeHandle): Action[] { + const actions: Action[] = []; + actions.push(cursorFeedbackAction(CursorCSS.DEFAULT)); + actions.push(deleteCssClasses(activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE)); + const resizeElement = findParentByFeature(activeResizeHandle, isResizable); + if (this.isActiveResizeElement(resizeElement)) { + this.createChangeBoundsAction(resizeElement).forEach(action => actions.push(action)); + } + return actions; + } + + selectionChanged(root: SModelRoot, selectedElements: string[]): void { + if (this.activeResizeElement) { + if (selectedElements.includes(this.activeResizeElement.id)) { + // our active element is still selected, nothing to do + return; + } + + // try to find some other selected element and mark that active + for (const elementId of selectedElements.reverse()) { + const element = root.index.getById(elementId); + if (element && this.setActiveResizeElement(element)) { + return; + } + } + this.reset(); + } + } + + protected setActiveResizeElement(target: SModelElement): boolean { + // check if we have a selected, moveable element (multi-selection allowed) + const moveableElement = findParentByFeature(target, isBoundsAwareMoveable); + if (isSelected(moveableElement)) { + // only allow one element to have the element resize handles + this.activeResizeElement = moveableElement; + if (isResizable(this.activeResizeElement)) { + this.tool.dispatchFeedback([new ShowChangeBoundsToolResizeFeedbackAction(this.activeResizeElement.id)], this); + } + return true; + } + return false; + } + + protected isActiveResizeElement(element?: SModelElement): element is SParentElement & BoundsAware { + return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; + } + + protected initPosition(event: MouseEvent) { + this.lastDragPosition = { x: event.pageX, y: event.pageY }; + if (this.activeResizeHandle) { + const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); + this.initialBounds = { x: resizeElement!.bounds.x, y: resizeElement!.bounds.y, width: resizeElement!.bounds.width, height: resizeElement!.bounds.height }; + } + } + + protected updatePosition(target: SModelElement, event: MouseEvent): Point | undefined { + if (this.lastDragPosition) { + const newDragPosition = { x: event.pageX, y: event.pageY }; + + const viewport = findParentByFeature(target, isViewport); + const zoom = viewport ? viewport.zoom : 1; + const dx = (event.pageX - this.lastDragPosition.x) / zoom; + const dy = (event.pageY - this.lastDragPosition.y) / zoom; + const deltaToLastPosition = { x: dx, y: dy }; + this.lastDragPosition = newDragPosition; + + // update position delta with latest delta + this.positionDelta.x += deltaToLastPosition.x; + this.positionDelta.y += deltaToLastPosition.y; + + // snap our delta and only send update if the position actually changes + // otherwise accumulate delta until we do snap to an update + const positionUpdate = this.snap(this.positionDelta, target, !event.shiftKey); + if (positionUpdate.x === 0 && positionUpdate.y === 0) { + return undefined; + } + + // we update our position so we need to reset our delta + this.positionDelta.x = 0; + this.positionDelta.y = 0; + return positionUpdate; + } + return undefined; + } + + protected reset() { + if (this.activeResizeElement && isResizable(this.activeResizeElement)) { + this.tool.dispatchFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this); + + } + this.tool.dispatchActions([cursorFeedbackAction(CursorCSS.DEFAULT)]); + this.resetPosition(); + } + + protected resetPosition() { + this.activeResizeHandle = undefined; + this.lastDragPosition = undefined; + this.positionDelta = { x: 0, y: 0 }; + } + + protected handleResizeOnClient(positionUpdate: Point): Action[] { + if (!this.activeResizeHandle) { + return []; + } + + const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); + if (this.isActiveResizeElement(resizeElement)) { + switch (this.activeResizeHandle.location) { + case ResizeHandleLocation.TopLeft: + return this.handleTopLeftResize(resizeElement, positionUpdate); + case ResizeHandleLocation.TopRight: + return this.handleTopRightResize(resizeElement, positionUpdate); + case ResizeHandleLocation.BottomLeft: + return this.handleBottomLeftResize(resizeElement, positionUpdate); + case ResizeHandleLocation.BottomRight: + return this.handleBottomRightResize(resizeElement, positionUpdate); + } + } + return []; + } + + protected handleTopLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + return this.createSetBoundsAction(resizeElement, + resizeElement.bounds.x + positionUpdate.x, + resizeElement.bounds.y + positionUpdate.y, + resizeElement.bounds.width - positionUpdate.x, + resizeElement.bounds.height - positionUpdate.y); + } + + protected handleTopRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + return this.createSetBoundsAction(resizeElement, + resizeElement.bounds.x, + resizeElement.bounds.y + positionUpdate.y, + resizeElement.bounds.width + positionUpdate.x, + resizeElement.bounds.height - positionUpdate.y); + } + + protected handleBottomLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + return this.createSetBoundsAction(resizeElement, + resizeElement.bounds.x + positionUpdate.x, + resizeElement.bounds.y, + resizeElement.bounds.width - positionUpdate.x, + resizeElement.bounds.height + positionUpdate.y); + } + + protected handleBottomRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + return this.createSetBoundsAction(resizeElement, + resizeElement.bounds.x, + resizeElement.bounds.y, + resizeElement.bounds.width + positionUpdate.x, + resizeElement.bounds.height + positionUpdate.y); + } + + protected createChangeBoundsAction(element: SModelElement & BoundsAware): Action[] { + if (this.isValidBoundChange(element, element.bounds, element.bounds)) { + return [new ChangeBoundsOperation([toElementAndBounds(element)])]; + } else if (this.initialBounds) { + const actions: Action[] = []; + if (this.tool.movementRestrictor) { + actions.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); + } + actions.push(new SetBoundsAction([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }])); + return actions; + } + return []; + } + + protected createElementAndBounds(element: SModelElement & BoundsAware): ElementAndBounds[] { + if (this.isValidBoundChange(element, element.bounds, element.bounds)) { + return [toElementAndBounds(element)]; + } + return []; + } + + protected createSetBoundsAction(element: SModelElement & BoundsAware, x: number, y: number, width: number, height: number): Action[] { + const newPosition = { x, y }; + const newSize = { width, height }; + const result: Action[] = []; + + if (this.isValidBoundChange(element, newPosition, newSize)) { + if (this.tool.movementRestrictor) { + result.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); + } + result.push(new SetBoundsAction([{ elementId: element.id, newPosition, newSize }])); + + } else if (this.isValidSize(element, newSize)) { + if (this.tool.movementRestrictor) { + result.push(createMovementRestrictionFeedback(element, this.tool.movementRestrictor)); + } + result.push(new SetBoundsAction([{ elementId: element.id, newPosition, newSize }])); + } + + return result; + } + + protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + return isSnap && this.tool.snapper + ? this.tool.snapper.snap(position, element) + : { x: position.x, y: position.y }; + } + + protected isValidBoundChange(element: SModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { + return this.isValidSize(element, newSize) && this.isValidMove(element, newPosition); + } + + protected isValidSize(element: SModelElement & BoundsAware, size: Dimension) { + return isValidSize(element, size); + } + + protected isValidMove(element: SModelElement & BoundsAware, newPosition: Point) { + return isValidMove(element, newPosition, this.tool.movementRestrictor); + } +} diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts new file mode 100644 index 0000000..c296106 --- /dev/null +++ b/packages/client/src/features/tools/delete-tool.ts @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + EnableDefaultToolsAction, + isCtrlOrCmd, + isDeletable, + isSelectable, + KeyListener, + KeyTool, + MouseListener, + SModelElement +} from "sprotty"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +import { DeleteElementOperation } from "../../base/operations/operation"; +import { GLSPTool } from "../../base/tool-manager/glsp-tool-manager"; +import { GLSP_TYPES } from "../../base/types"; +import { IMouseTool } from "../mouse-tool/mouse-tool"; +import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; + +/** + * Deletes selected elements when hitting the `Del` key. + */ +@injectable() +export class DelKeyDeleteTool implements GLSPTool { + static ID = "glsp.delete-keyboard"; + readonly id = DelKeyDeleteTool.ID; + + isEditTool = true; + protected deleteKeyListener: DeleteKeyListener = new DeleteKeyListener(); + + constructor(@inject(KeyTool) protected readonly keytool: KeyTool) { } + + enable() { + this.keytool.register(this.deleteKeyListener); + } + + disable() { + this.keytool.deregister(this.deleteKeyListener); + } +} + +@injectable() +export class DeleteKeyListener extends KeyListener { + keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'Delete')) { + const deleteElementIds = Array.from(element.root.index.all().filter(e => isDeletable(e) && isSelectable(e) && e.selected) + .filter(e => e.id !== e.root.id).map(e => e.id)); + if (deleteElementIds.length > 0) { + return [new DeleteElementOperation(deleteElementIds)]; + } + } + return []; + } +} + +/** + * Deletes selected elements when clicking on them. + */ +@injectable() +export class MouseDeleteTool implements GLSPTool { + + static ID = "glsp.delete-mouse"; + readonly id = MouseDeleteTool.ID; + + protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); + isEditTool = true; + constructor(@inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool, + @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher) { } + + enable() { + this.mouseTool.register(this.deleteToolMouseListener); + this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction(CursorCSS.ELEMENT_DELETION)]); + } + + disable() { + this.mouseTool.deregister(this.deleteToolMouseListener); + this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction()]); + } +} + +@injectable() +export class DeleteToolMouseListener extends MouseListener { + mouseUp(target: SModelElement, event: MouseEvent): Action[] { + if (!isDeletable(target)) { + return []; + } + const result: Action[] = []; + result.push(new DeleteElementOperation([target.id])); + if (!isCtrlOrCmd(event)) { + result.push(new EnableDefaultToolsAction()); + } + return result; + } +} diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts new file mode 100644 index 0000000..82a5c8b --- /dev/null +++ b/packages/client/src/features/tools/di.config.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureActionHandler } from "sprotty"; + +import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; +import { ChangeBoundsTool } from "./change-bounds-tool"; +import { DelKeyDeleteTool, MouseDeleteTool } from "./delete-tool"; +import { EdgeCreationTool } from "./edge-creation-tool"; +import { EdgeEditTool } from "./edge-edit-tool"; +import { NodeCreationTool } from "./node-creation-tool"; + +const toolsModule = new ContainerModule((bind, _unbind, isBound) => { + // Register default tools + bind(GLSP_TYPES.IDefaultTool).to(ChangeBoundsTool); + bind(GLSP_TYPES.IDefaultTool).to(EdgeEditTool); + bind(GLSP_TYPES.IDefaultTool).to(DelKeyDeleteTool); + + // Register tools + bind(GLSP_TYPES.ITool).to(MouseDeleteTool); + bind(NodeCreationTool).toSelf().inSingletonScope(); + bind(EdgeCreationTool).toSelf().inSingletonScope(); + bind(GLSP_TYPES.ITool).toService(EdgeCreationTool); + bind(GLSP_TYPES.ITool).toService(NodeCreationTool); + + configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); + configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); +}); + +export default toolsModule; diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts new file mode 100644 index 0000000..8eae539 --- /dev/null +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -0,0 +1,168 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + AnchorComputerRegistry, + EnableDefaultToolsAction, + EnableToolsAction, + findParentByFeature, + IActionHandler, + isConnectable, + isCtrlOrCmd, + SEdge, + SModelElement +} from "sprotty"; + +import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { + CreateEdgeOperation, + isTriggerElementTypeCreationAction, + TriggerEdgeCreationAction +} from "../../base/operations/operation"; +import { + DrawFeedbackEdgeAction, + FeedbackEdgeEndMovingMouseListener, + RemoveFeedbackEdgeAction +} from "../tool-feedback/creation-tool-feedback"; +import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +import { BaseGLSPTool } from "./base-glsp-tool"; + +/** + * Tool to create connections in a Diagram, by selecting a source and target node. + */ +@injectable() +export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { + static ID = "tool_create_edge"; + readonly id = EdgeCreationTool.ID; + + @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; + + protected triggerAction: TriggerEdgeCreationAction; + protected creationToolMouseListener: EdgeCreationToolMouseListener; + protected feedbackEndMovingMouseListener: FeedbackEdgeEndMovingMouseListener; + + enable() { + if (this.triggerAction === undefined) { + throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); + } + this.creationToolMouseListener = new EdgeCreationToolMouseListener(this.triggerAction, this); + this.mouseTool.register(this.creationToolMouseListener); + this.feedbackEndMovingMouseListener = new FeedbackEdgeEndMovingMouseListener(this.anchorRegistry); + this.mouseTool.register(this.feedbackEndMovingMouseListener); + this.dispatchFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); + } + + disable() { + this.mouseTool.deregister(this.creationToolMouseListener); + this.mouseTool.deregister(this.feedbackEndMovingMouseListener); + this.deregisterFeedback([new RemoveFeedbackEdgeAction(), cursorFeedbackAction()]); + } + + handle(action: Action): Action | void { + if (isTriggerElementTypeCreationAction(action)) { + this.triggerAction = action; + return new EnableToolsAction([this.id]); + } + } +} + +@injectable() +export class EdgeCreationToolMouseListener extends DragAwareMouseListener { + protected source?: string; + protected target?: string; + protected currentTarget?: SModelElement; + protected allowedTarget: boolean = false; + protected proxyEdge: SEdge; + + constructor(protected triggerAction: TriggerEdgeCreationAction, protected tool: EdgeCreationTool) { + super(); + this.proxyEdge = new SEdge(); + this.proxyEdge.type = triggerAction.elementTypeId; + } + + protected reinitialize() { + this.source = undefined; + this.target = undefined; + this.currentTarget = undefined; + this.allowedTarget = false; + this.tool.dispatchFeedback([new RemoveFeedbackEdgeAction()]); + } + + nonDraggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (event.button === 0) { + if (!this.isSourceSelected()) { + if (this.currentTarget && this.allowedTarget) { + this.source = this.currentTarget.id; + this.tool.dispatchFeedback([new DrawFeedbackEdgeAction(this.triggerAction.elementTypeId, this.source)]); + } + } else { + if (this.currentTarget && this.allowedTarget) { + this.target = this.currentTarget.id; + } + } + if (this.isSourceSelected() && this.isTargetSelected()) { + result.push(new CreateEdgeOperation(this.triggerAction.elementTypeId, this.source, this.target, this.triggerAction.args)); + if (!isCtrlOrCmd(event)) { + result.push(new EnableDefaultToolsAction()); + } else { + this.reinitialize(); + } + } + } else if (event.button === 2) { + result.push(new EnableDefaultToolsAction()); + } + return result; + } + + protected isSourceSelected() { + return this.source !== undefined; + } + + protected isTargetSelected() { + return this.target !== undefined; + } + + mouseOver(target: SModelElement, event: MouseEvent): Action[] { + const newCurrentTarget = findParentByFeature(target, isConnectable); + if (newCurrentTarget !== this.currentTarget) { + this.currentTarget = newCurrentTarget; + if (this.currentTarget) { + if (!this.isSourceSelected()) { + this.allowedTarget = this.isAllowedSource(newCurrentTarget); + } else if (!this.isTargetSelected()) { + this.allowedTarget = this.isAllowedTarget(newCurrentTarget); + } + if (this.allowedTarget) { + const action = !this.isSourceSelected() ? cursorFeedbackAction(CursorCSS.EDGE_CREATION_SOURCE) : + cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET); + return [action]; + } + } + return [cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]; + } + return []; + } + + protected isAllowedSource(element: SModelElement | undefined): boolean { + return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, "source"); + } + + protected isAllowedTarget(element: SModelElement | undefined): boolean { + return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, "target"); + } +} diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts new file mode 100644 index 0000000..5cf54a1 --- /dev/null +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -0,0 +1,315 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + AnchorComputerRegistry, + canEditRouting, + Connectable, + EdgeRouterRegistry, + findParentByFeature, + isConnectable, + isSelected, + SModelElement, + SModelRoot, + SRoutableElement, + SRoutingHandle +} from "sprotty"; + +import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from "../../base/operations/operation"; +import { GLSP_TYPES } from "../../base/types"; +import { isRoutable, isRoutingHandle } from "../../utils/smodel-util"; +import { + isReconnectable, + isReconnectHandle, + isSourceRoutingHandle, + isTargetRoutingHandle, + SReconnectHandle +} from "../reconnect/model"; +import { SelectionListener, SelectionService } from "../select/selection-service"; +import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from "../tool-feedback/creation-tool-feedback"; +import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +import { + DrawFeedbackEdgeSourceAction, + FeedbackEdgeRouteMovingMouseListener, + FeedbackEdgeSourceMovingMouseListener, + FeedbackEdgeTargetMovingMouseListener, + HideEdgeReconnectHandlesFeedbackAction, + ShowEdgeReconnectHandlesFeedbackAction, + SwitchRoutingModeAction +} from "../tool-feedback/edge-edit-tool-feedback"; +import { BaseGLSPTool } from "./base-glsp-tool"; + +@injectable() +export class EdgeEditTool extends BaseGLSPTool { + static ID = "glsp.edge-edit-tool"; + + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; + @inject(EdgeRouterRegistry) @optional() protected edgeRouterRegistry?: EdgeRouterRegistry; + + protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; + protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; + protected feedbackMovingListener: FeedbackEdgeRouteMovingMouseListener; + protected edgeEditListener: EdgeEditListener; + readonly id = EdgeEditTool.ID; + + enable(): void { + this.edgeEditListener = new EdgeEditListener(this); + this.mouseTool.register(this.edgeEditListener); + this.selectionService.register(this.edgeEditListener); + + // install feedback move mouse listener for client-side move updates + this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry); + this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry); + this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.edgeRouterRegistry); + } + + registerFeedbackListeners(): void { + this.mouseTool.register(this.feedbackMovingListener); + this.mouseTool.register(this.feedbackEdgeSourceMovingListener); + this.mouseTool.register(this.feedbackEdgeTargetMovingListener); + } + + deregisterFeedbackListeners(): void { + this.mouseTool.deregister(this.feedbackEdgeSourceMovingListener); + this.mouseTool.deregister(this.feedbackEdgeTargetMovingListener); + this.mouseTool.deregister(this.feedbackMovingListener); + } + + disable(): void { + this.edgeEditListener.reset(); + this.selectionService.deregister(this.edgeEditListener); + this.deregisterFeedbackListeners(); + this.mouseTool.deregister(this.edgeEditListener); + } +} + +class EdgeEditListener extends DragAwareMouseListener implements SelectionListener { + // active selection data + protected edge?: SRoutableElement; + protected routingHandle?: SRoutingHandle; + + // new connectable (source or target) for edge + protected newConnectable?: SModelElement & Connectable; + + // active reconnect handle data + protected reconnectMode?: 'NEW_SOURCE' | 'NEW_TARGET'; + + constructor(protected tool: EdgeEditTool) { + super(); + } + + protected isValidEdge(edge?: SRoutableElement): edge is SRoutableElement { + return edge !== undefined && edge.id !== feedbackEdgeId(edge.root) && isSelected(edge); + } + + protected setEdgeSelected(edge: SRoutableElement) { + if (this.edge && this.edge.id !== edge.id) { + // reset from a previously selected edge + this.reset(); + } + + this.edge = edge; + // note: order is important here as we want the reconnect handles to cover the routing handles + const feedbackActions = []; + if (canEditRouting(edge)) { + feedbackActions.push(new SwitchRoutingModeAction([this.edge.id], [])); + } + if (isReconnectable(edge)) { + feedbackActions.push(new ShowEdgeReconnectHandlesFeedbackAction(this.edge.id)); + } + this.tool.dispatchFeedback(feedbackActions); + } + + protected isEdgeSelected(): boolean { + return this.edge !== undefined && isSelected(this.edge); + } + + protected setReconnectHandleSelected(edge: SRoutableElement, reconnectHandle: SReconnectHandle) { + if (this.edge && this.edge.target && this.edge.source) { + if (isSourceRoutingHandle(edge, reconnectHandle)) { + this.tool.dispatchFeedback([new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), + new DrawFeedbackEdgeSourceAction(this.edge.type, this.edge.targetId)]); + this.reconnectMode = "NEW_SOURCE"; + } else if (isTargetRoutingHandle(edge, reconnectHandle)) { + this.tool.dispatchFeedback([new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), + new DrawFeedbackEdgeAction(this.edge.type, this.edge.sourceId)]); + this.reconnectMode = "NEW_TARGET"; + } + } + } + + protected isReconnecting(): boolean { + return this.reconnectMode !== undefined; + } + + protected isReconnectingNewSource(): boolean { + return this.reconnectMode === "NEW_SOURCE"; + } + + protected setRoutingHandleSelected(edge: SRoutableElement, routingHandle: SRoutingHandle) { + if (this.edge && this.edge.target && this.edge.source) { + this.routingHandle = routingHandle; + } + } + + protected requiresReconnect(sourceId: string, targetId: string): boolean { + return this.edge !== undefined && (this.edge.sourceId !== sourceId || this.edge.targetId !== targetId); + } + + protected setNewConnectable(connectable?: SModelElement & Connectable) { + this.newConnectable = connectable; + } + + protected isReadyToReconnect() { + return this.edge && this.isReconnecting() && this.newConnectable !== undefined; + } + + protected isReadyToReroute() { + return this.routingHandle !== undefined; + } + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = super.mouseDown(target, event); + if (event.button === 0) { + const reconnectHandle = findParentByFeature(target, isReconnectHandle); + const routingHandle = !reconnectHandle ? findParentByFeature(target, isRoutingHandle) : undefined; + const edge = findParentByFeature(target, isRoutable); + if (this.isEdgeSelected() && edge && reconnectHandle) { + // PHASE 2 Reconnect: Select reconnect handle on selected edge + this.setReconnectHandleSelected(edge, reconnectHandle); + } else if (this.isEdgeSelected() && edge && routingHandle) { + // PHASE 2 Reroute: Select routing handle on selected edge + this.setRoutingHandleSelected(edge, routingHandle); + } else if (this.isValidEdge(edge)) { + // PHASE 1: Select edge + this.tool.registerFeedbackListeners(); + this.setEdgeSelected(edge); + } + } else if (event.button === 2) { + this.reset(); + } + return result; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const result = super.mouseMove(target, event); + if (this.isMouseDrag) { + // reset any selected connectables when we are dragging, maybe the user is just panning + this.setNewConnectable(undefined); + } + return result; + } + + mouseUp(target: SModelElement, event: MouseEvent): Action[] { + const result = super.mouseUp(target, event); + if (!this.isReadyToReconnect() && !this.isReadyToReroute()) { + return result; + } + + if (this.edge && this.newConnectable) { + const sourceId = this.isReconnectingNewSource() ? this.newConnectable.id : this.edge.sourceId; + const targetId = this.isReconnectingNewSource() ? this.edge.targetId : this.newConnectable.id; + if (this.requiresReconnect(sourceId, targetId)) { + result.push(new ReconnectEdgeOperation(this.edge.id, sourceId, targetId)); + } + this.reset(); + } else if (this.edge && this.routingHandle) { + // we need to re-retrieve the edge as it might have changed due to a server udpate since we do not reset the state between reroute actions + const latestEdge = target.index.getById(this.edge.id); + if (latestEdge && isRoutable(latestEdge)) { + result.push(new ChangeRoutingPointsOperation([{ elementId: latestEdge.id, newRoutingPoints: latestEdge.routingPoints }])); + this.routingHandle = undefined; + } + } + return result; + } + + mouseOver(target: SModelElement, event: MouseEvent): Action[] { + if (this.edge && this.isReconnecting()) { + const currentTarget = findParentByFeature(target, isConnectable); + if (!this.newConnectable || currentTarget !== this.newConnectable) { + this.setNewConnectable(currentTarget); + if (currentTarget) { + if ((this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, "source")) || + (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, "target"))) { + + this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); + return []; + } + } + this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); + } + } + return []; + } + + selectionChanged(root: Readonly, selectedElements: string[]): void { + if (this.edge) { + if (selectedElements.indexOf(this.edge.id) > -1) { + // our active edge is still selected, nothing to do + return; + } + + if (this.isReconnecting()) { + // we are reconnecting, so we may have clicked on a potential target + return; + } + + // try to find some other selected element and mark that active + for (const elementId of selectedElements.reverse()) { + const element = root.index.getById(elementId); + if (element) { + const edge = findParentByFeature(element, isRoutable); + if (this.isValidEdge(edge)) { + // PHASE 1: Select edge + this.setEdgeSelected(edge); + return; + } + } + } + + this.reset(); + } + } + + public reset() { + this.resetFeedback(); + this.resetData(); + } + + protected resetData() { + this.edge = undefined; + this.reconnectMode = undefined; + this.newConnectable = undefined; + this.routingHandle = undefined; + } + + protected resetFeedback() { + const result: Action[] = []; + if (this.edge) { + result.push(new SwitchRoutingModeAction([], [this.edge.id])); + } + result.push(...[new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(), new RemoveFeedbackEdgeAction()]); + this.tool.deregisterFeedback(result); + this.tool.deregisterFeedbackListeners(); + } +} diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts new file mode 100644 index 0000000..a062707 --- /dev/null +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -0,0 +1,117 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + EnableDefaultToolsAction, + EnableToolsAction, + findParentByFeature, + IActionHandler, + isCtrlOrCmd, + ISnapper, + SModelElement, + SNode, + TYPES +} from "sprotty"; + +import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { + CreateNodeOperation, + isTriggerNodeCreationAction, + TriggerNodeCreationAction +} from "../../base/operations/operation"; +import { getAbsolutePosition } from "../../utils/viewpoint-util"; +import { Containable, isContainable } from "../hints/model"; +import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +import { BaseGLSPTool } from "./base-glsp-tool"; + +@injectable() +export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { + static ID = "tool_create_node"; + readonly id = NodeCreationTool.ID; + + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + + protected creationToolMouseListener: NodeCreationToolMouseListener; + protected triggerAction: TriggerNodeCreationAction; + + enable() { + if (this.triggerAction === undefined) { + throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); + } + this.creationToolMouseListener = new NodeCreationToolMouseListener(this.triggerAction, this); + this.mouseTool.register(this.creationToolMouseListener); + this.dispatchFeedback([cursorFeedbackAction(CursorCSS.NODE_CREATION)]); + } + + disable() { + this.mouseTool.deregister(this.creationToolMouseListener); + this.deregisterFeedback([cursorFeedbackAction()]); + } + + handle(action: Action): Action | void { + if (isTriggerNodeCreationAction(action)) { + this.triggerAction = action; + return new EnableToolsAction([this.id]); + } + } +} + +@injectable() +export class NodeCreationToolMouseListener extends DragAwareMouseListener { + protected container?: SModelElement & Containable; + constructor(protected triggerAction: TriggerNodeCreationAction, protected tool: NodeCreationTool) { + super(); + } + + protected creationAllowed(elementTypeId: string) { + return this.container && this.container.isContainableElement(elementTypeId); + } + + get elementTypeId() { + return this.triggerAction.elementTypeId; + } + nonDraggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (this.creationAllowed(this.elementTypeId)) { + const containerId = this.container ? this.container.id : undefined; + let location = getAbsolutePosition(target, event); + if (this.tool.snapper) { + // Create a 0-bounds proxy element for snapping + const elementProxy = new SNode(); + elementProxy.size = { width: 0, height: 0 }; + location = this.tool.snapper.snap(location, elementProxy); + } + result.push(new CreateNodeOperation(this.elementTypeId, location, containerId, this.triggerAction.args)); + if (!isCtrlOrCmd(event)) { + result.push(new EnableDefaultToolsAction()); + } + } + return result; + } + + mouseOver(target: SModelElement, event: MouseEvent): Action[] { + const currentContainer = findParentByFeature(target, isContainable); + if (!this.container || currentContainer !== this.container) { + this.container = currentContainer; + const feedback = this.creationAllowed(this.elementTypeId) + ? cursorFeedbackAction(CursorCSS.NODE_CREATION) : + cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED); + this.tool.dispatchFeedback([feedback]); + } + return []; + } +} diff --git a/packages/client/src/features/undo-redo/model.ts b/packages/client/src/features/undo-redo/model.ts new file mode 100644 index 0000000..372dc87 --- /dev/null +++ b/packages/client/src/features/undo-redo/model.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from "sprotty"; + +export class GlspUndoAction implements Action { + static readonly KIND = 'glspUndo'; + readonly kind = GlspUndoAction.KIND; +} + +export class GlspRedoAction implements Action { + static readonly KIND = 'glspRedo'; + readonly kind = GlspRedoAction.KIND; +} diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts new file mode 100644 index 0000000..3e97473 --- /dev/null +++ b/packages/client/src/features/validation/di.config.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureCommand, TYPES } from "sprotty"; + +import { GLSP_TYPES } from "../../base/types"; +import { + LeftToRightTopToBottomComparator, + MarkerNavigator, + MarkerNavigatorContextMenuItemProvider, + MarkerNavigatorKeyListener, + NavigateToMarkerCommand, + SModelElementComparator +} from "./marker-navigator"; +import { ApplyMarkersCommand, ClearMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from "./validate"; + +export const validationModule = new ContainerModule((bind, _unbind, isBound) => { + configureCommand({ bind, isBound }, SetMarkersCommand); + configureCommand({ bind, isBound }, ApplyMarkersCommand); + configureCommand({ bind, isBound }, ClearMarkersCommand); + bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); +}); + +export const markerNavigatorModule = new ContainerModule((bind, _unbind, isBound) => { + bind(SModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); + bind(MarkerNavigator).toSelf().inSingletonScope(); + configureCommand({ bind, isBound }, NavigateToMarkerCommand); +}); + +/** + * This module is not required if the diagram is deployed in Theia but only intended to be used + * in a standalone deployment of GLSP. If the GLSP diagram is in Theia use the Theia-native + * `registerMarkerNavigationCommands()` in `glsp-theia-integration` instead. + */ +export const markerNavigatorContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { + bind(GLSP_TYPES.IContextMenuProvider).to(MarkerNavigatorContextMenuItemProvider).inSingletonScope(); + bind(TYPES.KeyListener).to(MarkerNavigatorKeyListener).inSingletonScope(); +}); diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts new file mode 100644 index 0000000..9a04318 --- /dev/null +++ b/packages/client/src/features/validation/issue-marker.ts @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SDecoration, SIssue, SIssueMarker, SParentElement } from "sprotty"; + +import { Marker, MarkerKind } from "../../utils/marker"; + +export class GIssueMarker extends SIssueMarker { + constructor() { + super(); + this.features = new Set(SDecoration.DEFAULT_FEATURES); + } +} + +/** + * Retrieves the `SIssueMarker` contained by the provided model element as + * direct child or a newly instantiated `SIssueMarker` if no child + * `SIssueMarker` exists. + * @param modelElement for which the `SIssueMarker` should be retrieved or created. + * @returns the child `SIssueMarker` or a new `SIssueMarker` if no such child exists. + */ +export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMarker { + let issueMarker: SIssueMarker | undefined; + + issueMarker = getSIssueMarker(modelElement); + + if (issueMarker === undefined) { + issueMarker = new GIssueMarker(); + issueMarker.type = "marker"; + issueMarker.issues = new Array(); + modelElement.add(issueMarker); + } + + return issueMarker; +} + +/** + * Retrieves the `SIssueMarker` contained by the provided model element as + * direct child or `undefined` if such an `SIssueMarker` does not exist. + * @param modelElement for which the `SIssueMarker` should be retrieved. + * @returns the child `SIssueMarker` or `undefined` if no such child exists. + */ +export function getSIssueMarker(modelElement: SParentElement): SIssueMarker | undefined { + let issueMarker: SIssueMarker | undefined; + + for (const child of modelElement.children) { + if (child instanceof SIssueMarker) { + issueMarker = child; + } + } + + return issueMarker; +} + +/** + * Creates an `SIssue` with `severity` and `message` set according to + * the `kind` and `description` of the provided `Marker`. + * @param marker `Marker` for that an `SIssue` should be created. + * @returns the created `SIssue`. + */ +export function createSIssue(marker: Marker): SIssue { + const issue: SIssue = new SIssue(); + issue.message = marker.description; + + switch (marker.kind) { + case MarkerKind.ERROR: { + issue.severity = 'error'; + break; + } + case MarkerKind.INFO: { + issue.severity = 'info'; + break; + } + case MarkerKind.WARNING: { + issue.severity = 'warning'; + break; + } + } + + return issue; +} diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts new file mode 100644 index 0000000..0abd67a --- /dev/null +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -0,0 +1,206 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import "mocha"; +import "reflect-metadata"; + +import { expect } from "chai"; +import { Container } from "inversify"; +import { + BoundsAware, + decorationModule, + SChildElement, + SIssue, + SIssueMarker, + SModelElement, + SModelRoot, + SNodeSchema, + SParentElement, + TYPES +} from "sprotty/lib"; +import defaultModule from "sprotty/lib/base/di.config"; +import { SGraphFactory } from "sprotty/lib/graph/sgraph-factory"; + +import { markerNavigatorModule } from "./di.config"; +import { MarkerNavigator } from "./marker-navigator"; + +describe('MarkerNavigator', () => { + const container = new Container(); + container.load(defaultModule, decorationModule, markerNavigatorModule); + container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); + const graphFactory = container.get(TYPES.IModelFactory); + const markerNavigator = container.get(MarkerNavigator); + + const rootWithoutAnyMarkers = graphFactory.createRoot({ + id: 'root', type: 'graph', children: [ + { + id: '1', + type: 'node' + } + ] + }) as SModelRoot; + + const rootWithMarkers = graphFactory.createRoot({ + id: 'root', type: 'graph', children: [ + { + id: 'bottom-right', type: 'node' + }, + { + id: 'top-right', type: 'node' + }, + { + id: 'top-left', type: 'node' + }, + { + id: 'bottom-left', type: 'node' + } + ] + }) as SModelRoot; + + const elementTopLeft = rootWithMarkers.children[2] as BoundsAware & SChildElement; + elementTopLeft.bounds = { width: 10, height: 10, x: 100, y: 100 }; + const elementTopRight = rootWithMarkers.children[1] as BoundsAware & SChildElement; + elementTopRight.bounds = { width: 10, height: 10, x: 200, y: 100 }; + const elementBottomLeft = rootWithMarkers.children[3] as BoundsAware & SChildElement; + elementBottomLeft.bounds = { width: 10, height: 10, x: 100, y: 200 }; + const elementBottomRight = rootWithMarkers.children[0] as BoundsAware & SChildElement; + elementBottomRight.bounds = { width: 10, height: 10, x: 200, y: 200 }; + + beforeEach('clear issue marker', () => { + [elementTopLeft, elementTopRight, elementBottomLeft, elementBottomRight].forEach(clearMarker); + }); + + it('next(undefined) without any markers returns undefined', () => { + expect(markerNavigator.next(rootWithoutAnyMarkers)).to.be.undefined; + }); + + it('previous(undefined) without any markers returns undefined', () => { + expect(markerNavigator.previous(rootWithoutAnyMarkers)).to.be.undefined; + }); + + it('next(undefined) with one marker returns the one marker', () => { + const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const next = markerNavigator.next(rootWithMarkers); + expect(next).to.eql(marker); + }); + + it('next(firstMarker) with only one marker returns again the first marker', () => { + const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const next = markerNavigator.next(rootWithMarkers, marker); + expect(next).to.eql(marker); + // and again and again + const nextNext = markerNavigator.next(rootWithMarkers, next); + expect(nextNext).to.eql(marker); + }); + + it('previous(firstMarker) with only one marker returns again the first marker', () => { + const marker = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const previous = markerNavigator.previous(rootWithMarkers, marker); + expect(previous).to.eql(marker); + // and again and again + const previousPrevious = markerNavigator.previous(rootWithMarkers, previous); + expect(previousPrevious).to.eql(marker); + }); + + it('next(firstMarker) with two marker returns second marker then again first marker', () => { + const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]); + const next = markerNavigator.next(rootWithMarkers, marker1); + expect(next).to.eql(marker2); + // and again and again + const nextNext = markerNavigator.next(rootWithMarkers, next); + expect(nextNext).to.eql(marker1); + }); + + it('previous(firstMarker) with two marker returns second marker then again first marker', () => { + const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]); + const next = markerNavigator.previous(rootWithMarkers, marker1); + expect(next).to.eql(marker2); + // and again and again + const nextNext = markerNavigator.previous(rootWithMarkers, next); + expect(nextNext).to.eql(marker1); + }); + + it('returns markers in the order left-to-right, top-to-bottom with next()', () => { + const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]); + const marker3 = setIssues(elementBottomLeft, [{ message: 'msg', severity: 'error' }]); + const marker4 = setIssues(elementBottomRight, [{ message: 'msg', severity: 'error' }]); + const found1 = markerNavigator.next(rootWithMarkers); + const found2 = markerNavigator.next(rootWithMarkers, found1); + const found3 = markerNavigator.next(rootWithMarkers, found2); + const found4 = markerNavigator.next(rootWithMarkers, found3); + const found5 = markerNavigator.next(rootWithMarkers, found4); + expect(found1).to.eql(marker1); + expect(found2).to.eql(marker2); + expect(found3).to.eql(marker3); + expect(found4).to.eql(marker4); + expect(found5).to.eql(marker1); + }); + + it('returns markers in the order left-to-right, top-to-bottom with previous()', () => { + const marker1 = setIssues(elementTopLeft, [{ message: 'msg', severity: 'error' }]); + const marker2 = setIssues(elementTopRight, [{ message: 'msg', severity: 'error' }]); + const marker3 = setIssues(elementBottomLeft, [{ message: 'msg', severity: 'error' }]); + const marker4 = setIssues(elementBottomRight, [{ message: 'msg', severity: 'error' }]); + const found1 = markerNavigator.previous(rootWithMarkers); + const found2 = markerNavigator.previous(rootWithMarkers, found1); + const found3 = markerNavigator.previous(rootWithMarkers, found2); + const found4 = markerNavigator.previous(rootWithMarkers, found3); + const found5 = markerNavigator.previous(rootWithMarkers, found4); + expect(found1).to.eql(marker1); + expect(found2).to.eql(marker4); + expect(found3).to.eql(marker3); + expect(found4).to.eql(marker2); + expect(found5).to.eql(marker1); + }); + +}); + +function clearMarker(elem: SParentElement) { + elem.children.filter(isMarker).forEach(marker => elem.remove(marker)); +} + +function setIssues(elem: SParentElement, issues: SIssue[]) { + const marker = getOrCreateMarker(elem); + marker.issues = issues; + return marker; +} + +function getOrCreateMarker(elem: SParentElement): SIssueMarker { + const marker = findMarker(elem); + if (marker instanceof SIssueMarker) { + return marker; + } + return createMarker(elem as SParentElement); +} + +function findMarker(elem: SParentElement): SIssueMarker | undefined { + return elem.children.find(isMarker); +} + +function isMarker(element: SModelElement): element is SIssueMarker { + return element instanceof SIssueMarker; +} + +function createMarker(elem: SParentElement) { + const newMarker = new SIssueMarker(); + newMarker.type = 'marker'; + newMarker.id = `${elem.id}_marker`; + elem.add(newMarker); + return newMarker; +} + diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts new file mode 100644 index 0000000..c7783eb --- /dev/null +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -0,0 +1,245 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + BoundsAware, + CenterAction, + CenterCommand, + Command, + CommandExecutionContext, + CommandReturn, + findParentByFeature, + IContextMenuItemProvider, + isBoundsAware, + isSelectable, + KeyListener, + MenuItem, + Point, + SelectAction, + SIssueMarker, + SIssueSeverity, + SModelElement, + SModelRoot, + TYPES +} from "sprotty"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +import { GLSP_TYPES } from "../../base/types"; +import { collectIssueMarkers, MarkerPredicates } from "../../utils/marker"; +import { isSelectableAndBoundsAware } from "../../utils/smodel-util"; +import { SelectCommand, SelectionService } from "../select/selection-service"; + +export class NavigateToMarkerAction implements Action { + static readonly KIND = 'navigateToMarker'; + readonly kind = NavigateToMarkerAction.KIND; + constructor(readonly direction: 'next' | 'previous' = 'next', + readonly selectedElementIds?: string[], + readonly severities: SIssueSeverity[] = MarkerNavigator.ALL_SEVERITIES) { } +} + +export class SModelElementComparator { + compare(one: SModelElement, other: SModelElement): number { + return 0; + } +} + +/** Specifies the order of two selectable and bounds-aware elements left-to-right and top-to-bottom. */ +@injectable() +export class LeftToRightTopToBottomComparator { + compare(one: SModelElement, other: SModelElement): number { + const boundsOne = findParentByFeature(one, isSelectableAndBoundsAware); + const boundsOther = findParentByFeature(other, isSelectableAndBoundsAware); + if (boundsOne && boundsOther) { + if (boundsOne.bounds.y !== boundsOther.bounds.y) { + return boundsOne.bounds.y - boundsOther.bounds.y; + } + if (boundsOne.bounds.x !== boundsOther.bounds.x) { + return boundsOne.bounds.x - boundsOther.bounds.x; + } + } + return 0; + } +} + +/** + * Specifies the next/previous marker in a graph model. + * + * This navigator uses a `MarkerComparator` to determine the order of markers. It can also return next/previous + */ +@injectable() +export class MarkerNavigator { + static readonly ALL_SEVERITIES: SIssueSeverity[] = ['error', 'warning', 'info']; + + @inject(SModelElementComparator) + protected markerComparator: SModelElementComparator; + + next(root: SModelRoot, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL): SIssueMarker | undefined { + const markers = this.getMarkers(root, predicate); + if (current === undefined) { + return markers.length > 0 ? markers[0] : undefined; + } + return markers[this.getNextIndex(current, markers) % markers.length]; + } + + previous(root: SModelRoot, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL): SIssueMarker | undefined { + const markers = this.getMarkers(root, predicate); + if (current === undefined) { + return markers.length > 0 ? markers[0] : undefined; + } + return markers[this.getPreviousIndex(current, markers) % markers.length]; + } + + protected getMarkers(root: SModelRoot, predicate: (marker: SIssueMarker) => boolean) { + const markers = collectIssueMarkers(root); + return markers.filter(predicate).sort(this.markerComparator.compare); + } + + protected getNextIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]) { + for (let index = 0; index < markers.length; index++) { + if (this.markerComparator.compare(markers[index], current) > 0) { + return index; + } + } + return 0; + } + + protected getPreviousIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]) { + for (let index = markers.length - 1; index >= 0; index--) { + if (this.markerComparator.compare(markers[index], current) < 0) { + return index; + } + } + return markers.length - 1; + } +} + +@injectable() +export class NavigateToMarkerCommand extends Command { + static KIND = NavigateToMarkerAction.KIND; + + @inject(SModelElementComparator) + protected markerComparator: SModelElementComparator; + + @inject(MarkerNavigator) + protected markerNavigator: MarkerNavigator; + + @inject(GLSP_TYPES.SelectionService) + protected selectionService: SelectionService; + + protected selectCommand: SelectCommand; + protected centerCommand: CenterCommand; + + constructor(@inject(TYPES.Action) protected action: NavigateToMarkerAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const root = context.root; + const selected = this.getSelectedElements(root); + const target = this.getTarget(selected, root); + if (target === undefined) { + return root; + } + + const selectableTarget = findParentByFeature(target, isSelectable); + if (selectableTarget) { + const deselect = selected.map(e => e.id).filter(id => id !== selectableTarget.id); + this.selectCommand = new SelectCommand(new SelectAction([selectableTarget.id], deselect), this.selectionService); + this.centerCommand = new CenterCommand(new CenterAction([selectableTarget.id])); + this.centerCommand.execute(context); + return this.selectCommand.execute(context); + } + return root; + } + + protected getSelectedElements(root: SModelRoot) { + let selectedIds = []; + if (this.action.selectedElementIds !== undefined && this.action.selectedElementIds.length > 0) { + selectedIds = this.action.selectedElementIds; + } else { + selectedIds = Array.from(this.selectionService.getSelectedElementIDs()); + } + return selectedIds.map(id => root.index.getById(id)).filter(isSelectable); + } + + protected getTarget(selected: SModelElement[], root: SModelRoot) { + const selectedBoundsAware = selected.filter(isBoundsAware).sort(this.markerComparator.compare); + const currentTopmost = selectedBoundsAware.length > 0 ? selectedBoundsAware[0] : undefined; + if (this.action.direction === 'previous') { + return this.markerNavigator.previous(root, currentTopmost, (marker) => this.matchesSeverities(marker)); + } else { + return this.markerNavigator.next(root, currentTopmost, (marker) => this.matchesSeverities(marker)); + } + } + + protected matchesSeverities(marker: SIssueMarker): boolean { + return marker.issues.find(issue => this.action.severities.includes(issue.severity)) !== undefined; + } + + undo(context: CommandExecutionContext): CommandReturn { + if (this.selectCommand) { + context.root = this.selectCommand.undo(context); + } + return this.centerCommand ? this.centerCommand.undo(context) : context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + if (this.selectCommand) { + context.root = this.selectCommand.redo(context); + } + return this.centerCommand ? this.centerCommand.redo(context) : context.root; + } + +} + +@injectable() +export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemProvider { + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + getItems(root: Readonly, lastMousePosition?: Point): Promise { + const selectedElementIds = Array.from(this.selectionService.getSelectedElementIDs()); + const hasMarkers = collectIssueMarkers(root).length > 0; + return Promise.resolve([ + { + id: "navigate", label: "Go to", group: "navigate", actions: [], + children: [ + { + id: "next-marker", label: "Next marker", group: "marker", + actions: [new NavigateToMarkerAction('next', selectedElementIds)], + isEnabled: () => hasMarkers + }, + { + id: "previous-marker", label: "Previous marker", group: "marker", + actions: [new NavigateToMarkerAction('previous', selectedElementIds)], + isEnabled: () => hasMarkers + } + ] + } + ]); + } +} + +@injectable() +export class MarkerNavigatorKeyListener extends KeyListener { + keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'Period', 'ctrl')) { + return [new NavigateToMarkerAction('next')]; + } else if (matchesKeystroke(event, 'Comma', 'ctrl')) { + return [new NavigateToMarkerAction('previous')]; + } + return []; + } +} diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts new file mode 100644 index 0000000..ee142bc --- /dev/null +++ b/packages/client/src/features/validation/validate.ts @@ -0,0 +1,281 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { + Action, + Command, + CommandExecutionContext, + CommandReturn, + IActionDispatcher, + SIssue, + SIssueMarker, + SModelElement, + SModelRoot, + SParentElement, + TYPES +} from "sprotty"; + +import { EditorContextService } from "../../base/editor-context"; +import { GLSP_TYPES } from "../../base/types"; +import { Marker } from "../../utils/marker"; +import { removeCssClasses } from "../../utils/smodel-util"; +import { getSeverity } from "../hover/hover"; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; +import { FeedbackCommand } from "../tool-feedback/model"; +import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from "./issue-marker"; + + +/** + * Feedback emitter sending actions for visualizing model validation feedback and + * re-establishing this feedback visualization after the model has been updated. + */ +@injectable() +export class ValidationFeedbackEmitter implements IFeedbackEmitter { + + @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; + + @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; + + private registeredAction: MarkersAction; + + private constructor() { } + + /** + * Register the action that should be emitted for visualizing validation feedback. + * @param action the action that should be emitted when the model is updated and that will visualize the model validation feedback. + */ + registerValidationFeedbackAction(action: MarkersAction) { + // De-register old action responsible for applying markers and re-applying them when the model is updated + this.feedbackActionDispatcher.deregisterFeedback(this, []); + + // Clear existing markers + if (this.registeredAction !== undefined) { + const clearMarkersAction = new ClearMarkersAction(this.registeredAction.markers); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(clearMarkersAction)); + } + + // Register new action responsible for applying markers and re-applying them when the model is updated + this.feedbackActionDispatcher.registerFeedback(this, [action]); + this.registeredAction = action; + } +} + +/** + * Action to set markers for a model + */ +export class SetMarkersAction implements MarkersAction { + readonly kind = SetMarkersCommand.KIND; + constructor(public readonly markers: Marker[]) { } +} + +export function isSetMarkersAction(action: Action): action is SetMarkersAction { + return SetMarkersCommand.KIND === action.kind && ('markers' in action); +} + +/** + * Manages current markers for the outside of the GLSP. + * + * Typically this is rebound by the surrounding tool, e.g. Theia, to be aware of + * and propagate current markers. + */ +@injectable() +export abstract class ExternalMarkerManager { + + languageLabel: string; + + protected actionDispatcher?: IActionDispatcher; + + connect(actionDispatcher: IActionDispatcher) { + this.actionDispatcher = actionDispatcher; + } + + removeMarkers(markers: Marker[]) { + if (this.actionDispatcher) { + this.actionDispatcher.dispatch(new ClearMarkersAction(markers)); + } + } + + abstract setMarkers(markers: Marker[], sourceUri?: string): void; +} + +/** + * Command for handling `SetMarkersAction` + */ +@injectable() +export class SetMarkersCommand extends Command { + + @inject(ValidationFeedbackEmitter) protected validationFeedbackEmitter: ValidationFeedbackEmitter; + @inject(ExternalMarkerManager) @optional() protected externalMarkerManager?: ExternalMarkerManager; + @inject(EditorContextService) protected editorContextService: EditorContextService; + + static readonly KIND = 'setMarkers'; + + constructor(@inject(TYPES.Action) public action: SetMarkersAction) { + super(); + } + + async execute(context: CommandExecutionContext): Promise { + const markers: Marker[] = this.action.markers; + const uri = await this.editorContextService.getSourceUri(); + if (this.externalMarkerManager) this.externalMarkerManager.setMarkers(markers, uri); + const applyMarkersAction: ApplyMarkersAction = new ApplyMarkersAction(markers); + this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); + return context.root; + } + + undo(context: CommandExecutionContext): CommandReturn { + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + return this.execute(context); + } +} + + +/** +* Action to retrieve markers for a model +*/ +export class RequestMarkersAction implements Action { + + static readonly KIND = 'requestMarkers'; + readonly kind = RequestMarkersAction.KIND; + + constructor(public readonly elementsIDs: string[] = []) { } +} + +/** + * Interface for actions processing markers + */ +export interface MarkersAction extends Action { + readonly markers: Marker[]; +} + +/** + * Action for applying makers to a model + */ +@injectable() +export class ApplyMarkersAction implements MarkersAction { + readonly kind = ApplyMarkersCommand.KIND; + constructor(public readonly markers: Marker[]) { } +} + +/** + * Command for handling `ApplyMarkersAction` + */ +@injectable() +export class ApplyMarkersCommand extends FeedbackCommand { + static KIND = "applyMarkers"; + readonly priority = 0; + + constructor(@inject(TYPES.Action) protected action: ApplyMarkersAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const markers: Marker[] = this.action.markers; + for (const marker of markers) { + const modelElement: SModelElement | undefined = context.root.index.getById(marker.elementId); + if (modelElement instanceof SParentElement) { + const issueMarker: SIssueMarker = getOrCreateSIssueMarker(modelElement); + const issue: SIssue = createSIssue(marker); + issueMarker.issues.push(issue); + addMaxSeverityCSSClassToIssueParent(modelElement, issueMarker); + } + } + return context.root; + } + + undo(context: CommandExecutionContext): CommandReturn { + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + return this.execute(context); + } +} + +function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker) { + const maxSeverityCSSClass = getSeverity(issueMarker); + + if (!modelElement.cssClasses) { + modelElement.cssClasses = [maxSeverityCSSClass]; + } else { + modelElement.cssClasses = modelElement.cssClasses.filter((value: string) => !value.match('info|warning|error')); + modelElement.cssClasses.push(maxSeverityCSSClass); + } +} + +function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker) { + removeCssClasses(modelElement, [getSeverity(issueMarker)]); +} + + +/** + * Action for clearing makers of a model + */ +@injectable() +export class ClearMarkersAction implements MarkersAction { + readonly kind = ClearMarkersCommand.KIND; + constructor(public readonly markers: Marker[]) { } +} + +/** + * Command for handling `ClearMarkersAction` + */ +@injectable() +export class ClearMarkersCommand extends Command { + static KIND = "clearMarkers"; + + constructor(@inject(TYPES.Action) protected action: ClearMarkersAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const markers: Marker[] = this.action.markers; + for (const marker of markers) { + const modelElement: SModelElement | undefined = context.root.index.getById(marker.elementId); + if (modelElement instanceof SParentElement) { + const issueMarker: SIssueMarker | undefined = getSIssueMarker(modelElement); + if (issueMarker !== undefined) { + removeCSSClassFromIssueParent(modelElement, issueMarker); + for (let index = 0; index < issueMarker.issues.length; ++index) { + const issue: SIssue = issueMarker.issues[index]; + if (issue.message === marker.description) { + issueMarker.issues.splice(index--, 1); + } + } + if (issueMarker.issues.length === 0) { + modelElement.remove(issueMarker); + } else { + addMaxSeverityCSSClassToIssueParent(modelElement, issueMarker); + } + } + } + } + return context.root; + } + + undo(context: CommandExecutionContext): CommandReturn { + return context.root; + } + + redo(context: CommandExecutionContext): CommandReturn { + return this.execute(context); + } +} + + diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts new file mode 100644 index 0000000..7b6d156 --- /dev/null +++ b/packages/client/src/index.ts @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import defaultGLSPModule from "./base/di.config"; +import glspCommandPaletteModule from "./features/command-palette/di.config"; +import glspContextMenuModule from "./features/context-menu/di.config"; +import { copyPasteContextMenuModule, glspServerCopyPasteModule } from "./features/copy-paste/di.config"; +import glspDecorationModule from "./features/decoration/di.config"; +import glspEditLabelModule from "./features/edit-label/di.config"; +import executeCommandModule from "./features/execute/di.config"; +import modelHintsModule from "./features/hints/di.config"; +import glspHoverModule from "./features/hover/di.config"; +import layoutCommandsModule from "./features/layout/di.config"; +import glspMouseToolModule from "./features/mouse-tool/di.config"; +import { navigationModule } from "./features/navigation/di.config"; +import saveModule from "./features/save/di.config"; +import glspSelectModule from "./features/select/di.config"; +import toolFeedbackModule from "./features/tool-feedback/di.config"; +import paletteModule from "./features/tool-palette/di.config"; +import toolsModule from "./features/tools/di.config"; +import { markerNavigatorModule, validationModule } from "./features/validation/di.config"; + +export * from 'sprotty'; +export * from './base/action-dispatcher'; +export * from './base/actions/context-actions'; +export * from './base/actions/edit-mode-action'; +export * from './base/actions/edit-validation-actions'; +export * from './base/actions/protocol-actions'; +export * from './base/args'; +export * from './base/auto-complete/auto-complete-actions'; +export * from './base/auto-complete/auto-complete-widget'; +export * from './base/auto-complete/validation-decorator'; +export * from './base/command-stack'; +export * from './base/drag-aware-mouse-listener'; +export * from './base/editor-context'; +export * from './base/model-initialization-constraint'; +export * from './base/model/update-model-command'; +export * from './base/operations/operation'; +export * from './base/selection-clearing-mouse-listener'; +export * from './base/source-uri-aware'; +export * from './base/types'; +export * from './features/change-bounds/model'; +export * from './features/change-bounds/movement-restrictor'; +export * from './features/change-bounds/snap'; +export * from './features/command-palette/server-command-palette-provider'; +export * from './features/context-menu/delete-element-context-menu'; +export * from './features/copy-paste/copy-paste-handler'; +export * from './features/decoration/decoration-placer'; +export * from './features/decoration/view'; +export * from './features/edit-label/edit-label-tool'; +export * from './features/edit-label/edit-label-validator'; +export * from './features/execute/execute-command'; +export * from './features/execute/model'; +export * from './features/hints/model'; +export * from './features/hints/request-type-hints-action'; +export * from './features/hints/type-hints'; +export * from './features/hover/hover'; +export * from './features/layout/layout-commands'; +export * from './features/mouse-tool/mouse-tool'; +export * from './features/navigation/external-navigate-to-target-handler'; +export * from './features/navigation/navigation-action-handler'; +export * from './features/navigation/navigation-target-resolver'; +export * from './features/rank/model'; +export * from './features/reconnect/model'; +export * from './features/save/model'; +export * from './features/save/save'; +export * from './features/tool-feedback/change-bounds-tool-feedback'; +export * from './features/tool-feedback/creation-tool-feedback'; +export * from './features/tool-feedback/css-feedback'; +export * from './features/tool-feedback/edge-edit-tool-feedback'; +export * from './features/tool-feedback/feedback-action-dispatcher'; +export * from './features/tool-feedback/model'; +export * from './features/tool-palette/palette-item'; +export * from './features/tool-palette/tool-palette'; +export * from './features/tools/change-bounds-tool'; +export * from './features/tools/delete-tool'; +export * from './features/tools/edge-creation-tool'; +export * from './features/tools/edge-edit-tool'; +export * from './features/tools/node-creation-tool'; +export * from './features/undo-redo/model'; +export * from './features/validation/issue-marker'; +export * from './features/validation/validate'; +export * from './features/validation/marker-navigator'; +export * from './lib/model'; +export * from './model-source/glsp-server-status'; +export * from './model-source/glsp-diagram-server'; +export * from './utils/array-utils'; +export * from './utils/marker'; +export * from './utils/smodel-util'; +export * from './utils/viewpoint-util'; +export { + validationModule, saveModule, executeCommandModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, + glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, + glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule +}; diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts new file mode 100644 index 0000000..5f3e754 --- /dev/null +++ b/packages/client/src/lib/model.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { exportFeature, SGraph, SModelElement, SModelElementSchema, viewportFeature } from "sprotty/lib"; + +import { Containable, containerFeature } from "../features/hints/model"; +import { Saveable, saveFeature } from "../features/save/model"; + +export class GLSPGraph extends SGraph implements Saveable, Containable { + static readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, saveFeature, containerFeature]; + dirty: boolean = false; + isContainableElement(input: string | SModelElement | SModelElementSchema): boolean { + return true; + } +} diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts new file mode 100644 index 0000000..dcfb254 --- /dev/null +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -0,0 +1,173 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GLSPClient } from "@eclipse-glsp/protocol"; +import { injectable } from "inversify"; +import { + Action, + ActionHandlerRegistry, + ActionMessage, + ApplyLabelEditAction, + ComputedBoundsAction, + DiagramServer, + ExportSvgAction, + ICommand, + LayoutAction, + RequestBoundsCommand, + RequestModelAction, + RequestPopupModelAction, + ServerStatusAction, + SwitchEditModeCommand +} from "sprotty"; + +import { RequestContextActions } from "../base/actions/context-actions"; +import { isSetEditModeAction, SetEditModeAction } from "../base/actions/edit-mode-action"; +import { RequestEditValidationAction } from "../base/actions/edit-validation-actions"; +import { DisposeClientSessionAction, InitializeClientSessionAction } from "../base/actions/protocol-actions"; +import { + ChangeBoundsOperation, + ChangeRoutingPointsOperation, + CompoundOperation, + CreateEdgeOperation, + CreateNodeOperation, + DeleteElementOperation, + ReconnectEdgeOperation +} from "../base/operations/operation"; +import { SourceUriAware } from "../base/source-uri-aware"; +import { + CutOperationAction, + PasteOperationAction, + RequestClipboardDataAction +} from "../features/copy-paste/copy-paste-actions"; +import { ValidateLabelEditAction } from "../features/edit-label/edit-label-validator"; +import { ExecuteServerCommandAction } from "../features/execute/execute-command"; +import { RequestTypeHintsAction } from "../features/hints/request-type-hints-action"; +import { RequestNavigationTargetsAction } from "../features/navigation/navigation-action-handler"; +import { ResolveNavigationTargetAction } from "../features/navigation/navigation-target-resolver"; +import { SaveModelAction } from "../features/save/save"; +import { GlspRedoAction, GlspUndoAction } from "../features/undo-redo/model"; +import { RequestMarkersAction } from "../features/validation/validate"; +import { isServerMessageAction, ServerMessageAction } from "./glsp-server-status"; + +const receivedFromServerProperty = '__receivedFromServer'; +@injectable() +export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { + protected _sourceUri: string; + protected _glspClient?: GLSPClient; + protected ready = false; + + async connect(client: GLSPClient): Promise { + await client.start(); + client.onActionMessage(message => this.messageReceived(message)); + this._glspClient = client; + return this._glspClient; + } + + public get glspClient(): GLSPClient | undefined { + return this._glspClient; + } + + protected sendMessage(message: ActionMessage): void { + if (this.glspClient) { + this.glspClient.sendActionMessage(message); + } else { + throw new Error('GLSPClient is not connected'); + } + } + + initialize(registry: ActionHandlerRegistry): void { + registerDefaultGLSPServerActions(registry, this); + if (!this.clientId) + this.clientId = this.viewerOptions.baseDiv; + } + + handle(action: Action): void | ICommand | Action { + if (action instanceof RequestModelAction && action.options !== undefined) + this._sourceUri = action.options.sourceUri; + return super.handle(action); + } + + + handleLocally(action: Action): boolean { + if (isServerMessageAction(action)) { + return this.handleServerMessageAction(action); + } + if (isSetEditModeAction(action)) { + return this.handleSetEditModeAction(action); + } + return super.handleLocally(action); + } + protected handleServerMessageAction(action: ServerMessageAction): boolean { + this.logger.log('GLSPDiagramServer', `[${action.severity}] -${action.message}`); + return false; + } + + protected handleComputedBounds(action: ComputedBoundsAction): boolean { + return true; + } + + protected handleSetEditModeAction(action: SetEditModeAction): boolean { + return !isReceivedFromServer(action); + } + + public getSourceURI(): string { + return this._sourceUri; + } +} + +export function isReceivedFromServer(action: Action) { + return (action as any)[receivedFromServerProperty] === true; +} + +export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer) { + registry.register(SaveModelAction.KIND, diagramServer); + registry.register(GlspUndoAction.KIND, diagramServer); + registry.register(GlspRedoAction.KIND, diagramServer); + registry.register(CreateEdgeOperation.KIND, diagramServer); + registry.register(ReconnectEdgeOperation.KIND, diagramServer); + registry.register(ChangeRoutingPointsOperation.KIND, diagramServer); + registry.register(CreateNodeOperation.KIND, diagramServer); + registry.register(ChangeBoundsOperation.KIND, diagramServer); + registry.register(DeleteElementOperation.KIND, diagramServer); + registry.register(ExecuteServerCommandAction.KIND, diagramServer); + registry.register(InitializeClientSessionAction.KIND, diagramServer); + registry.register(RequestTypeHintsAction.KIND, diagramServer); + registry.register(ComputedBoundsAction.KIND, diagramServer); + registry.register(RequestBoundsCommand.KIND, diagramServer); + registry.register(RequestPopupModelAction.KIND, diagramServer); + registry.register(ServerStatusAction.KIND, diagramServer); + registry.register(RequestModelAction.KIND, diagramServer); + registry.register(ExportSvgAction.KIND, diagramServer); + registry.register(RequestContextActions.KIND, diagramServer); + registry.register(ValidateLabelEditAction.KIND, diagramServer); + registry.register(RequestMarkersAction.KIND, diagramServer); + registry.register(LayoutAction.KIND, diagramServer); + registry.register(ApplyLabelEditAction.KIND, diagramServer); + registry.register(RequestClipboardDataAction.KIND, diagramServer); + registry.register(PasteOperationAction.KIND, diagramServer); + registry.register(CutOperationAction.KIND, diagramServer); + registry.register(RequestEditValidationAction.KIND, diagramServer); + registry.register(RequestNavigationTargetsAction.KIND, diagramServer); + registry.register(ResolveNavigationTargetAction.KIND, diagramServer); + registry.register(CompoundOperation.KIND, diagramServer); + registry.register(SetEditModeAction.KIND, diagramServer); + registry.register(DisposeClientSessionAction.KIND, diagramServer); + registry.register(ServerMessageAction.KIND, diagramServer); + + // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. + // We don't want to support SwitchEditMode, but sprotty still sends some corresponding + // actions. + registry.register(SwitchEditModeCommand.KIND, { handle: action => undefined }); +} diff --git a/packages/client/src/model-source/glsp-server-status.ts b/packages/client/src/model-source/glsp-server-status.ts new file mode 100644 index 0000000..9bc5db6 --- /dev/null +++ b/packages/client/src/model-source/glsp-server-status.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, ServerStatusAction } from "sprotty"; + +export class GLSPServerStatusAction extends ServerStatusAction { + timeout: number = -1; +} + +export function isGLSPServerStatusAction(serverStatusAction: ServerStatusAction): serverStatusAction is GLSPServerStatusAction { + return (serverStatusAction).timeout !== undefined; +} + +export class ServerMessageAction implements Action { + static KIND = 'serverMessage'; + + kind = ServerMessageAction.KIND; + severity: 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL'; + message: string; + details: string = ''; + timeout: number = -1; +} + +export function isServerMessageAction(action: Action): action is ServerMessageAction { + return ServerMessageAction.KIND === action.kind && 'severity' in action && 'message' in action; +} diff --git a/packages/client/src/utils/array-utils.ts b/packages/client/src/utils/array-utils.ts new file mode 100644 index 0000000..b96fb49 --- /dev/null +++ b/packages/client/src/utils/array-utils.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export function remove(array: T[], value: T): boolean { + const index = array.indexOf(value); + if (index >= 0) { + array.splice(index, 1); + return true; + } + return false; +} + +export function distinctAdd(array: T[], value: T): boolean { + if (!array.includes(value)) { + array.push(value); + return true; + } + return false; +} + + diff --git a/packages/client/src/utils/html-utils.ts b/packages/client/src/utils/html-utils.ts new file mode 100644 index 0000000..e1098ac --- /dev/null +++ b/packages/client/src/utils/html-utils.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export function createElementFromHTML(html: string): HTMLElement | undefined { + const template = document.createElement('template'); + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + const node = template.content.firstChild; + if (node && node instanceof HTMLElement) { + return node as HTMLElement; + } + return undefined; +} diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts new file mode 100644 index 0000000..ff70ef0 --- /dev/null +++ b/packages/client/src/utils/layout-utils.ts @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement } from "sprotty"; + +import { IMovementRestrictor } from "../features/change-bounds/movement-restrictor"; + +export function minWidth(element: SModelElement & BoundsAware): number { + const layoutOptions = getLayoutOptions(element); + if (layoutOptions !== undefined && typeof layoutOptions.minWidth === 'number') { + return layoutOptions.minWidth; + } + return 1; +} + +export function minHeight(element: SModelElement & BoundsAware): number { + const layoutOptions = getLayoutOptions(element); + if (layoutOptions !== undefined && typeof layoutOptions.minHeight === 'number') { + return layoutOptions.minHeight; + } + return 1; +} + +export function getLayoutOptions(element: SModelElement): ModelLayoutOptions | undefined { + const layoutOptions = (element as any).layoutOptions; + if (layoutOptions !== undefined) { + return layoutOptions as ModelLayoutOptions; + } + return undefined; +} + +export function isValidSize(element: SModelElement & BoundsAware, size: Dimension) { + return size.width >= minWidth(element) && size.height >= minHeight(element); +} + +export function isValidMove(element: SModelElement & BoundsAware, newPosition: Point, movementRestrictor?: IMovementRestrictor) { + if (movementRestrictor) { + return movementRestrictor.validate(newPosition, element); + } + return true; +} + +export function toValidElementMove(element: SModelElement & BoundsAware, move: WriteableElementMove, movementRestrictor?: IMovementRestrictor) { + if (!isValidMove(element, move.toPosition, movementRestrictor)) { + return; + } + return move; +} + +export function toValidElementAndBounds(element: SModelElement & BoundsAware, bounds: WriteableElementAndBounds, movementRestrictor?: IMovementRestrictor) { + if (!isValidMove(element, bounds.newPosition, movementRestrictor)) { + return; + } + const elementMinWidth = minWidth(element); + if (bounds.newSize.width < elementMinWidth) { + bounds.newSize.width = elementMinWidth; + } + const elementMinHeight = minHeight(element); + if (bounds.newSize.height < elementMinHeight) { + bounds.newSize.height = elementMinHeight; + } + return bounds; +} + +export interface WriteablePoint extends Point { + x: number; + y: number; +} + +export interface WriteableElementMove extends ElementMove { + fromPosition?: WriteablePoint; + toPosition: WriteablePoint; +} + +export interface WriteableDimension extends Dimension { + width: number + height: number +} + +export interface WriteableElementAndBounds extends ElementAndBounds { + newPosition: WriteablePoint; + newSize: WriteableDimension +} diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts new file mode 100644 index 0000000..fcb4899 --- /dev/null +++ b/packages/client/src/utils/marker.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SIssueMarker, SParentElement } from "sprotty"; + +export namespace MarkerKind { + export const INFO = "info"; + export const WARNING = "warning"; + export const ERROR = "error"; +} + +export namespace MarkerPredicates { + export const ALL = () => true; + export const ERRORS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.ERROR); + export const WARNINGS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.WARNING); + export const INFOS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.INFO); + + export function hasIssueWithSeverity(marker: SIssueMarker, severity: 'info' | 'warning' | 'error') { + return marker.issues.find(issue => issue.severity === severity) !== undefined; + } +} + +export function collectIssueMarkers(root: SParentElement): SIssueMarker[] { + const markers = []; + for (const child of root.children) { + if (child instanceof SIssueMarker) { + markers.push(child); + } + markers.push(...collectIssueMarkers(child)); + } + return markers; +} + +export interface Marker { + readonly label: string; + readonly description: string; + readonly elementId: string; + readonly kind: string; +} diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts new file mode 100644 index 0000000..05c5885 --- /dev/null +++ b/packages/client/src/utils/smodel-util.ts @@ -0,0 +1,147 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + BoundsAware, + isBoundsAware, + isMoveable, + isSelectable, + isSelected, + Selectable, + SModelElement, + SModelElementSchema, + SRoutableElement, + SRoutingHandle +} from "sprotty"; + +import { ElementAndRoutingPoints } from "../base/operations/operation"; + + +export function getIndex(element: SModelElement) { + return element.root.index; +} + +export function forEachElement(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T, runnable: (element: SModelElement & T) => void) { + getIndex(element).all() + .filter(predicate) + .forEach(runnable); +} + +export function getMatchingElements(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T): (SModelElement & T)[] { + const matching: (SModelElement & T)[] = []; + forEachElement(element, predicate, item => matching.push(item)); + return matching; +} + +export function hasSelectedElements(element: SModelElement) { + return getSelectedElementCount(element) > 0; +} + +export function getSelectedElementCount(element: SModelElement): number { + let selected = 0; + getIndex(element).all() + .filter(isSelected) + .forEach(e => selected = selected + 1); + return selected; +} + +export function isNotUndefined(element: T | undefined): element is T { + return element !== undefined; +} + +export function addCssClasses(root: SModelElement, cssClasses: string[]) { + if (root.cssClasses === undefined) { + root.cssClasses = []; + } + for (const cssClass of cssClasses) { + if (root.cssClasses.indexOf(cssClass) < 0) { + root.cssClasses.push(cssClass); + } + } +} + +export function removeCssClasses(root: SModelElement, cssClasses: string[]) { + if (root.cssClasses === undefined || root.cssClasses.length === 0) { + return; + } + for (const cssClass of cssClasses) { + const index = root.cssClasses.indexOf(cssClass); + if (index !== -1) { + root.cssClasses.splice(root.cssClasses.indexOf(cssClass), 1); + } + } +} + +export function isNonRoutableSelectedMovableBoundsAware(element: SModelElement): element is SelectableBoundsAware { + return isNonRoutableSelectedBoundsAware(element) && isMoveable(element); +} + +export function isNonRoutableSelectedBoundsAware(element: SModelElement): element is SelectableBoundsAware { + return isBoundsAware(element) && isSelected(element) && !isRoutable(element); +} + +export function isRoutable(element: T): element is T & SRoutableElement { + return element instanceof SRoutableElement && (element as any).routingPoints !== undefined; +} + +export function isRoutingHandle(element: SModelElement | undefined): element is SRoutingHandle { + return element !== undefined && element instanceof SRoutingHandle; +} + +export function isSelectableAndBoundsAware(element: SModelElement): element is BoundsAware & Selectable & SModelElement { + return isSelectable(element) && isBoundsAware(element); +} + +export type SelectableBoundsAware = SModelElement & BoundsAware & Selectable; + +export type BoundsAwareModelElement = SModelElement & BoundsAware; + +export function toElementAndBounds(element: SModelElement & BoundsAware) { + return { + elementId: element.id, + newPosition: { + x: element.bounds.x, + y: element.bounds.y + }, + newSize: { + width: element.bounds.width, + height: element.bounds.height + } + }; +} + +export function toElementAndRoutingPoints(element: SRoutableElement): ElementAndRoutingPoints { + return { + elementId: element.id, + newRoutingPoints: element.routingPoints + }; +} + +/** + * Checks if the model is compatible with the passed type string. + * (either has the same type or a subtype of this type) + */ +export function hasCompatibleType(input: SModelElement | SModelElementSchema | string, type: string) { + const inputType = getElementTypeId(input); + return inputType === type ? true : inputType.split(":").includes(type); +} + +export function getElementTypeId(input: SModelElement | SModelElementSchema | string) { + if (typeof input === 'string') { + return input; + } else { + return (input)["type"]; + } +} diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts new file mode 100644 index 0000000..017f059 --- /dev/null +++ b/packages/client/src/utils/viewpoint-util.ts @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Bounds, + BoundsAware, + Dimension, + findParentByFeature, + isAlignable, + isViewport, + ORIGIN_POINT, + Point, + SModelElement, + translateBounds, + Viewport +} from "sprotty"; + +/** + * Return the position corresponding to this mouse event (Browser coordinates) + * in the diagram coordinates system (i.e. relative to the Diagram's 0;0 point) + * + * This functions takes into account the following transformations: + * - Location of the Diagram Canvas inside of the browser's page + * - Current viewport Scroll and Zoom + * + * @param target + * An element from the diagram + * @param mouseEvent + * A mouseEvent + */ +export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEvent): Point { + let xPos = mouseEvent.pageX, yPos = mouseEvent.pageY; + const canvasBounds = target.root.canvasBounds; + xPos -= canvasBounds.x; + yPos -= canvasBounds.y; + + const viewport: Viewport | undefined = findParentByFeature(target, isViewport); + const zoom = viewport ? viewport.zoom : 1; + if (viewport) { + const scroll: Point = { x: viewport.scroll.x, y: viewport.scroll.y }; + xPos += scroll.x * zoom; + yPos += scroll.y * zoom; + + xPos /= zoom; + yPos /= zoom; + } + xPos; + + return { + x: xPos, + y: yPos + }; +} + +/** + * Translates the bounds of the diagram element (local coordinates) into the diagram coordinates system + * (i.e. relative to the Diagram's 0;0 point) + * + * @param target A bounds-aware element from the diagram + */ +export function toAbsoluteBounds(element: SModelElement & BoundsAware): Bounds { + const location = isAlignable(element) ? element.alignment : ORIGIN_POINT; + const x = location.x; + const y = location.y; + const width = element.bounds.width; + const height = element.bounds.height; + return translateBounds({ x, y, width, height }, element, element.root); +} + +/** + * Translates the position of the diagram element (local coordinates) into the diagram coordinates system + * (i.e. relative to the Diagram's 0;0 point) + * + * @param target A bounds-aware element from the diagram + */ +export function toAbsolutePosition(target: SModelElement & BoundsAware): Point { + return toAbsoluteBounds(target); +} + +/** + * Translates the size of the diagram element (local coordinates) into the diagram coordinates system + * (i.e. relative to the Diagram's 0;0 point) + * + * @param target A bounds-aware element from the diagram + */ +export function toAbsoluteSize(target: SModelElement & BoundsAware): Dimension { + return toAbsoluteBounds(target); +} diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..e0b4d40 --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": "." + }, + "include": [ + "src" + ] +} diff --git a/packages/protocol/README.md b/packages/protocol/README.md new file mode 100644 index 0000000..ed1adc2 --- /dev/null +++ b/packages/protocol/README.md @@ -0,0 +1,12 @@ +# Eclipse GLSP - Client [![build-status](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.eclipse.org%2Fglsp%2Fjob%2Feclipse-glsp%2Fjob%2Fglsp-client%2Fjob%2Fmaster%2F)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) [![build-status-server](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/deploy-npm-glsp-client/&label=publish)](https://ci.eclipse.org/glsp/job/deploy-npm-glsp-client/) + +The generic client-server communication protocol for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) and a json-rpc based default implementation. + + +This project is built with `yarn` and is available from npm via [@eclipse-glsp/protocol](https://www.npmjs.com/package/@eclipse-glsp/protocol). + + + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). + +![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/protocol/package.json b/packages/protocol/package.json new file mode 100644 index 0000000..821fafc --- /dev/null +++ b/packages/protocol/package.json @@ -0,0 +1,68 @@ +{ + "name": "@eclipse-glsp/protocol", + "version": "0.7.0", + "description": "The protocol definition for client-server communication in GLSP", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "homepage": "https://www.eclipse.org/glsp/", + "bugs": "https://github.com/eclipse-glsp/glsp-client/issues", + "files": [ + "lib", + "src" + ], + "author": { + "name": "Eclipse GLSP" + }, + "contributors": [ + { + "name": "Philip Langer", + "email": "planger@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Tobias Ortmayr", + "email": "tortmayr@eclipsesource.com", + "url": "https://www.eclipsesource.com" + }, + { + "name": "Martin Fleck", + "email": "mfleck@eclipsesource.com", + "url": "https://www.eclipsesource.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "dependencies": { + "uuid": "7.0.3", + "vscode-ws-jsonrpc": "0.2.0" + }, + "devDependencies": { + "@types/uuid": "3.4.5", + "@babel/runtime": "^7.11.2", + "rimraf": "^2.6.1", + "tslint": "^5.5.0", + "typescript": "^3.9.2" + }, + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "rimraf lib", + "build": "tsc && yarn run lint", + "lint": "tslint -c ../../tslint.json --project ./tsconfig.json", + "watch": "tsc -w" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts new file mode 100644 index 0000000..eef49a2 --- /dev/null +++ b/packages/protocol/src/glsp-client.ts @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ActionMessage } from "sprotty"; +import * as uuid from "uuid"; + +export interface InitializeParameters<> { + /** + * Unique identifier for the current client application + */ + applicationId: string; + options?: any +} +export class ApplicationIdProvider { + private static _applicationId?: string; + static get(): string { + if (!ApplicationIdProvider._applicationId) { + ApplicationIdProvider._applicationId = uuid.v4(); + } + return ApplicationIdProvider._applicationId; + } +} +export type ActionMessageHandler = (message: ActionMessage) => void; + +export enum ClientState { + Initial, + Starting, + StartFailed, + Running, + Stopping, + Stopped, + ServerError +} + +export interface GLSPClient { + readonly id: string; + readonly name: string; + currentState(): ClientState; + /** + * Initialize the client and the server connection. + * + */ + start(): Promise; + /** + * Send an initalize request to ther server. The server needs to be initialized + * in order to accept and process action messages + * @param params Initialize parameter + * @returns true if the initialization was successfull + */ + initializeServer(params: InitializeParameters): Promise; + /** + * Send a shutdown notification to the server + */ + shutdownServer(): void + /** + * Stop the client and cleanup/dispose resources + */ + stop(): Promise; + /** + * Set a handler/listener for action messages received from the server + * @param handler The action message handler + */ + onActionMessage(handler: ActionMessageHandler): void; + /** + * Send an action message to the server + * @param message The message + */ + sendActionMessage(message: ActionMessage): void; +} + +export namespace GLSPClient { + export interface Options { + id: string; + name: string; + } + + export function isOptions(object: any): object is Options { + return object !== undefined && "id" in object && typeof object["id"] === "string" + && "name" in object && typeof object["name"] === "string"; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts new file mode 100644 index 0000000..84507e4 --- /dev/null +++ b/packages/protocol/src/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './glsp-client'; +export * from './jsonrpc/glsp-jsonrpc-client'; +export * from './jsonrpc/base-jsonrpc-glsp-client'; diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts new file mode 100644 index 0000000..dbc164a --- /dev/null +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -0,0 +1,145 @@ +/******************************************************************************** + * Copyright (c) 2019-2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ActionMessage } from "sprotty"; +import { Message, MessageConnection } from "vscode-ws-jsonrpc"; + +import { ActionMessageHandler, ClientState, GLSPClient, InitializeParameters } from "../glsp-client"; +import { ConnectionProvider, JsonrpcGLSPClient } from "./glsp-jsonrpc-client"; + +export class BaseJsonrpcGLSPClient implements GLSPClient { + + readonly name: string; + readonly id: string; + protected readonly connectionProvider: ConnectionProvider; + protected connectionPromise?: Promise; + protected resolvedConnection?: MessageConnection; + protected state: ClientState; + protected onStop?: Promise; + + constructor(options: JsonrpcGLSPClient.Options) { + Object.assign(this, options); + this.state = ClientState.Initial; + } + + shutdownServer(): void { + if (this.checkConnectionState()) { + this.resolvedConnection!.sendNotification(JsonrpcGLSPClient.ShutdownNotification); + } + } + + initializeServer(params: InitializeParameters): Promise { + if (this.checkConnectionState()) { + return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); + } + return Promise.resolve(false); + } + + onActionMessage(handler: ActionMessageHandler): void { + if (this.checkConnectionState()) { + this.resolvedConnection!.onNotification(JsonrpcGLSPClient.ActionMessageNotification, handler); + } + } + + sendActionMessage(message: ActionMessage): void { + if (this.checkConnectionState()) { + this.resolvedConnection!.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); + } + } + + protected checkConnectionState(): boolean { + if (!this.isConnectionActive()) { + throw new Error(JsonrpcGLSPClient.ClientNotReadyMsg); + } + return true; + } + + async start(): Promise { + try { + this.state = ClientState.Starting; + const connection = await this.resolveConnection(); + connection.listen(); + this.resolvedConnection = connection; + this.state = ClientState.Running; + } catch (error) { + JsonrpcGLSPClient.error('Failed to start connection to server', error); + this.state = ClientState.StartFailed; + } + } + + stop(): Promise { + if (!this.connectionPromise) { + this.state = ClientState.Stopped; + return Promise.resolve(); + } + if (this.state === ClientState.Stopping && this.onStop) { + return this.onStop; + } + this.state = ClientState.Stopping; + return this.onStop = this.resolveConnection().then(connection => { + connection.dispose(); + this.state = ClientState.Stopped; + this.onStop = undefined; + this.connectionPromise = undefined; + this.resolvedConnection = undefined; + }); + } + + private resolveConnection(): Promise { + if (!this.connectionPromise) { + this.connectionPromise = this.doCreateConnection(); + } + return this.connectionPromise; + } + + protected async doCreateConnection(): Promise { + const connection = typeof this.connectionProvider === 'function' ? await this.connectionProvider() : this.connectionProvider; + connection.onError((data: [Error, Message, number]) => this.handleConnectionError(data[0], data[1], data[2])); + connection.onClose(() => this.handleConnectionClosed()); + return connection; + } + + protected handleConnectionError(error: Error, message: Message, count: number): void { + JsonrpcGLSPClient.error('Connection to server is erroring. Shutting down server.', error); + this.stop(); + this.state = ClientState.ServerError; + } + + protected handleConnectionClosed(): void { + if (this.state === ClientState.Stopping || this.state === ClientState.Stopped) { + return; + } + try { + if (this.resolvedConnection) { + this.resolvedConnection.dispose(); + this.connectionPromise = undefined; + this.resolvedConnection = undefined; + } + } catch (error) { + // Disposing a connection could fail if error cases. + } + + JsonrpcGLSPClient.error('Connection to server got closed. Server will not be restarted.'); + this.state = ClientState.ServerError; + } + + protected isConnectionActive(): boolean { + return this.state === ClientState.Running && !!this.resolvedConnection; + } + + currentState(): ClientState { + return this.state; + } +} diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts new file mode 100644 index 0000000..5b0c396 --- /dev/null +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ActionMessage } from "sprotty"; +import { MessageConnection, NotificationType, RequestType } from "vscode-jsonrpc"; +import { + createMessageConnection, + Logger, + NotificationType0, + toSocket, + WebSocketMessageReader, + WebSocketMessageWriter +} from "vscode-ws-jsonrpc"; + +import { GLSPClient, InitializeParameters } from "../glsp-client"; + + +export type MaybePromise = T | Promise | PromiseLike; +export type ConnectionProvider = MessageConnection | (() => MaybePromise); + +export namespace JsonrpcGLSPClient { + export interface Options extends GLSPClient.Options { + connectionProvider: ConnectionProvider; + } + + export function isOptions(object: any): object is Options { + return GLSPClient.isOptions(object) && "connectionProvider" in object; + } + + export const ActionMessageNotification = new NotificationType('process'); + export const InitializeRequest = new RequestType('initialize'); + export const ShutdownNotification = new NotificationType0('shutdown'); + export const ClientNotReadyMsg = 'JsonrpcGLSPClient is not ready yet'; + + export function createWebsocketConnectionProvider(websocket: WebSocket, logger?: Logger): ConnectionProvider { + const socket = toSocket(websocket); + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + + return createMessageConnection(reader, writer, logger); + } + + + export function error(message: string, ...optionalParams: any[]): void { + console.error(`[JsonrpcGLSPClient] ${message}`, optionalParams); + } +} diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json new file mode 100644 index 0000000..e0b4d40 --- /dev/null +++ b/packages/protocol/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": "." + }, + "include": [ + "src" + ] +} From 1a29cf47f12e630c946e99994aa098bcbfa6c7a5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 7 Sep 2020 14:01:52 +0200 Subject: [PATCH 002/566] Migrate getPort function into @eclipse-glsp/protocol This enables reuse outside of Theia e.g. in VS Code --- packages/protocol/src/index.ts | 1 + packages/protocol/src/launch-util.ts | 30 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 packages/protocol/src/launch-util.ts diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 84507e4..99eead1 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -14,5 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './glsp-client'; +export * from './launch-util'; export * from './jsonrpc/glsp-jsonrpc-client'; export * from './jsonrpc/base-jsonrpc-glsp-client'; diff --git a/packages/protocol/src/launch-util.ts b/packages/protocol/src/launch-util.ts new file mode 100644 index 0000000..835189e --- /dev/null +++ b/packages/protocol/src/launch-util.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.npmorg/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * Utility function to parse a serverport that is defined via command line arg. + * @param argsKey Name/Key of the commandline arg + * @param defaultPort Default port that should be returned if no (valid) port was passed via CLI + */ +export function getPort(argsKey: string, defaultPort?: number): number { + argsKey = `--${argsKey.replace("--", "").replace("=", "")}=`; + const args = process.argv.filter(a => a.startsWith(argsKey)); + if (args.length > 0) { + return Number.parseInt(args[0].substring(argsKey.length), 10); + } + return defaultPort ? defaultPort : NaN; +} + From f7f628bbf049d3e2bcc5e696fcc0e6090c3501d2 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Wed, 23 Sep 2020 15:36:59 +0200 Subject: [PATCH 003/566] #110 Improve Palette-tool disabling #fixes eclipse-glsp/glsp/issues/110 Signed-off-by: Camille Letavernier --- .../src/features/tool-palette/tool-palette.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index ea4251a..0587f45 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -115,14 +115,15 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected createBody(): void { const bodyDiv = document.createElement("div"); bodyDiv.classList.add("palette-body"); + let tabIndex = 0; this.paletteItems.sort(compare) .forEach(item => { if (item.children) { const group = createToolGroup(item); - item.children.sort(compare).forEach(child => group.appendChild(this.createToolButton(child))); + item.children.sort(compare).forEach(child => group.appendChild(this.createToolButton(child, tabIndex++))); bodyDiv.appendChild(group); } else { - bodyDiv.appendChild(this.createToolButton(item)); + bodyDiv.appendChild(this.createToolButton(item, tabIndex++)); } }); if (this.paletteItems.length === 0) { @@ -195,7 +196,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.searchField.placeholder = " Search..."; this.searchField.style.display = "none"; this.searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); - this.searchField.onkeydown = (ev) => this.clearOnEspace(ev); + this.searchField.onkeydown = (ev) => this.clearOnEscape(ev); headerTools.appendChild(searchIcon); headerCompartment.appendChild(headerTools); @@ -203,11 +204,13 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.containerElement.appendChild(headerCompartment); } - protected createToolButton(item: PaletteItem): HTMLElement { + protected createToolButton(item: PaletteItem, index: number): HTMLElement { const button = document.createElement("div"); + button.tabIndex = index; button.classList.add("tool-button"); button.innerHTML = item.label; button.onclick = this.onClickCreateToolButton(button, item); + button.onkeydown = (ev) => this.clearToolOnEscape(ev); return button; } @@ -216,7 +219,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, if (!this.editorContext.isReadonly) { this.actionDispatcher.dispatchAll(item.actions); this.changeActiveButton(button); - this.restoreFocus(); + button.focus(); } }; } @@ -227,7 +230,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const action = toolId ? new EnableToolsAction([toolId]) : new EnableDefaultToolsAction(); this.actionDispatcher.dispatch(action); this.changeActiveButton(button); - this.restoreFocus(); + button.focus(); } }; } @@ -258,6 +261,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, }); } else if (action instanceof EnableDefaultToolsAction) { this.changeActiveButton(); + this.restoreFocus(); } } @@ -265,10 +269,16 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); } - protected clearOnEspace(event: KeyboardEvent) { - if (matchesKeystroke(event, 'Escape')) { - this.searchField.value = ''; - this.requestFilterUpdate(''); + protected clearOnEscape(event: KeyboardEvent) { + if (matchesKeystroke(event, "Escape")) { + this.searchField.value = ""; + this.requestFilterUpdate(""); + } + } + + protected clearToolOnEscape(event: KeyboardEvent) { + if (matchesKeystroke(event, "Escape")) { + this.actionDispatcher.dispatch(new EnableDefaultToolsAction()); } } From 5f219189be451c576ad76d0376a7c565f0bded53 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Wed, 30 Sep 2020 11:52:17 +0200 Subject: [PATCH 004/566] #123 Centralize configuration in the server (#87) * #123 Centralize configuration in the server - Add ConfigureServerHandlersAction refs eclipse-glsp/glsp/issues/123 Signed-off-by: Camille Letavernier Signed-off-by: Philip Langer Co-authored-by: Philip Langer --- .../src/base/actions/protocol-actions.ts | 40 ++++++++++- packages/client/src/base/di.config.ts | 4 ++ .../src/model-source/glsp-diagram-server.ts | 68 +++---------------- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/packages/client/src/base/actions/protocol-actions.ts b/packages/client/src/base/actions/protocol-actions.ts index 5dd8e14..5bf49e6 100644 --- a/packages/client/src/base/actions/protocol-actions.ts +++ b/packages/client/src/base/actions/protocol-actions.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { Action } from "sprotty"; +import { inject, injectable } from "inversify"; +import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } from "sprotty"; /** * Initializes the graphical representation (diagram) for a specific client session. @@ -49,3 +49,39 @@ export class DisposeClientSessionAction implements Action { export function isDisposeClientSessionAction(action: Action): action is DisposeClientSessionAction { return action.kind === DisposeClientSessionAction.KIND; } + +/** + * Sent by the server after ClientSessionInitialization, to indicate + * all the action kinds that the server can handle. + */ +@injectable() +export class ConfigureServerHandlersAction implements Action { + static readonly KIND = "configureServerHandlers"; + readonly kind = ConfigureServerHandlersAction.KIND; + + constructor(readonly actionKinds: string[]) { } +} + +export function isConfigureServerHandlersAction(action: Action): action is ConfigureServerHandlersAction { + return action.kind === ConfigureServerHandlersAction.KIND; +} + +@injectable() +export class ConfigureServerHandlersActionHandler implements IActionHandler { + + @inject(TYPES.ModelSource) + protected diagramServer: ModelSource; + + @inject(TYPES.ActionHandlerRegistryProvider) + actionHandlerRegistryProvider: () => Promise; + + handle(action: Action): void { + if (isConfigureServerHandlersAction(action)) { + this.actionHandlerRegistryProvider().then(registry => { + for (const actionKind of action.actionKinds) { + registry.register(actionKind, this.diagramServer); + } + }); + } + } +} diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 324c210..981c2bc 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -20,6 +20,7 @@ import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from import { GLSPActionDispatcher } from "./action-dispatcher"; import { SetEditModeAction } from "./actions/edit-mode-action"; +import { ConfigureServerHandlersAction, ConfigureServerHandlersActionHandler } from "./actions/protocol-actions"; import { GLSPCommandStack } from "./command-stack"; import { EditorContextService } from "./editor-context"; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from "./model-initialization-constraint"; @@ -49,6 +50,9 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = configureCommand(context, FeedbackAwareUpdateModelCommand); configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); + // Dynamically register all server-side action/operation handlers + configureActionHandler(context, ConfigureServerHandlersAction.KIND, ConfigureServerHandlersActionHandler); + bind(TYPES.MouseListener).to(SelectionClearingMouseListener); rebind(TYPES.ICommandStack).to(GLSPCommandStack); diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index dcfb254..0e905fa 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -19,46 +19,19 @@ import { Action, ActionHandlerRegistry, ActionMessage, - ApplyLabelEditAction, ComputedBoundsAction, DiagramServer, - ExportSvgAction, ICommand, - LayoutAction, - RequestBoundsCommand, RequestModelAction, - RequestPopupModelAction, ServerStatusAction, SwitchEditModeCommand } from "sprotty"; import { RequestContextActions } from "../base/actions/context-actions"; import { isSetEditModeAction, SetEditModeAction } from "../base/actions/edit-mode-action"; -import { RequestEditValidationAction } from "../base/actions/edit-validation-actions"; -import { DisposeClientSessionAction, InitializeClientSessionAction } from "../base/actions/protocol-actions"; -import { - ChangeBoundsOperation, - ChangeRoutingPointsOperation, - CompoundOperation, - CreateEdgeOperation, - CreateNodeOperation, - DeleteElementOperation, - ReconnectEdgeOperation -} from "../base/operations/operation"; +import { InitializeClientSessionAction } from "../base/actions/protocol-actions"; import { SourceUriAware } from "../base/source-uri-aware"; -import { - CutOperationAction, - PasteOperationAction, - RequestClipboardDataAction -} from "../features/copy-paste/copy-paste-actions"; -import { ValidateLabelEditAction } from "../features/edit-label/edit-label-validator"; -import { ExecuteServerCommandAction } from "../features/execute/execute-command"; import { RequestTypeHintsAction } from "../features/hints/request-type-hints-action"; -import { RequestNavigationTargetsAction } from "../features/navigation/navigation-action-handler"; -import { ResolveNavigationTargetAction } from "../features/navigation/navigation-target-resolver"; -import { SaveModelAction } from "../features/save/save"; -import { GlspRedoAction, GlspUndoAction } from "../features/undo-redo/model"; -import { RequestMarkersAction } from "../features/validation/validate"; import { isServerMessageAction, ServerMessageAction } from "./glsp-server-status"; const receivedFromServerProperty = '__receivedFromServer'; @@ -132,42 +105,19 @@ export function isReceivedFromServer(action: Action) { } export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer) { - registry.register(SaveModelAction.KIND, diagramServer); - registry.register(GlspUndoAction.KIND, diagramServer); - registry.register(GlspRedoAction.KIND, diagramServer); - registry.register(CreateEdgeOperation.KIND, diagramServer); - registry.register(ReconnectEdgeOperation.KIND, diagramServer); - registry.register(ChangeRoutingPointsOperation.KIND, diagramServer); - registry.register(CreateNodeOperation.KIND, diagramServer); - registry.register(ChangeBoundsOperation.KIND, diagramServer); - registry.register(DeleteElementOperation.KIND, diagramServer); - registry.register(ExecuteServerCommandAction.KIND, diagramServer); + // Register the InitializeClientSessionAction as a server action. Then, the server will + // notify us about all actions it handles (Via ConfigureServerHandlersAction/Handler) registry.register(InitializeClientSessionAction.KIND, diagramServer); + + // Register some additional early actions, that will happen before the server can tell us + // which actions it handles registry.register(RequestTypeHintsAction.KIND, diagramServer); - registry.register(ComputedBoundsAction.KIND, diagramServer); - registry.register(RequestBoundsCommand.KIND, diagramServer); - registry.register(RequestPopupModelAction.KIND, diagramServer); - registry.register(ServerStatusAction.KIND, diagramServer); registry.register(RequestModelAction.KIND, diagramServer); - registry.register(ExportSvgAction.KIND, diagramServer); registry.register(RequestContextActions.KIND, diagramServer); - registry.register(ValidateLabelEditAction.KIND, diagramServer); - registry.register(RequestMarkersAction.KIND, diagramServer); - registry.register(LayoutAction.KIND, diagramServer); - registry.register(ApplyLabelEditAction.KIND, diagramServer); - registry.register(RequestClipboardDataAction.KIND, diagramServer); - registry.register(PasteOperationAction.KIND, diagramServer); - registry.register(CutOperationAction.KIND, diagramServer); - registry.register(RequestEditValidationAction.KIND, diagramServer); - registry.register(RequestNavigationTargetsAction.KIND, diagramServer); - registry.register(ResolveNavigationTargetAction.KIND, diagramServer); - registry.register(CompoundOperation.KIND, diagramServer); - registry.register(SetEditModeAction.KIND, diagramServer); - registry.register(DisposeClientSessionAction.KIND, diagramServer); - registry.register(ServerMessageAction.KIND, diagramServer); + + registry.register(ServerStatusAction.KIND, diagramServer); // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. - // We don't want to support SwitchEditMode, but sprotty still sends some corresponding - // actions. + // We don't support SwitchEditMode, but Sprotty still sends those actions, so ignore them. registry.register(SwitchEditModeCommand.KIND, { handle: action => undefined }); } From ad9c57620fb34aa5a0b658afef1a2ae4bfea0ebd Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 8 Oct 2020 10:36:26 +0200 Subject: [PATCH 005/566] #66, #72: Refactor code for easier customization (#88) * #66, #72: Refactor code for easier customization - Use property injection over constructor injection - Avoid readonly properties in classes that should be customizable Fixes https://github.com/eclipse-glsp/glsp/issues/66 Fixes https://github.com/eclipse-glsp/glsp/issues/72 Signed-off-by: Martin Fleck * #66, #72: Refactor code for easier customization - Remove explicit 'public' modifier Fixes https://github.com/eclipse-glsp/glsp/issues/66 Fixes https://github.com/eclipse-glsp/glsp/issues/72 Signed-off-by: Martin Fleck --- .../src/base/actions/context-actions.ts | 12 +-- .../src/base/actions/edit-mode-action.ts | 6 +- .../base/actions/edit-validation-actions.ts | 11 +-- .../src/base/actions/protocol-actions.ts | 9 +-- .../auto-complete/auto-complete-widget.ts | 26 +++--- .../auto-complete/validation-decorator.ts | 3 +- packages/client/src/base/editor-context.ts | 11 ++- .../src/base/model/update-model-command.ts | 22 ++++-- .../client/src/base/operations/operation.ts | 48 ++++++----- .../base/selection-clearing-mouse-listener.ts | 2 +- .../base/tool-manager/glsp-tool-manager.ts | 20 +++-- .../src/features/change-bounds/model.ts | 8 +- .../change-bounds/movement-restrictor.ts | 4 +- .../command-palette/command-palette-tool.ts | 2 + .../server-command-palette-provider.ts | 2 +- .../delete-element-context-menu.ts | 25 +++--- ...rvice-aware-context-menu-mouse-listener.ts | 10 +-- .../features/copy-paste/copy-paste-actions.ts | 20 +++-- .../copy-paste/copy-paste-context-menu.ts | 44 +++++++---- .../features/copy-paste/copy-paste-handler.ts | 34 ++++---- .../client/src/features/decoration/view.tsx | 2 +- .../edit-label/edit-label-validator.ts | 1 + .../src/features/execute/execute-command.ts | 8 +- packages/client/src/features/hints/model.ts | 2 + .../hints/request-type-hints-action.ts | 9 ++- .../client/src/features/hints/type-hints.ts | 11 +-- packages/client/src/features/hover/hover.ts | 23 ++++-- .../src/features/layout/layout-commands.ts | 10 +-- .../src/features/mouse-tool/mouse-tool.ts | 2 +- .../client/src/features/reconnect/model.ts | 1 + packages/client/src/features/save/save.ts | 5 +- .../features/select/select-feedback-action.ts | 12 ++- .../change-bounds-tool-feedback.ts | 30 +++---- .../tool-feedback/creation-tool-feedback.ts | 16 ++-- .../features/tool-feedback/css-feedback.ts | 8 +- .../tool-feedback/edge-edit-tool-feedback.ts | 25 +++--- .../feedback-action-dispatcher.ts | 6 +- .../src/features/tool-feedback/model.ts | 1 + .../src/features/tool-palette/palette-item.ts | 2 +- .../src/features/tool-palette/tool-palette.ts | 79 +++++++++++-------- .../src/features/tools/change-bounds-tool.ts | 5 +- .../client/src/features/tools/delete-tool.ts | 21 +++-- .../src/features/tools/edge-creation-tool.ts | 7 +- .../src/features/tools/edge-edit-tool.ts | 5 +- .../src/features/tools/node-creation-tool.ts | 5 +- .../client/src/features/undo-redo/model.ts | 4 +- .../features/validation/marker-navigator.ts | 15 ++-- .../src/features/validation/validate.ts | 14 +--- .../src/model-source/glsp-diagram-server.ts | 5 +- packages/protocol/src/glsp-client.ts | 2 + 50 files changed, 364 insertions(+), 291 deletions(-) diff --git a/packages/client/src/base/actions/context-actions.ts b/packages/client/src/base/actions/context-actions.ts index 4416cce..f68a682 100644 --- a/packages/client/src/base/actions/context-actions.ts +++ b/packages/client/src/base/actions/context-actions.ts @@ -20,19 +20,21 @@ import { EditorContext } from "../editor-context"; export class RequestContextActions implements RequestAction { static readonly KIND = "requestContextActions"; - kind = RequestContextActions.KIND; constructor( public readonly contextId: string, public readonly editorContext: EditorContext, - public readonly requestId: string = generateRequestId()) { } + public readonly requestId: string = generateRequestId(), + public readonly kind: string = RequestContextActions.KIND) { } } export class SetContextActions implements ResponseAction { static readonly KIND = "setContextActions"; - kind = SetContextActions.KIND; - constructor(public readonly actions: LabeledAction[], + constructor( + public readonly actions: LabeledAction[], public readonly responseId: string = '', - readonly args?: Args) { } + public readonly args?: Args, + public readonly kind: string = SetContextActions.KIND) { + } } export function isSetContextActionsAction(action: Action): action is SetContextActions { diff --git a/packages/client/src/base/actions/edit-mode-action.ts b/packages/client/src/base/actions/edit-mode-action.ts index 26e2005..0696031 100644 --- a/packages/client/src/base/actions/edit-mode-action.ts +++ b/packages/client/src/base/actions/edit-mode-action.ts @@ -13,15 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from "sprotty"; import { injectable } from "inversify"; +import { Action } from "sprotty"; @injectable() export class SetEditModeAction implements Action { static readonly KIND = "setEditMode"; - kind = SetEditModeAction.KIND; - - constructor(public readonly editMode: string = EditMode.EDITABLE) { } + constructor(public readonly editMode: string = EditMode.EDITABLE, public readonly kind: string = SetEditModeAction.KIND) { } } export function isSetEditModeAction(action: Action): action is SetEditModeAction { diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts index 3a4627f..a5ff4cb 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -19,20 +19,21 @@ import { Args } from "../args"; export class RequestEditValidationAction implements RequestAction { static readonly KIND = "requestEditValidation"; - kind = RequestEditValidationAction.KIND; constructor( public readonly contextId: string, public readonly modelElementId: string, public readonly text: string, - public readonly requestId: string = generateRequestId()) { } + public readonly requestId: string = generateRequestId(), + public readonly kind: string = RequestEditValidationAction.KIND) { } } export class SetEditValidationResultAction implements ResponseAction { static readonly KIND = "setEditValidationResult"; - kind = SetEditValidationResultAction.KIND; - constructor(public readonly status: ValidationStatus, + constructor( + public readonly status: ValidationStatus, public readonly responseId: string = '', - readonly args?: Args) { } + public readonly args?: Args, + public readonly kind: string = SetEditValidationResultAction.KIND) { } } export function isSetEditValidationResultAction(action: Action): action is SetEditValidationResultAction { diff --git a/packages/client/src/base/actions/protocol-actions.ts b/packages/client/src/base/actions/protocol-actions.ts index 5bf49e6..76b59b2 100644 --- a/packages/client/src/base/actions/protocol-actions.ts +++ b/packages/client/src/base/actions/protocol-actions.ts @@ -24,10 +24,7 @@ import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } fro @injectable() export class InitializeClientSessionAction implements Action { static readonly KIND = "initializeClientSession"; - readonly kind = InitializeClientSessionAction.KIND; - - constructor(public readonly clientId: string) { } - + constructor(public readonly clientId: string, public readonly kind: string = InitializeClientSessionAction.KIND) { } } export function initializeClientSessionAction(action: Action): action is InitializeClientSessionAction { @@ -41,9 +38,7 @@ export function initializeClientSessionAction(action: Action): action is Initial @injectable() export class DisposeClientSessionAction implements Action { static readonly KIND = "disposeClientSession"; - readonly kind = DisposeClientSessionAction.KIND; - - constructor(public readonly clientId: string) { } + constructor(public readonly clientId: string, public readonly kind: string = DisposeClientSessionAction.KIND) { } } export function isDisposeClientSessionAction(action: Action): action is DisposeClientSessionAction { diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 3d2b466..671696f 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -51,8 +51,9 @@ export interface TextSubmitHandler { const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); -export class AutoCompleteWidget { + +export class AutoCompleteWidget { loadingIndicatorClasses = ['loading', 'fa', 'fa-spinner', 'fa-pulse', 'fa-3x', 'fa-fw']; protected containerElement: HTMLElement; @@ -87,18 +88,23 @@ export class AutoCompleteWidget { initialize(containerElement: HTMLElement): void { this.containerElement = containerElement; - this.inputElement = document.createElement('input'); - this.inputElement.style.position = 'absolute'; - this.inputElement.spellcheck = false; - this.inputElement.autocapitalize = 'false'; - this.inputElement.autocomplete = 'false'; - this.inputElement.style.width = '100%'; - this.inputElement.addEventListener('keydown', event => this.handleKeyDown(event)); - this.inputElement.addEventListener('blur', () => window.setTimeout(() => this.notifyClose(), 200)); + this.inputElement = this.createInputElement(); this.containerElement.appendChild(this.inputElement); this.containerElement.style.position = 'absolute'; } + protected createInputElement(): HTMLInputElement { + const inputElement = document.createElement('input'); + inputElement.style.position = 'absolute'; + inputElement.spellcheck = false; + inputElement.autocapitalize = 'false'; + inputElement.autocomplete = 'false'; + inputElement.style.width = '100%'; + inputElement.addEventListener('keydown', event => this.handleKeyDown(event)); + inputElement.addEventListener('blur', () => window.setTimeout(() => this.notifyClose(), 200)); + return inputElement; + } + protected handleKeyDown(event: KeyboardEvent) { if (matchesKeystroke(event, 'Escape')) { this.notifyClose(); @@ -193,7 +199,7 @@ export class AutoCompleteWidget { return this.suggestionProvider.provideSuggestions(text); } - protected onLoaded(success: 'success' | 'error') { + protected onLoaded(_success: 'success' | 'error') { if (this.containerElement.contains(this.loadingIndicator)) { this.containerElement.removeChild(this.loadingIndicator); } diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index bce8d80..1308fb8 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -24,7 +24,7 @@ export interface IValidationDecorator { export namespace IValidationDecorator { export const NO_DECORATION: IValidationDecorator = { - decorateValidationResult(status: ValidationStatus) { }, + decorateValidationResult(_status: ValidationStatus) { }, isValidatedOk(): boolean { return false; }, invalidate() { }, dispose() { } @@ -32,7 +32,6 @@ export namespace IValidationDecorator { } export class ValidationDecorator implements IValidationDecorator { - warningClasses = ['warning']; warningIconClasses = ['fa', 'fa-question-circle']; errorClasses = ['error']; diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context.ts index 493bc89..20ad822 100644 --- a/packages/client/src/base/editor-context.ts +++ b/packages/client/src/base/editor-context.ts @@ -32,13 +32,14 @@ export interface EditorContext { export interface EditModeListener { editModeChanged(newValue: string, oldvalue: string): void; } + @injectable() export class EditorContextService implements IActionHandler { @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; @inject(TYPES.ModelSourceProvider) protected modelSource: () => Promise; - editMode: string; + protected _editMode: string; constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) { } @@ -68,8 +69,8 @@ export class EditorContextService implements IActionHandler { handle(action: Action) { if (isSetEditModeAction(action)) { - const oldValue = this.editMode; - this.editMode = action.editMode; + const oldValue = this._editMode; + this._editMode = action.editMode; this.notifiyEditModeListeners(oldValue); } } @@ -86,6 +87,10 @@ export class EditorContextService implements IActionHandler { return undefined; } + get editMode(): string { + return this._editMode; + } + get modelRoot(): Readonly { return this.selectionService.getModelRoot(); } diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index df606c8..09523c3 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from "inversify"; +import { inject, injectable, multiInject, optional, postConstruct } from "inversify"; import { Action, ActionHandlerRegistry, @@ -60,14 +60,20 @@ export interface SModelRootListener { */ @injectable() export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { + @inject(TYPES.ILogger) protected logger: ILogger; + @inject(GLSP_TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; + @multiInject(GLSP_TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []; + protected actionHandlerRegistry?: ActionHandlerRegistry; - constructor(@inject(TYPES.Action) action: UpdateModelAction, - @inject(TYPES.ILogger) protected logger: ILogger, - @inject(GLSP_TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher, - @inject(TYPES.ActionHandlerRegistryProvider) actionHandlerRegistryProvider: () => Promise, - @multiInject(GLSP_TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []) { + + constructor(@inject(TYPES.Action) action: UpdateModelAction) { super(action); - actionHandlerRegistryProvider().then(registry => this.actionHandlerRegistry = registry); + } + + @postConstruct() + protected initialize(): void { + this.actionHandlerRegistryProvider().then(registry => this.actionHandlerRegistry = registry); } protected performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { @@ -91,7 +97,7 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { return super.performUpdate(oldRoot, newRoot, context); } - private getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { + protected getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { const result: Command[] = []; this.feedbackActionDispatcher.getRegisteredFeedback().forEach(action => { const handler = registry.get(action.kind).find(h => h instanceof CommandActionHandler); diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index c1a9313..4fabb73 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -17,6 +17,10 @@ import { Action, ElementAndBounds, isAction, Point } from "sprotty"; import { Args } from "../args"; +/** + * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the server. + * After a successful modification, the server sends the updated model back to the client using the `UpdateModelAction`. + */ export interface Operation extends Action { } export interface CreateOperation extends Operation { @@ -30,12 +34,12 @@ export function isCreateOperation(object?: any): object is CreateOperation { export class CreateNodeOperation implements CreateOperation { static readonly KIND = "createNode"; - readonly kind = CreateNodeOperation.KIND; constructor(public readonly elementTypeId: string, public location?: Point, public containerId?: string, - public args?: Args) { } + public args?: Args, + public readonly kind: string = CreateNodeOperation.KIND) { } } export function isCreateNodeOperation(object?: any): object is CreateNodeOperation { @@ -44,12 +48,12 @@ export function isCreateNodeOperation(object?: any): object is CreateNodeOperati export class CreateEdgeOperation implements CreateOperation { static readonly KIND = "createEdge"; - readonly kind = CreateEdgeOperation.KIND; constructor(public readonly elementTypeId: string, public sourceElementId?: string, public targetElementId?: string, - public args?: Args) { } + public args?: Args, + public readonly kind: string = CreateEdgeOperation.KIND) { } } export function isCreateConnectionOperation(object?: any): object is CreateEdgeOperation { @@ -58,61 +62,63 @@ export function isCreateConnectionOperation(object?: any): object is CreateEdgeO export class DeleteElementOperation implements Operation { static readonly KIND = "deleteElement"; - kind = DeleteElementOperation.KIND; - constructor(readonly elementIds: string[]) { } + constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) { } } export class ChangeBoundsOperation implements Operation { static readonly KIND = "changeBounds"; - readonly kind = ChangeBoundsOperation.KIND; - constructor(public newBounds: ElementAndBounds[]) { } + constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) { } } export class ChangeContainerOperation implements Operation { static readonly KIND = "changeContainer"; - readonly kind = ChangeContainerOperation.KIND; constructor(public readonly elementId: string, public readonly targetContainerId: string, - public readonly location?: string) { } + public readonly location?: string, + public readonly kind: string = ChangeContainerOperation.KIND) { } } export class ReconnectEdgeOperation implements Operation { static readonly KIND = "reconnectEdge"; - readonly kind = ReconnectEdgeOperation.KIND; constructor(public readonly connectionElementId: string, public readonly sourceElementId: string, - public readonly targetElementId: string) { } + public readonly targetElementId: string, + public readonly kind: string = ReconnectEdgeOperation.KIND) { } } export class ChangeRoutingPointsOperation implements Operation { static readonly KIND = "changeRoutingPoints"; - readonly kind = ChangeRoutingPointsOperation.KIND; - constructor(public newRoutingPoints: ElementAndRoutingPoints[]) { } + constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) { } } + export class CompoundOperation implements Operation { static readonly KIND = "compound"; - readonly kind = CompoundOperation.KIND; - - constructor(public operationList: Operation[]) { } + constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) { } } + export interface ElementAndRoutingPoints { elementId: string newRoutingPoints?: Point[]; } export abstract class TriggerElementCreationAction implements Action { - abstract readonly kind: string; - constructor(public readonly elementTypeId: string, readonly args?: Args) { } + constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = 'unknown') { } } export class TriggerNodeCreationAction extends TriggerElementCreationAction { static readonly KIND = "triggerNodeCreation"; - kind = TriggerNodeCreationAction.KIND; + + constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind = TriggerNodeCreationAction.KIND) { + super(elementTypeId, args, kind); + } } export class TriggerEdgeCreationAction extends TriggerElementCreationAction { static readonly KIND = "triggerEdgeCreation"; - kind = TriggerEdgeCreationAction.KIND; + + constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = TriggerEdgeCreationAction.KIND) { + super(elementTypeId, args, kind); + } } export function isTriggerElementTypeCreationAction(object?: any): object is TriggerElementCreationAction { diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts index 7ea110f..ad94c1e 100644 --- a/packages/client/src/base/selection-clearing-mouse-listener.ts +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -30,7 +30,7 @@ import { MouseListener, SModelElement } from "sprotty"; */ @injectable() export class SelectionClearingMouseListener extends MouseListener { - mouseDown(target: SModelElement, event: MouseEvent) { + mouseDown(_target: SModelElement, event: MouseEvent) { const selection = document.getSelection(); if (selection === null) { return []; diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index 80615c9..27be191 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from "inversify"; +import { inject, injectable, multiInject, optional, postConstruct } from "inversify"; import { Tool, ToolManager } from "sprotty"; import { distinctAdd } from "../../utils/array-utils"; @@ -25,17 +25,21 @@ export interface IGLSPToolManager extends ToolManager { /* Disables all actives tools and activates only default tool with non-edit function*/ disableEditTools(): void; } + @injectable() export class GLSPToolManager extends ToolManager implements IGLSPToolManager, EditModeListener { protected editorContext?: EditorContextService; - constructor(@multiInject(GLSP_TYPES.ITool) @optional() tools: Tool[], - @multiInject(GLSP_TYPES.IDefaultTool) @optional() defaultTools: Tool[], - @inject(GLSP_TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider) { - super(); - this.registerTools(...tools); - this.registerDefaultTools(...defaultTools); + + @multiInject(GLSP_TYPES.ITool) @optional() tools: Tool[]; + @multiInject(GLSP_TYPES.IDefaultTool) @optional() defaultTools: Tool[]; + @inject(GLSP_TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider; + + @postConstruct() + protected initialize(): void { + this.registerTools(...this.tools); + this.registerDefaultTools(...this.defaultTools); this.enableDefaultTools(); - contextServiceProvider().then(editorContext => { + this.contextServiceProvider().then(editorContext => { editorContext.register(this); this.editorContext = editorContext; }); diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 1261060..d882a56 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -49,13 +49,11 @@ export function isBoundsAwareMoveable(element: SModelElement): element is SModel export class SResizeHandle extends SChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; - type: string = SResizeHandle.TYPE; - hoverFeedback: boolean = false; - location?: ResizeHandleLocation; - constructor(location?: ResizeHandleLocation) { + constructor(public readonly location?: ResizeHandleLocation, + public readonly type: string = SResizeHandle.TYPE, + public readonly hoverFeedback: boolean = false) { super(); - this.location = location; } hasFeature(feature: symbol): boolean { diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 0b43400..f8ee75f 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -45,6 +45,8 @@ export function removeMovementRestrictionFeedback(element: SModelElement, moveme @injectable() export class NoOverlapMovmentRestrictor implements IMovementRestrictor { + cssClasses = ["movement-not-allowed"]; + validate(newLocation: Point, element: SModelElement): boolean { if (!isBoundsAwareMoveable(element)) { return false; @@ -62,8 +64,6 @@ export class NoOverlapMovmentRestrictor implements IMovementRestrictor { return !Array.from(element.root.index.all().filter(e => e.id !== ghostElement.id && e !== ghostElement.root && (e instanceof SNode)) .map(e => e as SModelElement & BoundsAware)).some(e => areOverlapping(e, ghostElement)); } - - cssClasses = ["movement-not-allowed"]; } export function areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware) { diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index fc5738b..13be171 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -31,9 +31,11 @@ export class CommandPaletteTool implements Tool { get id(): string { return CommandPaletteTool.ID; } + enable(): void { this.keyTool.register(this.commandPaletteKeyListener); } + disable(): void { this.keyTool.deregister(this.commandPaletteKeyListener); } diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 803c90d..cc1bb9e 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -31,7 +31,7 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; - getActions(root: Readonly, text: string, lastMousePosition?: Point, index?: number): Promise { + getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { const requestAction = new RequestContextActions(ServerCommandPalette.CONTEXT_ID, this.editorContext.get({ [ServerCommandPalette.TEXT]: text, [ServerCommandPalette.INDEX]: index ? index : 0 diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index b2514e8..45d0b8e 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -16,7 +16,7 @@ import { inject, injectable } from "inversify"; import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from "sprotty"; -import { EditorContextServiceProvider } from "../../base/editor-context"; +import { EditorContextService, EditorContextServiceProvider } from "../../base/editor-context"; import { DeleteElementOperation } from "../../base/operations/operation"; import { GLSP_TYPES } from "../../base/types"; @@ -24,18 +24,19 @@ import { GLSP_TYPES } from "../../base/types"; export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { @inject(GLSP_TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; - async getItems(root: Readonly, lastMousePosition?: Point): Promise { + async getItems(_root: Readonly, _lastMousePosition?: Point): Promise { const editorContextService = await this.editorContextServiceProvider(); + return [this.createDeleteMenuItem(editorContextService)]; + } - return [ - { - id: "delete", - label: "Delete", - sortString: "d", - group: "edit", - actions: [new DeleteElementOperation(editorContextService.selectedElements.map(e => e.id))], - isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 - } - ]; + protected createDeleteMenuItem(editorContextService: EditorContextService): MenuItem { + return { + id: "delete", + label: "Delete", + sortString: "d", + group: "edit", + actions: [new DeleteElementOperation(editorContextService.selectedElements.map(e => e.id))], + isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 + }; } } diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index affc5ad..a626f82 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -30,13 +30,9 @@ import { SelectionService } from "../select/selection-service"; @injectable() export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { - - constructor( - @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService: IContextMenuServiceProvider, - @inject(TYPES.IContextMenuProviderRegistry) @optional() protected readonly menuProvider: ContextMenuProviderRegistry, - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService) { - super(); - } + @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService: IContextMenuServiceProvider; + @inject(TYPES.IContextMenuProviderRegistry) @optional() protected readonly menuProvider: ContextMenuProviderRegistry; + @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { if (event.button === 2 && this.contextMenuService && this.menuProvider) { diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/client/src/features/copy-paste/copy-paste-actions.ts index d748194..0cff290 100644 --- a/packages/client/src/features/copy-paste/copy-paste-actions.ts +++ b/packages/client/src/features/copy-paste/copy-paste-actions.ts @@ -19,25 +19,28 @@ import { EditorContext } from "../../base/editor-context"; export class CutOperationAction implements Action { static readonly KIND = "cut"; - kind = CutOperationAction.KIND; - constructor(public readonly editorContext: EditorContext) { } + + constructor( + public readonly editorContext: EditorContext, + public readonly kind: string = CutOperationAction.KIND) { } } export class PasteOperationAction implements Action { static readonly KIND = "paste"; - kind = PasteOperationAction.KIND; + constructor( public readonly clipboardData: ClipboardData, - public readonly editorContext: EditorContext) { } + public readonly editorContext: EditorContext, + public readonly kind: string = PasteOperationAction.KIND) { } } export class RequestClipboardDataAction implements RequestAction { static readonly KIND = "requestClipboardData"; - kind = RequestClipboardDataAction.KIND; constructor( public readonly editorContext: EditorContext, - public readonly requestId: string = generateRequestId()) { } + public readonly requestId: string = generateRequestId(), + public readonly kind: string = RequestClipboardDataAction.KIND) { } static create(editorContext: EditorContext): RequestAction { return new RequestClipboardDataAction(editorContext); @@ -48,8 +51,9 @@ export type ClipboardData = { [format: string]: string }; export class SetClipboardDataAction implements ResponseAction { static readonly KIND = "setClipboardData"; - kind = SetClipboardDataAction.KIND; + constructor( public readonly clipboardData: ClipboardData, - public readonly responseId: string = '') { } + public readonly responseId: string = '', + public readonly kind: string = SetClipboardDataAction.KIND) { } } diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index d70aa50..5b080d7 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -30,17 +30,17 @@ import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/ export class InvokeCopyAction implements Action { static readonly KIND = "invoke-copy"; - kind = InvokeCopyAction.KIND; + constructor(public readonly kind = InvokeCopyAction.KIND) { } } export class InvokePasteAction implements Action { static readonly KIND = "invoke-paste"; - kind = InvokePasteAction.KIND; + constructor(public readonly kind = InvokePasteAction.KIND) { } } export class InvokeCutAction implements Action { static readonly KIND = "invoke-cut"; - kind = InvokeCutAction.KIND; + constructor(public readonly kind = InvokeCutAction.KIND) { } } @injectable() @@ -75,21 +75,33 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { @injectable() export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvider { - getItems(root: Readonly, lastMousePosition?: Point): Promise { + getItems(root: Readonly, _lastMousePosition?: Point): Promise { const hasSelectedElements = Array.from(root.index.all().filter(isSelected)).length > 0; return Promise.resolve([ - { - id: "copy", label: "Copy", group: "copy-paste", - actions: [new InvokeCopyAction()], isEnabled: () => hasSelectedElements - }, - { - id: "cut", label: "Cut", group: "copy-paste", - actions: [new InvokeCutAction()], isEnabled: () => hasSelectedElements - }, - { - id: "paste", label: "Paste", group: "copy-paste", - actions: [new InvokePasteAction()], isEnabled: () => true - } + this.createCopyMenuItem(hasSelectedElements), + this.createCutMenuItem(hasSelectedElements), + this.createPasteMenuItem() ]); } + + protected createPasteMenuItem(): MenuItem { + return { + id: "paste", label: "Paste", group: "copy-paste", + actions: [new InvokePasteAction()], isEnabled: () => true + }; + } + + protected createCutMenuItem(hasSelectedElements: boolean): MenuItem { + return { + id: "cut", label: "Cut", group: "copy-paste", + actions: [new InvokeCutAction()], isEnabled: () => hasSelectedElements + }; + } + + protected createCopyMenuItem(hasSelectedElements: boolean): MenuItem { + return { + id: "copy", label: "Copy", group: "copy-paste", + actions: [new InvokeCopyAction()], isEnabled: () => hasSelectedElements + }; + } } diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 50d3e70..3733714 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -23,9 +23,9 @@ import { GLSP_TYPES } from "../../base/types"; import { ClipboardData, CutOperationAction, PasteOperationAction, RequestClipboardDataAction } from "./copy-paste-actions"; export interface ICopyPasteHandler { - handleCopy(e: ClipboardEvent): void; - handleCut(e: ClipboardEvent): void; - handlePaste(e: ClipboardEvent): void; + handleCopy(event: ClipboardEvent): void; + handleCut(event: ClipboardEvent): void; + handlePaste(event: ClipboardEvent): void; } export interface IAsyncClipboardService { @@ -101,40 +101,40 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { @inject(GLSP_TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; @inject(EditorContextService) protected editorContext: EditorContextService; - handleCopy(e: ClipboardEvent) { - if (e.clipboardData && this.shouldCopy(e)) { + handleCopy(event: ClipboardEvent) { + if (event.clipboardData && this.shouldCopy(event)) { const clipboardId = uuid(); - e.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); + event.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); this.actionDispatcher .request(RequestClipboardDataAction.create(this.editorContext.get())) .then(action => this.clipboadService.put(action.clipboardData, clipboardId)); - e.preventDefault(); + event.preventDefault(); } else { - if (e.clipboardData) { e.clipboardData.clearData(); } + if (event.clipboardData) { event.clipboardData.clearData(); } this.clipboadService.clear(); } } - handleCut(e: ClipboardEvent): void { - if (e.clipboardData && this.shouldCopy(e)) { - this.handleCopy(e); + handleCut(event: ClipboardEvent): void { + if (event.clipboardData && this.shouldCopy(event)) { + this.handleCopy(event); this.actionDispatcher.dispatch(new CutOperationAction(this.editorContext.get())); - e.preventDefault(); + event.preventDefault(); } } - handlePaste(e: ClipboardEvent): void { - if (e.clipboardData) { - const clipboardId = getClipboardIdFromDataTransfer(e.clipboardData); + handlePaste(event: ClipboardEvent): void { + if (event.clipboardData) { + const clipboardId = getClipboardIdFromDataTransfer(event.clipboardData); const clipboardData = this.clipboadService.get(clipboardId); if (clipboardData) { this.actionDispatcher.dispatch(new PasteOperationAction(clipboardData, this.editorContext.get())); } - e.preventDefault(); + event.preventDefault(); } } - protected shouldCopy(e: ClipboardEvent) { + protected shouldCopy(_event: ClipboardEvent) { return this.editorContext.get().selectedElementIds.length > 0 && document.activeElement instanceof SVGElement && document.activeElement.parentElement && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; } diff --git a/packages/client/src/features/decoration/view.tsx b/packages/client/src/features/decoration/view.tsx index 4b5e46c..37c3cee 100644 --- a/packages/client/src/features/decoration/view.tsx +++ b/packages/client/src/features/decoration/view.tsx @@ -23,7 +23,7 @@ const JSX = { createElement: snabbdom.svg }; @injectable() export class GlspIssueMarkerView extends IssueMarkerView { - render(marker: SIssueMarker, context: RenderingContext): VNode { + render(marker: SIssueMarker, _context: RenderingContext): VNode { const maxSeverity = super.getMaxSeverity(marker); const group = diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index 57529e7..48466b2 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -34,6 +34,7 @@ import { export namespace LabelEditValidation { export const CONTEXT_ID = 'label-edit'; + export function toEditLabelValidationResult(status: ValidationStatus): EditLabelValidationResult { const message = status.message; let severity = 'ok'; diff --git a/packages/client/src/features/execute/execute-command.ts b/packages/client/src/features/execute/execute-command.ts index a3cbe2e..a41597b 100644 --- a/packages/client/src/features/execute/execute-command.ts +++ b/packages/client/src/features/execute/execute-command.ts @@ -19,12 +19,14 @@ import { isCommandExecutor } from "./model"; export class ExecuteServerCommandAction implements Action { static readonly KIND = "executeServerCommand"; - kind = ExecuteServerCommandAction.KIND; - constructor(public readonly commandId: String, public readonly options?: { [key: string]: string }) { } + constructor( + public readonly commandId: String, + public readonly options?: { [key: string]: string }, + public readonly kind: string = ExecuteServerCommandAction.KIND) { } } export class ExecuteCommandMouseListener extends MouseListener { - doubleClick(target: SModelElement, event: WheelEvent): (Action | Promise)[] { + doubleClick(target: SModelElement, _event: WheelEvent): (Action | Promise)[] { const result: Action[] = []; const commandExecutorTarget = findParentByFeature(target, isCommandExecutor); if (commandExecutorTarget) { diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 0fc677f..74d5816 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -16,6 +16,7 @@ import { SModelElement, SModelElementSchema, SModelExtension } from "sprotty"; export const containerFeature = Symbol("containable"); + export interface Containable extends SModelExtension { isContainableElement(input: SModelElement | SModelElementSchema | string): boolean } @@ -25,6 +26,7 @@ export function isContainable(element: SModelElement): element is SModelElement } export const reparentFeature = Symbol("reparentFeature"); + export interface Reparentable extends SModelExtension { } export function isReparentable(element: SModelElement): element is SModelElement & Reparentable { diff --git a/packages/client/src/features/hints/request-type-hints-action.ts b/packages/client/src/features/hints/request-type-hints-action.ts index d38d475..f4824e9 100644 --- a/packages/client/src/features/hints/request-type-hints-action.ts +++ b/packages/client/src/features/hints/request-type-hints-action.ts @@ -19,14 +19,15 @@ import { EdgeTypeHint, ShapeTypeHint } from "./type-hints"; export class RequestTypeHintsAction implements Action { static readonly KIND = "requestTypeHints"; - kind = RequestTypeHintsAction.KIND; - constructor(public readonly diagramType?: string) { } + constructor(public readonly diagramType?: string, public readonly kind: string = RequestTypeHintsAction.KIND) { } } export class SetTypeHintsAction implements Action { static readonly KIND = "setTypeHints"; - kind = SetTypeHintsAction.KIND; - constructor(public readonly shapeHints: ShapeTypeHint[], public readonly edgeHints: EdgeTypeHint[]) { } + constructor( + public readonly shapeHints: ShapeTypeHint[], + public readonly edgeHints: EdgeTypeHint[], + public readonly kind: string = SetTypeHintsAction.KIND) { } } export function isSetTypeHintsAction(action: Action): action is SetTypeHintsAction { diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 87277f9..8b172c7 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -107,17 +107,17 @@ export class EdgeTypeHint extends TypeHint { @injectable() export class ApplyTypeHintsAction implements Action { readonly kind = ApplyTypeHintsCommand.KIND; - constructor() { } } @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { - static KIND = "applyTypeHints"; - readonly priority = 10; + public static KIND = "applyTypeHints"; + public readonly priority = 10; - constructor(@inject(TYPES.Action) protected action: ApplyTypeHintsAction, - @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider) { + @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; + + constructor(@inject(TYPES.Action) protected action: ApplyTypeHintsAction) { super(); } @@ -189,6 +189,7 @@ function addOrRemove(features: Set, feature: symbol, add: boolean) { features.delete(feature); } } + function isModifiableFetureSet(featureSet?: FeatureSet): featureSet is FeatureSet & Set { return featureSet !== undefined && featureSet instanceof Set; } diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index e96981d..d3afea8 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -25,6 +25,7 @@ import { SIssueMarker, SIssueSeverity, SModelElement, + SModelElementSchema, SModelRootSchema } from "sprotty"; @@ -52,23 +53,29 @@ export class GlspHoverMouseListener extends HoverMouseListener { protected createPopupModel(marker: GIssueMarker, bounds: Bounds): SModelRootSchema { if (marker.issues !== undefined && marker.issues.length > 0) { - const message = '
    ' + marker.issues.map(i => '
  • ' + i.severity.toUpperCase() + ': ' + i.message + '
  • ').join('') + '
'; return { type: 'html', id: 'sprotty-popup', - children: [ - { - type: 'pre-rendered', - id: 'popup-title', - code: `
${message}
` - } - ], + children: [this.createMarkerIssuePopup(marker)], canvasBounds: this.modifyBounds(bounds) }; } return { type: EMPTY_ROOT.type, id: EMPTY_ROOT.id }; } + protected createMarkerIssuePopup(marker: GIssueMarker): SModelElementSchema { + const message = this.createIssueMessage(marker); + return { + type: 'pre-rendered', + id: 'popup-title', + code: `
${message}
` + }; + } + + protected createIssueMessage(marker: GIssueMarker): string { + return '
    ' + marker.issues.map(i => '
  • ' + i.severity.toUpperCase() + ': ' + i.message + '
  • ').join('') + '
'; + } + protected modifyBounds(bounds: Bounds): Bounds { return bounds; } diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index b162afd..b550d01 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -70,8 +70,6 @@ export namespace Reduce { } export class ResizeElementsAction implements Action { - readonly kind = ResizeElementsCommand.KIND; - constructor( /** * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized. @@ -84,7 +82,8 @@ export class ResizeElementsAction implements Action { /** * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. */ - public readonly reductionFunction: (...values: number[]) => number) { + public readonly reductionFunction: (...values: number[]) => number, + public readonly kind: string = ResizeElementsCommand.KIND) { } } @@ -113,8 +112,6 @@ export namespace Select { export class AlignElementsAction implements Action { - readonly kind = AlignElementsCommand.KIND; - constructor( /** * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. @@ -127,7 +124,8 @@ export class AlignElementsAction implements Action { /** * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. */ - public readonly selectionFunction: (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[] = Select.all) { + public readonly selectionFunction: (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[] = Select.all, + public readonly kind = AlignElementsCommand.KIND) { } } diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index 59f40d1..9435523 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -54,7 +54,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { overwriteOn(vnode, 'mouseup', this.mouseUp.bind(this), element); overwriteOn(vnode, 'mousemove', this.mouseMove.bind(this), element); overwriteOn(vnode, 'wheel', this.wheel.bind(this), element); - overwriteOn(vnode, 'contextmenu', (target: SModelElement, event: any) => { + overwriteOn(vnode, 'contextmenu', (_target: SModelElement, event: any) => { event.preventDefault(); }, element); overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this), element); diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index 2a5b12a..c0cca64 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -25,6 +25,7 @@ import { } from "sprotty"; export const reconnectFeature = Symbol("reconnectFeature"); + export interface Reconnectable extends SModelExtension { } diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save.ts index d9a63ac..b01e868 100644 --- a/packages/client/src/features/save/save.ts +++ b/packages/client/src/features/save/save.ts @@ -18,12 +18,11 @@ import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; export class SaveModelAction implements Action { static readonly KIND = "saveModel"; - readonly kind = SaveModelAction.KIND; - constructor() { } + constructor(public readonly kind: string = SaveModelAction.KIND) { } } export class SaveModelKeyboardListener extends KeyListener { - keyDown(element: SModelRoot, event: KeyboardEvent): Action[] { + keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { return [new SaveModelAction()]; } diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 691c10a..209f3e3 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -24,20 +24,18 @@ import { } from "sprotty"; export class SelectFeedbackAction { - kind = SelectFeedbackCommand.KIND; - - constructor(public readonly selectedElementsIDs: string[] = [], - public readonly deselectedElementsIDs: string[] = []) { + constructor( + public readonly selectedElementsIDs: string[] = [], + public readonly deselectedElementsIDs: string[] = [], + public readonly kind: string = SelectFeedbackCommand.KIND) { } } export class SelectAllFeedbackAction { - kind = SelectAllFeedbackCommand.KIND; - /** * If `select` is true, all elements are selected, othewise they are deselected. */ - constructor(public readonly select: boolean = true) { + constructor(public readonly select: boolean = true, public readonly kind: string = SelectFeedbackCommand.KIND) { } } diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index ba88a70..8e91023 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -39,25 +39,18 @@ import { ChangeBoundsTool } from "../tools/change-bounds-tool"; import { FeedbackCommand } from "./model"; export class ShowChangeBoundsToolResizeFeedbackAction implements Action { - kind = ShowChangeBoundsToolResizeFeedbackCommand.KIND; - constructor(readonly elementId?: string) { } + constructor(readonly elementId?: string, public readonly kind: string = ShowChangeBoundsToolResizeFeedbackCommand.KIND) { } } export class HideChangeBoundsToolResizeFeedbackAction implements Action { - kind = HideChangeBoundsToolResizeFeedbackCommand.KIND; - constructor() { } + constructor(public readonly kind: string = HideChangeBoundsToolResizeFeedbackCommand.KIND) { } } @injectable() export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { static readonly KIND = "showChangeBoundsToolResizeFeedback"; - constructor( - @inject(TYPES.Action) - protected action: ShowChangeBoundsToolResizeFeedbackAction - ) { - super(); - } + @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; @@ -80,12 +73,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { static readonly KIND = "hideChangeBoundsToolResizeFeedback"; - constructor( - @inject(TYPES.Action) - protected action: HideChangeBoundsToolResizeFeedbackAction - ) { - super(); - } + @inject(TYPES.Action) protected action: HideChangeBoundsToolResizeFeedbackAction; execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; @@ -105,13 +93,14 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { * (see also `tools/MoveTool`). */ export class FeedbackMoveMouseListener extends MouseListener { - hasDragged = false; - startDragPosition: Point | undefined; - elementId2startPos = new Map(); + protected hasDragged = false; + protected startDragPosition: Point | undefined; + protected elementId2startPos = new Map(); constructor(protected tool: ChangeBoundsTool) { super(); } + mouseDown(target: SModelElement, event: MouseEvent): Action[] { if (event.button === 0 && !(target instanceof SResizeHandle)) { const moveable = findParentByFeature(target, isMoveable); @@ -208,6 +197,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } return newPosition; } + protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { if (isSnap && this.tool.snapper) return this.tool.snapper.snap(position, element); else return position; @@ -237,7 +227,7 @@ export class FeedbackMoveMouseListener extends MouseListener { return result; } - decorate(vnode: VNode, element: SModelElement): VNode { + decorate(vnode: VNode, _element: SModelElement): VNode { return vnode; } } diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 3b54948..31eace5 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -43,8 +43,11 @@ import { getAbsolutePosition, toAbsolutePosition } from "../../utils/viewpoint-u import { FeedbackCommand } from "./model"; export class DrawFeedbackEdgeAction implements Action { - kind = DrawFeedbackEdgeCommand.KIND; - constructor(readonly elementTypeId: string, readonly sourceId: string, readonly edgeSchema?: SEdgeSchema) { } + constructor( + public readonly elementTypeId: string, + public readonly sourceId: string, + public readonly edgeSchema?: SEdgeSchema, + public readonly kind: string = DrawFeedbackEdgeCommand.KIND) { } } @injectable() @@ -63,8 +66,7 @@ export class DrawFeedbackEdgeCommand extends FeedbackCommand { } export class RemoveFeedbackEdgeAction implements Action { - kind = RemoveFeedbackEdgeCommand.KIND; - constructor() { } + constructor(public readonly kind: string = RemoveFeedbackEdgeCommand.KIND) { } } @injectable() @@ -79,10 +81,10 @@ export class RemoveFeedbackEdgeCommand extends FeedbackCommand { export class FeedbackEdgeEnd extends SDanglingAnchor { static readonly TYPE = 'feedback-edge-end'; - type = FeedbackEdgeEnd.TYPE; constructor(readonly sourceId: string, readonly elementTypeId: string, - public feedbackEdge: SRoutableElement | undefined = undefined) { + public feedbackEdge: SRoutableElement | undefined = undefined, + public readonly type: string = FeedbackEdgeEnd.TYPE) { super(); } } @@ -102,7 +104,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { const edge = edgeEnd.feedbackEdge; const position = getAbsolutePosition(edgeEnd, event); const endAtMousePosition = findChildrenAtPosition(target.root, position) - .find(e => isConnectable(e) && e.canConnect(edge, 'target')); + .find(element => isConnectable(element) && element.canConnect(edge, 'target')); if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, endAtMousePosition.anchorKind); diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index af06da1..b118329 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -20,9 +20,13 @@ import { addCssClasses, removeCssClasses } from "../../utils/smodel-util"; import { FeedbackCommand } from "./model"; export class ModifyCSSFeedbackAction implements Action { - kind = ModifyCssFeedbackCommand.KIND; readonly elementIds?: string[]; - constructor(elements?: SModelElement[], readonly addClasses?: string[], readonly removeClasses?: string[]) { + + constructor( + public readonly elements?: SModelElement[], + public readonly addClasses?: string[], + public readonly removeClasses?: string[], + public kind = ModifyCssFeedbackCommand.KIND) { if (elements) { this.elementIds = elements.map(elt => elt.id); } diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 617ba29..263028b 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -59,13 +59,11 @@ import { FeedbackCommand } from "./model"; */ export class ShowEdgeReconnectHandlesFeedbackAction implements Action { - kind = ShowEdgeReconnectHandlesFeedbackCommand.KIND; - constructor(readonly elementId?: string) { } + constructor(readonly elementId?: string, public readonly kind: string = ShowEdgeReconnectHandlesFeedbackCommand.KIND) { } } export class HideEdgeReconnectHandlesFeedbackAction implements Action { - kind = HideEdgeReconnectHandlesFeedbackCommand.KIND; - constructor() { } + constructor(public readonly kind: string = HideEdgeReconnectHandlesFeedbackCommand.KIND) { } } @injectable() @@ -109,8 +107,13 @@ export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { */ export class SwitchRoutingModeAction extends SwitchEditModeAction { - readonly kind = SwitchRoutingModeCommand.KIND; + constructor(public readonly elementsToActivate: string[] = [], + public readonly elementsToDeactivate: string[] = [], + public readonly kind: string = SwitchRoutingModeCommand.KIND) { + super(elementsToActivate, elementsToDeactivate); + } } + @injectable() export class SwitchRoutingModeCommand extends SwitchEditModeCommand { static KIND = "switchRoutingMode"; @@ -122,11 +125,9 @@ export class SwitchRoutingModeCommand extends SwitchEditModeCommand { */ export class DrawFeedbackEdgeSourceAction implements Action { - kind = DrawFeedbackEdgeSourceCommand.KIND; - constructor(readonly elementTypeId: string, readonly targetId: string) { } + constructor(readonly elementTypeId: string, readonly targetId: string, public readonly kind: string = DrawFeedbackEdgeSourceCommand.KIND) { } } - @injectable() export class DrawFeedbackEdgeSourceCommand extends FeedbackCommand { static readonly KIND = 'drawFeedbackEdgeSource'; @@ -183,8 +184,8 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { } export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { - hasDragged = false; - lastDragPosition: Point | undefined; + protected hasDragged = false; + protected lastDragPosition: Point | undefined; constructor(protected edgeRouterRegistry?: EdgeRouterRegistry) { super(); @@ -258,13 +259,13 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return []; } - mouseUp(target: SModelElement, event: MouseEvent): Action[] { + mouseUp(_target: SModelElement, event: MouseEvent): Action[] { this.hasDragged = false; this.lastDragPosition = undefined; return []; } - decorate(vnode: VNode, element: SModelElement): VNode { + decorate(vnode: VNode, _element: SModelElement): VNode { return vnode; } } diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index cf3ff83..6ddc040 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -61,10 +61,8 @@ export interface IFeedbackActionDispatcher { export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { protected feedbackEmitters: Map = new Map; - constructor( - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise, - @inject(TYPES.ILogger) protected logger: ILogger) { - } + @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; + @inject(TYPES.ILogger) protected logger: ILogger; registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { this.feedbackEmitters.set(feedbackEmitter, actions); diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts index 5b6080c..1fc79f9 100644 --- a/packages/client/src/features/tool-feedback/model.ts +++ b/packages/client/src/features/tool-feedback/model.ts @@ -20,6 +20,7 @@ export abstract class FeedbackCommand extends Command { readonly priority: number = 0; abstract execute(context: CommandExecutionContext): CommandReturn; + undo(context: CommandExecutionContext): CommandReturn { return context.root; } diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts index eb4c2a7..f472777 100644 --- a/packages/client/src/features/tool-palette/palette-item.ts +++ b/packages/client/src/features/tool-palette/palette-item.ts @@ -31,7 +31,7 @@ export namespace PaletteItem { export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { if (item) { const initiAction = item.actions.filter(a => isTriggerElementTypeCreationAction(a)) - .map(a => a as TriggerElementCreationAction); + .map(action => action as TriggerElementCreationAction); return initiAction.length > 0 ? initiAction[0] : undefined; } return undefined; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 0587f45..fdc41a4 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -49,6 +49,7 @@ export class EnableToolPaletteAction implements Action { @injectable() export class ToolPalette extends AbstractUIExtension implements IActionHandler, EditModeListener { + static readonly ID = "tool-palette"; @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; @inject(TYPES.IToolManager) protected readonly toolManager: IToolManager; @@ -65,26 +66,25 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, id() { return ToolPalette.ID; } containerClass() { return ToolPalette.ID; } - static readonly ID = "tool-palette"; - @postConstruct() - postConstruct() { + postConstruct(): void { this.editorContext.register(this); } - initialize() { + initialize(): boolean { if (!this.paletteItems) { return false; } return super.initialize(); } - protected initializeContents(containerElement: HTMLElement): void { + protected initializeContents(_containerElement: HTMLElement): void { this.createHeader(); this.createBody(); + this.lastActivebutton = this.defaultToolsButton; } - protected onBeforeShow(containerElement: HTMLElement, root: Readonly) { + protected onBeforeShow(_containerElement: HTMLElement, root: Readonly) { this.modelRootId = root.id; this.containerElement.style.maxHeight = PALETTE_HEIGHT; } @@ -97,7 +97,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, if (baseDiv) { const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); const minimizeIcon = createIcon(["fas", CHEVRON_DOWN]); - minimizeIcon.onclick = (ev) => { + minimizeIcon.onclick = _event => { if (this.containerElement.style.maxHeight !== "0px") { this.containerElement.style.maxHeight = "0px"; } else { @@ -144,14 +144,13 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.addMinimizePaletteButton(); const headerCompartment = document.createElement("div"); headerCompartment.classList.add("palette-header"); + headerCompartment.append(this.createHeaderTitle()); + headerCompartment.appendChild(this.createHeaderTools()); + headerCompartment.appendChild(this.searchField = this.createHeaderSearchField()); + this.containerElement.appendChild(headerCompartment); + } - // Title header - const header = document.createElement("div"); - header.classList.add("header-icon"); - header.appendChild(createIcon(["fa", "fa-palette"])); - header.insertAdjacentText("beforeend", "Palette"); - headerCompartment.append(header); - // Header Tools Compartment + private createHeaderTools(): HTMLElement { const headerTools = document.createElement("div"); headerTools.classList.add("header-tools"); @@ -160,7 +159,6 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.defaultToolsButton.id = "btn_default_tools"; this.defaultToolsButton.onclick = this.onClickStaticToolButton(this.defaultToolsButton); headerTools.appendChild(this.defaultToolsButton); - this.lastActivebutton = this.defaultToolsButton; // Create button for MouseDeleteTool const deleteToolButton = createIcon(["fas", "fa-eraser", "fa-xs"]); @@ -169,15 +167,22 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, // Create button for ValidationTool const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); - validateActionButton.onclick = (ev: MouseEvent) => { + validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; this.actionDispatcher.dispatch(new RequestMarkersAction(modelIds)); }; headerTools.appendChild(validateActionButton); // Create button for Search + const searchIcon = this.createSearchButton(); + + headerTools.appendChild(searchIcon); + return headerTools; + } + + protected createSearchButton() { const searchIcon = createIcon(["fas", SEARCH_ICON, "state-icon", "fa-xs"]); - searchIcon.onclick = (ev) => { + searchIcon.onclick = (_ev) => { const searchField = document.getElementById(this.containerElement.id + "_search_field"); if (searchField) { if (searchField.style.display === "inline") { @@ -189,19 +194,27 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } }; searchIcon.classList.add("search-icon"); - this.searchField = document.createElement("input"); - this.searchField.classList.add("search-input"); - this.searchField.id = this.containerElement.id + "_search_field"; - this.searchField.type = "text"; - this.searchField.placeholder = " Search..."; - this.searchField.style.display = "none"; - this.searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); - this.searchField.onkeydown = (ev) => this.clearOnEscape(ev); + return searchIcon; + } - headerTools.appendChild(searchIcon); - headerCompartment.appendChild(headerTools); - headerCompartment.appendChild(this.searchField); - this.containerElement.appendChild(headerCompartment); + protected createHeaderSearchField(): HTMLInputElement { + const searchField = document.createElement("input"); + searchField.classList.add("search-input"); + searchField.id = this.containerElement.id + "_search_field"; + searchField.type = "text"; + searchField.placeholder = " Search..."; + searchField.style.display = "none"; + searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); + searchField.onkeydown = (ev) => this.clearOnEscape(ev); + return searchField; + } + + protected createHeaderTitle(): HTMLElement { + const header = document.createElement("div"); + header.classList.add("header-icon"); + header.appendChild(createIcon(["fa", "fa-palette"])); + header.insertAdjacentText("beforeend", "Palette"); + return header; } protected createToolButton(item: PaletteItem, index: number): HTMLElement { @@ -215,7 +228,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } protected onClickCreateToolButton(button: HTMLElement, item: PaletteItem) { - return (ev: MouseEvent) => { + return (_ev: MouseEvent) => { if (!this.editorContext.isReadonly) { this.actionDispatcher.dispatchAll(item.actions); this.changeActiveButton(button); @@ -225,7 +238,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } protected onClickStaticToolButton(button: HTMLElement, toolId?: string) { - return (ev: MouseEvent) => { + return (_ev: MouseEvent) => { if (!this.editorContext.isReadonly) { const action = toolId ? new EnableToolsAction([toolId]) : new EnableDefaultToolsAction(); this.actionDispatcher.dispatch(action); @@ -265,7 +278,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - editModeChanged(oldValue: string, newValue: string) { + editModeChanged(_oldValue: string, _newValue: string) { this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); } @@ -341,7 +354,7 @@ export function createToolGroup(item: PaletteItem): HTMLElement { header.appendChild(createIcon(["fas", item.icon])); } header.insertAdjacentText('beforeend', item.label); - header.ondblclick = (ev) => { + header.ondblclick = (_ev) => { const css = "collapsed"; changeCSSClass(group, css); Array.from(group.children).forEach(child => changeCSSClass(child, css)); diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 31611d3..981c1f7 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -82,7 +82,6 @@ import { BaseGLSPTool } from "./base-glsp-tool"; @injectable() export class ChangeBoundsTool extends BaseGLSPTool { static ID = "glsp.change-bounds-tool"; - readonly id = ChangeBoundsTool.ID; @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @@ -91,6 +90,10 @@ export class ChangeBoundsTool extends BaseGLSPTool { protected feedbackMoveMouseListener: MouseListener; protected changeBoundsListener: MouseListener & SelectionListener; + get id(): string { + return ChangeBoundsTool.ID; + } + enable() { // install feedback move mouse listener for client-side move updates this.feedbackMoveMouseListener = this.createMoveMouseListener(); diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index c296106..3e82e8e 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -40,12 +40,15 @@ import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-disp @injectable() export class DelKeyDeleteTool implements GLSPTool { static ID = "glsp.delete-keyboard"; - readonly id = DelKeyDeleteTool.ID; isEditTool = true; protected deleteKeyListener: DeleteKeyListener = new DeleteKeyListener(); - constructor(@inject(KeyTool) protected readonly keytool: KeyTool) { } + @inject(KeyTool) protected readonly keytool: KeyTool; + + get id(): string { + return DelKeyDeleteTool.ID; + } enable() { this.keytool.register(this.deleteKeyListener); @@ -75,14 +78,18 @@ export class DeleteKeyListener extends KeyListener { */ @injectable() export class MouseDeleteTool implements GLSPTool { - static ID = "glsp.delete-mouse"; - readonly id = MouseDeleteTool.ID; - protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); isEditTool = true; - constructor(@inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool, - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher) { } + + protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); + + @inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher; + + get id(): string { + return MouseDeleteTool.ID; + } enable() { this.mouseTool.register(this.deleteToolMouseListener); diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 8eae539..0354c07 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -47,7 +47,6 @@ import { BaseGLSPTool } from "./base-glsp-tool"; @injectable() export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { static ID = "tool_create_edge"; - readonly id = EdgeCreationTool.ID; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @@ -55,6 +54,10 @@ export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { protected creationToolMouseListener: EdgeCreationToolMouseListener; protected feedbackEndMovingMouseListener: FeedbackEdgeEndMovingMouseListener; + get id(): string { + return EdgeCreationTool.ID; + } + enable() { if (this.triggerAction === undefined) { throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); @@ -102,7 +105,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.tool.dispatchFeedback([new RemoveFeedbackEdgeAction()]); } - nonDraggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + nonDraggingMouseUp(_element: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.button === 0) { if (!this.isSourceSelected()) { diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 5cf54a1..b4adc14 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -66,7 +66,10 @@ export class EdgeEditTool extends BaseGLSPTool { protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; protected feedbackMovingListener: FeedbackEdgeRouteMovingMouseListener; protected edgeEditListener: EdgeEditListener; - readonly id = EdgeEditTool.ID; + + get id(): string { + return EdgeEditTool.ID; + } enable(): void { this.edgeEditListener = new EdgeEditListener(this); diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index a062707..e583675 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -41,13 +41,16 @@ import { BaseGLSPTool } from "./base-glsp-tool"; @injectable() export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { static ID = "tool_create_node"; - readonly id = NodeCreationTool.ID; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; protected creationToolMouseListener: NodeCreationToolMouseListener; protected triggerAction: TriggerNodeCreationAction; + get id(): string { + return NodeCreationTool.ID; + } + enable() { if (this.triggerAction === undefined) { throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); diff --git a/packages/client/src/features/undo-redo/model.ts b/packages/client/src/features/undo-redo/model.ts index 372dc87..839c597 100644 --- a/packages/client/src/features/undo-redo/model.ts +++ b/packages/client/src/features/undo-redo/model.ts @@ -17,10 +17,10 @@ import { Action } from "sprotty"; export class GlspUndoAction implements Action { static readonly KIND = 'glspUndo'; - readonly kind = GlspUndoAction.KIND; + constructor(public readonly kind = GlspUndoAction.KIND) { } } export class GlspRedoAction implements Action { static readonly KIND = 'glspRedo'; - readonly kind = GlspRedoAction.KIND; + constructor(public readonly kind = GlspRedoAction.KIND) { } } diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index c7783eb..08a7274 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -45,14 +45,16 @@ import { SelectCommand, SelectionService } from "../select/selection-service"; export class NavigateToMarkerAction implements Action { static readonly KIND = 'navigateToMarker'; - readonly kind = NavigateToMarkerAction.KIND; - constructor(readonly direction: 'next' | 'previous' = 'next', - readonly selectedElementIds?: string[], - readonly severities: SIssueSeverity[] = MarkerNavigator.ALL_SEVERITIES) { } + + constructor( + public readonly direction: 'next' | 'previous' = 'next', + public readonly selectedElementIds?: string[], + public readonly severities: SIssueSeverity[] = MarkerNavigator.ALL_SEVERITIES, + public kind = NavigateToMarkerAction.KIND) { } } export class SModelElementComparator { - compare(one: SModelElement, other: SModelElement): number { + compare(_one: SModelElement, _other: SModelElement): number { return 0; } } @@ -209,6 +211,7 @@ export class NavigateToMarkerCommand extends Command { @injectable() export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemProvider { @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + getItems(root: Readonly, lastMousePosition?: Point): Promise { const selectedElementIds = Array.from(this.selectionService.getSelectedElementIDs()); const hasMarkers = collectIssueMarkers(root).length > 0; @@ -234,7 +237,7 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP @injectable() export class MarkerNavigatorKeyListener extends KeyListener { - keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Period', 'ctrl')) { return [new NavigateToMarkerAction('next')]; } else if (matchesKeystroke(event, 'Comma', 'ctrl')) { diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index ee142bc..d337251 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -77,8 +77,7 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { * Action to set markers for a model */ export class SetMarkersAction implements MarkersAction { - readonly kind = SetMarkersCommand.KIND; - constructor(public readonly markers: Marker[]) { } + constructor(public readonly markers: Marker[], public readonly kind = SetMarkersCommand.KIND) { } } export function isSetMarkersAction(action: Action): action is SetMarkersAction { @@ -150,11 +149,8 @@ export class SetMarkersCommand extends Command { * Action to retrieve markers for a model */ export class RequestMarkersAction implements Action { - static readonly KIND = 'requestMarkers'; - readonly kind = RequestMarkersAction.KIND; - - constructor(public readonly elementsIDs: string[] = []) { } + constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) { } } /** @@ -169,8 +165,7 @@ export interface MarkersAction extends Action { */ @injectable() export class ApplyMarkersAction implements MarkersAction { - readonly kind = ApplyMarkersCommand.KIND; - constructor(public readonly markers: Marker[]) { } + constructor(public readonly markers: Marker[], public readonly kind = ApplyMarkersCommand.KIND) { } } /** @@ -229,8 +224,7 @@ function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker */ @injectable() export class ClearMarkersAction implements MarkersAction { - readonly kind = ClearMarkersCommand.KIND; - constructor(public readonly markers: Marker[]) { } + constructor(public readonly markers: Marker[], public readonly kind = ClearMarkersCommand.KIND) { } } /** diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 0e905fa..3f499ea 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -35,6 +35,7 @@ import { RequestTypeHintsAction } from "../features/hints/request-type-hints-act import { isServerMessageAction, ServerMessageAction } from "./glsp-server-status"; const receivedFromServerProperty = '__receivedFromServer'; + @injectable() export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { protected _sourceUri: string; @@ -72,7 +73,6 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { return super.handle(action); } - handleLocally(action: Action): boolean { if (isServerMessageAction(action)) { return this.handleServerMessageAction(action); @@ -82,12 +82,13 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { } return super.handleLocally(action); } + protected handleServerMessageAction(action: ServerMessageAction): boolean { this.logger.log('GLSPDiagramServer', `[${action.severity}] -${action.message}`); return false; } - protected handleComputedBounds(action: ComputedBoundsAction): boolean { + protected handleComputedBounds(_action: ComputedBoundsAction): boolean { return true; } diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index eef49a2..6a4c638 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -23,6 +23,7 @@ export interface InitializeParameters<> { applicationId: string; options?: any } + export class ApplicationIdProvider { private static _applicationId?: string; static get(): string { @@ -32,6 +33,7 @@ export class ApplicationIdProvider { return ApplicationIdProvider._applicationId; } } + export type ActionMessageHandler = (message: ActionMessage) => void; export enum ClientState { From f8fd0250532b29777c566c0539cca4af03bfd76a Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 6 Oct 2020 18:15:38 +0200 Subject: [PATCH 006/566] #112, #131: Minor adaptations for consistency - Rename 'ClearMarkersAction' to 'DeleteMarkersAction' for consistency - Rename 'GlspRedoAction' to 'RedoOperation' for consistency - Rename 'GlspUndoAction' to 'UndoOperation' for consistency - Provide 'LayoutOperation' based on 'LayoutAction' for consistency - Provide 'ApplyLabelEditOperation' based on action for consistency - Rename 'GLSPServerStatusAction' to 'ServerStatusAction' - Rename 'connectionElementId' to 'edgeElementId' for consistency - Remove 'ExecuteServerCommandAction' and corresponding module - Remove 'simulate-command' from Workflow example - Add comments to some actions Part of https://github.com/eclipse-glsp/glsp/issues/112 Part of https://github.com/eclipse-glsp/glsp/issues/131 Signed-off-by: Martin Fleck --- examples/workflow-sprotty/src/di.config.ts | 3 +- examples/workflow-sprotty/src/model.ts | 9 +-- examples/workflow-standalone/app/example1.wf | 8 -- .../base/actions/edit-validation-actions.ts | 26 ++++++- packages/client/src/base/command-stack.ts | 6 +- packages/client/src/base/editor-context.ts | 16 ++++ .../client/src/base/operations/operation.ts | 22 +++++- .../features/copy-paste/copy-paste-actions.ts | 11 +-- .../features/copy-paste/copy-paste-handler.ts | 6 +- .../client/src/features/execute/di.config.ts | 25 ------- .../src/features/execute/execute-command.ts | 38 ---------- packages/client/src/features/execute/model.ts | 26 ------- .../src/features/tools/edge-creation-tool.ts | 2 +- .../client/src/features/undo-redo/model.ts | 10 +-- .../src/features/validation/di.config.ts | 4 +- .../src/features/validation/validate.ts | 20 ++--- packages/client/src/index.ts | 5 +- .../src/model-source/glsp-server-status.ts | 6 +- packages/client/src/utils/marker.ts | 12 +++ packages/protocol/src/glsp-client.ts | 73 +++++++++++++++---- 20 files changed, 165 insertions(+), 163 deletions(-) delete mode 100644 packages/client/src/features/execute/di.config.ts delete mode 100644 packages/client/src/features/execute/execute-command.ts delete mode 100644 packages/client/src/features/execute/model.ts diff --git a/examples/workflow-sprotty/src/di.config.ts b/examples/workflow-sprotty/src/di.config.ts index 59b7027..a11aa60 100644 --- a/examples/workflow-sprotty/src/di.config.ts +++ b/examples/workflow-sprotty/src/di.config.ts @@ -28,7 +28,6 @@ import { DiamondNodeView, edgeLayoutModule, editLabelFeature, - executeCommandModule, ExpandButtonView, expandModule, exportModule, @@ -117,7 +116,7 @@ export default function createContainer(widgetId: string): Container { container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, toolsModule, glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, - workflowDiagramModule, executeCommandModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, + workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, layoutCommandsModule, directTaskEditor, navigationModule, markerNavigatorModule); diff --git a/examples/workflow-sprotty/src/model.ts b/examples/workflow-sprotty/src/model.ts index edd8c9e..26ece52 100644 --- a/examples/workflow-sprotty/src/model.ts +++ b/examples/workflow-sprotty/src/model.ts @@ -14,13 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - Bounds, boundsFeature, - CommandExecutor, connectableFeature, deletableFeature, DiamondNode, - executeCommandFeature, fadeFeature, hoverFeedbackFeature, isEditableLabel, @@ -80,13 +77,11 @@ export class ActivityNode extends DiamondNode { } -export class Icon extends SShapeElement implements LayoutContainer, CommandExecutor { - static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature, executeCommandFeature]; +export class Icon extends SShapeElement implements LayoutContainer { + static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature]; - commandId: string; layout: string; layoutOptions?: { [key: string]: string | number | boolean; }; - bounds: Bounds; size = { width: 32, height: 32 diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index d9730ea..51d2937 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -10,7 +10,6 @@ "id": "task0_header", "children": [ { - "commandId": "simulate-command", "id": "task0_icon", "children": [ { @@ -96,7 +95,6 @@ "id": "task1_header", "children": [ { - "commandId": "simulate-command", "id": "task1_icon", "children": [ { @@ -182,7 +180,6 @@ "id": "task2_header", "children": [ { - "commandId": "simulate-command", "id": "task2_icon", "children": [ { @@ -268,7 +265,6 @@ "id": "task3_header", "children": [ { - "commandId": "simulate-command", "id": "task3_icon", "children": [ { @@ -354,7 +350,6 @@ "id": "task4_header", "children": [ { - "commandId": "simulate-command", "id": "task4_icon", "children": [ { @@ -440,7 +435,6 @@ "id": "task6_header", "children": [ { - "commandId": "simulate-command", "id": "task6_icon", "children": [ { @@ -526,7 +520,6 @@ "id": "task8_header", "children": [ { - "commandId": "simulate-command", "id": "task8_icon", "children": [ { @@ -612,7 +605,6 @@ "id": "task9_header", "children": [ { - "commandId": "simulate-command", "id": "task9_icon", "children": [ { diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts index a5ff4cb..8101a45 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -42,14 +42,36 @@ export function isSetEditValidationResultAction(action: Action): action is SetEd } export interface ValidationStatus { + /** + * The severity of the validation returned by the server. + */ readonly severity: ValidationStatus.Severity; - readonly message: string; - readonly error: ResponseError; + + /** + * The validation status message which may be rendered in the view. + */ + readonly message?: string; + + /** + * A potential error that encodes more details. + */ + readonly error?: ResponseError; } export interface ResponseError { + /** + * Code identifying the error kind. + */ readonly code: number; + + /** + * Error message. + */ readonly message: string; + + /** + * Additional custom data, e.g., a serialized stacktrace. + */ readonly data: Object; } diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 1721e09..98ed2e0 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -16,7 +16,7 @@ import { inject, injectable } from "inversify"; import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from "sprotty"; -import { GlspRedoAction, GlspUndoAction } from "../features/undo-redo/model"; +import { RedoOperation, UndoOperation } from "../features/undo-redo/model"; @injectable() export class GLSPCommandStack extends CommandStack { @@ -24,12 +24,12 @@ export class GLSPCommandStack extends CommandStack { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; undo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new GlspUndoAction())); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new UndoOperation())); return this.thenUpdate(); } redo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new GlspRedoAction())); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new RedoOperation())); return this.thenUpdate(); } } diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context.ts index 20ad822..35ea4c3 100644 --- a/packages/client/src/base/editor-context.ts +++ b/packages/client/src/base/editor-context.ts @@ -23,9 +23,25 @@ import { Args } from "./args"; import { isSourceUriAware } from "./source-uri-aware"; import { GLSP_TYPES } from "./types"; +/** + * The `EditorContext` may be used to represent the current state of the editor for particular actions. + * It encompasses the last recorded mouse position, the list of selected elements, and may contain + * custom arguments to encode additional state information. + */ export interface EditorContext { + /** + * The list of selected element identifiers. + */ readonly selectedElementIds: string[]; + + /** + * The last recorded mouse position. + */ readonly lastMousePosition?: Point; + + /** + * Custom arguments. + */ readonly args?: Args; } diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index 4fabb73..a3f9b8f 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ElementAndBounds, isAction, Point } from "sprotty"; +import { Action, ApplyLabelEditAction, ElementAndBounds, isAction, LayoutAction, Point } from "sprotty"; import { Args } from "../args"; @@ -50,8 +50,8 @@ export class CreateEdgeOperation implements CreateOperation { static readonly KIND = "createEdge"; constructor(public readonly elementTypeId: string, - public sourceElementId?: string, - public targetElementId?: string, + public sourceElementId: string, + public targetElementId: string, public args?: Args, public readonly kind: string = CreateEdgeOperation.KIND) { } } @@ -80,7 +80,7 @@ export class ChangeContainerOperation implements Operation { export class ReconnectEdgeOperation implements Operation { static readonly KIND = "reconnectEdge"; - constructor(public readonly connectionElementId: string, + constructor(public readonly edgeElementId: string, public readonly sourceElementId: string, public readonly targetElementId: string, public readonly kind: string = ReconnectEdgeOperation.KIND) { } @@ -96,8 +96,18 @@ export class CompoundOperation implements Operation { constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) { } } +/** + * The `ElementAndRoutingPoints` type is used to associate an edge with specific routing points. + */ export interface ElementAndRoutingPoints { + /** + * The identifier of an element. + */ elementId: string + + /** + * The new list of routing points. + */ newRoutingPoints?: Point[]; } @@ -132,3 +142,7 @@ export function isTriggerNodeCreationAction(object?: any): object is TriggerNode export function isTriggerEdgeCreationAction(object?: any): object is TriggerEdgeCreationAction { return isTriggerElementTypeCreationAction(object) && object.kind === TriggerEdgeCreationAction.KIND; } + +export class LayoutOperation extends LayoutAction implements Operation { } + +export class ApplyLabelEditOperation extends ApplyLabelEditAction implements Operation { } diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/client/src/features/copy-paste/copy-paste-actions.ts index 0cff290..1343c86 100644 --- a/packages/client/src/features/copy-paste/copy-paste-actions.ts +++ b/packages/client/src/features/copy-paste/copy-paste-actions.ts @@ -13,25 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, generateRequestId, RequestAction, ResponseAction } from "sprotty"; +import { generateRequestId, RequestAction, ResponseAction } from "sprotty"; +import { Operation } from "src/base/operations/operation"; import { EditorContext } from "../../base/editor-context"; -export class CutOperationAction implements Action { +export class CutOperation implements Operation { static readonly KIND = "cut"; constructor( public readonly editorContext: EditorContext, - public readonly kind: string = CutOperationAction.KIND) { } + public readonly kind: string = CutOperation.KIND) { } } -export class PasteOperationAction implements Action { +export class PasteOperation implements Operation { static readonly KIND = "paste"; constructor( public readonly clipboardData: ClipboardData, public readonly editorContext: EditorContext, - public readonly kind: string = PasteOperationAction.KIND) { } + public readonly kind: string = PasteOperation.KIND) { } } export class RequestClipboardDataAction implements RequestAction { diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 3733714..66306ec 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -20,7 +20,7 @@ import { v4 as uuid } from "uuid"; import { GLSPActionDispatcher } from "../../base/action-dispatcher"; import { EditorContextService } from "../../base/editor-context"; import { GLSP_TYPES } from "../../base/types"; -import { ClipboardData, CutOperationAction, PasteOperationAction, RequestClipboardDataAction } from "./copy-paste-actions"; +import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction } from "./copy-paste-actions"; export interface ICopyPasteHandler { handleCopy(event: ClipboardEvent): void; @@ -118,7 +118,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { handleCut(event: ClipboardEvent): void { if (event.clipboardData && this.shouldCopy(event)) { this.handleCopy(event); - this.actionDispatcher.dispatch(new CutOperationAction(this.editorContext.get())); + this.actionDispatcher.dispatch(new CutOperation(this.editorContext.get())); event.preventDefault(); } } @@ -128,7 +128,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { const clipboardId = getClipboardIdFromDataTransfer(event.clipboardData); const clipboardData = this.clipboadService.get(clipboardId); if (clipboardData) { - this.actionDispatcher.dispatch(new PasteOperationAction(clipboardData, this.editorContext.get())); + this.actionDispatcher.dispatch(new PasteOperation(clipboardData, this.editorContext.get())); } event.preventDefault(); } diff --git a/packages/client/src/features/execute/di.config.ts b/packages/client/src/features/execute/di.config.ts deleted file mode 100644 index 2692e5a..0000000 --- a/packages/client/src/features/execute/di.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { TYPES } from "sprotty"; - -import { ExecuteCommandMouseListener } from "./execute-command"; - -const executeCommandModule = new ContainerModule(bind => { - bind(TYPES.MouseListener).to(ExecuteCommandMouseListener); -}); - -export default executeCommandModule; diff --git a/packages/client/src/features/execute/execute-command.ts b/packages/client/src/features/execute/execute-command.ts deleted file mode 100644 index a41597b..0000000 --- a/packages/client/src/features/execute/execute-command.ts +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Action, findParentByFeature, MouseListener, SModelElement } from "sprotty"; - -import { isCommandExecutor } from "./model"; - -export class ExecuteServerCommandAction implements Action { - static readonly KIND = "executeServerCommand"; - constructor( - public readonly commandId: String, - public readonly options?: { [key: string]: string }, - public readonly kind: string = ExecuteServerCommandAction.KIND) { } -} - -export class ExecuteCommandMouseListener extends MouseListener { - doubleClick(target: SModelElement, _event: WheelEvent): (Action | Promise)[] { - const result: Action[] = []; - const commandExecutorTarget = findParentByFeature(target, isCommandExecutor); - if (commandExecutorTarget) { - result.push(new ExecuteServerCommandAction(commandExecutorTarget.commandId, { invokerId: commandExecutorTarget.id })); - } - - return result; - } -} diff --git a/packages/client/src/features/execute/model.ts b/packages/client/src/features/execute/model.ts deleted file mode 100644 index cb114b9..0000000 --- a/packages/client/src/features/execute/model.ts +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { SModelElement, SModelExtension } from "sprotty"; - -export const executeCommandFeature = Symbol('executeFeature'); - -export interface CommandExecutor extends SModelExtension { - commandId: string -} - -export function isCommandExecutor(element: SModelElement): element is SModelElement & CommandExecutor { - return element.hasFeature(executeCommandFeature); -} diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 0354c07..0b5fd51 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -118,7 +118,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.target = this.currentTarget.id; } } - if (this.isSourceSelected() && this.isTargetSelected()) { + if (this.source && this.target) { result.push(new CreateEdgeOperation(this.triggerAction.elementTypeId, this.source, this.target, this.triggerAction.args)); if (!isCtrlOrCmd(event)) { result.push(new EnableDefaultToolsAction()); diff --git a/packages/client/src/features/undo-redo/model.ts b/packages/client/src/features/undo-redo/model.ts index 839c597..ccff081 100644 --- a/packages/client/src/features/undo-redo/model.ts +++ b/packages/client/src/features/undo-redo/model.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from "sprotty"; +import { Operation } from "../../base/operations/operation"; -export class GlspUndoAction implements Action { +export class UndoOperation implements Operation { static readonly KIND = 'glspUndo'; - constructor(public readonly kind = GlspUndoAction.KIND) { } + constructor(public readonly kind = UndoOperation.KIND) { } } -export class GlspRedoAction implements Action { +export class RedoOperation implements Operation { static readonly KIND = 'glspRedo'; - constructor(public readonly kind = GlspRedoAction.KIND) { } + constructor(public readonly kind = RedoOperation.KIND) { } } diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index 3e97473..be59f51 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -25,12 +25,12 @@ import { NavigateToMarkerCommand, SModelElementComparator } from "./marker-navigator"; -import { ApplyMarkersCommand, ClearMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from "./validate"; +import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from "./validate"; export const validationModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, SetMarkersCommand); configureCommand({ bind, isBound }, ApplyMarkersCommand); - configureCommand({ bind, isBound }, ClearMarkersCommand); + configureCommand({ bind, isBound }, DeleteMarkersCommand); bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); }); diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index d337251..dede197 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -63,8 +63,8 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { // Clear existing markers if (this.registeredAction !== undefined) { - const clearMarkersAction = new ClearMarkersAction(this.registeredAction.markers); - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(clearMarkersAction)); + const deleteMarkersAction = new DeleteMarkersAction(this.registeredAction.markers); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(deleteMarkersAction)); } // Register new action responsible for applying markers and re-applying them when the model is updated @@ -103,7 +103,7 @@ export abstract class ExternalMarkerManager { removeMarkers(markers: Marker[]) { if (this.actionDispatcher) { - this.actionDispatcher.dispatch(new ClearMarkersAction(markers)); + this.actionDispatcher.dispatch(new DeleteMarkersAction(markers)); } } @@ -149,7 +149,7 @@ export class SetMarkersCommand extends Command { * Action to retrieve markers for a model */ export class RequestMarkersAction implements Action { - static readonly KIND = 'requestMarkers'; + static readonly KIND = "requestMarkers"; constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) { } } @@ -223,18 +223,18 @@ function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker * Action for clearing makers of a model */ @injectable() -export class ClearMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = ClearMarkersCommand.KIND) { } +export class DeleteMarkersAction implements MarkersAction { + constructor(public readonly markers: Marker[], public readonly kind = DeleteMarkersCommand.KIND) { } } /** - * Command for handling `ClearMarkersAction` + * Command for handling `DeleteMarkersAction` */ @injectable() -export class ClearMarkersCommand extends Command { - static KIND = "clearMarkers"; +export class DeleteMarkersCommand extends Command { + static KIND = "deleteMarkers"; - constructor(@inject(TYPES.Action) protected action: ClearMarkersAction) { + constructor(@inject(TYPES.Action) protected action: DeleteMarkersAction) { super(); } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7b6d156..6aac190 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -19,7 +19,6 @@ import glspContextMenuModule from "./features/context-menu/di.config"; import { copyPasteContextMenuModule, glspServerCopyPasteModule } from "./features/copy-paste/di.config"; import glspDecorationModule from "./features/decoration/di.config"; import glspEditLabelModule from "./features/edit-label/di.config"; -import executeCommandModule from "./features/execute/di.config"; import modelHintsModule from "./features/hints/di.config"; import glspHoverModule from "./features/hover/di.config"; import layoutCommandsModule from "./features/layout/di.config"; @@ -61,8 +60,6 @@ export * from './features/decoration/decoration-placer'; export * from './features/decoration/view'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; -export * from './features/execute/execute-command'; -export * from './features/execute/model'; export * from './features/hints/model'; export * from './features/hints/request-type-hints-action'; export * from './features/hints/type-hints'; @@ -101,7 +98,7 @@ export * from './utils/marker'; export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export { - validationModule, saveModule, executeCommandModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, + validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule }; diff --git a/packages/client/src/model-source/glsp-server-status.ts b/packages/client/src/model-source/glsp-server-status.ts index 9bc5db6..4967971 100644 --- a/packages/client/src/model-source/glsp-server-status.ts +++ b/packages/client/src/model-source/glsp-server-status.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ServerStatusAction } from "sprotty"; +import { Action, ServerStatusAction as SprottyServerStatusAction } from "sprotty"; -export class GLSPServerStatusAction extends ServerStatusAction { +export class GLSPServerStatusAction extends SprottyServerStatusAction { timeout: number = -1; } -export function isGLSPServerStatusAction(serverStatusAction: ServerStatusAction): serverStatusAction is GLSPServerStatusAction { +export function isGLSPServerStatusAction(serverStatusAction: SprottyServerStatusAction): serverStatusAction is GLSPServerStatusAction { return (serverStatusAction).timeout !== undefined; } diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts index fcb4899..6e7bfa4 100644 --- a/packages/client/src/utils/marker.ts +++ b/packages/client/src/utils/marker.ts @@ -44,8 +44,20 @@ export function collectIssueMarkers(root: SParentElement): SIssueMarker[] { } export interface Marker { + /** + * Short label describing this marker message, e.g., short validation message + */ readonly label: string; + /** + * Full description of this marker, e.g., full validation message + */ readonly description: string; + /** + * Id of the model element this marker refers to + */ readonly elementId: string; + /** + * Marker kind, e.g., info, warning, error or custom kind + */ readonly kind: string; } diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index 6a4c638..fdcd3ee 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -18,9 +18,13 @@ import * as uuid from "uuid"; export interface InitializeParameters<> { /** - * Unique identifier for the current client application - */ + * Unique identifier for the current client application. + */ applicationId: string; + + /** + * Options that can include application-specific parameters. + */ options?: any } @@ -37,49 +41,88 @@ export class ApplicationIdProvider { export type ActionMessageHandler = (message: ActionMessage) => void; export enum ClientState { + /** + * The client has been created. + */ Initial, + /** + * `Start` has been called on the client and the start process is still on-going. + */ Starting, + /** + * The client failed to complete the start process. + */ StartFailed, + /** + * The client was successfully started and is now running. + */ Running, + /** + * `Stop` has been called on the client and the stop process is still on-going. + */ Stopping, + /** + * The client stopped and disposed the server connection. Thus, action messages can no longer be sent. + */ Stopped, + /** + * An error was encountered while connecting to the server. No action messages can be sent. + */ ServerError } export interface GLSPClient { + /** + * Unique client Id. + */ readonly id: string; + + /** + * Client name. + */ readonly name: string; + + /** + * Current client state. + */ currentState(): ClientState; + /** - * Initialize the client and the server connection. - * + * Initializes the client and the server connection. During the start procedure the client is in the `Starting` state and will transition to either `Running` or `StartFailed`. */ start(): Promise; + /** - * Send an initalize request to ther server. The server needs to be initialized - * in order to accept and process action messages + * Send an `initialize` request to the server. The server needs to be initialized in order to accept and process action messages. + * * @param params Initialize parameter - * @returns true if the initialization was successfull + * @returns true if the initialization was successful */ initializeServer(params: InitializeParameters): Promise; + /** - * Send a shutdown notification to the server + * Send a `shutdown` notification to the server. */ shutdownServer(): void + /** - * Stop the client and cleanup/dispose resources + * Stops the client and disposes any resources. During the stop procedure the client is in the `Stopping` state and will transition to either `Stopped` or `ServerError`. */ stop(): Promise; + /** - * Set a handler/listener for action messages received from the server - * @param handler The action message handler - */ - onActionMessage(handler: ActionMessageHandler): void; - /** - * Send an action message to the server + * Send an action message to the server. + * * @param message The message */ sendActionMessage(message: ActionMessage): void; + + /** + * Sets a handler/listener for action messages received from the server. + * + * @param handler The action message handler + */ + onActionMessage(handler: ActionMessageHandler): void; } export namespace GLSPClient { From fb295602e6e9c2412ce605a074023f24d33407ef Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 21 Oct 2020 12:53:58 +0200 Subject: [PATCH 007/566] Add tooltips in palette and fix mouse delete tool Fixes https://github.com/eclipse-glsp/glsp/issues/125 Fixes https://github.com/eclipse-glsp/glsp/issues/84 Signed-off-by: Philip Langer --- packages/client/css/glsp-sprotty.css | 4 ++ .../src/features/tool-palette/tool-palette.ts | 60 ++++++++++++++----- .../client/src/features/tools/delete-tool.ts | 6 +- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index bfc4c3e..db3ca6e 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -96,3 +96,7 @@ .resize-mode { cursor: none; } + +.element-deletion-mode { + cursor: pointer; +} diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index fdc41a4..50cfd9f 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -97,12 +97,14 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, if (baseDiv) { const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); const minimizeIcon = createIcon(["fas", CHEVRON_DOWN]); + this.updateMinimizePaletteButtonTooltip(minPaletteDiv); minimizeIcon.onclick = _event => { - if (this.containerElement.style.maxHeight !== "0px") { + if (this.isPaletteMaximized()) { this.containerElement.style.maxHeight = "0px"; } else { this.containerElement.style.maxHeight = PALETTE_HEIGHT; } + this.updateMinimizePaletteButtonTooltip(minPaletteDiv); changeCSSClass(minimizeIcon, PALETTE_ICON); changeCSSClass(minimizeIcon, "fa"); changeCSSClass(minimizeIcon, "fas"); @@ -112,6 +114,18 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } + protected updateMinimizePaletteButtonTooltip(button: HTMLDivElement) { + if (this.isPaletteMaximized()) { + button.title = 'Minimize palette'; + } else { + button.title = 'Maximize palette'; + } + } + + protected isPaletteMaximized() { + return this.containerElement && this.containerElement.style.maxHeight !== "0px"; + } + protected createBody(): void { const bodyDiv = document.createElement("div"); bodyDiv.classList.add("palette-body"); @@ -154,32 +168,47 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const headerTools = document.createElement("div"); headerTools.classList.add("header-tools"); - // Create button for DefaultTools - this.defaultToolsButton = createIcon(["fas", "fa-mouse-pointer", "fa-xs", "clicked"]); - this.defaultToolsButton.id = "btn_default_tools"; - this.defaultToolsButton.onclick = this.onClickStaticToolButton(this.defaultToolsButton); + this.defaultToolsButton = this.createDefaultToolButton(); headerTools.appendChild(this.defaultToolsButton); - // Create button for MouseDeleteTool - const deleteToolButton = createIcon(["fas", "fa-eraser", "fa-xs"]); - deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); + const deleteToolButton = this.createMouseDeleteToolButton(); headerTools.appendChild(deleteToolButton); - // Create button for ValidationTool - const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); - validateActionButton.onclick = _event => { - const modelIds: string[] = [this.modelRootId]; - this.actionDispatcher.dispatch(new RequestMarkersAction(modelIds)); - }; + const validateActionButton = this.createValidateButton(); headerTools.appendChild(validateActionButton); // Create button for Search const searchIcon = this.createSearchButton(); - headerTools.appendChild(searchIcon); + return headerTools; } + protected createDefaultToolButton() { + const button = createIcon(["fas", "fa-mouse-pointer", "fa-xs", "clicked"]); + button.id = "btn_default_tools"; + button.title = 'Enable selection tool'; + button.onclick = this.onClickStaticToolButton(this.defaultToolsButton); + return button; + } + + protected createMouseDeleteToolButton() { + const deleteToolButton = createIcon(["fas", "fa-eraser", "fa-xs"]); + deleteToolButton.title = 'Enable deletion tool'; + deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); + return deleteToolButton; + } + + protected createValidateButton() { + const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); + validateActionButton.title = 'Validate model'; + validateActionButton.onclick = _event => { + const modelIds: string[] = [this.modelRootId]; + this.actionDispatcher.dispatch(new RequestMarkersAction(modelIds)); + }; + return validateActionButton; + } + protected createSearchButton() { const searchIcon = createIcon(["fas", SEARCH_ICON, "state-icon", "fa-xs"]); searchIcon.onclick = (_ev) => { @@ -194,6 +223,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } }; searchIcon.classList.add("search-icon"); + searchIcon.title = 'Filter palette entries'; return searchIcon; } diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index 3e82e8e..be6e21e 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -17,6 +17,7 @@ import { inject, injectable } from "inversify"; import { Action, EnableDefaultToolsAction, + findParentByFeature, isCtrlOrCmd, isDeletable, isSelectable, @@ -105,11 +106,12 @@ export class MouseDeleteTool implements GLSPTool { @injectable() export class DeleteToolMouseListener extends MouseListener { mouseUp(target: SModelElement, event: MouseEvent): Action[] { - if (!isDeletable(target)) { + const deletableParent = findParentByFeature(target, isDeletable); + if (deletableParent === undefined) { return []; } const result: Action[] = []; - result.push(new DeleteElementOperation([target.id])); + result.push(new DeleteElementOperation([deletableParent.id])); if (!isCtrlOrCmd(event)) { result.push(new EnableDefaultToolsAction()); } From f98decbe2dc99714ebcb7de86eb76c59e47c277e Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 29 Oct 2020 17:26:02 +0100 Subject: [PATCH 008/566] Handle model source change notification https://github.com/eclipse-glsp/glsp/issues/134 --- examples/workflow-sprotty/src/di.config.ts | 3 +- .../model-source-watcher/di.config.ts | 26 +++++++ .../model-source-changed-action-handler.ts | 70 +++++++++++++++++++ .../model-source-changed-action.ts | 26 +++++++ packages/client/src/index.ts | 5 +- 5 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/features/model-source-watcher/di.config.ts create mode 100644 packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts create mode 100644 packages/client/src/features/model-source-watcher/model-source-changed-action.ts diff --git a/examples/workflow-sprotty/src/di.config.ts b/examples/workflow-sprotty/src/di.config.ts index a11aa60..803b609 100644 --- a/examples/workflow-sprotty/src/di.config.ts +++ b/examples/workflow-sprotty/src/di.config.ts @@ -51,6 +51,7 @@ import { markerNavigatorModule, modelHintsModule, modelSourceModule, + modelSourceWatcherModule, navigationModule, NoOverlapMovmentRestrictor, openModule, @@ -116,7 +117,7 @@ export default function createContainer(widgetId: string): Container { container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, toolsModule, glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, - workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, + workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, modelSourceWatcherModule, glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, layoutCommandsModule, directTaskEditor, navigationModule, markerNavigatorModule); diff --git a/packages/client/src/features/model-source-watcher/di.config.ts b/packages/client/src/features/model-source-watcher/di.config.ts new file mode 100644 index 0000000..fb44570 --- /dev/null +++ b/packages/client/src/features/model-source-watcher/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from "inversify"; +import { configureActionHandler } from "sprotty/lib"; + +import { ModelSourceChangedAction } from "./model-source-changed-action"; +import { ModelSourceChangedActionHandler } from "./model-source-changed-action-handler"; + +const modelSourceWatcherModule = new ContainerModule((bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, ModelSourceChangedAction.KIND, ModelSourceChangedActionHandler); +}); + +export default modelSourceWatcherModule; diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts new file mode 100644 index 0000000..9cfef8c --- /dev/null +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, optional } from "inversify"; +import { Action, IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from "sprotty/lib"; + +import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; +import { isModelSourceChangedAction, ModelSourceChangedAction } from "./model-source-changed-action"; + +/** + * An external handler of the model source change event. + * + * This allows external applications to react specifically to this event, e.g. by bringing up the diagram, + * check their dirty state, show a dialog, etc. + */ +@injectable() +export abstract class ExternalModelSourceChangedHandler { + /** + * Notifies about a change of the model source. + * @returns actions to be dispatched after the notification. + */ + abstract notifyModelSourceChange(modelSourceName: string, options: ViewerOptions): Promise; +} + +@injectable() +export class ModelSourceChangedActionHandler implements IActionHandler { + + @inject(TYPES.IActionDispatcher) + protected dispatcher: IActionDispatcher; + + @inject(TYPES.ViewerOptions) + protected options: ViewerOptions; + + @inject(ExternalModelSourceChangedHandler) @optional() + protected externalModelSourceChangedHandler?: ExternalModelSourceChangedHandler; + + handle(action: Action) { + if (isModelSourceChangedAction(action)) { + if (this.externalModelSourceChangedHandler) { + this.externalModelSourceChangedHandler + .notifyModelSourceChange(action.modelSourceName, this.options) + .then(actions => this.dispatcher.dispatchAll(actions)); + return; + } + this.showSimpleNotification(action); + } + } + + protected showSimpleNotification(action: ModelSourceChangedAction) { + const message = `The model source ${action.modelSourceName} has changed. You might want to consider reloading.`; + const timeout = 0; + const severity = 'WARNING'; + this.dispatcher.dispatchAll([ + { kind: GLSPServerStatusAction.KIND, severity, timeout, message }, + { kind: ServerMessageAction.KIND, severity, timeout, message } + ]); + } +} diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts new file mode 100644 index 0000000..824e2a1 --- /dev/null +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from "sprotty"; + +export class ModelSourceChangedAction implements Action { + static KIND = 'modelSourceChanged'; + readonly kind = ModelSourceChangedAction.KIND; + constructor(public readonly modelSourceName: string) { } +} + +export function isModelSourceChangedAction(action: Action): action is ModelSourceChangedAction { + return action.kind === ModelSourceChangedAction.KIND; +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6aac190..6f9bf0a 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -22,6 +22,7 @@ import glspEditLabelModule from "./features/edit-label/di.config"; import modelHintsModule from "./features/hints/di.config"; import glspHoverModule from "./features/hover/di.config"; import layoutCommandsModule from "./features/layout/di.config"; +import modelSourceWatcherModule from "./features/model-source-watcher/di.config"; import glspMouseToolModule from "./features/mouse-tool/di.config"; import { navigationModule } from "./features/navigation/di.config"; import saveModule from "./features/save/di.config"; @@ -65,6 +66,8 @@ export * from './features/hints/request-type-hints-action'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; export * from './features/layout/layout-commands'; +export * from './features/model-source-watcher/model-source-changed-action'; +export * from './features/model-source-watcher/model-source-changed-action-handler'; export * from './features/mouse-tool/mouse-tool'; export * from './features/navigation/external-navigate-to-target-handler'; export * from './features/navigation/navigation-action-handler'; @@ -100,5 +103,5 @@ export * from './utils/viewpoint-util'; export { validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, - glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule + glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule, modelSourceWatcherModule }; From 1577cb0280adb907e7ad251c27008d8836fc9f29 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 9 Nov 2020 14:26:48 +0100 Subject: [PATCH 009/566] Fix #143 Properly decode navigation URis (#92) Fixes eclipse-glsp/glsp/issues/143 --- .../src/features/navigation/navigation-target-resolver.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 342578f..d6a6efd 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from "inversify"; -import { Action, generateRequestId, IActionDispatcher, RequestAction, ResponseAction, TYPES } from "sprotty"; +import { Action, generateRequestId, IActionDispatcher, ILogger, RequestAction, ResponseAction, TYPES } from "sprotty"; import { Args } from "../../base/args"; import { EditorContextServiceProvider } from "../../base/editor-context"; @@ -114,6 +114,7 @@ export class NavigationTargetResolver { @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + @inject(TYPES.ILogger) protected readonly logger: ILogger; async resolve(navigationTarget: NavigationTarget): Promise { const contextService = await this.editorContextService(); @@ -122,8 +123,10 @@ export class NavigationTargetResolver { } async resolveWithSourceUri(sourceUri: string | undefined, target: NavigationTarget): Promise { - if (sourceUri && sourceUri !== target.uri && `file://${sourceUri}` !== target.uri) { + const targetUri = decodeURIComponent(target.uri); + if (sourceUri && sourceUri !== targetUri && `file://${sourceUri}` !== targetUri) { // different URI, so we can't resolve it locally + this.logger.info("Source and Target URI are different. Can't resolve locally.", sourceUri, targetUri); return undefined; } if (NavigationTarget.getElementIds(target).length > 0) { From 884a1358783f4db19569509fb878ece351718f1c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Nov 2020 13:36:18 +0100 Subject: [PATCH 010/566] #92 Cleanup Workflow example Remove depreacted model-schema code and add relevant classed to index. Fixes eclipse-glsp/glsp/issues/92 --- examples/workflow-sprotty/src/index.ts | 3 ++ examples/workflow-sprotty/src/model-schema.ts | 43 ------------------- examples/workflow-sprotty/src/model.ts | 15 ++++++- 3 files changed, 16 insertions(+), 45 deletions(-) delete mode 100644 examples/workflow-sprotty/src/model-schema.ts diff --git a/examples/workflow-sprotty/src/index.ts b/examples/workflow-sprotty/src/index.ts index a94b0e3..d422ce8 100644 --- a/examples/workflow-sprotty/src/index.ts +++ b/examples/workflow-sprotty/src/index.ts @@ -15,5 +15,8 @@ ********************************************************************************/ import createWorkflowDiagramContainer from "./di.config"; +export * from "./model"; +export * from "./workflow-views"; +export * from "./direct-task-editing/direct-task-editor"; export { createWorkflowDiagramContainer }; diff --git a/examples/workflow-sprotty/src/model-schema.ts b/examples/workflow-sprotty/src/model-schema.ts deleted file mode 100644 index 531ac59..0000000 --- a/examples/workflow-sprotty/src/model-schema.ts +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { SEdgeSchema, SNodeSchema } from "@eclipse-glsp/client"; - -export namespace ActivityNodeSchema { - export namespace Type { - export const INITIAL = 'initalNode'; - export const FINAL = 'finalNode'; - export const DECISION = 'decisionNode'; - export const MERGE = 'mergeNode'; - export const JOIN = 'joinNode'; - export const FORK = 'forkNode'; - export const UNDEFINED = "undefined"; - } -} - -export interface TaskNodeSchema extends SNodeSchema { - name?: string - duration?: number - taskType?: string - reference?: string -} - -export interface WeightedEdgeSchema extends SEdgeSchema { - probability?: string -} - -export interface ActivityNodeSchema extends SNodeSchema { - nodeType: string -} diff --git a/examples/workflow-sprotty/src/model.ts b/examples/workflow-sprotty/src/model.ts index 26ece52..e068787 100644 --- a/examples/workflow-sprotty/src/model.ts +++ b/examples/workflow-sprotty/src/model.ts @@ -37,7 +37,6 @@ import { withEditLabelFeature } from "@eclipse-glsp/client"; -import { ActivityNodeSchema } from "./model-schema"; export class TaskNode extends RectangularNode implements Nameable, WithEditableLabel { static readonly DEFAULT_FEATURES = [connectableFeature, deletableFeature, selectFeature, boundsFeature, @@ -67,8 +66,9 @@ export class WeightedEdge extends SEdge { probability?: string; } + export class ActivityNode extends DiamondNode { - nodeType: string = ActivityNodeSchema.Type.UNDEFINED; + nodeType: string = ActivityNode.Type.UNDEFINED; size = { width: 32, height: 32 @@ -76,6 +76,17 @@ export class ActivityNode extends DiamondNode { strokeWidth = 1; } +export namespace ActivityNode { + export namespace Type { + export const INITIAL = 'initalNode'; + export const FINAL = 'finalNode'; + export const DECISION = 'decisionNode'; + export const MERGE = 'mergeNode'; + export const JOIN = 'joinNode'; + export const FORK = 'forkNode'; + export const UNDEFINED = "undefined"; + } +} export class Icon extends SShapeElement implements LayoutContainer { static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature]; From 5f069d66a8564a7519eda505a5bc9373b830e53a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 20 Nov 2020 08:32:08 +0100 Subject: [PATCH 011/566] #114 Update metadata and License information -Add license information and Readme.MD files to all packages that are published to npm. -Rename "workflow-sprotty" to "workflow-glsp" to avoid naming confusions. Part of eclipse-glsp/glsp/issues/114 --- .../LICENSE | 0 examples/workflow-glsp/README.md | 14 + .../css/diagram.css | 0 .../package.json | 4 +- .../src/di.config.ts | 0 .../src/direct-task-editing/di.config.ts | 0 .../direct-task-editing/direct-task-editor.ts | 0 .../src/index.ts | 0 .../src/model.ts | 0 .../src/workflow-views.tsx | 0 .../tsconfig.json | 0 examples/workflow-sprotty/README.md | 3 - examples/workflow-standalone/LICENSE | 277 ++++++++++++++++++ examples/workflow-standalone/package.json | 2 +- examples/workflow-standalone/src/di.config.ts | 2 +- packages/client/LICENSE | 277 ++++++++++++++++++ packages/client/README.md | 2 +- packages/protocol/LICENSE | 277 ++++++++++++++++++ packages/protocol/README.md | 1 + 19 files changed, 851 insertions(+), 8 deletions(-) rename examples/{workflow-sprotty => workflow-glsp}/LICENSE (100%) create mode 100644 examples/workflow-glsp/README.md rename examples/{workflow-sprotty => workflow-glsp}/css/diagram.css (100%) rename examples/{workflow-sprotty => workflow-glsp}/package.json (90%) rename examples/{workflow-sprotty => workflow-glsp}/src/di.config.ts (100%) rename examples/{workflow-sprotty => workflow-glsp}/src/direct-task-editing/di.config.ts (100%) rename examples/{workflow-sprotty => workflow-glsp}/src/direct-task-editing/direct-task-editor.ts (100%) rename examples/{workflow-sprotty => workflow-glsp}/src/index.ts (100%) rename examples/{workflow-sprotty => workflow-glsp}/src/model.ts (100%) rename examples/{workflow-sprotty => workflow-glsp}/src/workflow-views.tsx (100%) rename examples/{workflow-sprotty => workflow-glsp}/tsconfig.json (100%) delete mode 100644 examples/workflow-sprotty/README.md create mode 100644 examples/workflow-standalone/LICENSE create mode 100644 packages/client/LICENSE create mode 100644 packages/protocol/LICENSE diff --git a/examples/workflow-sprotty/LICENSE b/examples/workflow-glsp/LICENSE similarity index 100% rename from examples/workflow-sprotty/LICENSE rename to examples/workflow-glsp/LICENSE diff --git a/examples/workflow-glsp/README.md b/examples/workflow-glsp/README.md new file mode 100644 index 0000000..0a688cb --- /dev/null +++ b/examples/workflow-glsp/README.md @@ -0,0 +1,14 @@ +# workflow-glsp + +This package contains the configuration for the GLSP diagrams of the Workflow example language. It contains the base code that is independent from the actual application framework and integration variant. Separating this base configuration and the actual integration code means that it can be reused for various different integration variants of the GLSP Workflow example language e.g in a Theia application, Standalone application, VS Code plugin or integrated into the Eclipse IDE. + +## Building +This package is built with `yarn` and is available from npm via [@eclipse-glsp-examples/workflow-glsp](https://www.npmjs.com/package/@eclipse-glsp-examples/worfklow-glsp) + + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). + +![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) + diff --git a/examples/workflow-sprotty/css/diagram.css b/examples/workflow-glsp/css/diagram.css similarity index 100% rename from examples/workflow-sprotty/css/diagram.css rename to examples/workflow-glsp/css/diagram.css diff --git a/examples/workflow-sprotty/package.json b/examples/workflow-glsp/package.json similarity index 90% rename from examples/workflow-sprotty/package.json rename to examples/workflow-glsp/package.json index e41f702..04e1481 100644 --- a/examples/workflow-sprotty/package.json +++ b/examples/workflow-glsp/package.json @@ -1,7 +1,7 @@ { - "name": "@eclipse-glsp-examples/workflow-sprotty", + "name": "@eclipse-glsp-examples/workflow-glsp", "version": "0.7.0", - "description": "GLSP sprotty diagrams for the Workflow DSL", + "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "author": "Eclipse GLSP", "contributors": [ diff --git a/examples/workflow-sprotty/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts similarity index 100% rename from examples/workflow-sprotty/src/di.config.ts rename to examples/workflow-glsp/src/di.config.ts diff --git a/examples/workflow-sprotty/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts similarity index 100% rename from examples/workflow-sprotty/src/direct-task-editing/di.config.ts rename to examples/workflow-glsp/src/direct-task-editing/di.config.ts diff --git a/examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts similarity index 100% rename from examples/workflow-sprotty/src/direct-task-editing/direct-task-editor.ts rename to examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts diff --git a/examples/workflow-sprotty/src/index.ts b/examples/workflow-glsp/src/index.ts similarity index 100% rename from examples/workflow-sprotty/src/index.ts rename to examples/workflow-glsp/src/index.ts diff --git a/examples/workflow-sprotty/src/model.ts b/examples/workflow-glsp/src/model.ts similarity index 100% rename from examples/workflow-sprotty/src/model.ts rename to examples/workflow-glsp/src/model.ts diff --git a/examples/workflow-sprotty/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx similarity index 100% rename from examples/workflow-sprotty/src/workflow-views.tsx rename to examples/workflow-glsp/src/workflow-views.tsx diff --git a/examples/workflow-sprotty/tsconfig.json b/examples/workflow-glsp/tsconfig.json similarity index 100% rename from examples/workflow-sprotty/tsconfig.json rename to examples/workflow-glsp/tsconfig.json diff --git a/examples/workflow-sprotty/README.md b/examples/workflow-sprotty/README.md deleted file mode 100644 index 5cd92ef..0000000 --- a/examples/workflow-sprotty/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# workflow-sprotty - -This package contains the configuration for the sprotty-glsp diagrams of the workflow example language. diff --git a/examples/workflow-standalone/LICENSE b/examples/workflow-standalone/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/examples/workflow-standalone/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index ece0653..e7f1e8f 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -4,7 +4,7 @@ "version": "0.7.0", "description": "Standalone browser-app for the Workflow example", "dependencies": { - "@eclipse-glsp-examples/workflow-sprotty": "0.7.0", + "@eclipse-glsp-examples/workflow-glsp": "0.7.0", "@eclipse-glsp/client": "0.7.1" }, "author": "Eclipse GLSP", diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index dc10713..e265d2b 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import "../css/diagram.css"; -import { createWorkflowDiagramContainer } from "@eclipse-glsp-examples/workflow-sprotty/lib"; +import { createWorkflowDiagramContainer } from "@eclipse-glsp-examples/workflow-glsp/lib"; import { GLSPDiagramServer } from "@eclipse-glsp/client"; import { Container } from "inversify"; import { ConsoleLogger, LogLevel, TYPES } from "sprotty"; diff --git a/packages/client/LICENSE b/packages/client/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/packages/client/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/packages/client/README.md b/packages/client/README.md index 41e7593..fac2f42 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -6,7 +6,7 @@ A web-based diagram client framework for the [Graphical Language Server Platform ## Building This project is built with `yarn` and is available from npm via [@eclipse-glsp/client](https://www.npmjs.com/package/@eclipse-glsp/client). - +## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/protocol/LICENSE b/packages/protocol/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/packages/protocol/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/packages/protocol/README.md b/packages/protocol/README.md index ed1adc2..f1004a8 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -6,6 +6,7 @@ The generic client-server communication protocol for the [Graphical Language Ser This project is built with `yarn` and is available from npm via [@eclipse-glsp/protocol](https://www.npmjs.com/package/@eclipse-glsp/protocol). +## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). From bbeec7c5e28cb715c69b84bf557e4fd81e7bd9fe Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 20 Nov 2020 09:46:29 +0100 Subject: [PATCH 012/566] Add publishconfig for @eclipse-glsp-examples/workflow-glsp --- examples/workflow-glsp/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 04e1481..b577c25 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -50,5 +50,8 @@ "css" ], "main": "lib/index", - "types": "lib/index" + "types": "lib/index", + "publishConfig": { + "access": "public" + } } From 1a37f6104ccc9c6ec069f32a44e5d8c374b71ad6 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 20 Nov 2020 12:38:05 +0100 Subject: [PATCH 013/566] 0.8.0 Release --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 16 ++++++++-------- packages/protocol/package.json | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index b577c25..704da36 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "0.7.0", + "version": "0.8.0", "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "author": "Eclipse GLSP", @@ -28,7 +28,7 @@ "example" ], "dependencies": { - "@eclipse-glsp/client": "0.7.1", + "@eclipse-glsp/client": "0.8.1", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index e7f1e8f..462fda1 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,11 +1,11 @@ { "private": true, "name": "workflow-standalone", - "version": "0.7.0", + "version": "0.8.0", "description": "Standalone browser-app for the Workflow example", "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "0.7.0", - "@eclipse-glsp/client": "0.7.1" + "@eclipse-glsp-examples/workflow-glsp": "0.8.0", + "@eclipse-glsp/client": "0.8.1" }, "author": "Eclipse GLSP", "contributors": [ diff --git a/packages/client/package.json b/packages/client/package.json index 520785b..06c380f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "0.7.1", + "version": "0.8.1", "description": "A sprotty-based client for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -44,21 +44,21 @@ "url": "https://github.com/eclipse-glsp/glsp-client.git" }, "dependencies": { + "@eclipse-glsp/protocol": "0.8.0", "autocompleter": "5.1.0", - "sprotty": "0.9.0", - "@eclipse-glsp/protocol": "0.7.0" + "sprotty": "0.9.0" }, "devDependencies": { - "@types/node": "10.14.18", - "@types/mocha": "^5.2.7", "@babel/runtime": "^7.11.2", "@types/chai": "4.1.3", - "mocha": "^6.2.0", - "jenkins-mocha": "^8.0.0", + "@types/mocha": "^5.2.7", + "@types/node": "10.14.18", "chai": "^4.2.0", - "ts-node": "^8.3.0", + "jenkins-mocha": "^8.0.0", + "mocha": "^6.2.0", "reflect-metadata": "^0.1.13", "rimraf": "^2.6.1", + "ts-node": "^8.3.0", "tslint": "^5.5.0", "typescript": "^3.9.2" }, diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 821fafc..4ffa4d8 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "0.7.0", + "version": "0.8.0", "description": "The protocol definition for client-server communication in GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ From 7e548c8f82a05744cb58b6676c8bc66fc3a4b0c8 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 22 Nov 2020 15:33:23 +0100 Subject: [PATCH 014/566] Setup 0.9.0 next deployment --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 6 +++--- packages/protocol/package.json | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 704da36..f69150f 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "0.8.0", + "version": "0.9.0", "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "author": "Eclipse GLSP", @@ -28,7 +28,7 @@ "example" ], "dependencies": { - "@eclipse-glsp/client": "0.8.1", + "@eclipse-glsp/client": "0.9.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 462fda1..aab234b 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,11 +1,11 @@ { "private": true, "name": "workflow-standalone", - "version": "0.8.0", + "version": "0.9.0", "description": "Standalone browser-app for the Workflow example", "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "0.8.0", - "@eclipse-glsp/client": "0.8.1" + "@eclipse-glsp-examples/workflow-glsp": "0.9.0", + "@eclipse-glsp/client": "0.9.0" }, "author": "Eclipse GLSP", "contributors": [ diff --git a/packages/client/package.json b/packages/client/package.json index 06c380f..4feca19 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "0.8.1", + "version": "0.9.0", "description": "A sprotty-based client for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -44,9 +44,9 @@ "url": "https://github.com/eclipse-glsp/glsp-client.git" }, "dependencies": { - "@eclipse-glsp/protocol": "0.8.0", + "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", - "sprotty": "0.9.0" + "sprotty": "next" }, "devDependencies": { "@babel/runtime": "^7.11.2", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 4ffa4d8..e7eaf3e 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "0.8.0", + "version": "0.9.0", "description": "The protocol definition for client-server communication in GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ From 7e6459e22cc5d5f810fd3980bf5fedfa899e6122 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 30 Nov 2020 18:35:17 +0100 Subject: [PATCH 015/566] #153: Improve external navigation support through dedicated action - Introduce dedicated 'NavigateToExternalTarget' action - Replace external target service with action handler Part of eclipse-glsp/glsp/issues/153 Signed-off-by: Martin Fleck --- .../src/features/navigation/di.config.ts | 2 + .../external-navigate-to-target-handler.ts | 23 ----------- .../navigation/navigation-action-handler.ts | 39 ++++++++++++++----- packages/client/src/index.ts | 1 - 4 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 packages/client/src/features/navigation/external-navigate-to-target-handler.ts diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts index b9a3e05..3d08555 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/di.config.ts @@ -18,6 +18,7 @@ import { configureActionHandler } from "sprotty/lib"; import { NavigateAction, + NavigateToExternalTargetAction, NavigateToTargetAction, NavigationActionHandler, ProcessNavigationArgumentsAction @@ -30,4 +31,5 @@ export const navigationModule = new ContainerModule((bind, _unbind, isBound) => configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); configureActionHandler({ bind, isBound }, NavigateToTargetAction.KIND, NavigationActionHandler); configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, NavigateToExternalTargetAction.KIND, NavigationActionHandler); }); diff --git a/packages/client/src/features/navigation/external-navigate-to-target-handler.ts b/packages/client/src/features/navigation/external-navigate-to-target-handler.ts deleted file mode 100644 index 16403bf..0000000 --- a/packages/client/src/features/navigation/external-navigate-to-target-handler.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { injectable } from "inversify"; - -import { Args } from "../../base/args"; - -@injectable() -export abstract class ExternalNavigateToTargetHandler { - abstract navigateTo(uri: string, args?: Args): Promise; -} diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 76674ef..3910cc6 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable } from "inversify"; import { + ActionHandlerRegistry, CenterAction, generateRequestId, ILogger, @@ -29,7 +30,6 @@ import { Args } from "../../base/args"; import { EditorContext, EditorContextServiceProvider } from "../../base/editor-context"; import { GLSP_TYPES } from "../../base/types"; import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; -import { ExternalNavigateToTargetHandler } from "./external-navigate-to-target-handler"; import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTargetAction } from "./navigation-target-resolver"; /** @@ -86,6 +86,17 @@ export function isNavigateToTargetAction(action: Action): action is NavigateToTa && (action).target !== undefined; } +export class NavigateToExternalTargetAction implements Action { + static readonly KIND = 'navigateToExternalTarget'; + readonly kind = NavigateToExternalTargetAction.KIND; + constructor(readonly target: NavigationTarget) { } +} + +export function isNavigateToExternalTargetAction(action: Action): action is NavigateToExternalTargetAction { + return action !== undefined && (action.kind === NavigateToExternalTargetAction.KIND) + && (action).target !== undefined; +} + /** Action to trigger the processing of additional navigation arguments. * * The resolution of a `NavigationTarget` may entail additional arguments. In this case, this action is @@ -123,9 +134,9 @@ export function isProcessNavigationArgumentsAction(action: Action): action is Pr * navigation target needs to be resolved into actual elment IDs by the `NavigationTargetResolver`. * This can for instance be useful, if the navigation deals with queries or some other more complex * logic that can't be dealt with on the client. - * *(c)* the target isn't resolved by the `NavigationTargetResolver`, e.g. because the `uri` doesn't match - * the URI of the current diagram. In this case, the navigation request is forwarded to the - * `ExternalNavigateToTargetHandler`, if it exists. + * *(c)* the target isn't resolved by the `NavigationTargetResolver`, e.g. because the `uri` doesn't match + * the URI of the current diagram. In this case, the navigation request is forwarded by dispatching + * a `NavigateToExternalTargetAction`. */ @injectable() export class NavigationActionHandler implements IActionHandler { @@ -134,9 +145,9 @@ export class NavigationActionHandler implements IActionHandler { @inject(TYPES.ILogger) protected logger: ILogger; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; @inject(NavigationTargetResolver) protected resolver: NavigationTargetResolver; - @inject(ExternalNavigateToTargetHandler) @optional() protected externalHandler: ExternalNavigateToTargetHandler; handle(action: Action): ICommand | Action | void { if (isNavigateAction(action)) { @@ -145,6 +156,8 @@ export class NavigationActionHandler implements IActionHandler { this.handleNavigateToTarget(action); } else if (isProcessNavigationArgumentsAction(action)) { this.processNavigationArguments(action.args); + } else if (isNavigateToExternalTargetAction(action)) { + this.handleNavigateToExternalTarget(action); } } @@ -173,11 +186,10 @@ export class NavigationActionHandler implements IActionHandler { this.navigateTo(resolvedElements); this.handleResolutionArguments(resolvedElements); return; - } else if (this.externalHandler) { + } else { this.navigateToExternal(action.target); return; } - this.warnAboutFailedNavigation('Could not resolve or navigate to target', action.target); } catch (reason) { this.logger.error(this, 'Failed to navigate', reason, action); } @@ -216,7 +228,7 @@ export class NavigationActionHandler implements IActionHandler { } protected navigateToExternal(target: NavigationTarget): Promise { - return this.externalHandler.navigateTo(target.uri, target.args); + return this.dispatcher.dispatch(new NavigateToExternalTargetAction(target)); } protected processNavigationArguments(args: Args) { @@ -231,6 +243,15 @@ export class NavigationActionHandler implements IActionHandler { } } + protected async handleNavigateToExternalTarget(action: NavigateToExternalTargetAction) { + const registry = await this.actionHandlerRegistryProvider(); + const handlers = registry.get(NavigateToExternalTargetAction.KIND); + if (handlers.length === 1) { + // we are the only handler so we know nobody took care of it + this.warnAboutFailedNavigation('Could not resolve or navigate to target', action.target); + } + } + protected warnAboutFailedNavigation(msg: string, target?: NavigationTarget) { const message = `${msg}` + (target ? `: '${target.uri}' (arguments: ${JSON.stringify(target.args)})` : ''); this.logger.warn(this, msg, target); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6f9bf0a..a23c5b4 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -69,7 +69,6 @@ export * from './features/layout/layout-commands'; export * from './features/model-source-watcher/model-source-changed-action'; export * from './features/model-source-watcher/model-source-changed-action-handler'; export * from './features/mouse-tool/mouse-tool'; -export * from './features/navigation/external-navigate-to-target-handler'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; export * from './features/rank/model'; From 69e93af9ec964e9a2bed299a59397a25b8879545 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 21 Dec 2020 16:34:50 +0100 Subject: [PATCH 016/566] Removes needsClientLayout flag (#98) https://github.com/eclipse-glsp/glsp/issues/123 --- examples/workflow-glsp/src/di.config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 803b609..149fb9e 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -123,8 +123,7 @@ export default function createContainer(widgetId: string): Container { overrideViewerOptions(container, { baseDiv: widgetId, - hiddenDiv: widgetId + "_hidden", - needsClientLayout: true + hiddenDiv: widgetId + "_hidden" }); return container; From caa07fb5314621dba6f09aa80cd4cbebbba58faf Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Thu, 7 Jan 2021 14:32:23 +0100 Subject: [PATCH 017/566] Added download script for server (#99) fixed #171 Signed-off-by: Jonas Helming Signed-off-by: Philip Langer Co-authored-by: Philip Langer --- examples/workflow-glsp/package.json | 8 ++++-- examples/workflow-glsp/scripts/download.ts | 33 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 examples/workflow-glsp/scripts/download.ts diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index f69150f..9410847 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -34,7 +34,8 @@ "devDependencies": { "rimraf": "^2.6.1", "tslint": "^5.5.0", - "typescript": "^3.9.2" + "typescript": "^3.9.2", + "mvn-artifact-download": "5.1.0" }, "scripts": { "prepare": "yarn run clean && yarn run build", @@ -47,11 +48,12 @@ "lib", "src", "build", - "css" + "css", + "scripts" ], "main": "lib/index", "types": "lib/index", "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts new file mode 100644 index 0000000..8470ac6 --- /dev/null +++ b/examples/workflow-glsp/scripts/download.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2020 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import download from "mvn-artifact-download"; +import { join, resolve } from "path"; + +const downloadDir = resolve(join(__dirname)) + "/../../.."; +const mavenRepository = "https://oss.sonatype.org/content/repositories/snapshots/"; +const groupId = "org.eclipse.glsp.example"; +const artifactId = "org.eclipse.glsp.example.workflow"; +const version = "0.9.0"; +const classifier = "glsp"; +const { exec } = require("child_process"); + +console.log("Downloading latest version of the Workflow Example Java Server from the maven repository..."); +download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository) + .then(() => console.log("Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-" + + version + "-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n" + + "After starting the server, access the following file locally in your browser to see the running example:\n" + + "./examples/workflow-standalone/app/diagram.html" + )); From c0b0a3de0bb2854f874bd8657430a46cd0173f67 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Mon, 8 Feb 2021 10:15:05 +0100 Subject: [PATCH 018/566] Removed unused assignment (#101) fixed #174 Signed-off-by: Jonas Helming --- examples/workflow-glsp/scripts/download.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index 8470ac6..1f071fd 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -22,7 +22,6 @@ const groupId = "org.eclipse.glsp.example"; const artifactId = "org.eclipse.glsp.example.workflow"; const version = "0.9.0"; const classifier = "glsp"; -const { exec } = require("child_process"); console.log("Downloading latest version of the Workflow Example Java Server from the maven repository..."); download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository) From cacb089abf37722821b91be32b53fd5f75e90ed5 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 5 Feb 2021 14:08:33 +0100 Subject: [PATCH 019/566] Add focus tracking and remove hover on focus Re https://github.com/eclipse-glsp/glsp/issues/184 --- .../src/base/actions/focus-change-action.ts | 26 ++++++++++ packages/client/src/base/di.config.ts | 5 ++ packages/client/src/base/focus-tracker.ts | 50 +++++++++++++++++++ .../client/src/features/hover/di.config.ts | 3 +- packages/client/src/index.ts | 2 + 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/base/actions/focus-change-action.ts create mode 100644 packages/client/src/base/focus-tracker.ts diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/actions/focus-change-action.ts new file mode 100644 index 0000000..bf13e7e --- /dev/null +++ b/packages/client/src/base/actions/focus-change-action.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from "sprotty"; + +export class FocusStateChangedAction implements Action { + static KIND = 'focusStateChanged'; + readonly kind = FocusStateChangedAction.KIND; + constructor(public readonly hasFocus: boolean) { } +} + +export function isFocusStateChangedAction(action: Action): action is FocusStateChangedAction { + return action.kind === FocusStateChangedAction.KIND; +} diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 981c2bc..20aa364 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -20,9 +20,11 @@ import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from import { GLSPActionDispatcher } from "./action-dispatcher"; import { SetEditModeAction } from "./actions/edit-mode-action"; +import { FocusStateChangedAction } from "./actions/focus-change-action"; import { ConfigureServerHandlersAction, ConfigureServerHandlersActionHandler } from "./actions/protocol-actions"; import { GLSPCommandStack } from "./command-stack"; import { EditorContextService } from "./editor-context"; +import { FocusTracker } from "./focus-tracker"; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from "./model-initialization-constraint"; import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from "./model/update-model-command"; import { SelectionClearingMouseListener } from "./selection-clearing-mouse-listener"; @@ -46,6 +48,9 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); + bind(FocusTracker).toSelf().inSingletonScope(); + configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); + // Model update initialization ------------------------------------ configureCommand(context, FeedbackAwareUpdateModelCommand); configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts new file mode 100644 index 0000000..cd06b02 --- /dev/null +++ b/packages/client/src/base/focus-tracker.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from "sprotty"; + +import { isFocusStateChangedAction } from "./actions/focus-change-action"; + +@injectable() +export class FocusTracker implements IActionHandler { + + protected inActiveCssClass = 'inactive'; + protected _hasFocus = true; + + @inject(TYPES.ViewerOptions) protected options: ViewerOptions; + + get hasFocus(): boolean { + return this._hasFocus; + } + + handle(action: Action): void | Action | ICommand { + if (isFocusStateChangedAction(action)) { + this._hasFocus = action.hasFocus; + const placeholder = document.getElementById(this.options.baseDiv); + if (!placeholder) { + return; + } + if (this.hasFocus) { + if (placeholder.classList.contains(this.inActiveCssClass)) { + placeholder.classList.remove(this.inActiveCssClass); + } + } else { + placeholder.classList.add(this.inActiveCssClass); + } + } + } + +} diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index e2b0936..9742cc0 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -31,9 +31,9 @@ import { } from "sprotty"; import { PopupPositionUpdater } from "sprotty/lib/features/hover/popup-position-updater"; +import { FocusStateChangedAction } from "../../base/actions/focus-change-action"; import { GlspHoverMouseListener } from "./hover"; - const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { bind(TYPES.PopupVNodePostprocessor).to(PopupPositionUpdater).inSingletonScope(); bind(TYPES.MouseListener).to(GlspHoverMouseListener); @@ -55,6 +55,7 @@ const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler(context, CenterCommand.KIND, ClosePopupActionHandler); configureActionHandler(context, SetViewportCommand.KIND, ClosePopupActionHandler); configureActionHandler(context, MoveCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, FocusStateChangedAction.KIND, ClosePopupActionHandler); }); export default glspHoverModule; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index a23c5b4..ae1dba2 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -37,6 +37,7 @@ export * from './base/action-dispatcher'; export * from './base/actions/context-actions'; export * from './base/actions/edit-mode-action'; export * from './base/actions/edit-validation-actions'; +export * from './base/actions/focus-change-action'; export * from './base/actions/protocol-actions'; export * from './base/args'; export * from './base/auto-complete/auto-complete-actions'; @@ -45,6 +46,7 @@ export * from './base/auto-complete/validation-decorator'; export * from './base/command-stack'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context'; +export * from './base/focus-tracker'; export * from './base/model-initialization-constraint'; export * from './base/model/update-model-command'; export * from './base/operations/operation'; From e5261541ccdf3d2ee2d54ff31c015c1593cc42df Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 11 Feb 2021 14:23:29 +0100 Subject: [PATCH 020/566] Generalize input for ModifyCSSFeedbackAction Adapt `ModifyCSSFeedbackAction` to support both a string[] with the smodel ids or a SModelElement[] as input. --- .../features/tool-feedback/css-feedback.ts | 7 ++++--- packages/client/src/utils/array-utils.ts | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index b118329..0630600 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -16,6 +16,7 @@ import { inject, injectable } from "inversify"; import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from "sprotty"; +import { isStringArray } from "../../utils/array-utils"; import { addCssClasses, removeCssClasses } from "../../utils/smodel-util"; import { FeedbackCommand } from "./model"; @@ -23,12 +24,12 @@ export class ModifyCSSFeedbackAction implements Action { readonly elementIds?: string[]; constructor( - public readonly elements?: SModelElement[], + public readonly input?: string[] | SModelElement[], public readonly addClasses?: string[], public readonly removeClasses?: string[], public kind = ModifyCssFeedbackCommand.KIND) { - if (elements) { - this.elementIds = elements.map(elt => elt.id); + if (input) { + this.elementIds = isStringArray(input) ? input : input.map(element => element.id); } } } diff --git a/packages/client/src/utils/array-utils.ts b/packages/client/src/utils/array-utils.ts index b96fb49..d3c0f14 100644 --- a/packages/client/src/utils/array-utils.ts +++ b/packages/client/src/utils/array-utils.ts @@ -31,4 +31,25 @@ export function distinctAdd(array: T[], value: T): boolean { return false; } +interface Constructor { new(...args: any[]): T } +type PrimitiveType = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'; +export function isArrayOfType(object: any, typeGuard: (elem: any) => elem is T, supportEmpty = false): object is T[] { + return isArrayMatching(object, element => typeGuard(element), supportEmpty); +} + +export function isArrayOfClass(object: any, className: Constructor, supportEmpty = false): object is T[] { + return isArrayMatching(object, element => element instanceof className, supportEmpty); +} + +export function isArrayOfPrimitive(object: any, primitiveType: PrimitiveType, supportEmpty = false): object is T[] { + return isArrayMatching(object, element => typeof element === primitiveType, supportEmpty); +} + +export function isStringArray(object: any, supportEmpty = false): object is string[] { + return isArrayOfPrimitive(object, 'string', supportEmpty); +} + +export function isArrayMatching(object: any, predicate: (elem: any) => boolean, supportEmpty = false): boolean { + return Array.isArray(object) && object.every(predicate) && (supportEmpty || object.length > 0); +} From ca13aba96634c5e4589dbda44257f9f052e227b3 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Tue, 23 Feb 2021 09:56:19 +0100 Subject: [PATCH 021/566] Make auto complete widget more flexible Allow to change auto complete settings, suggestion provider, and suggestion submit handler after instantiation. This is often used in the onBeforeShow of UI extensions using the auto complete widget. In the onBeforeShow we have more context and may want to change the settings without having to reinstantiate the entire widget. --- .../base/auto-complete/auto-complete-widget.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 671696f..ddfddd4 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -23,10 +23,10 @@ import { isSetAutoCompleteValueAction } from "./auto-complete-actions"; import { IValidationDecorator } from "./validation-decorator"; export interface AutoCompleteSettings { - readonly noSuggestionsMessage: string; - readonly suggestionsClass: string; - readonly debounceWaitMs: number; - readonly showOnFocus: boolean; + readonly noSuggestionsMessage?: string; + readonly suggestionsClass?: string; + readonly debounceWaitMs?: number; + readonly showOnFocus?: boolean; } export interface InputValidator { @@ -51,8 +51,6 @@ export interface TextSubmitHandler { const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); - - export class AutoCompleteWidget { loadingIndicatorClasses = ['loading', 'fa', 'fa-spinner', 'fa-pulse', 'fa-3x', 'fa-fw']; @@ -69,9 +67,9 @@ export class AutoCompleteWidget { protected textSubmitHandler?: TextSubmitHandler; constructor( - protected autoSuggestionSettings: AutoCompleteSettings, - protected suggestionProvider: SuggestionProvider, - protected suggestionSubmitHandler: SuggestionSubmitHandler, + public autoSuggestionSettings: AutoCompleteSettings, + public suggestionProvider: SuggestionProvider, + public suggestionSubmitHandler: SuggestionSubmitHandler, protected notifyClose: () => void = () => { }, protected logger?: ILogger ) { } From 5fe1f3604127501f6a6b78b184acc49389407054 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 1 Mar 2021 13:13:19 +0100 Subject: [PATCH 022/566] Upgrade Sprotty to fix inversify version mismatch (#105) Sprotty had a hard dependency on 5.0.1 which forced glsp-client to also depend hard on 5.0.1. Theia however has ^5.0.1 which resolves to 5.0.5, so we had a version mismatch. We fixed this dependency in Sprotty and now we can upgrade the Sprotty dependency in glsp-client to resolve the version mismatch between glsp-client and glsp-theia-integration. --- packages/client/src/features/tools/node-creation-tool.ts | 3 +++ packages/client/src/index.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index e583675..875fc19 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -75,7 +75,9 @@ export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { @injectable() export class NodeCreationToolMouseListener extends DragAwareMouseListener { + protected container?: SModelElement & Containable; + constructor(protected triggerAction: TriggerNodeCreationAction, protected tool: NodeCreationTool) { super(); } @@ -87,6 +89,7 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { get elementTypeId() { return this.triggerAction.elementTypeId; } + nonDraggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (this.creationAllowed(this.elementTypeId)) { diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index ae1dba2..0e75137 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -85,6 +85,7 @@ export * from './features/tool-feedback/feedback-action-dispatcher'; export * from './features/tool-feedback/model'; export * from './features/tool-palette/palette-item'; export * from './features/tool-palette/tool-palette'; +export * from './features/tools/base-glsp-tool'; export * from './features/tools/change-bounds-tool'; export * from './features/tools/delete-tool'; export * from './features/tools/edge-creation-tool'; From 76ce827ea7dbca0616cd2043ecee099b58444a89 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 1 Mar 2021 14:08:13 +0100 Subject: [PATCH 023/566] Add SArgumentable interface denoting elements that contain args (#106) * Add SArgumentable interface denoting elements that contain args https://github.com/eclipse-glsp/glsp/issues/194 --- packages/client/src/base/args.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/client/src/base/args.ts b/packages/client/src/base/args.ts index 0abdeed..ebaf822 100644 --- a/packages/client/src/base/args.ts +++ b/packages/client/src/base/args.ts @@ -13,4 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { SModelElement } from "sprotty"; + export type Args = { [key: string]: string | number | boolean }; + +export interface SArgumentable { + args: Args; +} + +export function hasArguments(element?: SModelElement): element is SModelElement & SArgumentable { + return element !== undefined && 'args' in element; +} From abd177942872428270516e58ff135a09d63fd784 Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Tue, 23 Mar 2021 12:37:55 +0100 Subject: [PATCH 024/566] Add marquee tool (#108) --- examples/workflow-glsp/css/diagram.css | 5 + examples/workflow-glsp/src/di.config.ts | 4 +- packages/client/css/glsp-sprotty.css | 4 + .../features/tool-feedback/css-feedback.ts | 3 +- .../src/features/tool-feedback/di.config.ts | 4 + .../tool-feedback/marquee-tool-feedback.ts | 103 +++++++++ .../src/features/tool-palette/tool-palette.ts | 11 + .../client/src/features/tools/di.config.ts | 16 +- .../src/features/tools/marquee-mouse-tool.ts | 204 ++++++++++++++++++ .../client/src/features/tools/marquee-tool.ts | 51 +++++ packages/client/src/features/tools/model.ts | 23 ++ packages/client/src/features/tools/view.tsx | 38 ++++ .../client/src/features/viewport/di.config.ts | 47 ++++ .../viewport/glsp-scroll-mouse-listener.ts | 41 ++++ packages/client/src/index.ts | 4 +- 15 files changed, 552 insertions(+), 6 deletions(-) create mode 100644 packages/client/src/features/tool-feedback/marquee-tool-feedback.ts create mode 100644 packages/client/src/features/tools/marquee-mouse-tool.ts create mode 100644 packages/client/src/features/tools/marquee-tool.ts create mode 100644 packages/client/src/features/tools/model.ts create mode 100644 packages/client/src/features/tools/view.tsx create mode 100644 packages/client/src/features/viewport/di.config.ts create mode 100644 packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index 9b3a88e..a827621 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -115,6 +115,11 @@ stroke-width: 1px; } +.sprotty-node.marquee { + fill: grey; + opacity: 0.2; +} + .sprotty-node.task.automated { fill: gray; } diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 149fb9e..d45e3fc 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -74,7 +74,7 @@ import { toolsModule, TYPES, validationModule, - viewportModule, + glspViewportModule, zorderModule } from "@eclipse-glsp/client"; import { Container, ContainerModule } from "inversify"; @@ -115,7 +115,7 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind export default function createContainer(widgetId: string): Container { const container = new Container(); - container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, toolsModule, + container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, glspViewportModule, toolsModule, glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, modelSourceWatcherModule, glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index db3ca6e..2079028 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -100,3 +100,7 @@ .element-deletion-mode { cursor: pointer; } + +.marquee-mode { + cursor: crosshair; +} diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 0630600..dd993a0 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -77,7 +77,8 @@ export enum CursorCSS { EDGE_RECONNECT = 'edge-reconnect-select-target-mode', OPERATION_NOT_ALLOWED = 'edge-modification-not-allowed-mode', ELEMENT_DELETION = "element-deletion-mode", - RESIZE = "resize-mode" + RESIZE = "resize-mode", + MARQUEE = "marquee-mode" } export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAction { diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index 990c07d..2d39b9c 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -31,6 +31,7 @@ import { SwitchRoutingModeCommand } from "./edge-edit-tool-feedback"; import { FeedbackActionDispatcher } from "./feedback-action-dispatcher"; +import { DrawMarqueeCommand, RemoveMarqueeCommand } from "./marquee-tool-feedback"; import { FeedbackEdgeEndView, SResizeHandleView } from "./view"; const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { @@ -42,6 +43,9 @@ const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, DrawFeedbackEdgeCommand); configureCommand({ bind, isBound }, RemoveFeedbackEdgeCommand); + configureCommand({ bind, isBound }, DrawMarqueeCommand); + configureCommand({ bind, isBound }, RemoveMarqueeCommand); + configureView({ bind, isBound }, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); // move tool feedback: we use sprotties MoveCommand as client-side visual feedback configureCommand({ bind, isBound }, MoveCommand); diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts new file mode 100644 index 0000000..c0d25c0 --- /dev/null +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { + Action, + AnchorComputerRegistry, + CommandExecutionContext, + CommandReturn, + MouseListener, + Point, + SChildElement, + SModelElement, + SModelRoot, + TYPES +} from "sprotty"; + +import { FeedbackCommand } from "./model"; + +export class DrawMarqueeAction implements Action { + constructor( + public readonly startPoint: Point, + public readonly endPoint: Point, + public readonly kind: string = DrawMarqueeCommand.KIND) { } +} + +@injectable() +export class DrawMarqueeCommand extends FeedbackCommand { + static readonly KIND = 'drawMarquee'; + + constructor(@inject(TYPES.Action) protected action: DrawMarqueeAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + drawMarquee(context, this.action.startPoint, this.action.endPoint); + return context.root; + } +} + +export class RemoveMarqueeAction implements Action { + constructor(public readonly kind: string = RemoveMarqueeCommand.KIND) { } +} + +@injectable() +export class RemoveMarqueeCommand extends FeedbackCommand { + static readonly KIND = 'removeMarqueeCommand'; + + execute(context: CommandExecutionContext): CommandReturn { + removeMarquee(context.root); + return context.root; + } +} + +export class MarqueeEndMovingMouseListener extends MouseListener { + constructor(protected anchorRegistry: AnchorComputerRegistry) { + super(); + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + return []; + } +} + +export function marqueeId(root: SModelRoot): string { + return root.id + '_' + MARQUEE; +} + +export const MARQUEE = 'marquee'; + +export function drawMarquee(context: CommandExecutionContext, startPoint: Point, endPoint: Point) { + const root = context.root; + + removeMarquee(root); + + const marqueeSchema = { + type: MARQUEE, + id: marqueeId(root), + startPoint: startPoint, + endPoint: endPoint, + }; + + const marquee = context.modelFactory.createElement(marqueeSchema); + root.add(marquee); +} + +export function removeMarquee(root: SModelRoot) { + const marquee = root.index.getById(marqueeId(root)); + if (marquee instanceof SChildElement) + root.remove(marquee); +} diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 50cfd9f..5112777 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -31,6 +31,7 @@ import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; import { GLSPActionDispatcher } from "../../base/action-dispatcher"; import { isSetContextActionsAction, RequestContextActions, SetContextActions } from "../../base/actions/context-actions"; import { EditModeListener, EditorContextService } from "../../base/editor-context"; +import { MarqueeMouseTool } from "../tools/marquee-mouse-tool"; import { MouseDeleteTool } from "../tools/delete-tool"; import { RequestMarkersAction } from "../validation/validate"; import { PaletteItem } from "./palette-item"; @@ -174,6 +175,9 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const deleteToolButton = this.createMouseDeleteToolButton(); headerTools.appendChild(deleteToolButton); + const marqueeToolButton = this.createMarqueeToolButton(); + headerTools.appendChild(marqueeToolButton); + const validateActionButton = this.createValidateButton(); headerTools.appendChild(validateActionButton); @@ -199,6 +203,13 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, return deleteToolButton; } + protected createMarqueeToolButton() { + const marqueeToolButton = createIcon(["far", "fa-object-group", "fa-xs"]); + marqueeToolButton.title = 'Enable marquee tool'; + marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID); + return marqueeToolButton; + } + protected createValidateButton() { const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); validateActionButton.title = 'Validate model'; diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 82a5c8b..f55a449 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -13,16 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureActionHandler } from "sprotty"; +import { ContainerModule, interfaces } from "inversify"; +import { configureActionHandler, configureModelElement } from "sprotty"; import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from "../../base/operations/operation"; import { GLSP_TYPES } from "../../base/types"; +import { MARQUEE } from "../tool-feedback/marquee-tool-feedback"; import { ChangeBoundsTool } from "./change-bounds-tool"; import { DelKeyDeleteTool, MouseDeleteTool } from "./delete-tool"; import { EdgeCreationTool } from "./edge-creation-tool"; import { EdgeEditTool } from "./edge-edit-tool"; +import { MarqueeMouseTool } from "./marquee-mouse-tool"; +import { MarqueeTool } from "./marquee-tool"; +import { MarqueeNode } from "./model"; import { NodeCreationTool } from "./node-creation-tool"; +import { MarqueeView } from "./view"; const toolsModule = new ContainerModule((bind, _unbind, isBound) => { // Register default tools @@ -37,8 +42,15 @@ const toolsModule = new ContainerModule((bind, _unbind, isBound) => { bind(GLSP_TYPES.ITool).toService(EdgeCreationTool); bind(GLSP_TYPES.ITool).toService(NodeCreationTool); + configureMarqueeTool({ bind, isBound }); configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); }); +export function configureMarqueeTool(context: { bind: interfaces.Bind, isBound: interfaces.IsBound }) { + configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); + context.bind(GLSP_TYPES.IDefaultTool).to(MarqueeTool); + context.bind(GLSP_TYPES.ITool).to(MarqueeMouseTool); +} + export default toolsModule; diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts new file mode 100644 index 0000000..adb81a4 --- /dev/null +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -0,0 +1,204 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, BoundsAware, EnableDefaultToolsAction, isSelectable, KeyListener, Point, SEdge, SelectAction, SModelElement, SNode, TYPES } from "sprotty"; +import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +import { DrawMarqueeAction, RemoveMarqueeAction } from "../tool-feedback/marquee-tool-feedback"; +import { BaseGLSPTool } from "../tools/base-glsp-tool"; +import { getAbsolutePosition, toAbsoluteBounds } from "../../utils/viewpoint-util"; +import { DOMHelper } from "sprotty/lib/base/views/dom-helper"; + +@injectable() +export class MarqueeMouseTool extends BaseGLSPTool { + static ID = "glsp.marquee-mouse-tool"; + + @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; + + protected marqueeMouseListener: MarqueeMouseListener; + protected shiftKeyListener: ShiftKeyListener = new ShiftKeyListener(); + + get id(): string { + return MarqueeMouseTool.ID; + } + + enable(): void { + this.marqueeMouseListener = new MarqueeMouseListener(this.domHelper); + this.mouseTool.register(this.marqueeMouseListener); + this.keyTool.register(this.shiftKeyListener); + this.dispatchFeedback([cursorFeedbackAction(CursorCSS.MARQUEE)]); + } + + disable(): void { + this.mouseTool.deregister(this.marqueeMouseListener); + this.keyTool.deregister(this.shiftKeyListener); + this.deregisterFeedback([cursorFeedbackAction()]); + } +} + +@injectable() +export class MarqueeMouseListener extends DragAwareMouseListener { + + protected startPoint: Point; + + protected currentPoint: Point; + + protected domHelper: DOMHelper; + + protected isActive: boolean = false; + + constructor(domHelper: DOMHelper) { + super(); + this.domHelper = domHelper; + } + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + this.isActive = true; + this.startPoint = { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }; + return []; + } + + mouseMove(target: SModelElement, event: MouseEvent): Action[] { + this.currentPoint = { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }; + if (this.isActive) { + const nodeIdsSelected = Array.from(target.root.index.all() + .map(e => e as SModelElement & BoundsAware) + .filter(e => isSelectable(e)) + .filter(e => e instanceof SNode) + .filter(e => this.isNodeMarked(e)).map(e => e.id)); + const edgeIdsSelected = this.getMarkedEdges(target.root); + const selected = nodeIdsSelected.concat(edgeIdsSelected); + return [ + new SelectAction([], Array.from(target.index.all().map(e => e.id))), + new SelectAction(selected, []), + new DrawMarqueeAction(this.startPoint, + { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }) + ]; + } + return []; + } + + mouseUp(target: SModelElement, event: MouseEvent): Action[] { + this.isActive = false; + if (event.shiftKey) { + return [new RemoveMarqueeAction()]; + } + return [new RemoveMarqueeAction(), new EnableDefaultToolsAction()]; + } + + getMarkedEdges(root: SModelElement): string[] { + const elements = Array.from(document.querySelectorAll("g")); + const edges = Array.from(root.index.all().filter(e => e instanceof SEdge). + filter(e => isSelectable(e)).map(e => e.id)); + return elements.filter(e => edges.includes(this.domHelper.findSModelIdByDOMElement(e))) + .filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e)); + } + + isEdgeMarked(element: SVGElement): boolean { + if (element.getAttribute("transform") == null) { + if (element.children[0] != null) { + const path = element.children[0].getAttribute("d"); + if (path != null) { + const points = path.split(/M|L/); + for (let i = 0; i < points.length - 1; i++) { + const coord1 = points[i].split(","); + const coord2 = points[i + 1].split(","); + const point1 = { x: parseInt(coord1[0]), y: parseInt(coord1[1]) }; + const point2 = { x: parseInt(coord2[0]), y: parseInt(coord2[1]) }; + if (this.isLineMarked(point1, point2)) return true; + } + } + } + } + return false; + } + + isLineMarked(point1: Point, point2: Point): boolean { + if (this.pointInRect(point1) || this.pointInRect(point2)) return true; + if (this.linesIntersect(point1, point2, this.startPoint, { x: this.startPoint.x, y: this.currentPoint.y })) return true; + if (this.linesIntersect(point1, point2, this.startPoint, { x: this.currentPoint.x, y: this.startPoint.y })) return true; + if (this.linesIntersect(point1, point2, { x: this.currentPoint.x, y: this.startPoint.y }, this.currentPoint)) return true; + if (this.linesIntersect(point1, point2, { x: this.startPoint.x, y: this.currentPoint.y }, this.currentPoint)) return true; + return false; + } + + linesIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean { + const tCount = ((p1.x - p3.x) * (p3.y - p4.y)) - ((p1.y - p3.y) * (p3.x - p4.x)); + const tDenom = ((p1.x - p2.x) * (p3.y - p4.y)) - ((p1.y - p2.y) * (p3.x - p4.x)); + const t = tCount / tDenom; + const uCount = ((p2.x - p1.x) * (p1.y - p3.y)) - ((p2.y - p1.y) * (p1.x - p3.x)); + const uDenom = ((p1.x - p2.x) * (p3.y - p4.y)) - ((p1.y - p2.y) * (p3.x - p4.x)); + const u = uCount / uDenom; + if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) return true; + return false; + } + + pointInRect(point: Point): boolean { + const boolX = this.startPoint.x <= this.currentPoint.x ? + this.isBetween(point.x, this.startPoint.x, this.currentPoint.x) : this.isBetween(point.x, this.currentPoint.x, this.startPoint.x); + const boolY = this.startPoint.y <= this.currentPoint.y ? + this.isBetween(point.y, this.startPoint.y, this.currentPoint.y) : this.isBetween(point.y, this.currentPoint.y, this.startPoint.y); + return boolX && boolY; + } + + isNodeMarked(element: SModelElement & BoundsAware): boolean { + const horizontallyIn = this.startPoint.x < this.currentPoint.x ? + this.isElementBetweenXAxis(element, this.startPoint.x, this.currentPoint.x) : + this.isElementBetweenXAxis(element, this.currentPoint.x, this.startPoint.x); + const verticallyIn = this.startPoint.y < this.currentPoint.y ? + this.isElementBetweenYAxis(element, this.startPoint.y, this.currentPoint.y) : + this.isElementBetweenYAxis(element, this.currentPoint.y, this.startPoint.y); + if (horizontallyIn && verticallyIn) return true; + return false; + } + + isElementBetweenXAxis(element: SModelElement & BoundsAware, marqueeLeft: number, marqueeRight: number): boolean { + if (this.isBetween(marqueeLeft, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width) + || this.isBetween(marqueeRight, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width)) { + return true; + } + const leftEdge = this.isBetween(toAbsoluteBounds(element).x, marqueeLeft, marqueeRight); + const rightEdge = this.isBetween(toAbsoluteBounds(element).x + toAbsoluteBounds(element).width, marqueeLeft, marqueeRight); + return leftEdge || rightEdge; + } + + isElementBetweenYAxis(element: SModelElement & BoundsAware, marqueeTop: number, marqueeBottom: number): boolean { + if (this.isBetween(marqueeTop, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height) + || this.isBetween(marqueeBottom, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height)) { + return true; + } + const topEdge = this.isBetween(toAbsoluteBounds(element).y, marqueeTop, marqueeBottom); + const bottomEdge = this.isBetween(toAbsoluteBounds(element).y + toAbsoluteBounds(element).height, marqueeTop, marqueeBottom); + return topEdge || bottomEdge; + } + + isBetween(x: number, lower: number, upper: number): boolean { + if (lower <= x && x <= upper) return true; + return false; + } + +} + +@injectable() +export class ShiftKeyListener extends KeyListener { + + keyUp(element: SModelElement, event: KeyboardEvent): Action[] { + if (event.shiftKey) { + return []; + } + return [new RemoveMarqueeAction(), new EnableDefaultToolsAction()]; + } +} diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts new file mode 100644 index 0000000..5351dfd --- /dev/null +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from "inversify"; +import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from "sprotty"; +import { BaseGLSPTool } from "../tools/base-glsp-tool"; +import { MarqueeMouseTool } from "./marquee-mouse-tool"; + +@injectable() +export class MarqueeTool extends BaseGLSPTool { + static ID = "glsp.marquee-tool"; + + protected marqueeKeyListener: MarqueeKeyListener = new MarqueeKeyListener(); + + @inject(KeyTool) protected readonly keytool: KeyTool; + + get id(): string { + return MarqueeTool.ID; + } + + enable(): void { + this.keyTool.register(this.marqueeKeyListener); + } + + disable(): void { + this.keyTool.deregister(this.marqueeKeyListener); + } +} + +@injectable() +export class MarqueeKeyListener extends KeyListener { + + keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (event.shiftKey) { + return [new EnableToolsAction([MarqueeMouseTool.ID])]; + } + return []; + } +} diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/model.ts new file mode 100644 index 0000000..9179f3e --- /dev/null +++ b/packages/client/src/features/tools/model.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { boundsFeature, Point, RectangularNode } from "sprotty"; + +export class MarqueeNode extends RectangularNode { + static readonly DEFAULT_FEATURES = [boundsFeature]; + startPoint: Point; + endPoint: Point; +} diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx new file mode 100644 index 0000000..0e027b6 --- /dev/null +++ b/packages/client/src/features/tools/view.tsx @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from "inversify"; +import * as snabbdom from "snabbdom-jsx"; +import { VNode } from "snabbdom/vnode"; +import { RectangularNodeView, RenderingContext } from "sprotty"; +import { MarqueeNode } from "./model"; + +const JSX = { createElement: snabbdom.svg }; + +@injectable() +export class MarqueeView extends RectangularNodeView { + render(node: MarqueeNode, context: RenderingContext): VNode { + const graph = + + ; + return graph; + } +} \ No newline at end of file diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/di.config.ts new file mode 100644 index 0000000..f81ca7c --- /dev/null +++ b/packages/client/src/features/viewport/di.config.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + CenterCommand, + CenterKeyboardListener, + configureActionHandler, + configureCommand, + EnableDefaultToolsAction, + EnableToolsAction, + FitToScreenCommand, + GetViewportCommand, + SetViewportCommand, + TYPES, + ZoomMouseListener, +} from "sprotty"; +import { ContainerModule } from "inversify"; + +import { GLSPScrollMouseListener } from "./glsp-scroll-mouse-listener"; + +const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { + configureCommand({ bind, isBound }, CenterCommand); + configureCommand({ bind, isBound }, FitToScreenCommand); + configureCommand({ bind, isBound }, GetViewportCommand); + configureCommand({ bind, isBound }, SetViewportCommand); + bind(TYPES.KeyListener).to(CenterKeyboardListener); + bind(TYPES.MouseListener).to(ZoomMouseListener); + bind(GLSPScrollMouseListener).toSelf().inSingletonScope(); + bind(TYPES.MouseListener).toService(GLSPScrollMouseListener); + + configureActionHandler({ bind, isBound }, EnableToolsAction.KIND, GLSPScrollMouseListener); + configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); +}); + +export default glspViewportModule; diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts new file mode 100644 index 0000000..4195b33 --- /dev/null +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from "sprotty"; +import { injectable } from "inversify"; +import { MarqueeMouseTool } from "../tools/marquee-mouse-tool"; + +@injectable() +export class GLSPScrollMouseListener extends ScrollMouseListener implements IActionHandler { + + preventScrolling = false; + + handle(action: Action): void | Action | ICommand { + if (action instanceof EnableToolsAction) { + if (action.toolIds.includes(MarqueeMouseTool.ID)) { + this.preventScrolling = true; + } + } else if (action instanceof EnableDefaultToolsAction) { + this.preventScrolling = false; + } + } + + mouseDown(target: SModelElement, event: MouseEvent): Action[] { + if (this.preventScrolling) { + return []; + } + return super.mouseDown(target, event); + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 0e75137..dc6ec44 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -31,6 +31,7 @@ import toolFeedbackModule from "./features/tool-feedback/di.config"; import paletteModule from "./features/tool-palette/di.config"; import toolsModule from "./features/tools/di.config"; import { markerNavigatorModule, validationModule } from "./features/validation/di.config"; +import glspViewportModule from "./features/viewport/di.config"; export * from 'sprotty'; export * from './base/action-dispatcher'; @@ -95,6 +96,7 @@ export * from './features/undo-redo/model'; export * from './features/validation/issue-marker'; export * from './features/validation/validate'; export * from './features/validation/marker-navigator'; +export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './lib/model'; export * from './model-source/glsp-server-status'; export * from './model-source/glsp-diagram-server'; @@ -105,5 +107,5 @@ export * from './utils/viewpoint-util'; export { validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, - glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule, modelSourceWatcherModule + glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule, modelSourceWatcherModule, glspViewportModule }; From 682c4c224d5fcc10edbe5b0a1cee066e1ed16906 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 24 Mar 2021 13:40:24 +0100 Subject: [PATCH 025/566] #208 Add `fileUri` to `SaveModelAction` Part of eclipse-glsp/glsp/issues/208 --- packages/client/src/features/save/save.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save.ts index b01e868..8db7ad1 100644 --- a/packages/client/src/features/save/save.ts +++ b/packages/client/src/features/save/save.ts @@ -18,7 +18,11 @@ import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; export class SaveModelAction implements Action { static readonly KIND = "saveModel"; - constructor(public readonly kind: string = SaveModelAction.KIND) { } + constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) { } +} + +export function isSaveModelAction(action: Action): action is SaveModelAction { + return action.kind === SaveModelAction.KIND; } export class SaveModelKeyboardListener extends KeyListener { From 1f69ed85e2caacbf4f925d42cbcb600c1f750b65 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 25 Mar 2021 13:34:48 +0100 Subject: [PATCH 026/566] #212 Add missing typeguard functions for operations (#110) Fixes eclipse-glsp/glsp/issues/212 --- .../client/src/base/operations/operation.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index a3f9b8f..95a2633 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -65,11 +65,19 @@ export class DeleteElementOperation implements Operation { constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) { } } +export function isDeleteElementOperation(object?: any): object is DeleteElementOperation { + return isAction(object) && object.kind === DeleteElementOperation.KIND && object.hasOwnProperty('elementIds'); +} + export class ChangeBoundsOperation implements Operation { static readonly KIND = "changeBounds"; constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) { } } +export function isChangeBoundsOperation(object?: any): object is ChangeBoundsOperation { + return isAction(object) && object.kind === ChangeBoundsOperation.KIND && object.hasOwnProperty('newBounds'); +} + export class ChangeContainerOperation implements Operation { static readonly KIND = "changeContainer"; constructor(public readonly elementId: string, @@ -78,6 +86,12 @@ export class ChangeContainerOperation implements Operation { public readonly kind: string = ChangeContainerOperation.KIND) { } } +export function isChangeContainerOperation(object?: any): object is ChangeContainerOperation { + return isAction(object) && object.kind === ChangeContainerOperation.KIND + && 'elementId' in object && typeof object['elementId'] === 'string' + && 'targetContainerId' in object && typeof object['targetContainerId'] === 'string'; +} + export class ReconnectEdgeOperation implements Operation { static readonly KIND = "reconnectEdge"; constructor(public readonly edgeElementId: string, @@ -86,16 +100,32 @@ export class ReconnectEdgeOperation implements Operation { public readonly kind: string = ReconnectEdgeOperation.KIND) { } } +export function isReconnectEdgeOperation(object?: any): object is ReconnectEdgeOperation { + return isAction(object) && object.kind === ReconnectEdgeOperation.KIND + && 'edgeElementId' in object && typeof object['edgeElementId'] === 'string' + && 'sourceElementId' in object && typeof object['sourceElementId'] === 'string' + && 'targetElementId' in object && typeof object['targetElementId'] === 'string'; +} + export class ChangeRoutingPointsOperation implements Operation { static readonly KIND = "changeRoutingPoints"; constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) { } } +export function isChangeRoutingsPointsOperation(object?: any): object is ChangeRoutingPointsOperation { + return isAction(object) && object.kind === ChangeRoutingPointsOperation.KIND + && 'newRoutingPoints' in object && typeof object['newRoutingPoints'] === 'string'; +} + export class CompoundOperation implements Operation { static readonly KIND = "compound"; constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) { } } +export function isCompoundOperation(object?: any): object is CompoundOperation { + return isAction(object) && object.kind === CompoundOperation.KIND && 'operationList' in object; +} + /** * The `ElementAndRoutingPoints` type is used to associate an edge with specific routing points. */ From f11ab98d5e1a19a6d194b24bce4f49a4b2a2c354 Mon Sep 17 00:00:00 2001 From: Nina Doschek <47414588+ndoschek@users.noreply.github.com> Date: Fri, 2 Apr 2021 12:37:04 +0200 Subject: [PATCH 027/566] #103 Migrate to ESLint (#112) - Installed and configured ESLint, removed TSLint - Migrated TSLint rules to ESLint were applicable; added additional rules via eslint-plugins (i.e. header, import, no-null) - Updated VSCode settings and extension recommendations - Fix small issues that didn't match the linting rules - Ensure consistent single quote formatting Signed-off-by: Nina Doschek --- examples/workflow-glsp/.eslintrc.js | 15 ++ examples/workflow-glsp/package.json | 9 +- examples/workflow-glsp/src/di.config.ts | 20 +- .../src/direct-task-editing/di.config.ts | 6 +- .../direct-task-editing/direct-task-editor.ts | 30 +-- examples/workflow-glsp/src/index.ts | 8 +- examples/workflow-glsp/src/model.ts | 15 +- examples/workflow-glsp/src/workflow-views.tsx | 18 +- examples/workflow-standalone/.eslintrc.js | 15 ++ examples/workflow-standalone/package.json | 4 +- examples/workflow-standalone/src/di.config.ts | 10 +- examples/workflow-standalone/src/main.ts | 24 +-- packages/client/.eslintrc.js | 15 ++ packages/client/package.json | 3 +- packages/client/src/base/action-dispatcher.ts | 10 +- .../src/base/actions/context-actions.ts | 12 +- .../src/base/actions/edit-mode-action.ts | 12 +- .../base/actions/edit-validation-actions.ts | 13 +- .../src/base/actions/focus-change-action.ts | 2 +- .../src/base/actions/protocol-actions.ts | 10 +- packages/client/src/base/args.ts | 4 +- .../auto-complete/auto-complete-actions.ts | 4 +- .../auto-complete/auto-complete-widget.ts | 60 +++--- .../auto-complete/validation-decorator.ts | 15 +- packages/client/src/base/command-stack.ts | 6 +- packages/client/src/base/di.config.ts | 48 +++-- .../src/base/drag-aware-mouse-listener.ts | 10 +- packages/client/src/base/editor-context.ts | 26 +-- packages/client/src/base/focus-tracker.ts | 6 +- .../base/model-initialization-constraint.ts | 24 ++- .../src/base/model/update-model-command.ts | 16 +- .../client/src/base/operations/operation.ts | 34 ++-- .../base/selection-clearing-mouse-listener.ts | 7 +- .../base/tool-manager/glsp-tool-manager.ts | 24 +-- packages/client/src/base/types.ts | 40 ++-- .../src/features/change-bounds/model.ts | 16 +- .../change-bounds/movement-restrictor.ts | 22 ++- .../client/src/features/change-bounds/snap.ts | 8 +- .../command-palette/command-palette-tool.ts | 8 +- .../src/features/command-palette/di.config.ts | 14 +- .../server-command-palette-provider.ts | 16 +- .../delete-element-context-menu.ts | 18 +- .../src/features/context-menu/di.config.ts | 26 ++- ...rvice-aware-context-menu-mouse-listener.ts | 8 +- .../server-context-menu-provider.ts | 12 +- .../features/copy-paste/copy-paste-actions.ts | 16 +- .../copy-paste/copy-paste-context-menu.ts | 26 +-- .../features/copy-paste/copy-paste-handler.ts | 28 +-- .../src/features/copy-paste/di.config.ts | 10 +- .../features/decoration/decoration-placer.ts | 7 +- .../src/features/decoration/di.config.ts | 10 +- .../client/src/features/decoration/view.tsx | 10 +- .../src/features/edit-label/di.config.ts | 10 +- .../features/edit-label/edit-label-tool.ts | 8 +- .../edit-label/edit-label-validator.ts | 16 +- .../client/src/features/hints/di.config.ts | 10 +- packages/client/src/features/hints/model.ts | 8 +- .../hints/request-type-hints-action.ts | 10 +- .../client/src/features/hints/type-hints.ts | 50 ++--- .../client/src/features/hover/di.config.ts | 10 +- packages/client/src/features/hover/hover.ts | 16 +- .../client/src/features/layout/di.config.ts | 6 +- .../features/layout/layout-commands.spec.ts | 103 +++++----- .../src/features/layout/layout-commands.ts | 58 +++--- .../model-source-watcher/di.config.ts | 8 +- .../model-source-changed-action-handler.ts | 16 +- .../model-source-changed-action.ts | 2 +- .../src/features/mouse-tool/di.config.ts | 8 +- .../src/features/mouse-tool/mouse-tool.ts | 30 +-- .../src/features/navigation/di.config.ts | 8 +- .../navigation/navigation-action-handler.ts | 56 +++--- .../navigation-target-resolver.spec.ts | 9 +- .../navigation/navigation-target-resolver.ts | 18 +- packages/client/src/features/rank/model.ts | 2 +- .../client/src/features/reconnect/model.ts | 15 +- .../client/src/features/save/di.config.ts | 6 +- packages/client/src/features/save/model.ts | 2 +- packages/client/src/features/save/save.ts | 6 +- .../client/src/features/select/di.config.ts | 12 +- .../features/select/select-feedback-action.ts | 4 +- .../features/select/select-mouse-listener.ts | 4 +- .../features/select/selection-service.spec.ts | 31 +-- .../src/features/select/selection-service.ts | 25 ++- .../change-bounds-tool-feedback.ts | 53 +++-- .../tool-feedback/creation-tool-feedback.ts | 26 +-- .../features/tool-feedback/css-feedback.ts | 20 +- .../src/features/tool-feedback/di.config.ts | 22 +-- .../tool-feedback/edge-edit-tool-feedback.ts | 37 ++-- .../feedback-action-dispatcher.ts | 12 +- .../tool-feedback/marquee-tool-feedback.ts | 15 +- .../src/features/tool-feedback/model.ts | 2 +- .../src/features/tool-feedback/view.tsx | 11 +- .../src/features/tool-palette/di.config.ts | 8 +- .../src/features/tool-palette/palette-item.ts | 6 +- .../src/features/tool-palette/tool-palette.ts | 186 +++++++++--------- .../src/features/tools/base-glsp-tool.ts | 20 +- .../src/features/tools/change-bounds-tool.ts | 42 ++-- .../client/src/features/tools/delete-tool.ts | 30 +-- .../client/src/features/tools/di.config.ts | 30 +-- .../src/features/tools/edge-creation-tool.ts | 32 +-- .../src/features/tools/edge-edit-tool.ts | 77 ++++---- .../src/features/tools/marquee-mouse-tool.ts | 87 +++++--- .../client/src/features/tools/marquee-tool.ts | 11 +- packages/client/src/features/tools/model.ts | 3 +- .../src/features/tools/node-creation-tool.ts | 26 +-- packages/client/src/features/tools/view.tsx | 13 +- .../client/src/features/undo-redo/model.ts | 2 +- .../src/features/validation/di.config.ts | 10 +- .../src/features/validation/issue-marker.ts | 6 +- .../validation/marker-navigator.spec.ts | 45 ++--- .../features/validation/marker-navigator.ts | 35 ++-- .../src/features/validation/validate.ts | 45 +++-- .../client/src/features/viewport/di.config.ts | 8 +- .../viewport/glsp-scroll-mouse-listener.ts | 15 +- packages/client/src/index.ts | 38 ++-- packages/client/src/lib/model.ts | 8 +- .../src/model-source/glsp-diagram-server.ts | 30 +-- .../src/model-source/glsp-server-status.ts | 10 +- packages/client/src/utils/layout-utils.ts | 20 +- packages/client/src/utils/marker.ts | 18 +- packages/client/src/utils/smodel-util.ts | 30 +-- packages/client/src/utils/viewpoint-util.ts | 5 +- packages/protocol/.eslintrc.js | 15 ++ packages/protocol/package.json | 3 +- packages/protocol/src/glsp-client.ts | 14 +- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 10 +- .../src/jsonrpc/glsp-jsonrpc-client.ts | 14 +- packages/protocol/src/launch-util.ts | 2 +- 128 files changed, 1357 insertions(+), 1192 deletions(-) create mode 100644 examples/workflow-glsp/.eslintrc.js create mode 100644 examples/workflow-standalone/.eslintrc.js create mode 100644 packages/client/.eslintrc.js create mode 100644 packages/protocol/.eslintrc.js diff --git a/examples/workflow-glsp/.eslintrc.js b/examples/workflow-glsp/.eslintrc.js new file mode 100644 index 0000000..09bded5 --- /dev/null +++ b/examples/workflow-glsp/.eslintrc.js @@ -0,0 +1,15 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + '**/{css,node_modules,lib,scripts}' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 9410847..baeec88 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -32,16 +32,15 @@ "balloon-css": "^0.5.0" }, "devDependencies": { + "mvn-artifact-download": "5.1.0", "rimraf": "^2.6.1", - "tslint": "^5.5.0", - "typescript": "^3.9.2", - "mvn-artifact-download": "5.1.0" + "typescript": "^3.9.2" }, "scripts": { "prepare": "yarn run clean && yarn run build", "clean": "rimraf lib", "build": "tsc && yarn run lint", - "lint": "tslint -c ../../configs/tslint.json --project ./tsconfig.json", + "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w" }, "files": [ @@ -56,4 +55,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index d45e3fc..edf2831 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../css/diagram.css"; -import "balloon-css/balloon.min.css"; -import "sprotty/css/edit-label.css"; +import '../css/diagram.css'; +import 'balloon-css/balloon.min.css'; +import 'sprotty/css/edit-label.css'; import { boundsModule, @@ -42,6 +42,7 @@ import { glspMouseToolModule, glspSelectModule, glspServerCopyPasteModule, + glspViewportModule, GridSnapper, HtmlRoot, HtmlRootView, @@ -74,14 +75,13 @@ import { toolsModule, TYPES, validationModule, - glspViewportModule, zorderModule -} from "@eclipse-glsp/client"; -import { Container, ContainerModule } from "inversify"; +} from '@eclipse-glsp/client'; +import { Container, ContainerModule } from 'inversify'; -import { directTaskEditor } from "./direct-task-editing/di.config"; -import { ActivityNode, Icon, TaskNode, WeightedEdge } from "./model"; -import { ForkOrJoinNodeView, IconView, TaskNodeView, WeightedEdgeView, WorkflowEdgeView } from "./workflow-views"; +import { directTaskEditor } from './direct-task-editing/di.config'; +import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; +import { ForkOrJoinNodeView, IconView, TaskNodeView, WeightedEdgeView, WorkflowEdgeView } from './workflow-views'; const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -123,7 +123,7 @@ export default function createContainer(widgetId: string): Container { overrideViewerOptions(container, { baseDiv: widgetId, - hiddenDiv: widgetId + "_hidden" + hiddenDiv: widgetId + '_hidden' }); return container; diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts index cb2e5df..935f657 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/di.config.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { TYPES } from 'sprotty'; -import { TaskEditor } from "./direct-task-editor"; +import { TaskEditor } from './direct-task-editor'; export const directTaskEditor = new ContainerModule((bind, _unbind, isBound) => { bind(TaskEditor).toSelf().inSingletonScope(); diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index c48a263..740197d 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -32,12 +32,12 @@ import { ValidationDecorator, ValidationStatus, ViewerOptions -} from "@eclipse-glsp/client"; -import { inject, injectable } from "inversify"; -import { TYPES } from "sprotty/lib"; -import { DOMHelper } from "sprotty/lib/base/views/dom-helper"; +} from '@eclipse-glsp/client'; +import { inject, injectable } from 'inversify'; +import { TYPES } from 'sprotty/lib'; +import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; -import { isTaskNode, TaskNode } from "../model"; +import { isTaskNode, TaskNode } from '../model'; export class ApplyTaskEditOperation implements Action { static readonly KIND = 'applyTaskEdit'; @@ -74,8 +74,12 @@ export class TaskEditor extends AbstractUIExtension { protected task: TaskNode; protected autoSuggestion: AutoCompleteWidget; - id() { return TaskEditor.ID; } - containerClass() { return 'command-palette'; } + id(): string { + return TaskEditor.ID; + } + containerClass(): string { + return 'command-palette'; + } protected initializeContents(containerElement: HTMLElement): void { this.autoSuggestion = new AutoCompleteWidget( @@ -93,18 +97,18 @@ export class TaskEditor extends AbstractUIExtension { this.autoSuggestion.initialize(containerElement); } - show(root: Readonly, ...contextElementIds: string[]) { + show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.autoSuggestion.open(root); } - protected onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]) { + protected onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { this.task = getTask(contextElementIds, root)[0]; this.autoSuggestion.inputField.value = ''; this.setPosition(containerElement); } - protected setPosition(containerElement: HTMLElement) { + protected setPosition(containerElement: HTMLElement): void { let x = 0; let y = 0; @@ -136,16 +140,16 @@ export class TaskEditor extends AbstractUIExtension { return Promise.reject(); } - protected executeFromSuggestion(input: LabeledAction | Action[] | Action) { + protected executeFromSuggestion(input: LabeledAction | Action[] | Action): void { this.actionDispatcher.dispatchAll(toActionArray(input)); } - protected executeFromTextOnlyInput(input: string) { + protected executeFromTextOnlyInput(input: string): void { const action = new ApplyTaskEditOperation(this.task.id, input); this.actionDispatcher.dispatch(action); } - hide() { + hide(): void { this.autoSuggestion.dispose(); super.hide(); } diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index d422ce8..37d541c 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import createWorkflowDiagramContainer from "./di.config"; +import createWorkflowDiagramContainer from './di.config'; -export * from "./model"; -export * from "./workflow-views"; -export * from "./direct-task-editing/direct-task-editor"; +export * from './model'; +export * from './workflow-views'; +export * from './direct-task-editing/direct-task-editor'; export { createWorkflowDiagramContainer }; diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index e068787..cbf0a6e 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -18,6 +18,7 @@ import { connectableFeature, deletableFeature, DiamondNode, + EditableLabel, fadeFeature, hoverFeedbackFeature, isEditableLabel, @@ -29,24 +30,24 @@ import { nameFeature, popupFeature, RectangularNode, + SChildElement, SEdge, selectFeature, SModelElement, SShapeElement, WithEditableLabel, withEditLabelFeature -} from "@eclipse-glsp/client"; - +} from '@eclipse-glsp/client'; export class TaskNode extends RectangularNode implements Nameable, WithEditableLabel { static readonly DEFAULT_FEATURES = [connectableFeature, deletableFeature, selectFeature, boundsFeature, moveFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature, nameFeature, withEditLabelFeature]; - name: string = ""; + name = ''; duration?: number; taskType?: string; reference?: string; - get editableLabel() { + get editableLabel(): (SChildElement & EditableLabel) | undefined { const headerComp = this.children.find(element => element.type === 'comp:header'); if (headerComp) { const label = headerComp.children.find(element => element.type === 'label:heading'); @@ -66,7 +67,6 @@ export class WeightedEdge extends SEdge { probability?: string; } - export class ActivityNode extends DiamondNode { nodeType: string = ActivityNode.Type.UNDEFINED; size = { @@ -76,6 +76,7 @@ export class ActivityNode extends DiamondNode { strokeWidth = 1; } +// eslint-disable-next-line no-redeclare export namespace ActivityNode { export namespace Type { export const INITIAL = 'initalNode'; @@ -84,7 +85,7 @@ export namespace ActivityNode { export const MERGE = 'mergeNode'; export const JOIN = 'joinNode'; export const FORK = 'forkNode'; - export const UNDEFINED = "undefined"; + export const UNDEFINED = 'undefined'; } } @@ -92,7 +93,7 @@ export class Icon extends SShapeElement implements LayoutContainer { static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature]; layout: string; - layoutOptions?: { [key: string]: string | number | boolean; }; + layoutOptions?: { [key: string]: string | number | boolean }; size = { width: 32, height: 32 diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index a9d83e1..9640ba4 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -23,13 +23,14 @@ import { SEdge, SShapeElement, toDegrees -} from "@eclipse-glsp/client"; -import { injectable } from "inversify"; -import * as snabbdom from "snabbdom-jsx"; -import { VNode } from "snabbdom/vnode"; +} from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { VNode } from 'snabbdom/vnode'; -import { ActivityNode, Icon, TaskNode, WeightedEdge } from "./model"; +import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: snabbdom.svg }; @injectable() @@ -82,8 +83,9 @@ export class WeightedEdgeView extends WorkflowEdgeView { render(edge: Readonly, context: RenderingContext): VNode { const router = this.edgeRouterRegistry.get(edge.routerKind); const route = router.route(edge); - if (route.length === 0) - return this.renderDanglingEdge("Cannot compute route", edge, context); + if (route.length === 0) { + return this.renderDanglingEdge('Cannot compute route', edge, context); + } return ; } - getRadius() { + getRadius(): number { return 16; } } diff --git a/examples/workflow-standalone/.eslintrc.js b/examples/workflow-standalone/.eslintrc.js new file mode 100644 index 0000000..c7982ca --- /dev/null +++ b/examples/workflow-standalone/.eslintrc.js @@ -0,0 +1,15 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + '**/{css,node_modules,lib}' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index aab234b..dbaffd4 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -47,8 +47,6 @@ "source-map-loader": "^0.2.3", "style-loader": "^0.23.1", "ts-loader": "^4.4.2", - "tslint": "^5.5.0", - "tslint-loader": "^3.4.3", "typescript": "^3.9.2", "typings": "^2.1.0", "webpack": "^4.15.1", @@ -60,7 +58,7 @@ "scripts": { "prepare": "yarn run clean && yarn build", "clean": "rimraf lib app/bundle.js app/bundle.js.map app/css", - "lint": "tslint -c ../../configs/tslint.json --project ./tsconfig.json", + "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "build": "tsc && yarn lint && webpack", "watch": "tsc -w -p ./tsconfig.json" } diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index e265d2b..1d183fd 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../css/diagram.css"; +import '../css/diagram.css'; -import { createWorkflowDiagramContainer } from "@eclipse-glsp-examples/workflow-glsp/lib"; -import { GLSPDiagramServer } from "@eclipse-glsp/client"; -import { Container } from "inversify"; -import { ConsoleLogger, LogLevel, TYPES } from "sprotty"; +import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp/lib'; +import { GLSPDiagramServer } from '@eclipse-glsp/client'; +import { Container } from 'inversify'; +import { ConsoleLogger, LogLevel, TYPES } from 'sprotty'; export default function createContainer(): Container { const container = createWorkflowDiagramContainer('sprotty'); diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/main.ts index 03ac7ca..78f6195 100644 --- a/examples/workflow-standalone/src/main.ts +++ b/examples/workflow-standalone/src/main.ts @@ -13,23 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "reflect-metadata"; +import 'reflect-metadata'; import { EnableToolPaletteAction, GLSPDiagramServer, InitializeClientSessionAction, RequestTypeHintsAction -} from "@eclipse-glsp/client"; -import { ApplicationIdProvider, BaseJsonrpcGLSPClient, JsonrpcGLSPClient } from "@eclipse-glsp/protocol"; -import { join, resolve } from "path"; -import { IActionDispatcher, RequestModelAction, TYPES } from "sprotty"; +} from '@eclipse-glsp/client'; +import { ApplicationIdProvider, BaseJsonrpcGLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; +import { join, resolve } from 'path'; +import { IActionDispatcher, RequestModelAction, TYPES } from 'sprotty'; -import createContainer from "./di.config"; +import createContainer from './di.config'; const port = 8081; -const id = "workflow"; -const name = "workflow"; +const id = 'workflow'; +const name = 'workflow'; const websocket = new WebSocket(`ws://localhost:${port}/${id}`); const container = createContainer(); @@ -38,7 +38,7 @@ const currentDir = loc.substring(0, loc.lastIndexOf('/')); const examplePath = resolve(join(currentDir, '..', 'app', 'example1.wf')); const diagramServer = container.get(TYPES.ModelSource); -diagramServer.clientId = ApplicationIdProvider.get() + "_" + examplePath; +diagramServer.clientId = ApplicationIdProvider.get() + '_' + examplePath; const actionDispatcher = container.get(TYPES.IActionDispatcher); @@ -50,12 +50,12 @@ websocket.onopen = () => { actionDispatcher.dispatch(new InitializeClientSessionAction(diagramServer.clientId)); actionDispatcher.dispatch(new RequestModelAction({ sourceUri: `file://${examplePath}`, - diagramType: "workflow-diagram", + diagramType: 'workflow-diagram' })); - actionDispatcher.dispatch(new RequestTypeHintsAction("workflow-diagram")); + actionDispatcher.dispatch(new RequestTypeHintsAction('workflow-diagram')); actionDispatcher.dispatch(new EnableToolPaletteAction()); }); }; -websocket.onerror = (ev) => alert("Connection to server errored. Please make sure that the server is running"); +websocket.onerror = ev => alert('Connection to server errored. Please make sure that the server is running'); diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js new file mode 100644 index 0000000..c7982ca --- /dev/null +++ b/packages/client/.eslintrc.js @@ -0,0 +1,15 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + '**/{css,node_modules,lib}' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/packages/client/package.json b/packages/client/package.json index 4feca19..35f132b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -59,14 +59,13 @@ "reflect-metadata": "^0.1.13", "rimraf": "^2.6.1", "ts-node": "^8.3.0", - "tslint": "^5.5.0", "typescript": "^3.9.2" }, "scripts": { "prepare": "yarn run clean && yarn run build", "clean": "rimraf lib", "build": "tsc && yarn run lint", - "lint": "tslint -c ../../tslint.json --project ./tsconfig.json", + "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"" }, diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 249f349..c98c86e 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject } from "inversify"; -import { Action, ActionDispatcher, isResponseAction, RequestAction, ResponseAction } from "sprotty"; +import { inject } from 'inversify'; +import { Action, ActionDispatcher, isResponseAction, RequestAction, ResponseAction } from 'sprotty'; -import { ModelInitializationConstraint } from "./model-initialization-constraint"; +import { ModelInitializationConstraint } from './model-initialization-constraint'; export class GLSPActionDispatcher extends ActionDispatcher { @@ -29,7 +29,7 @@ export class GLSPActionDispatcher extends ActionDispatcher { return super.initialize().then(() => this.startModelInitialization()); } - startModelInitialization() { + startModelInitialization(): void { this.logger.log(this, 'Starting model initialization mode'); this.onModelInitialized = this.initiailizationConstraint.onInitialized(); this.onModelInitialized.then(() => this.logger.log(this, 'Model initialization completed')); @@ -74,7 +74,7 @@ export class GLSPActionDispatcher extends ActionDispatcher { * If `rejectOnTimeout` is set to false (default) the returned promise will be resolved with * no value, otherwise it will be rejected. */ - requestUntil(action: RequestAction, timeoutMs: number = 2000, rejectOnTimeout: boolean = false): Promise { + requestUntil(action: RequestAction, timeoutMs = 2000, rejectOnTimeout = false): Promise { if (!action.requestId) { return Promise.reject(new Error('Request without requestId')); } diff --git a/packages/client/src/base/actions/context-actions.ts b/packages/client/src/base/actions/context-actions.ts index f68a682..44d9103 100644 --- a/packages/client/src/base/actions/context-actions.ts +++ b/packages/client/src/base/actions/context-actions.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, generateRequestId, LabeledAction, RequestAction, ResponseAction } from "sprotty"; +import { Action, generateRequestId, LabeledAction, RequestAction, ResponseAction } from 'sprotty'; -import { Args } from "../args"; -import { EditorContext } from "../editor-context"; +import { Args } from '../args'; +import { EditorContext } from '../editor-context'; export class RequestContextActions implements RequestAction { - static readonly KIND = "requestContextActions"; + static readonly KIND = 'requestContextActions'; constructor( public readonly contextId: string, public readonly editorContext: EditorContext, @@ -28,7 +28,7 @@ export class RequestContextActions implements RequestAction { } export class SetContextActions implements ResponseAction { - static readonly KIND = "setContextActions"; + static readonly KIND = 'setContextActions'; constructor( public readonly actions: LabeledAction[], public readonly responseId: string = '', @@ -39,5 +39,5 @@ export class SetContextActions implements ResponseAction { export function isSetContextActionsAction(action: Action): action is SetContextActions { return action !== undefined && (action.kind === SetContextActions.KIND) - && (action).actions !== undefined; + && (action as SetContextActions).actions !== undefined; } diff --git a/packages/client/src/base/actions/edit-mode-action.ts b/packages/client/src/base/actions/edit-mode-action.ts index 0696031..dc9cd16 100644 --- a/packages/client/src/base/actions/edit-mode-action.ts +++ b/packages/client/src/base/actions/edit-mode-action.ts @@ -13,21 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { Action } from "sprotty"; +import { injectable } from 'inversify'; +import { Action } from 'sprotty'; @injectable() export class SetEditModeAction implements Action { - static readonly KIND = "setEditMode"; + static readonly KIND = 'setEditMode'; constructor(public readonly editMode: string = EditMode.EDITABLE, public readonly kind: string = SetEditModeAction.KIND) { } } export function isSetEditModeAction(action: Action): action is SetEditModeAction { return action !== undefined && (action.kind === SetEditModeAction.KIND) - && "editMode" in action && typeof action["editMode"] === "string"; + && 'editMode' in action && typeof action['editMode'] === 'string'; } export namespace EditMode { - export const READONLY: string = "readonly"; - export const EDITABLE: string = "editable"; + export const READONLY = 'readonly'; + export const EDITABLE = 'editable'; } diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts index 8101a45..df6b223 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, generateRequestId, RequestAction, ResponseAction } from "sprotty"; +import { Action, generateRequestId, RequestAction, ResponseAction } from 'sprotty'; -import { Args } from "../args"; +import { Args } from '../args'; export class RequestEditValidationAction implements RequestAction { - static readonly KIND = "requestEditValidation"; + static readonly KIND = 'requestEditValidation'; constructor( public readonly contextId: string, public readonly modelElementId: string, @@ -28,7 +28,7 @@ export class RequestEditValidationAction implements RequestActionaction).status !== undefined; + && (action as SetEditValidationResultAction).status !== undefined; } export interface ValidationStatus { @@ -72,12 +72,13 @@ export interface ResponseError { /** * Additional custom data, e.g., a serialized stacktrace. */ - readonly data: Object; + readonly data: Record; } export namespace ValidationStatus { export enum Severity { + // eslint-disable-next-line no-shadow FATAL, ERROR, WARNING, INFO, OK, NONE } diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/actions/focus-change-action.ts index bf13e7e..da7afcd 100644 --- a/packages/client/src/base/actions/focus-change-action.ts +++ b/packages/client/src/base/actions/focus-change-action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from "sprotty"; +import { Action } from 'sprotty'; export class FocusStateChangedAction implements Action { static KIND = 'focusStateChanged'; diff --git a/packages/client/src/base/actions/protocol-actions.ts b/packages/client/src/base/actions/protocol-actions.ts index 76b59b2..37d171b 100644 --- a/packages/client/src/base/actions/protocol-actions.ts +++ b/packages/client/src/base/actions/protocol-actions.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } from 'sprotty'; /** * Initializes the graphical representation (diagram) for a specific client session. @@ -23,7 +23,7 @@ import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } fro */ @injectable() export class InitializeClientSessionAction implements Action { - static readonly KIND = "initializeClientSession"; + static readonly KIND = 'initializeClientSession'; constructor(public readonly clientId: string, public readonly kind: string = InitializeClientSessionAction.KIND) { } } @@ -37,7 +37,7 @@ export function initializeClientSessionAction(action: Action): action is Initial */ @injectable() export class DisposeClientSessionAction implements Action { - static readonly KIND = "disposeClientSession"; + static readonly KIND = 'disposeClientSession'; constructor(public readonly clientId: string, public readonly kind: string = DisposeClientSessionAction.KIND) { } } @@ -51,7 +51,7 @@ export function isDisposeClientSessionAction(action: Action): action is DisposeC */ @injectable() export class ConfigureServerHandlersAction implements Action { - static readonly KIND = "configureServerHandlers"; + static readonly KIND = 'configureServerHandlers'; readonly kind = ConfigureServerHandlersAction.KIND; constructor(readonly actionKinds: string[]) { } diff --git a/packages/client/src/base/args.ts b/packages/client/src/base/args.ts index ebaf822..655859e 100644 --- a/packages/client/src/base/args.ts +++ b/packages/client/src/base/args.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement } from "sprotty"; +import { SModelElement } from 'sprotty'; -export type Args = { [key: string]: string | number | boolean }; +export interface Args { [key: string]: string | number | boolean } export interface SArgumentable { args: Args; diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts index 9d57dd7..2532a02 100644 --- a/packages/client/src/base/auto-complete/auto-complete-actions.ts +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LabeledAction } from "sprotty/lib"; +import { LabeledAction } from 'sprotty/lib'; export interface SetAutoCompleteValueAction extends LabeledAction { readonly text: string; } export function isSetAutoCompleteValueAction(action: LabeledAction): action is SetAutoCompleteValueAction { - return action !== undefined && (action).text !== undefined; + return action !== undefined && (action as SetAutoCompleteValueAction).text !== undefined; } diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index ddfddd4..c4ec0d0 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AutocompleteResult, AutocompleteSettings } from "autocompleter"; -import { Action, ILogger, isAction, isLabeledAction, LabeledAction, SModelRoot } from "sprotty/lib"; -import { toArray } from "sprotty/lib/utils/iterable"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; +import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; +import { Action, ILogger, isAction, isLabeledAction, LabeledAction, SModelRoot } from 'sprotty/lib'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { ValidationStatus } from "../actions/edit-validation-actions"; -import { isSetAutoCompleteValueAction } from "./auto-complete-actions"; -import { IValidationDecorator } from "./validation-decorator"; +import { ValidationStatus } from '../actions/edit-validation-actions'; +import { isSetAutoCompleteValueAction } from './auto-complete-actions'; +import { IValidationDecorator } from './validation-decorator'; export interface AutoCompleteSettings { readonly noSuggestionsMessage?: string; @@ -49,6 +49,7 @@ export interface TextSubmitHandler { executeFromTextOnlyInput(input: string): void; } +// eslint-disable-next-line @typescript-eslint/no-var-requires const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); export class AutoCompleteWidget { @@ -70,6 +71,7 @@ export class AutoCompleteWidget { public autoSuggestionSettings: AutoCompleteSettings, public suggestionProvider: SuggestionProvider, public suggestionSubmitHandler: SuggestionSubmitHandler, + // eslint-disable-next-line @typescript-eslint/no-empty-function protected notifyClose: () => void = () => { }, protected logger?: ILogger ) { } @@ -103,7 +105,7 @@ export class AutoCompleteWidget { return inputElement; } - protected handleKeyDown(event: KeyboardEvent) { + protected handleKeyDown(event: KeyboardEvent): void { if (matchesKeystroke(event, 'Escape')) { this.notifyClose(); return; @@ -127,16 +129,16 @@ export class AutoCompleteWidget { } } - protected isInputElementChanged() { + protected isInputElementChanged(): boolean { return this.inputElement.value !== this.previousContent; } - protected invalidateValidationResultAndContextActions() { + protected invalidateValidationResultAndContextActions(): void { this.contextActions = undefined; this.validationDecorator.invalidate(); } - open(root: Readonly, ...contextElementIds: string[]) { + open(root: Readonly, ...contextElementIds: string[]): void { this.contextActions = undefined; this.autoCompleteResult = configureAutocomplete(this.autocompleteSettings(root)); this.previousContent = this.inputElement.value; @@ -163,14 +165,14 @@ export class AutoCompleteWidget { }; } - protected customizeInputElement(input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) { + protected customizeInputElement(input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number): void { // move container into our UIExtension container as this is already positioned correctly if (this.containerElement) { this.containerElement.appendChild(container); } } - protected updateSuggestions(update: (items: LabeledAction[]) => void, text: string, root: Readonly, ...contextElementIds: string[]) { + protected updateSuggestions(update: (items: LabeledAction[]) => void, text: string, root: Readonly, ...contextElementIds: string[]): void { this.onLoading(); this.doUpdateSuggestions(text, root) .then(actions => { @@ -179,12 +181,14 @@ export class AutoCompleteWidget { this.onLoaded('success'); }) .catch(reason => { - if (this.logger) { this.logger.error(this, 'Failed to obtain suggestions', reason); } + if (this.logger) { + this.logger.error(this, 'Failed to obtain suggestions', reason); + } this.onLoaded('error'); }); } - protected onLoading() { + protected onLoading(): void { if (this.loadingIndicator && this.containerElement.contains(this.loadingIndicator)) { return; } @@ -197,7 +201,7 @@ export class AutoCompleteWidget { return this.suggestionProvider.provideSuggestions(text); } - protected onLoaded(_success: 'success' | 'error') { + protected onLoaded(_success: 'success' | 'error'): void { if (this.containerElement.contains(this.loadingIndicator)) { this.containerElement.removeChild(this.loadingIndicator); } @@ -206,7 +210,7 @@ export class AutoCompleteWidget { this.previousContent = this.inputElement.value; } - protected renderSuggestions(item: LabeledAction, value: string) { + protected renderSuggestions(item: LabeledAction, value: string): HTMLDivElement { const itemElement = document.createElement('div'); const wordMatcher = this.espaceForRegExp(value).split(' ').join('|'); const regex = new RegExp(wordMatcher, 'gi'); @@ -217,11 +221,11 @@ export class AutoCompleteWidget { return itemElement; } - protected espaceForRegExp(value: string) { + protected espaceForRegExp(value: string): string { return value.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } - protected renderIcon(itemElement: HTMLDivElement, icon: string) { + protected renderIcon(itemElement: HTMLDivElement, icon: string): void { itemElement.innerHTML += ``; } @@ -233,7 +237,7 @@ export class AutoCompleteWidget { })); } - protected onSelect(item: LabeledAction) { + protected onSelect(item: LabeledAction): void { if (isSetAutoCompleteValueAction(item)) { this.inputElement.value = item.text; // trigger update of suggestions with an keyup event @@ -244,7 +248,7 @@ export class AutoCompleteWidget { } } - protected validateInputIfNoContextActions() { + protected validateInputIfNoContextActions(): void { if (this.isNoOrExactlyOneMatchingContextAction()) { this.validateInput(); } else { @@ -252,18 +256,18 @@ export class AutoCompleteWidget { } } - private isNoOrExactlyOneMatchingContextAction() { + private isNoOrExactlyOneMatchingContextAction(): boolean | undefined { return !this.isSuggestionAvailable() || (this.contextActions && this.contextActions.length === 1 && this.inputElement.value.endsWith(this.contextActions[0].label)); } - protected isSuggestionAvailable() { + protected isSuggestionAvailable(): boolean | undefined { return this.contextActions && this.contextActions.length > 0; } - validateInput() { + validateInput(): void { if (this.inputValidator) { this.inputValidator.validate(this.inputElement.value) .then(result => this.validationDecorator.decorateValidationResult(result)) @@ -271,8 +275,10 @@ export class AutoCompleteWidget { } } - protected handleErrorDuringValidation(error: Error) { - if (this.logger) { this.logger.error(this, 'Failed to validate input', error); } + protected handleErrorDuringValidation(error: Error): void { + if (this.logger) { + this.logger.error(this, 'Failed to validate input', error); + } this.validationDecorator.dispose(); } @@ -290,7 +296,7 @@ export class AutoCompleteWidget { return this.inputElement; } - dispose() { + dispose(): void { this.validationDecorator.dispose(); if (this.autoCompleteResult) { this.autoCompleteResult.destroy(); diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index 1308fb8..97cbd4b 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ValidationStatus } from "../actions/edit-validation-actions"; +import { ValidationStatus } from '../actions/edit-validation-actions'; export interface IValidationDecorator { decorateValidationResult(status: ValidationStatus): void; @@ -24,9 +24,14 @@ export interface IValidationDecorator { export namespace IValidationDecorator { export const NO_DECORATION: IValidationDecorator = { + // eslint-disable-next-line @typescript-eslint/no-empty-function decorateValidationResult(_status: ValidationStatus) { }, - isValidatedOk(): boolean { return false; }, + isValidatedOk(): boolean { + return false; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function invalidate() { }, + // eslint-disable-next-line @typescript-eslint/no-empty-function dispose() { } }; } @@ -37,8 +42,8 @@ export class ValidationDecorator implements IValidationDecorator { errorClasses = ['error']; errorIconClasses = ['fa', 'fa-exclamation-circle']; - isValidated: boolean = false; - hasValidationError: boolean = false; + isValidated = false; + hasValidationError = false; protected decorationDiv?: HTMLDivElement; @@ -74,7 +79,7 @@ export class ValidationDecorator implements IValidationDecorator { this.adjustPosition(); } - protected switchCssClasses(element: HTMLElement, cssClasses: string[]) { + protected switchCssClasses(element: HTMLElement, cssClasses: string[]): void { element.classList.remove(...this.errorClasses, ...this.warningClasses); element.classList.add(...cssClasses); } diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 98ed2e0..c88a2f4 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from 'sprotty'; -import { RedoOperation, UndoOperation } from "../features/undo-redo/model"; +import { RedoOperation, UndoOperation } from '../features/undo-redo/model'; @injectable() export class GLSPCommandStack extends CommandStack { diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 20aa364..94d36ca 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -13,38 +13,34 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../../css/glsp-sprotty.css"; +import '../../css/glsp-sprotty.css'; -import { ContainerModule } from "inversify"; -import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from 'sprotty'; -import { GLSPActionDispatcher } from "./action-dispatcher"; -import { SetEditModeAction } from "./actions/edit-mode-action"; -import { FocusStateChangedAction } from "./actions/focus-change-action"; -import { ConfigureServerHandlersAction, ConfigureServerHandlersActionHandler } from "./actions/protocol-actions"; -import { GLSPCommandStack } from "./command-stack"; -import { EditorContextService } from "./editor-context"; -import { FocusTracker } from "./focus-tracker"; -import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from "./model-initialization-constraint"; -import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from "./model/update-model-command"; -import { SelectionClearingMouseListener } from "./selection-clearing-mouse-listener"; -import { GLSPToolManager } from "./tool-manager/glsp-tool-manager"; -import { GLSP_TYPES } from "./types"; +import { GLSPActionDispatcher } from './action-dispatcher'; +import { SetEditModeAction } from './actions/edit-mode-action'; +import { FocusStateChangedAction } from './actions/focus-change-action'; +import { ConfigureServerHandlersAction, ConfigureServerHandlersActionHandler } from './actions/protocol-actions'; +import { GLSPCommandStack } from './command-stack'; +import { EditorContextService } from './editor-context'; +import { FocusTracker } from './focus-tracker'; +import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; +import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from './model/update-model-command'; +import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; +import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; +import { GLSP_TYPES } from './types'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, _unbind, isBound, rebind }; bind(EditorContextService).toSelf().inSingletonScope(); - bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider(ctx => { - return () => { - return new Promise((resolve, reject) => { - if (ctx.container.isBound(EditorContextService)) { - resolve(ctx.container.get(EditorContextService)); - } else { - reject(); - } - }); - }; - }); + bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider(ctx => () => new Promise((resolve, reject) => { + if (ctx.container.isBound(EditorContextService)) { + resolve(ctx.container.get(EditorContextService)); + } else { + reject(); + } + })); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index af10e32..1b7fcac 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MouseListener, SModelElement } from "sprotty"; +import { Action, MouseListener, SModelElement } from 'sprotty'; /** * A mouse listener that is aware of prior mouse dragging. @@ -25,8 +25,8 @@ import { Action, MouseListener, SModelElement } from "sprotty"; */ export class DragAwareMouseListener extends MouseListener { - private _isMouseDown: boolean = false; - private _isMouseDrag: boolean = false; + private _isMouseDown = false; + private _isMouseDrag = false; mouseDown(target: SModelElement, event: MouseEvent): Action[] { this._isMouseDown = true; @@ -58,11 +58,11 @@ export class DragAwareMouseListener extends MouseListener { return []; } - get isMouseDrag() { + get isMouseDrag(): boolean { return this._isMouseDrag; } - get isMouseDown() { + get isMouseDown(): boolean { return this._isMouseDown; } diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context.ts index 35ea4c3..f3a40e9 100644 --- a/packages/client/src/base/editor-context.ts +++ b/packages/client/src/base/editor-context.ts @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from "inversify"; -import { Action, IActionHandler, ModelSource, MousePositionTracker, Point, SModelElement, SModelRoot, TYPES } from "sprotty"; +import { inject, injectable, multiInject, optional } from 'inversify'; +import { Action, IActionHandler, ModelSource, MousePositionTracker, Point, SModelElement, SModelRoot, TYPES } from 'sprotty'; -import { SelectionService } from "../features/select/selection-service"; -import { distinctAdd, remove } from "../utils/array-utils"; -import { EditMode, isSetEditModeAction } from "./actions/edit-mode-action"; -import { Args } from "./args"; -import { isSourceUriAware } from "./source-uri-aware"; -import { GLSP_TYPES } from "./types"; +import { SelectionService } from '../features/select/selection-service'; +import { distinctAdd, remove } from '../utils/array-utils'; +import { EditMode, isSetEditModeAction } from './actions/edit-mode-action'; +import { Args } from './args'; +import { isSourceUriAware } from './source-uri-aware'; +import { GLSP_TYPES } from './types'; /** * The `EditorContext` may be used to represent the current state of the editor for particular actions. @@ -59,11 +59,11 @@ export class EditorContextService implements IActionHandler { constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) { } - register(editModeListener: EditModeListener) { + register(editModeListener: EditModeListener): void { distinctAdd(this.editModeListeners, editModeListener); } - deregister(editModeListener: EditModeListener) { + deregister(editModeListener: EditModeListener): void { remove(this.editModeListeners, editModeListener); } @@ -83,7 +83,7 @@ export class EditorContextService implements IActionHandler { }; } - handle(action: Action) { + handle(action: Action): void { if (isSetEditModeAction(action)) { const oldValue = this._editMode; this._editMode = action.editMode; @@ -91,11 +91,11 @@ export class EditorContextService implements IActionHandler { } } - protected notifiyEditModeListeners(oldValue: string) { + protected notifiyEditModeListeners(oldValue: string): void { this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); } - async getSourceUri() { + async getSourceUri(): Promise { const modelSource = await this.modelSource(); if (isSourceUriAware(modelSource)) { return modelSource.getSourceURI(); diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts index cd06b02..a1002f2 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus-tracker.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from 'sprotty'; -import { isFocusStateChangedAction } from "./actions/focus-change-action"; +import { isFocusStateChangedAction } from './actions/focus-change-action'; @injectable() export class FocusTracker implements IActionHandler { diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts index 684816a..30c433b 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { Action, InitializeCanvasBoundsAction, UpdateModelAction } from "sprotty"; -import { Deferred } from "sprotty/lib/utils/async"; +import { injectable } from 'inversify'; +import { Action, InitializeCanvasBoundsAction, UpdateModelAction } from 'sprotty'; +import { Deferred } from 'sprotty/lib/utils/async'; /** * The constraint defining when the initialization of the GLSP model is completed. @@ -38,23 +38,27 @@ import { Deferred } from "sprotty/lib/utils/async"; @injectable() export abstract class ModelInitializationConstraint { protected completion: Deferred = new Deferred(); - protected completed: boolean = false; + protected completed = false; get isCompleted(): boolean { return this.completed; } - protected setCompleted(isCompleted: boolean) { + protected setCompleted(isCompleted: boolean): void { this.completed = isCompleted; - if (isCompleted) { this.completion.resolve(); } + if (isCompleted) { + this.completion.resolve(); + } } onInitialized(): Promise { return this.completion.promise; } - notifyDispatched(action: Action) { - if (this.isCompleted) { return; } + notifyDispatched(action: Action): void { + if (this.isCompleted) { + return; + } if (this.isInitializedAfter(action)) { this.setCompleted(true); } @@ -69,9 +73,9 @@ export abstract class ModelInitializationConstraint { */ @injectable() export class DefaultModelInitializationConstraint extends ModelInitializationConstraint { - protected seenNonEmptyUpdateModel: boolean = false; + protected seenNonEmptyUpdateModel = false; - isInitializedAfter(action: Action) { + isInitializedAfter(action: Action): boolean { if (this.isNonEmptyUpdateModel(action)) { this.seenNonEmptyUpdateModel = true; } else if (this.seenNonEmptyUpdateModel && action.kind === InitializeCanvasBoundsAction.KIND) { diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index 09523c3..584c9a4 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct } from "inversify"; +import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { Action, ActionHandlerRegistry, @@ -27,12 +27,12 @@ import { SetModelCommand, SModelRoot, TYPES -} from "sprotty"; -import { UpdateModelAction, UpdateModelCommand } from "sprotty/lib/features/update/update-model"; +} from 'sprotty'; +import { UpdateModelAction, UpdateModelCommand } from 'sprotty/lib/features/update/update-model'; -import { IFeedbackActionDispatcher } from "../../features/tool-feedback/feedback-action-dispatcher"; -import { FeedbackCommand } from "../../features/tool-feedback/model"; -import { GLSP_TYPES } from "../types"; +import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../../features/tool-feedback/model'; +import { GLSP_TYPES } from '../types'; /* ActionHandler that transforms a SetModelAction into an (feedback-aware) UpdateModelAction. This can be done because in sprotty * UpdateModel behaves the same as SetModel if no model is present yet.*/ @@ -47,11 +47,11 @@ export class SetModelActionHandler implements IActionHandler { export function isSetModelAction(action: Action): action is SetModelAction { return action !== undefined && (action.kind === SetModelCommand.KIND) - && (action).newRoot !== undefined; + && (action as SetModelAction).newRoot !== undefined; } export interface SModelRootListener { - modelRootChanged(root: Readonly): void + modelRootChanged(root: Readonly): void; } /** diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index 95a2633..d335110 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ApplyLabelEditAction, ElementAndBounds, isAction, LayoutAction, Point } from "sprotty"; +import { Action, ApplyLabelEditAction, ElementAndBounds, isAction, LayoutAction, Point } from 'sprotty'; -import { Args } from "../args"; +import { Args } from '../args'; /** * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the server. @@ -29,11 +29,11 @@ export interface CreateOperation extends Operation { } export function isCreateOperation(object?: any): object is CreateOperation { - return isAction(object) && "elementTypeId" in object; + return isAction(object) && 'elementTypeId' in object; } export class CreateNodeOperation implements CreateOperation { - static readonly KIND = "createNode"; + static readonly KIND = 'createNode'; constructor(public readonly elementTypeId: string, public location?: Point, @@ -47,7 +47,7 @@ export function isCreateNodeOperation(object?: any): object is CreateNodeOperati } export class CreateEdgeOperation implements CreateOperation { - static readonly KIND = "createEdge"; + static readonly KIND = 'createEdge'; constructor(public readonly elementTypeId: string, public sourceElementId: string, @@ -61,25 +61,25 @@ export function isCreateConnectionOperation(object?: any): object is CreateEdgeO } export class DeleteElementOperation implements Operation { - static readonly KIND = "deleteElement"; + static readonly KIND = 'deleteElement'; constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) { } } export function isDeleteElementOperation(object?: any): object is DeleteElementOperation { - return isAction(object) && object.kind === DeleteElementOperation.KIND && object.hasOwnProperty('elementIds'); + return isAction(object) && object.kind === DeleteElementOperation.KIND && 'elementIds' in object; } export class ChangeBoundsOperation implements Operation { - static readonly KIND = "changeBounds"; + static readonly KIND = 'changeBounds'; constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) { } } export function isChangeBoundsOperation(object?: any): object is ChangeBoundsOperation { - return isAction(object) && object.kind === ChangeBoundsOperation.KIND && object.hasOwnProperty('newBounds'); + return isAction(object) && object.kind === ChangeBoundsOperation.KIND && 'newBounds' in object; } export class ChangeContainerOperation implements Operation { - static readonly KIND = "changeContainer"; + static readonly KIND = 'changeContainer'; constructor(public readonly elementId: string, public readonly targetContainerId: string, public readonly location?: string, @@ -93,7 +93,7 @@ export function isChangeContainerOperation(object?: any): object is ChangeContai } export class ReconnectEdgeOperation implements Operation { - static readonly KIND = "reconnectEdge"; + static readonly KIND = 'reconnectEdge'; constructor(public readonly edgeElementId: string, public readonly sourceElementId: string, public readonly targetElementId: string, @@ -108,7 +108,7 @@ export function isReconnectEdgeOperation(object?: any): object is ReconnectEdgeO } export class ChangeRoutingPointsOperation implements Operation { - static readonly KIND = "changeRoutingPoints"; + static readonly KIND = 'changeRoutingPoints'; constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) { } } @@ -118,7 +118,7 @@ export function isChangeRoutingsPointsOperation(object?: any): object is ChangeR } export class CompoundOperation implements Operation { - static readonly KIND = "compound"; + static readonly KIND = 'compound'; constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) { } } @@ -133,7 +133,7 @@ export interface ElementAndRoutingPoints { /** * The identifier of an element. */ - elementId: string + elementId: string; /** * The new list of routing points. @@ -146,7 +146,7 @@ export abstract class TriggerElementCreationAction implements Action { } export class TriggerNodeCreationAction extends TriggerElementCreationAction { - static readonly KIND = "triggerNodeCreation"; + static readonly KIND = 'triggerNodeCreation'; constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind = TriggerNodeCreationAction.KIND) { super(elementTypeId, args, kind); @@ -154,7 +154,7 @@ export class TriggerNodeCreationAction extends TriggerElementCreationAction { } export class TriggerEdgeCreationAction extends TriggerElementCreationAction { - static readonly KIND = "triggerEdgeCreation"; + static readonly KIND = 'triggerEdgeCreation'; constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = TriggerEdgeCreationAction.KIND) { super(elementTypeId, args, kind); @@ -162,7 +162,7 @@ export class TriggerEdgeCreationAction extends TriggerElementCreationAction { } export function isTriggerElementTypeCreationAction(object?: any): object is TriggerElementCreationAction { - return isAction(object) && "elementTypeId" in object; + return isAction(object) && 'elementTypeId' in object; } export function isTriggerNodeCreationAction(object?: any): object is TriggerNodeCreationAction { diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts index ad94c1e..fe02a7b 100644 --- a/packages/client/src/base/selection-clearing-mouse-listener.ts +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { MouseListener, SModelElement } from "sprotty"; +import { injectable } from 'inversify'; +import { MouseListener, SModelElement } from 'sprotty'; /** * A mouse listener that clears the document selection on click @@ -30,8 +30,9 @@ import { MouseListener, SModelElement } from "sprotty"; */ @injectable() export class SelectionClearingMouseListener extends MouseListener { - mouseDown(_target: SModelElement, event: MouseEvent) { + mouseDown(_target: SModelElement, event: MouseEvent): never[] { const selection = document.getSelection(); + // eslint-disable-next-line no-null/no-null if (selection === null) { return []; } diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index 27be191..8c4f34d 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct } from "inversify"; -import { Tool, ToolManager } from "sprotty"; +import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; +import { Tool, ToolManager } from 'sprotty'; -import { distinctAdd } from "../../utils/array-utils"; -import { EditMode } from "../actions/edit-mode-action"; -import { EditModeListener, EditorContextService, EditorContextServiceProvider } from "../editor-context"; -import { GLSP_TYPES } from "../types"; +import { distinctAdd } from '../../utils/array-utils'; +import { EditMode } from '../actions/edit-mode-action'; +import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context'; +import { GLSP_TYPES } from '../types'; export interface IGLSPToolManager extends ToolManager { /* Disables all actives tools and activates only default tool with non-edit function*/ @@ -45,19 +45,19 @@ export class GLSPToolManager extends ToolManager implements IGLSPToolManager, Ed }); } - registerDefaultTools(...tools: Tool[]) { + registerDefaultTools(...tools: Tool[]): void { for (const tool of tools) { distinctAdd(this.defaultTools, tool); } } - registerTools(...tools: Tool[]) { + registerTools(...tools: Tool[]): void { for (const tool of tools) { distinctAdd(this.tools, tool); } } - enable(toolIds: string[]) { + enable(toolIds: string[]): void { this.disableActiveTools(); let tools = toolIds.map(id => this.tool(id)); if (this.editorContext && this.editorContext.isReadonly) { @@ -71,12 +71,12 @@ export class GLSPToolManager extends ToolManager implements IGLSPToolManager, Ed }); } - disableEditTools() { + disableEditTools(): void { this.disableActiveTools(); this.enable(this.defaultTools.filter(tool => !isGLSPTool(tool) || tool.isEditTool === false).map(tool => tool.id)); } - editModeChanged(oldValue: string, newValue: string) { + editModeChanged(oldValue: string, newValue: string): void { if (oldValue === newValue) { return; } @@ -93,5 +93,5 @@ export interface GLSPTool extends Tool { } export function isGLSPTool(tool: Tool): tool is GLSPTool { - return "isEditTool" in tool && typeof tool["isEditTool"] === "boolean"; + return 'isEditTool' in tool && typeof tool['isEditTool'] === 'boolean'; } diff --git a/packages/client/src/base/types.ts b/packages/client/src/base/types.ts index 169ab77..59f79bd 100644 --- a/packages/client/src/base/types.ts +++ b/packages/client/src/base/types.ts @@ -14,24 +14,24 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export const GLSP_TYPES = { - IAsyncClipboardService: Symbol.for("IAsyncClipboardService"), - ICommandPaletteActionProviderRegistry: Symbol.for("ICommandPaletteActionProviderRegistry"), - IEditorContextServiceProvider: Symbol.for("IEditorContextProvider"), - IFeedbackActionDispatcher: Symbol.for("IFeedbackActionDispatcher"), - IToolFactory: Symbol.for("Factory"), - ITypeHintProvider: Symbol.for("ITypeHintProvider"), - IMovementRestrictor: Symbol.for("IMovmementRestrictor"), - SelectionService: Symbol.for("SelectionService"), - SelectionListener: Symbol.for("SelectionListener"), - SModelRootListener: Symbol.for("SModelRootListener"), - MouseTool: Symbol.for("MouseTool"), - IContextMenuService: Symbol.for("IContextMenuService"), - IContextMenuServiceProvider: Symbol.for("IContextMenuServiceProvider"), - IContextMenuProviderRegistry: Symbol.for("IContextMenuProviderRegistry"), - IContextMenuProvider: Symbol.for("IContextMenuProvider"), - ICopyPasteHandler: Symbol.for("ICopyPasteHandler"), - IGLSPToolManager: Symbol.for("IGLSPToolManager"), - ITool: Symbol.for("ITool"), - IDefaultTool: Symbol.for("IDefaultTool"), - IEditModeListener: Symbol.for("IEditModeListener") + IAsyncClipboardService: Symbol.for('IAsyncClipboardService'), + ICommandPaletteActionProviderRegistry: Symbol.for('ICommandPaletteActionProviderRegistry'), + IEditorContextServiceProvider: Symbol.for('IEditorContextProvider'), + IFeedbackActionDispatcher: Symbol.for('IFeedbackActionDispatcher'), + IToolFactory: Symbol.for('Factory'), + ITypeHintProvider: Symbol.for('ITypeHintProvider'), + IMovementRestrictor: Symbol.for('IMovmementRestrictor'), + SelectionService: Symbol.for('SelectionService'), + SelectionListener: Symbol.for('SelectionListener'), + SModelRootListener: Symbol.for('SModelRootListener'), + MouseTool: Symbol.for('MouseTool'), + IContextMenuService: Symbol.for('IContextMenuService'), + IContextMenuServiceProvider: Symbol.for('IContextMenuServiceProvider'), + IContextMenuProviderRegistry: Symbol.for('IContextMenuProviderRegistry'), + IContextMenuProvider: Symbol.for('IContextMenuProvider'), + ICopyPasteHandler: Symbol.for('ICopyPasteHandler'), + IGLSPToolManager: Symbol.for('IGLSPToolManager'), + ITool: Symbol.for('ITool'), + IDefaultTool: Symbol.for('IDefaultTool'), + IEditModeListener: Symbol.for('IEditModeListener') }; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index d882a56..e51b250 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -25,9 +25,9 @@ import { Selectable, SModelElement, SParentElement -} from "sprotty"; +} from 'sprotty'; -export const resizeFeature = Symbol("resizeFeature"); +export const resizeFeature = Symbol('resizeFeature'); export interface Resizable extends BoundsAware, Selectable { } @@ -37,10 +37,10 @@ export function isResizable(element: SModelElement): element is SParentElement & } export enum ResizeHandleLocation { - TopLeft = "top-left", - TopRight = "top-right", - BottomLeft = "bottom-left", - BottomRight = "bottom-right" + TopLeft = 'top-left', + TopRight = 'top-right', + BottomLeft = 'bottom-left', + BottomRight = 'bottom-right' } export function isBoundsAwareMoveable(element: SModelElement): element is SModelElement & Locateable & BoundsAware { @@ -61,7 +61,7 @@ export class SResizeHandle extends SChildElement implements Hoverable { } } -export function addResizeHandles(element: SParentElement) { +export function addResizeHandles(element: SParentElement): void { removeResizeHandles(element); element.add(new SResizeHandle(ResizeHandleLocation.TopLeft)); element.add(new SResizeHandle(ResizeHandleLocation.TopRight)); @@ -69,6 +69,6 @@ export function addResizeHandles(element: SParentElement) { element.add(new SResizeHandle(ResizeHandleLocation.BottomRight)); } -export function removeResizeHandles(element: SParentElement) { +export function removeResizeHandles(element: SParentElement): void { element.removeAll(child => child instanceof SResizeHandle); } diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index f8ee75f..c461623 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { BoundsAware, Point, SModelElement, SNode, SParentElement } from "sprotty"; +import { injectable } from 'inversify'; +import { BoundsAware, Point, SModelElement, SNode, SParentElement } from 'sprotty'; -import { toAbsoluteBounds } from "../../utils/viewpoint-util"; -import { ModifyCSSFeedbackAction } from "../tool-feedback/css-feedback"; -import { isBoundsAwareMoveable, SResizeHandle } from "./model"; +import { toAbsoluteBounds } from '../../utils/viewpoint-util'; +import { ModifyCSSFeedbackAction } from '../tool-feedback/css-feedback'; +import { isBoundsAwareMoveable, SResizeHandle } from './model'; export interface IMovementRestrictor { validate(newLocation: Point, element: SModelElement): boolean; @@ -45,7 +45,7 @@ export function removeMovementRestrictionFeedback(element: SModelElement, moveme @injectable() export class NoOverlapMovmentRestrictor implements IMovementRestrictor { - cssClasses = ["movement-not-allowed"]; + cssClasses = ['movement-not-allowed']; validate(newLocation: Point, element: SModelElement): boolean { if (!isBoundsAwareMoveable(element)) { @@ -59,14 +59,14 @@ export class NoOverlapMovmentRestrictor implements IMovementRestrictor { width: element.bounds.width, height: element.bounds.height }; - ghostElement.type = "Ghost"; + ghostElement.type = 'Ghost'; ghostElement.id = element.id; return !Array.from(element.root.index.all().filter(e => e.id !== ghostElement.id && e !== ghostElement.root && (e instanceof SNode)) .map(e => e as SModelElement & BoundsAware)).some(e => areOverlapping(e, ghostElement)); } } -export function areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware) { +export function areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware): boolean { const b1 = toAbsoluteBounds(element1); const b2 = toAbsoluteBounds(element2); const r1TopLeft: Point = b1; @@ -75,12 +75,14 @@ export function areOverlapping(element1: SModelElement & BoundsAware, element2: const r2BottomRight = { x: b2.x + b2.width, y: b2.y + b2.height }; // If one rectangle is on left side of other - if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) + if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) { return false; + } // If one rectangle is above other - if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) + if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) { return false; + } return true; diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index d3f7c0d..403ca02 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -13,16 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { ISnapper, Point, SModelElement } from "sprotty"; +import { injectable } from 'inversify'; +import { ISnapper, Point, SModelElement } from 'sprotty'; @injectable() export class GridSnapper implements ISnapper { - get gridX() { + get gridX(): number { return 10; } - get gridY() { + get gridY(): number { return 10; } diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index 13be171..38b42e0 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, postConstruct } from "inversify"; -import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from "sprotty"; +import { inject, injectable, postConstruct } from 'inversify'; +import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from 'sprotty'; @injectable() export class CommandPaletteTool implements Tool { - static ID = "glsp.command-palette-tool"; + static ID = 'glsp.command-palette-tool'; protected commandPaletteKeyListener: KeyListener; @inject(KeyTool) protected keyTool: KeyTool; @postConstruct() - protected postConstruct() { + protected postConstruct(): void { this.commandPaletteKeyListener = this.createCommandPaleteKeyListener(); } diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/di.config.ts index 29e9c3c..010772a 100644 --- a/packages/client/src/features/command-palette/di.config.ts +++ b/packages/client/src/features/command-palette/di.config.ts @@ -13,16 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../../../css/command-palette.css"; +import '../../../css/command-palette.css'; -import { ContainerModule } from "inversify"; -import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { CommandPaletteTool } from "./command-palette-tool"; -import { ServerCommandPaletteActionProvider } from "./server-command-palette-provider"; +import { GLSP_TYPES } from '../../base/types'; +import { CommandPaletteTool } from './command-palette-tool'; +import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; -const glspCommandPaletteModule = new ContainerModule((bind) => { +const glspCommandPaletteModule = new ContainerModule(bind => { bind(CommandPalette).toSelf().inSingletonScope(); bind(TYPES.IUIExtension).toService(CommandPalette); bind(CommandPaletteActionProviderRegistry).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index cc1bb9e..53484ac 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -13,17 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, ICommandPaletteActionProvider, LabeledAction, Point, SModelElement, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, ICommandPaletteActionProvider, LabeledAction, Point, SModelElement, TYPES } from 'sprotty'; -import { GLSPActionDispatcher } from "../../base/action-dispatcher"; -import { isSetContextActionsAction, RequestContextActions } from "../../base/actions/context-actions"; -import { EditorContextService } from "../../base/editor-context"; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { isSetContextActionsAction, RequestContextActions } from '../../base/actions/context-actions'; +import { EditorContextService } from '../../base/editor-context'; export namespace ServerCommandPalette { - export const CONTEXT_ID = "command-palette"; - export const TEXT = "text"; - export const INDEX = "index"; + export const CONTEXT_ID = 'command-palette'; + export const TEXT = 'text'; + export const INDEX = 'index'; } @injectable() diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index 45d0b8e..f13f358 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from 'sprotty'; -import { EditorContextService, EditorContextServiceProvider } from "../../base/editor-context"; -import { DeleteElementOperation } from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; +import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context'; +import { DeleteElementOperation } from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; @injectable() export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { @@ -31,10 +31,10 @@ export class DeleteElementContextMenuItemProvider implements IContextMenuItemPro protected createDeleteMenuItem(editorContextService: EditorContextService): MenuItem { return { - id: "delete", - label: "Delete", - sortString: "d", - group: "edit", + id: 'delete', + label: 'Delete', + sortString: 'd', + group: 'edit', actions: [new DeleteElementOperation(editorContextService.selectedElements.map(e => e.id))], isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 }; diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts index 0cbbc97..62d47d2 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/di.config.ts @@ -13,24 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { ContextMenuProviderRegistry, IContextMenuService, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { ContextMenuProviderRegistry, IContextMenuService, TYPES } from 'sprotty'; -import { SelectionServiceAwareContextMenuMouseListener } from "./selection-service-aware-context-menu-mouse-listener"; -import { ServerContextMenuItemProvider } from "./server-context-menu-provider"; +import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; +import { ServerContextMenuItemProvider } from './server-context-menu-provider'; const glspContextMenuModule = new ContainerModule(bind => { - bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => { - return () => { - return new Promise((resolve, reject) => { - if (ctx.container.isBound(TYPES.IContextMenuService)) { - resolve(ctx.container.get(TYPES.IContextMenuService)); - } else { - reject(); - } - }); - }; - }); + bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => () => new Promise((resolve, reject) => { + if (ctx.container.isBound(TYPES.IContextMenuService)) { + resolve(ctx.container.get(TYPES.IContextMenuService)); + } else { + reject(); + } + })); bind(TYPES.MouseListener).to(SelectionServiceAwareContextMenuMouseListener); bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); bind(TYPES.IContextMenuItemProvider).to(ServerContextMenuItemProvider); diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index a626f82..4b7d5b2 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, ContextMenuProviderRegistry, @@ -23,10 +23,10 @@ import { MouseListener, SModelElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { SelectionService } from "../select/selection-service"; +import { GLSP_TYPES } from '../../base/types'; +import { SelectionService } from '../select/selection-service'; @injectable() export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 451bac4..8db45e7 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, IContextMenuItemProvider, isSelected, LabeledAction, Point, SModelElement, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, IContextMenuItemProvider, isSelected, LabeledAction, Point, SModelElement, TYPES } from 'sprotty'; -import { GLSPActionDispatcher } from "../../base/action-dispatcher"; -import { isSetContextActionsAction, RequestContextActions } from "../../base/actions/context-actions"; -import { EditorContextService } from "../../base/editor-context"; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { isSetContextActionsAction, RequestContextActions } from '../../base/actions/context-actions'; +import { EditorContextService } from '../../base/editor-context'; export namespace ServerContextMenu { - export const CONTEXT_ID = "context-menu"; + export const CONTEXT_ID = 'context-menu'; } @injectable() diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/client/src/features/copy-paste/copy-paste-actions.ts index 1343c86..d504ca3 100644 --- a/packages/client/src/features/copy-paste/copy-paste-actions.ts +++ b/packages/client/src/features/copy-paste/copy-paste-actions.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { generateRequestId, RequestAction, ResponseAction } from "sprotty"; -import { Operation } from "src/base/operations/operation"; +import { generateRequestId, RequestAction, ResponseAction } from 'sprotty'; -import { EditorContext } from "../../base/editor-context"; +import { EditorContext } from '../../base/editor-context'; +import { Operation } from '../../base/operations/operation'; export class CutOperation implements Operation { - static readonly KIND = "cut"; + static readonly KIND = 'cut'; constructor( public readonly editorContext: EditorContext, @@ -27,7 +27,7 @@ export class CutOperation implements Operation { } export class PasteOperation implements Operation { - static readonly KIND = "paste"; + static readonly KIND = 'paste'; constructor( public readonly clipboardData: ClipboardData, @@ -36,7 +36,7 @@ export class PasteOperation implements Operation { } export class RequestClipboardDataAction implements RequestAction { - static readonly KIND = "requestClipboardData"; + static readonly KIND = 'requestClipboardData'; constructor( public readonly editorContext: EditorContext, @@ -48,10 +48,10 @@ export class RequestClipboardDataAction implements RequestAction{ kind: GLSPServerStatusAction.KIND, severity, timeout, message }, - { kind: ServerMessageAction.KIND, severity, timeout, message } + { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, + { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction ]); } } @@ -86,21 +86,21 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide protected createPasteMenuItem(): MenuItem { return { - id: "paste", label: "Paste", group: "copy-paste", + id: 'paste', label: 'Paste', group: 'copy-paste', actions: [new InvokePasteAction()], isEnabled: () => true }; } protected createCutMenuItem(hasSelectedElements: boolean): MenuItem { return { - id: "cut", label: "Cut", group: "copy-paste", + id: 'cut', label: 'Cut', group: 'copy-paste', actions: [new InvokeCutAction()], isEnabled: () => hasSelectedElements }; } protected createCopyMenuItem(hasSelectedElements: boolean): MenuItem { return { - id: "copy", label: "Copy", group: "copy-paste", + id: 'copy', label: 'Copy', group: 'copy-paste', actions: [new InvokeCopyAction()], isEnabled: () => hasSelectedElements }; } diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 66306ec..b244259 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { TYPES, ViewerOptions } from "sprotty"; -import { v4 as uuid } from "uuid"; +import { inject, injectable } from 'inversify'; +import { TYPES, ViewerOptions } from 'sprotty'; +import { v4 as uuid } from 'uuid'; -import { GLSPActionDispatcher } from "../../base/action-dispatcher"; -import { EditorContextService } from "../../base/editor-context"; -import { GLSP_TYPES } from "../../base/types"; -import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction } from "./copy-paste-actions"; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { EditorContextService } from '../../base/editor-context'; +import { GLSP_TYPES } from '../../base/types'; +import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction } from './copy-paste-actions'; export interface ICopyPasteHandler { handleCopy(event: ClipboardEvent): void; @@ -55,12 +55,12 @@ export class LocalClipboardService implements IAsyncClipboardService { protected currentId?: string; protected data?: ClipboardData; - clear() { + clear(): void { this.currentId = undefined; this.data = undefined; } - put(data: ClipboardData, id: string) { + put(data: ClipboardData, id: string): void { this.currentId = id; this.data = data; } @@ -91,7 +91,7 @@ function getClipboardIdFromDataTransfer(dataTransfer: DataTransfer): string | un return isClipboardId(jsonObject) ? jsonObject.clipboardId : undefined; } -const CLIPBOARD_DATA_FORMAT = "application/json"; +const CLIPBOARD_DATA_FORMAT = 'application/json'; @injectable() export class ServerCopyPasteHandler implements ICopyPasteHandler { @@ -101,7 +101,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { @inject(GLSP_TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; @inject(EditorContextService) protected editorContext: EditorContextService; - handleCopy(event: ClipboardEvent) { + handleCopy(event: ClipboardEvent): void { if (event.clipboardData && this.shouldCopy(event)) { const clipboardId = uuid(); event.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); @@ -110,7 +110,9 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { .then(action => this.clipboadService.put(action.clipboardData, clipboardId)); event.preventDefault(); } else { - if (event.clipboardData) { event.clipboardData.clearData(); } + if (event.clipboardData) { + event.clipboardData.clearData(); + } this.clipboadService.clear(); } } @@ -134,7 +136,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { } } - protected shouldCopy(_event: ClipboardEvent) { + protected shouldCopy(_event: ClipboardEvent): boolean | null { return this.editorContext.get().selectedElementIds.length > 0 && document.activeElement instanceof SVGElement && document.activeElement.parentElement && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; } diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/di.config.ts index ccf2c8f..2004287 100644 --- a/packages/client/src/features/copy-paste/di.config.ts +++ b/packages/client/src/features/copy-paste/di.config.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureActionHandler } from "sprotty/lib"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler } from 'sprotty/lib'; -import { GLSP_TYPES } from "../../base/types"; +import { GLSP_TYPES } from '../../base/types'; import { CopyPasteContextMenuItemProvider, InvokeCopyAction, InvokeCopyPasteActionHandler, InvokeCutAction, InvokePasteAction -} from "./copy-paste-context-menu"; -import { LocalClipboardService, ServerCopyPasteHandler } from "./copy-paste-handler"; +} from './copy-paste-context-menu'; +import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { bind(GLSP_TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 7b25603..b15a187 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; +import { injectable } from 'inversify'; import { Decoration, DecorationPlacer, @@ -23,7 +23,7 @@ import { SChildElement, SModelElement, SRoutableElement -} from "sprotty"; +} from 'sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { @@ -34,11 +34,12 @@ export class GlspDecorationPlacer extends DecorationPlacer { if (element instanceof SChildElement && element.parent instanceof SRoutableElement) { return super.getPosition(element); } - if (isSizeable(element)) + if (isSizeable(element)) { return { x: GlspDecorationPlacer.DECORATION_OFFSET.x * element.bounds.width, y: GlspDecorationPlacer.DECORATION_OFFSET.y * element.bounds.height }; + } return ORIGIN_POINT; } diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts index e64aefb..6019739 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/di.config.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../../../css/decoration.css"; +import '../../../css/decoration.css'; -import { ContainerModule } from "inversify"; -import { configureModelElement, SIssueMarker, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureModelElement, SIssueMarker, TYPES } from 'sprotty'; -import { GlspDecorationPlacer } from "./decoration-placer"; -import { GlspIssueMarkerView } from "./view"; +import { GlspDecorationPlacer } from './decoration-placer'; +import { GlspIssueMarkerView } from './view'; const glspDecorationModule = new ContainerModule((bind, _unbind, isBound) => { configureModelElement({ bind, isBound }, 'marker', SIssueMarker, GlspIssueMarkerView); diff --git a/packages/client/src/features/decoration/view.tsx b/packages/client/src/features/decoration/view.tsx index 37c3cee..582de0c 100644 --- a/packages/client/src/features/decoration/view.tsx +++ b/packages/client/src/features/decoration/view.tsx @@ -13,11 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import * as snabbdom from "snabbdom-jsx"; -import { VNode } from "snabbdom/vnode"; -import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity } from "sprotty"; +/* eslint-disable max-len */ +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { VNode } from 'snabbdom/vnode'; +import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity } from 'sprotty'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: snabbdom.svg }; @injectable() diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/edit-label/di.config.ts index f9f9d1f..ec27ce4 100644 --- a/packages/client/src/features/edit-label/di.config.ts +++ b/packages/client/src/features/edit-label/di.config.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { ApplyLabelEditCommand, configureCommand, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { ApplyLabelEditCommand, configureCommand, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { DirectLabelEditTool } from "./edit-label-tool"; -import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from "./edit-label-validator"; +import { GLSP_TYPES } from '../../base/types'; +import { DirectLabelEditTool } from './edit-label-tool'; +import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; const glspEditLabelModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/edit-label/edit-label-tool.ts index 840f00d..b6597a5 100644 --- a/packages/client/src/features/edit-label/edit-label-tool.ts +++ b/packages/client/src/features/edit-label/edit-label-tool.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from "sprotty"; +import { injectable } from 'inversify'; +import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from 'sprotty'; -import { BaseGLSPTool } from "../tools/base-glsp-tool"; +import { BaseGLSPTool } from '../tools/base-glsp-tool'; @injectable() export class DirectLabelEditTool extends BaseGLSPTool { - static readonly ID = "glsp.direct-label-edit-tool"; + static readonly ID = 'glsp.direct-label-edit-tool'; protected editLabelMouseListener: MouseListener; protected editLabelKeyListener: KeyListener; diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index 48466b2..2c6eff0 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, EditableLabel, @@ -23,25 +23,25 @@ import { Severity, SModelElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { GLSPActionDispatcher } from "../../base/action-dispatcher"; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { isSetEditValidationResultAction, RequestEditValidationAction, ValidationStatus -} from "../../base/actions/edit-validation-actions"; +} from '../../base/actions/edit-validation-actions'; export namespace LabelEditValidation { export const CONTEXT_ID = 'label-edit'; export function toEditLabelValidationResult(status: ValidationStatus): EditLabelValidationResult { const message = status.message; - let severity = 'ok'; + let severity = 'ok' as Severity; if (ValidationStatus.isError(status)) { - severity = 'error'; + severity = 'error' as Severity; } else if (ValidationStatus.isWarning(status)) { - severity = 'warning'; + severity = 'warning' as Severity; } return { message, severity }; } @@ -67,7 +67,7 @@ export class ServerEditLabelValidator implements IEditLabelValidator { if (isSetEditValidationResultAction(action)) { return LabelEditValidation.toEditLabelValidationResult(action.status); } - return { severity: 'ok' }; + return { severity: 'ok' as Severity }; } } diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/di.config.ts index 1dd3407..1b51668 100644 --- a/packages/client/src/features/hints/di.config.ts +++ b/packages/client/src/features/hints/di.config.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureActionHandler, configureCommand } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler, configureCommand } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { SetTypeHintsAction } from "./request-type-hints-action"; -import { ApplyTypeHintsCommand, TypeHintProvider } from "./type-hints"; +import { GLSP_TYPES } from '../../base/types'; +import { SetTypeHintsAction } from './request-type-hints-action'; +import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { bind(TypeHintProvider).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 74d5816..d346e8c 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -13,19 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelElementSchema, SModelExtension } from "sprotty"; +import { SModelElement, SModelElementSchema, SModelExtension } from 'sprotty'; -export const containerFeature = Symbol("containable"); +export const containerFeature = Symbol('containable'); export interface Containable extends SModelExtension { - isContainableElement(input: SModelElement | SModelElementSchema | string): boolean + isContainableElement(input: SModelElement | SModelElementSchema | string): boolean; } export function isContainable(element: SModelElement): element is SModelElement & Containable { return element.hasFeature(containerFeature); } -export const reparentFeature = Symbol("reparentFeature"); +export const reparentFeature = Symbol('reparentFeature'); export interface Reparentable extends SModelExtension { } diff --git a/packages/client/src/features/hints/request-type-hints-action.ts b/packages/client/src/features/hints/request-type-hints-action.ts index f4824e9..b6ea9ea 100644 --- a/packages/client/src/features/hints/request-type-hints-action.ts +++ b/packages/client/src/features/hints/request-type-hints-action.ts @@ -13,17 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from "sprotty"; +import { Action } from 'sprotty'; -import { EdgeTypeHint, ShapeTypeHint } from "./type-hints"; +import { EdgeTypeHint, ShapeTypeHint } from './type-hints'; export class RequestTypeHintsAction implements Action { - static readonly KIND = "requestTypeHints"; + static readonly KIND = 'requestTypeHints'; constructor(public readonly diagramType?: string, public readonly kind: string = RequestTypeHintsAction.KIND) { } } export class SetTypeHintsAction implements Action { - static readonly KIND = "setTypeHints"; + static readonly KIND = 'setTypeHints'; constructor( public readonly shapeHints: ShapeTypeHint[], public readonly edgeHints: EdgeTypeHint[], @@ -32,5 +32,5 @@ export class SetTypeHintsAction implements Action { export function isSetTypeHintsAction(action: Action): action is SetTypeHintsAction { return action !== undefined && (action.kind === SetTypeHintsAction.KIND) - && (action).shapeHints !== undefined && (action).edgeHints !== undefined; + && (action as SetTypeHintsAction).shapeHints !== undefined && (action as SetTypeHintsAction).edgeHints !== undefined; } diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 8b172c7..385b0c0 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, @@ -32,16 +32,16 @@ import { SModelRoot, SShapeElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { getElementTypeId, hasCompatibleType } from "../../utils/smodel-util"; -import { resizeFeature } from "../change-bounds/model"; -import { reconnectFeature } from "../reconnect/model"; -import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; -import { FeedbackCommand } from "../tool-feedback/model"; -import { Containable, containerFeature, reparentFeature } from "./model"; -import { isSetTypeHintsAction } from "./request-type-hints-action"; +import { GLSP_TYPES } from '../../base/types'; +import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; +import { resizeFeature } from '../change-bounds/model'; +import { reconnectFeature } from '../reconnect/model'; +import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../tool-feedback/model'; +import { Containable, containerFeature, reparentFeature } from './model'; +import { isSetTypeHintsAction } from './request-type-hints-action'; export abstract class TypeHint { /** @@ -112,7 +112,7 @@ export class ApplyTypeHintsAction implements Action { @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { - public static KIND = "applyTypeHints"; + public static KIND = 'applyTypeHints'; public readonly priority = 10; @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; @@ -134,7 +134,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { return context.root; } - protected applyEdgeTypeHint(element: SModelElement) { + protected applyEdgeTypeHint(element: SModelElement): void { const hint = this.typeHintProvider.getEdgeTypeHint(element); if (hint && isModifiableFetureSet(element.features)) { addOrRemove(element.features, deletableFeature, hint.deletable); @@ -143,7 +143,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { } } - protected applyShapeTypeHint(element: SModelElement) { + protected applyShapeTypeHint(element: SModelElement): void { const hint = this.typeHintProvider.getShapeTypeHint(element); if (hint && isModifiableFetureSet(element.features)) { addOrRemove(element.features, deletableFeature, hint.deletable); @@ -156,8 +156,8 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { Object.assign(element, containable); addOrRemove(element.features, connectableFeature, true); - const validSourceEdges = this.typeHintProvider.getValidEdgeElementTypes(element, "source"); - const validTargetEdges = this.typeHintProvider.getValidEdgeElementTypes(element, "target"); + const validSourceEdges = this.typeHintProvider.getValidEdgeElementTypes(element, 'source'); + const validTargetEdges = this.typeHintProvider.getValidEdgeElementTypes(element, 'target'); const connectable = createConnectable(validSourceEdges, validTargetEdges); Object.assign(element, connectable); } @@ -167,7 +167,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { function createConnectable(validSourceEdges: string[], validTargetEdges: string[]): Connectable { return { canConnect: (routable, role) => - role === "source" ? + role === 'source' ? validSourceEdges.includes(routable.type) : validTargetEdges.includes(routable.type) }; @@ -175,14 +175,14 @@ function createConnectable(validSourceEdges: string[], validTargetEdges: string[ function createContainable(hint: ShapeTypeHint): Containable { return { - isContainableElement: (element) => + isContainableElement: element => hint.containableElementTypeIds ? hint.containableElementTypeIds.includes(getElementTypeId(element)) : false }; } -function addOrRemove(features: Set, feature: symbol, add: boolean) { +function addOrRemove(features: Set, feature: symbol, add: boolean): void { if (add && !features.has(feature)) { features.add(feature); } else if (!add && features.has(feature)) { @@ -197,7 +197,7 @@ function isModifiableFetureSet(featureSet?: FeatureSet): featureSet is FeatureSe export interface ITypeHintProvider { getShapeTypeHint(input: SModelElement | SModelElement | string): ShapeTypeHint | undefined; getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined; - getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: "source" | "target"): string[]; + getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: 'source' | 'target'): string[]; } @injectable() @@ -217,9 +217,9 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider { } } - getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: "source" | "target"): string[] { + getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: 'source' | 'target'): string[] { const elementTypeId = getElementTypeId(input); - if (role === "source") { + if (role === 'source') { return Array.from( Array.from(this.edgeHints.values()) .filter(hint => hint.sourceElementTypeIds.some(sourceElementTypeId => hasCompatibleType(elementTypeId, sourceElementTypeId))) @@ -232,11 +232,11 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider { } } - getShapeTypeHint(input: SModelElement | SModelElement | string) { + getShapeTypeHint(input: SModelElement | SModelElement | string): ShapeTypeHint | undefined { return getTypeHint(input, this.shapeHints); } - getEdgeTypeHint(input: SModelElement | SModelElement | string) { + getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined { return getTypeHint(input, this.edgeHints); } } @@ -246,10 +246,10 @@ function getTypeHint(input: SModelElement | SModelElement | let hint = hints.get(type); // Check subtypes if (hint === undefined) { - const subtypes = type.split(":"); + const subtypes = type.split(':'); while (hint === undefined && subtypes.length > 0) { subtypes.pop(); - hint = hints.get(subtypes.join(":")); + hint = hints.get(subtypes.join(':')); } } return hint; diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index 9742cc0..8ab32da 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; +import { ContainerModule } from 'inversify'; import { CenterCommand, ClosePopupActionHandler, @@ -28,11 +28,11 @@ import { SetPopupModelCommand, SetViewportCommand, TYPES -} from "sprotty"; -import { PopupPositionUpdater } from "sprotty/lib/features/hover/popup-position-updater"; +} from 'sprotty'; +import { PopupPositionUpdater } from 'sprotty/lib/features/hover/popup-position-updater'; -import { FocusStateChangedAction } from "../../base/actions/focus-change-action"; -import { GlspHoverMouseListener } from "./hover"; +import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { GlspHoverMouseListener } from './hover'; const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { bind(TYPES.PopupVNodePostprocessor).to(PopupPositionUpdater).inSingletonScope(); diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index d3afea8..d215aeb 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; +import { injectable } from 'inversify'; import { Action, Bounds, @@ -27,9 +27,9 @@ import { SModelElement, SModelElementSchema, SModelRootSchema -} from "sprotty"; +} from 'sprotty'; -import { GIssueMarker } from "../validation/issue-marker"; +import { GIssueMarker } from '../validation/issue-marker'; @injectable() export class GlspHoverMouseListener extends HoverMouseListener { @@ -65,11 +65,11 @@ export class GlspHoverMouseListener extends HoverMouseListener { protected createMarkerIssuePopup(marker: GIssueMarker): SModelElementSchema { const message = this.createIssueMessage(marker); - return { + return { type: 'pre-rendered', id: 'popup-title', code: `
${message}
` - }; + } as PreRenderedElementSchema; } protected createIssueMessage(marker: GIssueMarker): string { @@ -84,10 +84,12 @@ export class GlspHoverMouseListener extends HoverMouseListener { export function getSeverity(marker: SIssueMarker): SIssueSeverity { let currentSeverity: SIssueSeverity = 'info'; for (const severity of marker.issues.map(s => s.severity)) { - if (severity === 'error') + if (severity === 'error') { return severity; - if (severity === 'warning' && currentSeverity === 'info') + } + if (severity === 'warning' && currentSeverity === 'info') { currentSeverity = severity; + } } return currentSeverity; } diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 5c7045a..0cc4d29 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureCommand } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureCommand } from 'sprotty'; -import { AlignElementsCommand, ResizeElementsCommand } from "./layout-commands"; +import { AlignElementsCommand, ResizeElementsCommand } from './layout-commands'; const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, ResizeElementsCommand); diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-commands.spec.ts index 2d4f675..aca3557 100644 --- a/packages/client/src/features/layout/layout-commands.spec.ts +++ b/packages/client/src/features/layout/layout-commands.spec.ts @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "mocha"; -import "reflect-metadata"; +/* eslint-disable import/no-deprecated,no-unused-expressions */ +import 'mocha'; +import 'reflect-metadata'; -import { expect } from "chai"; -import { Container } from "inversify"; +import { expect } from 'chai'; +import { Container } from 'inversify'; import { Action, AnimationFrameSyncer, @@ -39,13 +40,13 @@ import { SGraphFactory, SModelRoot, TYPES -} from "sprotty"; +} from 'sprotty'; -import { ChangeBoundsOperation } from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; -import { resizeFeature } from "../change-bounds/model"; -import { SelectionService } from "../select/selection-service"; -import { FeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; +import { ChangeBoundsOperation } from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; +import { resizeFeature } from '../change-bounds/model'; +import { SelectionService } from '../select/selection-service'; +import { FeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { AlignElementsAction, AlignElementsCommand, @@ -54,7 +55,7 @@ import { ResizeDimension, ResizeElementsAction, ResizeElementsCommand -} from "./layout-commands"; +} from './layout-commands'; class MockActionDispatcher implements IActionDispatcher { constructor(public dispatchedActions: Action[] = []) { } @@ -67,7 +68,7 @@ class MockActionDispatcher implements IActionDispatcher { return Promise.resolve(); } request(action: RequestAction): Promise { - throw new Error("Method not implemented."); + throw new Error('Method not implemented.'); } } @@ -96,7 +97,7 @@ const node3 = { }; const model = createModel(); -function createModel() { +function createModel(): SModelRoot { const root = graphFactory.createRoot({ id: 'model1', type: 'graph', @@ -106,7 +107,7 @@ function createModel() { return root; } -function applyFeature(element: SChildElement, feature: symbol) { +function applyFeature(element: SChildElement, feature: symbol): void { (element.features as FeatureSet & Set).add(feature); } @@ -135,9 +136,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -153,9 +154,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -171,9 +172,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -189,9 +190,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -207,9 +208,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -225,9 +226,9 @@ describe('AlignElementsCommand', () => { command.execute(newContext(newModel)); assertAllBounds(new Map([ - ["node1", { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], - ["node2", { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], - ["node3", { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }] + ['node1', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }] ])); }); @@ -248,9 +249,9 @@ describe('ResizeElementsCommand', () => { // resize is keeping the center, so the X moves by diff / 2 assertAllBoundsInChangeBounds(new Map([ - ["node1", { x: 90, y: 100, height: 10, width: 30 }], - ["node2", { x: 95, y: 200, height: 20, width: 30 }], - ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ['node1', { x: 90, y: 100, height: 10, width: 30 }], + ['node2', { x: 95, y: 200, height: 20, width: 30 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] ])); }); @@ -267,9 +268,9 @@ describe('ResizeElementsCommand', () => { // resize is keeping the center, so the Y moves by diff / 2 assertAllBoundsInChangeBounds(new Map([ - ["node1", { x: 100, y: 90, height: 30, width: 10 }], - ["node2", { x: 100, y: 195, height: 30, width: 20 }], - ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ['node1', { x: 100, y: 90, height: 30, width: 10 }], + ['node2', { x: 100, y: 195, height: 30, width: 20 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] ])); }); @@ -286,9 +287,9 @@ describe('ResizeElementsCommand', () => { // resize is keeping the center, so the Y moves by diff / 2 assertAllBoundsInChangeBounds(new Map([ - ["node1", { x: 90, y: 90, height: 30, width: 30 }], - ["node2", { x: 95, y: 195, height: 30, width: 30 }], - ["node3", { x: 100, y: 300, height: 30, width: 30 }] + ['node1', { x: 90, y: 90, height: 30, width: 30 }], + ['node2', { x: 95, y: 195, height: 30, width: 30 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] ])); }); @@ -300,7 +301,7 @@ function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { return setBoundsCommand.execute(context) as SModelRoot; } -function newContext(root: SModelRoot) { +function newContext(root: SModelRoot): CommandExecutionContext { return { root: root, modelFactory: graphFactory, @@ -311,27 +312,27 @@ function newContext(root: SModelRoot) { }; } -function assertAllBounds(allBounds: Map) { +function assertAllBounds(allBounds: Map): void { allBounds.forEach((bounds, nodeId) => assertBounds(nodeId, bounds)); } -function assertAllBoundsInChangeBounds(allBounds: Map) { +function assertAllBoundsInChangeBounds(allBounds: Map): void { allBounds.forEach((bounds, nodeId) => assertBoundsInChangeBoundsActions(nodeId, bounds)); } -function assertBounds(nodeId: string, bounds: Bounds) { +function assertBounds(nodeId: string, bounds: Bounds): void { assertBoundsInMoves(nodeId, bounds); assertBoundsInChangeBoundsActions(nodeId, bounds); } -function assertBoundsInMoves(nodeId: string, bounds: Bounds) { +function assertBoundsInMoves(nodeId: string, bounds: Bounds): void { const moves = dispatchedElementMoves(); const move = getMoveById(nodeId, moves); expect(move.toPosition.x).to.be.equal(bounds.x); expect(move.toPosition.y).to.be.equal(bounds.y); } -function assertBoundsInChangeBoundsActions(nodeId: string, bounds: Bounds) { +function assertBoundsInChangeBoundsActions(nodeId: string, bounds: Bounds): void { const allChangeBounds = dispatchedChangeBounds(); const changeBounds = getElementAndBoundsById(nodeId, allChangeBounds); expect(changeBounds.newPosition!.x).to.be.equal(bounds.x); @@ -340,19 +341,19 @@ function assertBoundsInChangeBoundsActions(nodeId: string, bounds: Bounds) { expect(changeBounds.newSize!.width).to.be.equal(bounds.width); } -function getMoveById(id: string, moves: ElementMove[]) { +function getMoveById(id: string, moves: ElementMove[]): ElementMove { return moves.filter(m => m.elementId === id)[0]; } -function getElementAndBoundsById(id: string, elementAndBounds: ElementAndBounds[]) { +function getElementAndBoundsById(id: string, elementAndBounds: ElementAndBounds[]): ElementAndBounds { return elementAndBounds.filter(m => m.elementId === id)[0]; } -function dispatchedElementMoves() { +function dispatchedElementMoves(): ElementMove[] { return actionDispatcher.dispatchedActions.filter(isMoveAction).map(a => a.moves).reduce((acc, val) => acc.concat(val), []); } -function dispatchedChangeBounds() { +function dispatchedChangeBounds(): ElementAndBounds[] { return actionDispatcher.dispatchedActions.filter(isChangeBounds).map(a => a.newBounds).reduce((acc, val) => acc.concat(val), []); } diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index b550d01..ba83093 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, Command, @@ -26,24 +26,25 @@ import { SetBoundsAction, SModelElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { ChangeBoundsOperation } from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; +import { ChangeBoundsOperation } from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove -} from "../../utils/layout-utils"; -import { BoundsAwareModelElement } from "../../utils/smodel-util"; -import { isBoundsAwareMoveable, isResizable } from "../change-bounds/model"; -import { IMovementRestrictor } from "../change-bounds/movement-restrictor"; -import { SelectionService } from "../select/selection-service"; +} from '../../utils/layout-utils'; +import { BoundsAwareModelElement } from '../../utils/smodel-util'; +import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; +import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; +import { SelectionService } from '../select/selection-service'; export enum ResizeDimension { Width, Height, + // eslint-disable-next-line @typescript-eslint/camelcase Width_And_Height } @@ -97,20 +98,19 @@ export enum Alignment { } export namespace Select { - export function all(elements: BoundsAwareModelElement[]) { + export function all(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { return elements; } - export function first(elements: BoundsAwareModelElement[]) { + export function first(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { return [elements[0]]; } - export function last(elements: BoundsAwareModelElement[]) { + export function last(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { return [elements[elements.length - 1]]; } } - export class AlignElementsAction implements Action { constructor( /** @@ -157,11 +157,11 @@ abstract class LayoutElementsCommand extends Command { protected abstract isActionElement(element: SModelElement): element is BoundsAwareModelElement; - dispatchAction(action: Action) { + dispatchAction(action: Action): void { this.actionDispatcher.dispatch(action); } - dispatchActions(actions: Action[]) { + dispatchActions(actions: Action[]): void { this.actionDispatcher.dispatchAll(actions); } } @@ -199,7 +199,7 @@ export class ResizeElementsCommand extends LayoutElementsCommand { return context.root; } - resizeWidth(elements: BoundsAwareModelElement[]) { + resizeWidth(elements: BoundsAwareModelElement[]): void { const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); this.dispatchResizeActions(elements, (element, bounds) => { // resize around center @@ -209,7 +209,7 @@ export class ResizeElementsCommand extends LayoutElementsCommand { }); } - resizeHeight(elements: BoundsAwareModelElement[]) { + resizeHeight(elements: BoundsAwareModelElement[]): void { const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); this.dispatchResizeActions(elements, (element, bounds) => { // resize around middle @@ -219,7 +219,7 @@ export class ResizeElementsCommand extends LayoutElementsCommand { }); } - resizeWidthAndHeight(elements: BoundsAwareModelElement[]) { + resizeWidthAndHeight(elements: BoundsAwareModelElement[]): void { const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); this.dispatchResizeActions(elements, (element, bounds) => { @@ -233,7 +233,7 @@ export class ResizeElementsCommand extends LayoutElementsCommand { }); } - dispatchResizeActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void) { + dispatchResizeActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void): void { const elementAndBounds: ElementAndBounds[] = []; // client- and server-side resize elements.forEach(element => { const elementChange = this.createElementAndBounds(element, change); @@ -245,7 +245,9 @@ export class ResizeElementsCommand extends LayoutElementsCommand { this.dispatchActions([new SetBoundsAction(elementAndBounds), new ChangeBoundsOperation(elementAndBounds)]); } - createElementAndBounds(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void) { + createElementAndBounds(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, + bounds: WriteableElementAndBounds) => void): WriteableElementAndBounds | undefined { + const bounds: WriteableElementAndBounds = { elementId: element.id, newPosition: { @@ -314,13 +316,13 @@ export class AlignElementsCommand extends LayoutElementsCommand { return context.root; } - alignLeft(elements: BoundsAwareModelElement[]) { + alignLeft(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); this.dispatchAlignActions(elements, (_, move) => move.toPosition.x = minX); } - alignCenter(elements: BoundsAwareModelElement[]) { + alignCenter(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); @@ -329,19 +331,19 @@ export class AlignElementsCommand extends LayoutElementsCommand { this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = centerX - 0.5 * element.bounds.width); } - alignRight(elements: BoundsAwareModelElement[]) { + alignRight(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = maxX - element.bounds.width); } - alignTop(elements: BoundsAwareModelElement[]) { + alignTop(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); this.dispatchAlignActions(elements, (_, move) => move.toPosition.y = minY); } - alignMiddle(elements: BoundsAwareModelElement[]) { + alignMiddle(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); @@ -350,13 +352,13 @@ export class AlignElementsCommand extends LayoutElementsCommand { this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = middleY - 0.5 * element.bounds.height); } - alignBottom(elements: BoundsAwareModelElement[]) { + alignBottom(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = maxY - element.bounds.height); } - dispatchAlignActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void) { + dispatchAlignActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void): void { const moves: ElementMove[] = []; // client-side move const elementAndBounds: ElementAndBounds[] = []; // server-side move elements.forEach(element => { @@ -371,7 +373,7 @@ export class AlignElementsCommand extends LayoutElementsCommand { this.dispatchActions([new MoveAction(moves), new ChangeBoundsOperation(elementAndBounds)]); } - createElementMove(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void) { + createElementMove(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void): WriteableElementMove | undefined { const move: WriteableElementMove = { elementId: element.id, fromPosition: { diff --git a/packages/client/src/features/model-source-watcher/di.config.ts b/packages/client/src/features/model-source-watcher/di.config.ts index fb44570..5b359ce 100644 --- a/packages/client/src/features/model-source-watcher/di.config.ts +++ b/packages/client/src/features/model-source-watcher/di.config.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureActionHandler } from "sprotty/lib"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler } from 'sprotty/lib'; -import { ModelSourceChangedAction } from "./model-source-changed-action"; -import { ModelSourceChangedActionHandler } from "./model-source-changed-action-handler"; +import { ModelSourceChangedAction } from './model-source-changed-action'; +import { ModelSourceChangedActionHandler } from './model-source-changed-action-handler'; const modelSourceWatcherModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, ModelSourceChangedAction.KIND, ModelSourceChangedActionHandler); diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts index 9cfef8c..ef1b752 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; -import { Action, IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from "sprotty/lib"; +import { inject, injectable, optional } from 'inversify'; +import { Action, IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from 'sprotty/lib'; -import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; -import { isModelSourceChangedAction, ModelSourceChangedAction } from "./model-source-changed-action"; +import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/glsp-server-status'; +import { isModelSourceChangedAction, ModelSourceChangedAction } from './model-source-changed-action'; /** * An external handler of the model source change event. @@ -46,7 +46,7 @@ export class ModelSourceChangedActionHandler implements IActionHandler { @inject(ExternalModelSourceChangedHandler) @optional() protected externalModelSourceChangedHandler?: ExternalModelSourceChangedHandler; - handle(action: Action) { + handle(action: Action): void { if (isModelSourceChangedAction(action)) { if (this.externalModelSourceChangedHandler) { this.externalModelSourceChangedHandler @@ -58,13 +58,13 @@ export class ModelSourceChangedActionHandler implements IActionHandler { } } - protected showSimpleNotification(action: ModelSourceChangedAction) { + protected showSimpleNotification(action: ModelSourceChangedAction): void { const message = `The model source ${action.modelSourceName} has changed. You might want to consider reloading.`; const timeout = 0; const severity = 'WARNING'; this.dispatcher.dispatchAll([ - { kind: GLSPServerStatusAction.KIND, severity, timeout, message }, - { kind: ServerMessageAction.KIND, severity, timeout, message } + { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, + { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction ]); } } diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts index 824e2a1..9804dd8 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from "sprotty"; +import { Action } from 'sprotty'; export class ModelSourceChangedAction implements Action { static KIND = 'modelSourceChanged'; diff --git a/packages/client/src/features/mouse-tool/di.config.ts b/packages/client/src/features/mouse-tool/di.config.ts index 7034bd9..0c35385 100644 --- a/packages/client/src/features/mouse-tool/di.config.ts +++ b/packages/client/src/features/mouse-tool/di.config.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { MouseTool } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { MouseTool } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { RankingMouseTool } from "./mouse-tool"; +import { GLSP_TYPES } from '../../base/types'; +import { RankingMouseTool } from './mouse-tool'; const glspMouseToolModule = new ContainerModule((bind, _unbind, _isBound, rebind) => { bind(RankingMouseTool).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index 9435523..02304ce 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, multiInject, optional } from "inversify"; -import { VNode } from "snabbdom/vnode"; -import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from "sprotty"; +import { injectable, multiInject, optional } from 'inversify'; +import { VNode, VNodeData } from 'snabbdom/vnode'; +import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; -import { getRank } from "../rank/model"; +import { getRank } from '../rank/model'; export interface IMouseTool { register(mouseListener: MouseListener): void; @@ -33,17 +33,17 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { this.rankedMouseListeners = groupBy(mouseListeners, listener => getRank(listener)); } - register(mouseListener: MouseListener) { + register(mouseListener: MouseListener): void { super.register(mouseListener); this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); } - deregister(mouseListener: MouseListener) { + deregister(mouseListener: MouseListener): void { super.deregister(mouseListener); this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); } - decorate(vnode: VNode, element: SModelElement) { + decorate(vnode: VNode, element: SModelElement): VNode { // we need to overwrite the existing event handlers registered by the original mouse tool if (element instanceof SModelRoot) { overwriteOn(vnode, 'mouseover', this.mouseOver.bind(this), element); @@ -65,7 +65,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { return vnode; } - protected handleEvent(methodName: K, model: SModelRoot, event: MouseEvent) { + protected handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { this.focusOnMouseEvent(methodName, model); const element = this.getTargetElement(model, event); if (!element) { @@ -74,7 +74,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { this.notifyListenersByRank(element, methodName, model, event); } - async notifyListenersByRank(element: SModelElement, methodName: K, model: SModelRoot, event: MouseEvent) { + async notifyListenersByRank(element: SModelElement, methodName: K, model: SModelRoot, event: MouseEvent): Promise { for (const rank of this.rankedMouseListeners) { await this.dispatchActions(rank[1], methodName, element, event); } @@ -115,21 +115,23 @@ function groupBy(array: Array, keyFunction: (x: T) => K): Map { return new Map([...unsortedMap.entries()].sort()); } -function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, event: Event) => void, element: SModelElement) { +function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, event: Event) => void, element: SModelElement): void { const val = getOn(vnode); // ignore any previous val[event] registrations (val as any)[event] = [listener, element]; } -function getOn(vnode: VNode) { +function getOn(vnode: VNode): any { const data = getData(vnode); - if (!data.on) + if (!data.on) { data.on = {}; + } return data.on; } -function getData(vnode: VNode) { - if (!vnode.data) +function getData(vnode: VNode): VNodeData { + if (!vnode.data) { vnode.data = {}; + } return vnode.data; } diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts index 3d08555..79e2f28 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/di.config.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureActionHandler } from "sprotty/lib"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler } from 'sprotty/lib'; import { NavigateAction, @@ -22,8 +22,8 @@ import { NavigateToTargetAction, NavigationActionHandler, ProcessNavigationArgumentsAction -} from "./navigation-action-handler"; -import { NavigationTargetResolver } from "./navigation-target-resolver"; +} from './navigation-action-handler'; +import { NavigationTargetResolver } from './navigation-target-resolver'; export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { bind(NavigationTargetResolver).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 3910cc6..190a2bc 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -13,24 +13,28 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { + Action, ActionHandlerRegistry, CenterAction, generateRequestId, + IActionDispatcher, + IActionHandler, + ICommand, ILogger, RequestAction, ResponseAction, SelectAction, - SelectAllAction -} from "sprotty"; -import { Action, IActionDispatcher, IActionHandler, ICommand, TYPES } from "sprotty/lib"; + SelectAllAction, + TYPES +} from 'sprotty'; -import { Args } from "../../base/args"; -import { EditorContext, EditorContextServiceProvider } from "../../base/editor-context"; -import { GLSP_TYPES } from "../../base/types"; -import { GLSPServerStatusAction, ServerMessageAction } from "../../model-source/glsp-server-status"; -import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTargetAction } from "./navigation-target-resolver"; +import { Args } from '../../base/args'; +import { EditorContext, EditorContextServiceProvider } from '../../base/editor-context'; +import { GLSP_TYPES } from '../../base/types'; +import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/glsp-server-status'; +import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTargetAction } from './navigation-target-resolver'; /** * Action for triggering a navigation of a certain target type. @@ -51,12 +55,12 @@ export class NavigateAction implements Action { export function isNavigateAction(action: Action): action is NavigateAction { return action !== undefined && (action.kind === NavigateAction.KIND) - && (action).targetTypeId !== undefined; + && (action as NavigateAction).targetTypeId !== undefined; } /** Action that is usually sent to the server to request navigation targets for a navigation type. */ export class RequestNavigationTargetsAction implements RequestAction { - static readonly KIND = "requestNavigationTargets"; + static readonly KIND = 'requestNavigationTargets'; kind = RequestNavigationTargetsAction.KIND; constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, readonly requestId: string = generateRequestId()) { } @@ -64,14 +68,14 @@ export class RequestNavigationTargetsAction implements RequestActionaction).targets !== undefined; + && (action as SetNavigationTargetsAction).targets !== undefined; } /** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ @@ -83,7 +87,7 @@ export class NavigateToTargetAction implements Action { export function isNavigateToTargetAction(action: Action): action is NavigateToTargetAction { return action !== undefined && (action.kind === NavigateToTargetAction.KIND) - && (action).target !== undefined; + && (action as NavigateToTargetAction).target !== undefined; } export class NavigateToExternalTargetAction implements Action { @@ -94,7 +98,7 @@ export class NavigateToExternalTargetAction implements Action { export function isNavigateToExternalTargetAction(action: Action): action is NavigateToExternalTargetAction { return action !== undefined && (action.kind === NavigateToExternalTargetAction.KIND) - && (action).target !== undefined; + && (action as NavigateToExternalTargetAction).target !== undefined; } /** Action to trigger the processing of additional navigation arguments. @@ -113,7 +117,7 @@ export class ProcessNavigationArgumentsAction implements Action { export function isProcessNavigationArgumentsAction(action: Action): action is ProcessNavigationArgumentsAction { return action !== undefined && (action.kind === ProcessNavigationArgumentsAction.KIND) - && (action).args !== undefined; + && (action as ProcessNavigationArgumentsAction).args !== undefined; } /** @@ -161,7 +165,7 @@ export class NavigationActionHandler implements IActionHandler { } } - protected async handleNavigateAction(action: NavigateAction) { + protected async handleNavigateAction(action: NavigateAction): Promise { try { const editorContextService = await this.editorContextService(); const context = editorContextService.get(action.args); @@ -179,7 +183,7 @@ export class NavigationActionHandler implements IActionHandler { } } - protected async handleNavigateToTarget(action: NavigateToTargetAction) { + protected async handleNavigateToTarget(action: NavigateToTargetAction): Promise { try { const resolvedElements = await this.resolveElements(action); if (this.containsElementIdsOrArguments(resolvedElements)) { @@ -211,7 +215,7 @@ export class NavigationActionHandler implements IActionHandler { return args !== undefined && args !== undefined && Object.keys(args).length > 0; } - protected navigateTo(target: SetResolvedNavigationTargetAction) { + protected navigateTo(target: SetResolvedNavigationTargetAction): void { const elementIds = target.elementIds; if (!this.containsElementIds(elementIds)) { return; @@ -219,7 +223,7 @@ export class NavigationActionHandler implements IActionHandler { this.dispatcher.dispatchAll([new SelectAllAction(false), new SelectAction(elementIds), new CenterAction(elementIds)]); } - protected handleResolutionArguments(target: SetResolvedNavigationTargetAction) { + protected handleResolutionArguments(target: SetResolvedNavigationTargetAction): void { const args = target.args; if (!this.containsArguments(args)) { return; @@ -231,7 +235,7 @@ export class NavigationActionHandler implements IActionHandler { return this.dispatcher.dispatch(new NavigateToExternalTargetAction(target)); } - protected processNavigationArguments(args: Args) { + protected processNavigationArguments(args: Args): void { if (args.info && args.info.toString().length > 0) { this.notify('INFO', args.info.toString()); } @@ -243,7 +247,7 @@ export class NavigationActionHandler implements IActionHandler { } } - protected async handleNavigateToExternalTarget(action: NavigateToExternalTargetAction) { + protected async handleNavigateToExternalTarget(action: NavigateToExternalTargetAction): Promise { const registry = await this.actionHandlerRegistryProvider(); const handlers = registry.get(NavigateToExternalTargetAction.KIND); if (handlers.length === 1) { @@ -252,17 +256,17 @@ export class NavigationActionHandler implements IActionHandler { } } - protected warnAboutFailedNavigation(msg: string, target?: NavigationTarget) { + protected warnAboutFailedNavigation(msg: string, target?: NavigationTarget): void { const message = `${msg}` + (target ? `: '${target.uri}' (arguments: ${JSON.stringify(target.args)})` : ''); this.logger.warn(this, msg, target); this.notify('WARNING', message); } - private notify(severity: string, message: string) { + private notify(severity: string, message: string): void { const timeout = this.notificationTimeout; this.dispatcher.dispatchAll([ - { kind: GLSPServerStatusAction.KIND, severity, timeout, message }, - { kind: ServerMessageAction.KIND, severity, timeout, message } + { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, + { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction ]); } } diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index e3ef08a..bec24c1 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -13,12 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "mocha"; -import "reflect-metadata"; +/* eslint-disable no-unused-expressions */ +import 'mocha'; +import 'reflect-metadata'; -import { expect } from "chai"; +import { expect } from 'chai'; -import { NavigationTarget } from "./navigation-target-resolver"; +import { NavigationTarget } from './navigation-target-resolver'; describe('NavigationTarget', () => { diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index d6a6efd..81f3f63 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, generateRequestId, IActionDispatcher, ILogger, RequestAction, ResponseAction, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, generateRequestId, IActionDispatcher, ILogger, RequestAction, ResponseAction, TYPES } from 'sprotty'; -import { Args } from "../../base/args"; -import { EditorContextServiceProvider } from "../../base/editor-context"; -import { GLSP_TYPES } from "../../base/types"; +import { Args } from '../../base/args'; +import { EditorContextServiceProvider } from '../../base/editor-context'; +import { GLSP_TYPES } from '../../base/types'; export interface NavigationTarget { uri: string; @@ -37,7 +37,7 @@ export namespace NavigationTarget { return target.args !== undefined && Object.keys(target.args).length > 0; } - export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean) { + export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean): void { if (target.args === undefined) { target.args = {}; } @@ -52,14 +52,14 @@ export namespace NavigationTarget { return elementIdsValue.split(NavigationTarget.ELEMENT_IDS_SEPARATOR); } - export function setElementIds(target: NavigationTarget, elementIds: string[]) { + export function setElementIds(target: NavigationTarget, elementIds: string[]): string { if (target.args === undefined) { target.args = {}; } return target.args[NavigationTarget.ELEMENT_IDS] = elementIds.join(NavigationTarget.ELEMENT_IDS_SEPARATOR); } - export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined) { + export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void { if (position) { if (target.args === undefined) { target.args = {}; @@ -139,7 +139,7 @@ export class NavigationTargetResolver { return undefined; } - protected requestResolution(target: NavigationTarget) { + protected requestResolution(target: NavigationTarget): Promise { return this.dispatcher.request(new ResolveNavigationTargetAction(target)); } } diff --git a/packages/client/src/features/rank/model.ts b/packages/client/src/features/rank/model.ts index cab7dd5..1827e21 100644 --- a/packages/client/src/features/rank/model.ts +++ b/packages/client/src/features/rank/model.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export const DEFAULT_RANK: number = 0; +export const DEFAULT_RANK = 0; export interface Ranked { rank: number; diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index c0cca64..ed9e390 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -22,9 +22,9 @@ import { SModelExtension, SRoutableElement, SRoutingHandle -} from "sprotty"; +} from 'sprotty'; -export const reconnectFeature = Symbol("reconnectFeature"); +export const reconnectFeature = Symbol('reconnectFeature'); export interface Reconnectable extends SModelExtension { } @@ -33,28 +33,27 @@ export function isReconnectable(element: SModelElement): element is SRoutableEle return element instanceof SRoutableElement && element.hasFeature(reconnectFeature); } -const ROUTING_HANDLE_SOURCE_INDEX: number = -2; +const ROUTING_HANDLE_SOURCE_INDEX = -2; export function isReconnectHandle(element: SModelElement | undefined): element is SReconnectHandle { return element !== undefined && element instanceof SReconnectHandle; } - -export function addReconnectHandles(element: SRoutableElement) { +export function addReconnectHandles(element: SRoutableElement): void { removeReconnectHandles(element); createReconnectHandle(element, 'source', ROUTING_HANDLE_SOURCE_INDEX); createReconnectHandle(element, 'target', element.routingPoints.length); } -export function removeReconnectHandles(element: SRoutableElement) { +export function removeReconnectHandles(element: SRoutableElement): void { element.removeAll(child => child instanceof SReconnectHandle); } -export function isSourceRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle) { +export function isSourceRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle): boolean { return routingHandle.pointIndex === ROUTING_HANDLE_SOURCE_INDEX; } -export function isTargetRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle) { +export function isTargetRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle): boolean { return routingHandle.pointIndex === edge.routingPoints.length; } diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index bab4c64..7545e5b 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { TYPES } from 'sprotty'; -import { SaveModelKeyboardListener } from "./save"; +import { SaveModelKeyboardListener } from './save'; /** * This module is not required the diagram is deployed in Theia with the `GLSPDiagramWidget` diff --git a/packages/client/src/features/save/model.ts b/packages/client/src/features/save/model.ts index 9b68181..f618768 100644 --- a/packages/client/src/features/save/model.ts +++ b/packages/client/src/features/save/model.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelExtension, SModelRoot } from "sprotty"; +import { SModelElement, SModelExtension, SModelRoot } from 'sprotty'; export const saveFeature = Symbol.for('saveFeature'); diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save.ts index 8db7ad1..e73f7d9 100644 --- a/packages/client/src/features/save/save.ts +++ b/packages/client/src/features/save/save.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, KeyListener, SModelRoot } from "sprotty"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; +import { Action, KeyListener, SModelRoot } from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; export class SaveModelAction implements Action { - static readonly KIND = "saveModel"; + static readonly KIND = 'saveModel'; constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) { } } diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts index 51cb096..91cd953 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/di.config.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureCommand, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureCommand, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { SelectAllFeedbackCommand, SelectFeedbackCommand } from "./select-feedback-action"; -import { RankedSelectMouseListener } from "./select-mouse-listener"; -import { SelectAllCommand, SelectCommand, SelectionService } from "./selection-service"; +import { GLSP_TYPES } from '../../base/types'; +import { SelectAllFeedbackCommand, SelectFeedbackCommand } from './select-feedback-action'; +import { RankedSelectMouseListener } from './select-mouse-listener'; +import { SelectAllCommand, SelectCommand, SelectionService } from './selection-service'; const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { bind(SelectionService).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 209f3e3..6035753 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Command, CommandExecutionContext, @@ -21,7 +21,7 @@ import { SelectCommand as SprottySelectCommand, SModelRoot, TYPES -} from "sprotty"; +} from 'sprotty'; export class SelectFeedbackAction { constructor( diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 55c2ebb..7e1aedc 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SelectMouseListener } from "sprotty"; +import { SelectMouseListener } from 'sprotty'; -import { DEFAULT_RANK, Ranked } from "../rank/model"; +import { DEFAULT_RANK, Ranked } from '../rank/model'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index 1a078d8..a7e2585 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -13,14 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AssertionError, expect } from "chai"; -import { Container, injectable } from "inversify"; -import { Action, defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from "sprotty"; +/* eslint-disable import/no-deprecated,no-unused-expressions */ +import { AssertionError, expect } from 'chai'; +import { Container, injectable } from 'inversify'; +import { Action, defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; -import { SelectFeedbackAction } from "./select-feedback-action"; -import { SelectionListener, SelectionService } from "./selection-service"; +import { GLSP_TYPES } from '../../base/types'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; +import { SelectFeedbackAction } from './select-feedback-action'; +import { SelectionListener, SelectionService } from './selection-service'; @injectable() class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { @@ -47,7 +48,7 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { } class MockSelectionListener implements SelectionListener { - private called: number = 0; + private called = 0; private root: SModelRoot; private selectedElements: string[] = []; @@ -57,15 +58,15 @@ class MockSelectionListener implements SelectionListener { this.selectedElements = selectedElements; } - getRoot() { + getRoot(): SModelRoot { return this.root; } - getSelectedElements() { + getSelectedElements(): string[] { return this.selectedElements; } - getCalled() { + getCalled(): number { return this.called; } } @@ -332,12 +333,12 @@ describe('SelectionService', () => { }); } - function assertSelectionAndFeedback(expectedSelection: string[], expectedDeselection: string[]) { + function assertSelectionAndFeedback(expectedSelection: string[], expectedDeselection: string[]): void { assertSelectionService(expectedSelection); assertDispatchedFeedback(expectedSelection, expectedDeselection); } - function assertSelectionService(expectedSelection: string[]) { + function assertSelectionService(expectedSelection: string[]): void { expect(selectionService.isSingleSelection()).to.equal(expectedSelection.length === 1); expect(selectionService.isMultiSelection()).to.equal(expectedSelection.length > 1); expect(selectionService.hasSelectedElements()).to.equal(expectedSelection.length > 0); @@ -347,7 +348,7 @@ describe('SelectionService', () => { } } - function assertDispatchedFeedback(expectedSelection: string[], expectedDeselection: string[]) { + function assertDispatchedFeedback(expectedSelection: string[], expectedDeselection: string[]): void { // a single feedback action reflects aggregated selection/deselection const hasFeedback = expectedSelection.length > 0 || expectedDeselection.length > 0; if (hasFeedback) { @@ -362,7 +363,7 @@ describe('SelectionService', () => { } } - function assertListener(listener: MockSelectionListener, expectedRoot: SModelRoot | undefined, expectedSelection: string[], expectedCalled: number) { + function assertListener(listener: MockSelectionListener, expectedRoot: SModelRoot | undefined, expectedSelection: string[], expectedCalled: number): void { expect(listener.getRoot()).to.equal(expectedRoot); expect(listener.getSelectedElements()).to.have.lengthOf(expectedSelection.length); expect(listener.getSelectedElements()).to.have.ordered.members(expectedSelection); diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index f959741..ab6118f 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from "inversify"; +import { inject, injectable, multiInject, optional } from 'inversify'; import { Action, Command, @@ -30,13 +30,13 @@ import { SModelRoot, SParentElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { SModelRootListener } from "../../base/model/update-model-command"; -import { GLSP_TYPES } from "../../base/types"; -import { distinctAdd, remove } from "../../utils/array-utils"; -import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; -import { SelectFeedbackAction } from "./select-feedback-action"; +import { SModelRootListener } from '../../base/model/update-model-command'; +import { GLSP_TYPES } from '../../base/types'; +import { distinctAdd, remove } from '../../utils/array-utils'; +import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; +import { SelectFeedbackAction } from './select-feedback-action'; export interface SelectionListener { selectionChanged(root: Readonly, selectedElements: string[]): void; @@ -52,11 +52,11 @@ export class SelectionService implements SModelRootListener { constructor(@multiInject(GLSP_TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) { } - register(selectionListener: SelectionListener) { + register(selectionListener: SelectionListener): void { distinctAdd(this.selectionListeners, selectionListener); } - deregister(selectionListener: SelectionListener) { + deregister(selectionListener: SelectionListener): void { remove(this.selectionListeners, selectionListener); } @@ -64,7 +64,7 @@ export class SelectionService implements SModelRootListener { this.updateSelection(root, [], []); } - updateSelection(root: Readonly, select: string[], deselect: string[]) { + updateSelection(root: Readonly, select: string[], deselect: string[]): void { if (root === undefined && select.length === 0 && deselect.length === 0) { return; } @@ -112,11 +112,11 @@ export class SelectionService implements SModelRootListener { } } - dispatchFeedback(actions: Action[]) { + dispatchFeedback(actions: Action[]): void { this.feedbackDispatcher.registerFeedback(this, actions); } - notifyListeners(root: SModelRoot, selectedElementIDs: Set) { + notifyListeners(root: SModelRoot, selectedElementIDs: Set): void { this.selectionListeners.forEach(listener => listener.selectionChanged(root, Array.from(selectedElementIDs))); } @@ -149,7 +149,6 @@ export class SelectionService implements SModelRootListener { } } - @injectable() export class SelectCommand extends Command { static readonly KIND = SprottySelectCommand.KIND; diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 8e91023..254ae0c 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { VNode } from "snabbdom/vnode"; +import { inject, injectable } from 'inversify'; +import { VNode } from 'snabbdom/vnode'; import { Action, CommandExecutionContext, @@ -30,13 +30,13 @@ import { SModelElement, SModelRoot, TYPES -} from "sprotty"; +} from 'sprotty'; -import { isNotUndefined } from "../../utils/smodel-util"; -import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from "../change-bounds/model"; -import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from "../change-bounds/movement-restrictor"; -import { ChangeBoundsTool } from "../tools/change-bounds-tool"; -import { FeedbackCommand } from "./model"; +import { isNotUndefined } from '../../utils/smodel-util'; +import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from '../change-bounds/model'; +import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; +import { ChangeBoundsTool } from '../tools/change-bounds-tool'; +import { FeedbackCommand } from './model'; export class ShowChangeBoundsToolResizeFeedbackAction implements Action { constructor(readonly elementId?: string, public readonly kind: string = ShowChangeBoundsToolResizeFeedbackCommand.KIND) { } @@ -48,7 +48,7 @@ export class HideChangeBoundsToolResizeFeedbackAction implements Action { @injectable() export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { - static readonly KIND = "showChangeBoundsToolResizeFeedback"; + static readonly KIND = 'showChangeBoundsToolResizeFeedback'; @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; @@ -71,7 +71,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { @injectable() export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { - static readonly KIND = "hideChangeBoundsToolResizeFeedback"; + static readonly KIND = 'hideChangeBoundsToolResizeFeedback'; @inject(TYPES.Action) protected action: HideChangeBoundsToolResizeFeedbackAction; @@ -116,19 +116,22 @@ export class FeedbackMoveMouseListener extends MouseListener { mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; - if (event.buttons === 0) this.mouseUp(target, event); - else if (this.startDragPosition) { + if (event.buttons === 0) { + this.mouseUp(target, event); + } else if (this.startDragPosition) { if (this.elementId2startPos.size === 0) { this.collectStartPositions(target.root); } this.hasDragged = true; const moveAction = this.getElementMoves(target, event, false); - if (moveAction) result.push(moveAction); + if (moveAction) { + result.push(moveAction); + } } return result; } - protected collectStartPositions(root: SModelRoot) { + protected collectStartPositions(root: SModelRoot): void { root.index .all() .filter(element => isSelectable(element) && element.selected) @@ -140,7 +143,9 @@ export class FeedbackMoveMouseListener extends MouseListener { } protected getElementMoves(target: SModelElement, event: MouseEvent, isFinished: boolean): MoveAction | undefined { - if (!this.startDragPosition) return undefined; + if (!this.startDragPosition) { + return undefined; + } const elementMoves: ElementMove[] = []; const viewport = findParentByFeature(target, isViewport); const zoom = viewport ? viewport.zoom : 1; @@ -173,12 +178,14 @@ export class FeedbackMoveMouseListener extends MouseListener { } } }); - if (elementMoves.length > 0) + if (elementMoves.length > 0) { return new MoveAction(elementMoves, false, isFinished); - else return undefined; + } else { + return undefined; + } } - protected validateMove(startPostion: Point, toPosition: Point, element: SModelElement, isFinished: boolean) { + protected validateMove(startPostion: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { let newPosition = toPosition; if (this.tool.movementRestrictor) { const valid = this.tool.movementRestrictor.validate(toPosition, element); @@ -199,13 +206,17 @@ export class FeedbackMoveMouseListener extends MouseListener { } protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { - if (isSnap && this.tool.snapper) return this.tool.snapper.snap(position, element); - else return position; + if (isSnap && this.tool.snapper) { + return this.tool.snapper.snap(position, element); + } else { + return position; + } } mouseEnter(target: SModelElement, event: MouseEvent): Action[] { - if (target instanceof SModelRoot && event.buttons === 0) + if (target instanceof SModelRoot && event.buttons === 0) { this.mouseUp(target, event); + } return []; } diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 31eace5..c474a18 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, AnchorComputerRegistry, @@ -36,11 +36,11 @@ import { SModelRoot, SRoutableElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { isRoutable } from "../../utils/smodel-util"; -import { getAbsolutePosition, toAbsolutePosition } from "../../utils/viewpoint-util"; -import { FeedbackCommand } from "./model"; +import { isRoutable } from '../../utils/smodel-util'; +import { getAbsolutePosition, toAbsolutePosition } from '../../utils/viewpoint-util'; +import { FeedbackCommand } from './model'; export class DrawFeedbackEdgeAction implements Action { constructor( @@ -128,12 +128,12 @@ export function feedbackEdgeEndId(root: SModelRoot): string { return root.id + '_feedback_anchor'; } -export const defaultFeedbackEdgeSchema: SEdgeSchema = { - cssClasses: ["feedback-edge"], +export const defaultFeedbackEdgeSchema: SEdgeSchema = { + cssClasses: ['feedback-edge'], opacity: 0.3 -}; +} as SEdgeSchema; -export function drawFeedbackEdge(context: CommandExecutionContext, sourceId: string, elementTypeId: string, feedbackEdgeSchema: SEdgeSchema) { +export function drawFeedbackEdge(context: CommandExecutionContext, sourceId: string, elementTypeId: string, feedbackEdgeSchema: SEdgeSchema): void { const root = context.root; const sourceChild = root.index.getById(sourceId); if (!sourceChild) { @@ -162,11 +162,13 @@ export function drawFeedbackEdge(context: CommandExecutionContext, sourceId: str } } -export function removeFeedbackEdge(root: SModelRoot) { +export function removeFeedbackEdge(root: SModelRoot): void { const feedbackEdge = root.index.getById(feedbackEdgeId(root)); const feedbackEdgeEnd = root.index.getById(feedbackEdgeEndId(root)); - if (feedbackEdge instanceof SChildElement) + if (feedbackEdge instanceof SChildElement) { root.remove(feedbackEdge); - if (feedbackEdgeEnd instanceof SChildElement) + } + if (feedbackEdgeEnd instanceof SChildElement) { root.remove(feedbackEdgeEnd); + } } diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index dd993a0..7ce7df7 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from 'sprotty'; -import { isStringArray } from "../../utils/array-utils"; -import { addCssClasses, removeCssClasses } from "../../utils/smodel-util"; -import { FeedbackCommand } from "./model"; +import { isStringArray } from '../../utils/array-utils'; +import { addCssClasses, removeCssClasses } from '../../utils/smodel-util'; +import { FeedbackCommand } from './model'; export class ModifyCSSFeedbackAction implements Action { readonly elementIds?: string[]; @@ -76,9 +76,9 @@ export enum CursorCSS { EDGE_CREATION_TARGET = 'edge-creation-select-target-mode', EDGE_RECONNECT = 'edge-reconnect-select-target-mode', OPERATION_NOT_ALLOWED = 'edge-modification-not-allowed-mode', - ELEMENT_DELETION = "element-deletion-mode", - RESIZE = "resize-mode", - MARQUEE = "marquee-mode" + ELEMENT_DELETION = 'element-deletion-mode', + RESIZE = 'resize-mode', + MARQUEE = 'marquee-mode' } export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAction { @@ -89,10 +89,10 @@ export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAc return new ModifyCSSFeedbackAction(undefined, addCss, Object.values(CursorCSS)); } -export function applyCssClasses(element: SModelElement, ...classes: string[]) { +export function applyCssClasses(element: SModelElement, ...classes: string[]): ModifyCSSFeedbackAction { return new ModifyCSSFeedbackAction([element], classes, []); } -export function deleteCssClasses(element: SModelElement, ...classes: string[]) { +export function deleteCssClasses(element: SModelElement, ...classes: string[]): ModifyCSSFeedbackAction { return new ModifyCSSFeedbackAction([element], [], classes); } diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index 2d39b9c..f261fe8 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -13,26 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; -import { SResizeHandle } from "../change-bounds/model"; +import { GLSP_TYPES } from '../../base/types'; +import { SResizeHandle } from '../change-bounds/model'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand -} from "./change-bounds-tool-feedback"; -import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from "./creation-tool-feedback"; -import { ModifyCssFeedbackCommand } from "./css-feedback"; +} from './change-bounds-tool-feedback'; +import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from './creation-tool-feedback'; +import { ModifyCssFeedbackCommand } from './css-feedback'; import { DrawFeedbackEdgeSourceCommand, HideEdgeReconnectHandlesFeedbackCommand, ShowEdgeReconnectHandlesFeedbackCommand, SwitchRoutingModeCommand -} from "./edge-edit-tool-feedback"; -import { FeedbackActionDispatcher } from "./feedback-action-dispatcher"; -import { DrawMarqueeCommand, RemoveMarqueeCommand } from "./marquee-tool-feedback"; -import { FeedbackEdgeEndView, SResizeHandleView } from "./view"; +} from './edge-edit-tool-feedback'; +import { FeedbackActionDispatcher } from './feedback-action-dispatcher'; +import { DrawMarqueeCommand, RemoveMarqueeCommand } from './marquee-tool-feedback'; +import { FeedbackEdgeEndView, SResizeHandleView } from './view'; const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { bind(GLSP_TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 263028b..af21f42 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { VNode } from "snabbdom/vnode"; +import { inject, injectable } from 'inversify'; +import { VNode } from 'snabbdom/vnode'; import { Action, AnchorComputerRegistry, @@ -41,18 +41,18 @@ import { SwitchEditModeAction, SwitchEditModeCommand, TYPES -} from "sprotty"; +} from 'sprotty'; -import { isNotUndefined, isRoutable, isRoutingHandle } from "../../utils/smodel-util"; -import { getAbsolutePosition } from "../../utils/viewpoint-util"; -import { addReconnectHandles, removeReconnectHandles } from "../reconnect/model"; +import { isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; +import { getAbsolutePosition } from '../../utils/viewpoint-util'; +import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; import { FeedbackEdgeEnd, feedbackEdgeEndId, FeedbackEdgeEndMovingMouseListener, feedbackEdgeId -} from "./creation-tool-feedback"; -import { FeedbackCommand } from "./model"; +} from './creation-tool-feedback'; +import { FeedbackCommand } from './model'; /** * RECONNECT HANDLES FEEDBACK @@ -116,8 +116,10 @@ export class SwitchRoutingModeAction extends SwitchEditModeAction { @injectable() export class SwitchRoutingModeCommand extends SwitchEditModeCommand { - static KIND = "switchRoutingMode"; - constructor(@inject(TYPES.Action) action: SwitchRoutingModeAction) { super(action); } + static KIND = 'switchRoutingMode'; + constructor(@inject(TYPES.Action) action: SwitchRoutingModeAction) { + super(action); + } } /** @@ -208,9 +210,9 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; - if (event.buttons === 0) + if (event.buttons === 0) { this.mouseUp(target, event); - else if (this.lastDragPosition) { + } else if (this.lastDragPosition) { const viewport = findParentByFeature(target, isViewport); this.hasDragged = true; const zoom = viewport ? viewport.zoom : 1; @@ -235,8 +237,9 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } }); this.lastDragPosition = { x: event.pageX, y: event.pageY }; - if (handleMoves.length > 0) + if (handleMoves.length > 0) { result.push(new MoveAction(handleMoves, false)); + } } return result; } @@ -244,8 +247,9 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { protected getHandlePosition(handle: SRoutingHandle): Point | undefined { if (this.edgeRouterRegistry) { const parent = handle.parent; - if (!isRoutable(parent)) + if (!isRoutable(parent)) { return undefined; + } const router = this.edgeRouterRegistry.get(parent.routerKind); const route = router.route(parent); return router.getHandlePosition(parent, route, handle); @@ -254,8 +258,9 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } mouseEnter(target: SModelElement, event: MouseEvent): Action[] { - if (target instanceof SModelRoot && event.buttons === 0) + if (target instanceof SModelRoot && event.buttons === 0) { this.mouseUp(target, event); + } return []; } @@ -274,7 +279,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { * UTILITY FUNCTIONS */ -function drawFeedbackEdgeSource(context: CommandExecutionContext, targetId: string, elementTypeId: string) { +function drawFeedbackEdgeSource(context: CommandExecutionContext, targetId: string, elementTypeId: string): void { const root = context.root; const targetChild = root.index.getById(targetId); if (!targetChild) { diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index 6ddc040..1bd8d4e 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, IActionDispatcher, ILogger, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, IActionDispatcher, ILogger, TYPES } from 'sprotty'; export interface IFeedbackEmitter { } @@ -54,7 +54,7 @@ export interface IFeedbackActionDispatcher { /** * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. */ - getRegisteredFeedback(): Action[] + getRegisteredFeedback(): Action[]; } @injectable() @@ -74,20 +74,20 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { this.dispatch(actions, feedbackEmitter); } - private dispatch(actions: Action[], feedbackEmitter: IFeedbackEmitter) { + private dispatch(actions: Action[], feedbackEmitter: IFeedbackEmitter): void { this.actionDispatcher() .then(dispatcher => dispatcher.dispatchAll(actions)) .then(() => this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`)) .catch(reason => this.logger.error(this, 'Failed to dispatch feedback actions', reason)); } - getRegisteredFeedback() { + getRegisteredFeedback(): Action[] { const result: Action[] = []; this.feedbackEmitters.forEach((value, key) => result.push(...value)); return result; } - getRegisteredFeedbackEmitters(action: Action) { + getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { const result: IFeedbackEmitter[] = []; this.feedbackEmitters.forEach((value, key) => { if (value.find(a => a === action)) { diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index c0d25c0..90fad14 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, AnchorComputerRegistry, @@ -25,9 +25,9 @@ import { SModelElement, SModelRoot, TYPES -} from "sprotty"; +} from 'sprotty'; -import { FeedbackCommand } from "./model"; +import { FeedbackCommand } from './model'; export class DrawMarqueeAction implements Action { constructor( @@ -80,7 +80,7 @@ export function marqueeId(root: SModelRoot): string { export const MARQUEE = 'marquee'; -export function drawMarquee(context: CommandExecutionContext, startPoint: Point, endPoint: Point) { +export function drawMarquee(context: CommandExecutionContext, startPoint: Point, endPoint: Point): void { const root = context.root; removeMarquee(root); @@ -89,15 +89,16 @@ export function drawMarquee(context: CommandExecutionContext, startPoint: Point, type: MARQUEE, id: marqueeId(root), startPoint: startPoint, - endPoint: endPoint, + endPoint: endPoint }; const marquee = context.modelFactory.createElement(marqueeSchema); root.add(marquee); } -export function removeMarquee(root: SModelRoot) { +export function removeMarquee(root: SModelRoot): void { const marquee = root.index.getById(marqueeId(root)); - if (marquee instanceof SChildElement) + if (marquee instanceof SChildElement) { root.remove(marquee); + } } diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts index 1fc79f9..a308472 100644 --- a/packages/client/src/features/tool-feedback/model.ts +++ b/packages/client/src/features/tool-feedback/model.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Command, CommandExecutionContext, CommandReturn } from "sprotty"; +import { Command, CommandExecutionContext, CommandReturn } from 'sprotty'; export abstract class FeedbackCommand extends Command { // used by the `FeedbackAwareUpdateModelCommand` diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index 733c1c3..f3aba9a 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -13,13 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from "inversify"; -import * as snabbdom from "snabbdom-jsx"; -import { VNode } from "snabbdom/vnode"; -import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement } from "sprotty"; +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { VNode } from 'snabbdom/vnode'; +import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement } from 'sprotty'; -import { isResizable, ResizeHandleLocation, SResizeHandle } from "../change-bounds/model"; +import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: snabbdom.svg }; /** diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts index 51d1dfd..be30fef 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "../../../css/tool-palette.css"; +import '../../../css/tool-palette.css'; -import { ContainerModule } from "inversify"; -import { configureActionHandler, EnableDefaultToolsAction, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureActionHandler, EnableDefaultToolsAction, TYPES } from 'sprotty'; -import { EnableToolPaletteAction, ToolPalette } from "./tool-palette"; +import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; const toolPaletteModule = new ContainerModule((bind, _unbind, isBound) => { bind(ToolPalette).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts index f472777..9a32eb1 100644 --- a/packages/client/src/features/tool-palette/palette-item.ts +++ b/packages/client/src/features/tool-palette/palette-item.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isLabeledAction, LabeledAction } from "sprotty"; +import { isLabeledAction, LabeledAction } from 'sprotty'; -import { isTriggerElementTypeCreationAction, TriggerElementCreationAction } from "../../base/operations/operation"; +import { isTriggerElementTypeCreationAction, TriggerElementCreationAction } from '../../base/operations/operation'; export interface PaletteItem extends LabeledAction { readonly id: string; @@ -24,7 +24,7 @@ export interface PaletteItem extends LabeledAction { } export function isPaletteItem(object?: any): object is PaletteItem { - return isLabeledAction(object) && "id" in object && "sortString" in object; + return isLabeledAction(object) && 'id' in object && 'sortString' in object; } export namespace PaletteItem { diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 5112777..4b23db5 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, postConstruct } from "inversify"; +import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, Action, @@ -25,32 +25,32 @@ import { SetUIExtensionVisibilityAction, SModelRoot, TYPES -} from "sprotty"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; - -import { GLSPActionDispatcher } from "../../base/action-dispatcher"; -import { isSetContextActionsAction, RequestContextActions, SetContextActions } from "../../base/actions/context-actions"; -import { EditModeListener, EditorContextService } from "../../base/editor-context"; -import { MarqueeMouseTool } from "../tools/marquee-mouse-tool"; -import { MouseDeleteTool } from "../tools/delete-tool"; -import { RequestMarkersAction } from "../validation/validate"; -import { PaletteItem } from "./palette-item"; - -const CLICKED_CSS_CLASS = "clicked"; -const SEARCH_ICON = "fa-search"; -const PALETTE_ICON = "fa-palette"; -const CHEVRON_DOWN = "fa-chevron-down"; -const PALETTE_HEIGHT = "500px"; +} from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; + +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { isSetContextActionsAction, RequestContextActions, SetContextActions } from '../../base/actions/context-actions'; +import { EditModeListener, EditorContextService } from '../../base/editor-context'; +import { MouseDeleteTool } from '../tools/delete-tool'; +import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; +import { RequestMarkersAction } from '../validation/validate'; +import { PaletteItem } from './palette-item'; + +const CLICKED_CSS_CLASS = 'clicked'; +const SEARCH_ICON = 'fa-search'; +const PALETTE_ICON = 'fa-palette'; +const CHEVRON_DOWN = 'fa-chevron-down'; +const PALETTE_HEIGHT = '500px'; @injectable() export class EnableToolPaletteAction implements Action { - static readonly KIND = "enableToolPalette"; + static readonly KIND = 'enableToolPalette'; readonly kind = EnableToolPaletteAction.KIND; } @injectable() export class ToolPalette extends AbstractUIExtension implements IActionHandler, EditModeListener { - static readonly ID = "tool-palette"; + static readonly ID = 'tool-palette'; @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; @inject(TYPES.IToolManager) protected readonly toolManager: IToolManager; @@ -64,8 +64,12 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected searchField: HTMLInputElement; modelRootId: string; - id() { return ToolPalette.ID; } - containerClass() { return ToolPalette.ID; } + id(): string { + return ToolPalette.ID; + } + containerClass(): string { + return ToolPalette.ID; + } @postConstruct() postConstruct(): void { @@ -85,37 +89,37 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.lastActivebutton = this.defaultToolsButton; } - protected onBeforeShow(_containerElement: HTMLElement, root: Readonly) { + protected onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { this.modelRootId = root.id; this.containerElement.style.maxHeight = PALETTE_HEIGHT; } protected addMinimizePaletteButton(): void { const baseDiv = document.getElementById(this.options.baseDiv); - const minPaletteDiv = document.createElement("div"); - minPaletteDiv.classList.add("minimize-palette-button"); - this.containerElement.classList.add("collapsible-palette"); + const minPaletteDiv = document.createElement('div'); + minPaletteDiv.classList.add('minimize-palette-button'); + this.containerElement.classList.add('collapsible-palette'); if (baseDiv) { const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); - const minimizeIcon = createIcon(["fas", CHEVRON_DOWN]); + const minimizeIcon = createIcon(['fas', CHEVRON_DOWN]); this.updateMinimizePaletteButtonTooltip(minPaletteDiv); minimizeIcon.onclick = _event => { if (this.isPaletteMaximized()) { - this.containerElement.style.maxHeight = "0px"; + this.containerElement.style.maxHeight = '0px'; } else { this.containerElement.style.maxHeight = PALETTE_HEIGHT; } this.updateMinimizePaletteButtonTooltip(minPaletteDiv); changeCSSClass(minimizeIcon, PALETTE_ICON); - changeCSSClass(minimizeIcon, "fa"); - changeCSSClass(minimizeIcon, "fas"); + changeCSSClass(minimizeIcon, 'fa'); + changeCSSClass(minimizeIcon, 'fas'); changeCSSClass(minimizeIcon, CHEVRON_DOWN); }; insertedDiv.appendChild(minimizeIcon); } } - protected updateMinimizePaletteButtonTooltip(button: HTMLDivElement) { + protected updateMinimizePaletteButtonTooltip(button: HTMLDivElement): void { if (this.isPaletteMaximized()) { button.title = 'Minimize palette'; } else { @@ -123,13 +127,13 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - protected isPaletteMaximized() { - return this.containerElement && this.containerElement.style.maxHeight !== "0px"; + protected isPaletteMaximized(): boolean { + return this.containerElement && this.containerElement.style.maxHeight !== '0px'; } protected createBody(): void { - const bodyDiv = document.createElement("div"); - bodyDiv.classList.add("palette-body"); + const bodyDiv = document.createElement('div'); + bodyDiv.classList.add('palette-body'); let tabIndex = 0; this.paletteItems.sort(compare) .forEach(item => { @@ -142,9 +146,9 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } }); if (this.paletteItems.length === 0) { - const noResultsDiv = document.createElement("div"); - noResultsDiv.innerText = "No results found."; - noResultsDiv.classList.add("tool-button"); + const noResultsDiv = document.createElement('div'); + noResultsDiv.innerText = 'No results found.'; + noResultsDiv.classList.add('tool-button'); bodyDiv.appendChild(noResultsDiv); } // Remove existing body to refresh filtered entries @@ -157,8 +161,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected createHeader(): void { this.addMinimizePaletteButton(); - const headerCompartment = document.createElement("div"); - headerCompartment.classList.add("palette-header"); + const headerCompartment = document.createElement('div'); + headerCompartment.classList.add('palette-header'); headerCompartment.append(this.createHeaderTitle()); headerCompartment.appendChild(this.createHeaderTools()); headerCompartment.appendChild(this.searchField = this.createHeaderSearchField()); @@ -166,8 +170,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } private createHeaderTools(): HTMLElement { - const headerTools = document.createElement("div"); - headerTools.classList.add("header-tools"); + const headerTools = document.createElement('div'); + headerTools.classList.add('header-tools'); this.defaultToolsButton = this.createDefaultToolButton(); headerTools.appendChild(this.defaultToolsButton); @@ -188,30 +192,30 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, return headerTools; } - protected createDefaultToolButton() { - const button = createIcon(["fas", "fa-mouse-pointer", "fa-xs", "clicked"]); - button.id = "btn_default_tools"; + protected createDefaultToolButton(): HTMLElement { + const button = createIcon(['fas', 'fa-mouse-pointer', 'fa-xs', 'clicked']); + button.id = 'btn_default_tools'; button.title = 'Enable selection tool'; button.onclick = this.onClickStaticToolButton(this.defaultToolsButton); return button; } - protected createMouseDeleteToolButton() { - const deleteToolButton = createIcon(["fas", "fa-eraser", "fa-xs"]); + protected createMouseDeleteToolButton(): HTMLElement { + const deleteToolButton = createIcon(['fas', 'fa-eraser', 'fa-xs']); deleteToolButton.title = 'Enable deletion tool'; deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); return deleteToolButton; } - protected createMarqueeToolButton() { - const marqueeToolButton = createIcon(["far", "fa-object-group", "fa-xs"]); + protected createMarqueeToolButton(): HTMLElement { + const marqueeToolButton = createIcon(['far', 'fa-object-group', 'fa-xs']); marqueeToolButton.title = 'Enable marquee tool'; marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID); return marqueeToolButton; } - protected createValidateButton() { - const validateActionButton = createIcon(["fas", "fa-check-square", "fa-xs"]); + protected createValidateButton(): HTMLElement { + const validateActionButton = createIcon(['fas', 'fa-check-square', 'fa-xs']); validateActionButton.title = 'Validate model'; validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; @@ -220,51 +224,51 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, return validateActionButton; } - protected createSearchButton() { - const searchIcon = createIcon(["fas", SEARCH_ICON, "state-icon", "fa-xs"]); - searchIcon.onclick = (_ev) => { - const searchField = document.getElementById(this.containerElement.id + "_search_field"); + protected createSearchButton(): HTMLElement { + const searchIcon = createIcon(['fas', SEARCH_ICON, 'state-icon', 'fa-xs']); + searchIcon.onclick = _ev => { + const searchField = document.getElementById(this.containerElement.id + '_search_field'); if (searchField) { - if (searchField.style.display === "inline") { - searchField.style.display = "none"; + if (searchField.style.display === 'inline') { + searchField.style.display = 'none'; } else { - searchField.style.display = "inline"; + searchField.style.display = 'inline'; searchField.focus(); } } }; - searchIcon.classList.add("search-icon"); + searchIcon.classList.add('search-icon'); searchIcon.title = 'Filter palette entries'; return searchIcon; } protected createHeaderSearchField(): HTMLInputElement { - const searchField = document.createElement("input"); - searchField.classList.add("search-input"); - searchField.id = this.containerElement.id + "_search_field"; - searchField.type = "text"; - searchField.placeholder = " Search..."; - searchField.style.display = "none"; + const searchField = document.createElement('input'); + searchField.classList.add('search-input'); + searchField.id = this.containerElement.id + '_search_field'; + searchField.type = 'text'; + searchField.placeholder = ' Search...'; + searchField.style.display = 'none'; searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); - searchField.onkeydown = (ev) => this.clearOnEscape(ev); + searchField.onkeydown = ev => this.clearOnEscape(ev); return searchField; } protected createHeaderTitle(): HTMLElement { - const header = document.createElement("div"); - header.classList.add("header-icon"); - header.appendChild(createIcon(["fa", "fa-palette"])); - header.insertAdjacentText("beforeend", "Palette"); + const header = document.createElement('div'); + header.classList.add('header-icon'); + header.appendChild(createIcon(['fa', 'fa-palette'])); + header.insertAdjacentText('beforeend', 'Palette'); return header; } protected createToolButton(item: PaletteItem, index: number): HTMLElement { - const button = document.createElement("div"); + const button = document.createElement('div'); button.tabIndex = index; - button.classList.add("tool-button"); + button.classList.add('tool-button'); button.innerHTML = item.label; button.onclick = this.onClickCreateToolButton(button, item); - button.onkeydown = (ev) => this.clearToolOnEscape(ev); + button.onkeydown = ev => this.clearToolOnEscape(ev); return button; } @@ -289,7 +293,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, }; } - changeActiveButton(button?: HTMLElement) { + changeActiveButton(button?: HTMLElement): void { if (this.lastActivebutton) { this.lastActivebutton.classList.remove(CLICKED_CSS_CLASS); } @@ -319,24 +323,24 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - editModeChanged(_oldValue: string, _newValue: string) { + editModeChanged(_oldValue: string, _newValue: string): void { this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); } - protected clearOnEscape(event: KeyboardEvent) { - if (matchesKeystroke(event, "Escape")) { - this.searchField.value = ""; - this.requestFilterUpdate(""); + protected clearOnEscape(event: KeyboardEvent): void { + if (matchesKeystroke(event, 'Escape')) { + this.searchField.value = ''; + this.requestFilterUpdate(''); } } - protected clearToolOnEscape(event: KeyboardEvent) { - if (matchesKeystroke(event, "Escape")) { + protected clearToolOnEscape(event: KeyboardEvent): void { + if (matchesKeystroke(event, 'Escape')) { this.actionDispatcher.dispatch(new EnableDefaultToolsAction()); } } - protected handleSetContextActions(action: SetContextActions) { + protected handleSetContextActions(action: SetContextActions): void { this.paletteItems = action.actions.map(e => e as PaletteItem); this.createBody(); } @@ -379,24 +383,24 @@ export function compare(a: PaletteItem, b: PaletteItem): number { return a.label.localeCompare(b.label); } -export function createIcon(cssClasses: string[]) { - const icon = document.createElement("i"); +export function createIcon(cssClasses: string[]): HTMLElement { + const icon = document.createElement('i'); icon.classList.add(...cssClasses); return icon; } export function createToolGroup(item: PaletteItem): HTMLElement { - const group = document.createElement("div"); - group.classList.add("tool-group"); + const group = document.createElement('div'); + group.classList.add('tool-group'); group.id = item.id; - const header = document.createElement("div"); - header.classList.add("group-header"); + const header = document.createElement('div'); + header.classList.add('group-header'); if (item.icon) { - header.appendChild(createIcon(["fas", item.icon])); + header.appendChild(createIcon(['fas', item.icon])); } header.insertAdjacentText('beforeend', item.label); - header.ondblclick = (_ev) => { - const css = "collapsed"; + header.ondblclick = _ev => { + const css = 'collapsed'; changeCSSClass(group, css); Array.from(group.children).forEach(child => changeCSSClass(child, css)); window!.getSelection()!.removeAllRanges(); @@ -406,7 +410,7 @@ export function createToolGroup(item: PaletteItem): HTMLElement { return group; } -export function changeCSSClass(element: Element, css: string) { +export function changeCSSClass(element: Element, css: string): void { element.classList.contains(css) ? element.classList.remove(css) : element.classList.add(css); } diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 086e439..7cfaea5 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, IActionDispatcher, KeyTool, TYPES } from "sprotty"; +import { inject, injectable } from 'inversify'; +import { Action, IActionDispatcher, KeyTool, TYPES } from 'sprotty'; -import { EditorContextService } from "../../base/editor-context"; -import { GLSPTool } from "../../base/tool-manager/glsp-tool-manager"; -import { GLSP_TYPES } from "../../base/types"; -import { IMouseTool } from "../mouse-tool/mouse-tool"; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; +import { EditorContextService } from '../../base/editor-context'; +import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; +import { GLSP_TYPES } from '../../base/types'; +import { IMouseTool } from '../mouse-tool/mouse-tool'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; @injectable() export abstract class BaseGLSPTool implements GLSPTool { @@ -39,15 +39,15 @@ export abstract class BaseGLSPTool implements GLSPTool { return true; } - dispatchFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter) { + dispatchFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter): void { this.feedbackDispatcher.registerFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); } - dispatchActions(actions: Action[]) { + dispatchActions(actions: Action[]): void { this.actionDispatcher.dispatchAll(actions); } - deregisterFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter) { + deregisterFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter): void { this.feedbackDispatcher.deregisterFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); } } diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 981c1f7..2222319 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, Bounds, @@ -33,38 +33,38 @@ import { SModelRoot, SParentElement, TYPES -} from "sprotty"; +} from 'sprotty'; -import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { ChangeBoundsOperation, ChangeRoutingPointsOperation, CompoundOperation, ElementAndRoutingPoints, Operation -} from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; -import { isValidMove, isValidSize, WriteablePoint } from "../../utils/layout-utils"; +} from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; +import { isValidMove, isValidSize, WriteablePoint } from '../../utils/layout-utils'; import { forEachElement, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds, toElementAndRoutingPoints -} from "../../utils/smodel-util"; -import { isBoundsAwareMoveable, isResizable, Resizable, ResizeHandleLocation, SResizeHandle } from "../change-bounds/model"; +} from '../../utils/smodel-util'; +import { isBoundsAwareMoveable, isResizable, Resizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; import { createMovementRestrictionFeedback, IMovementRestrictor, removeMovementRestrictionFeedback -} from "../change-bounds/movement-restrictor"; -import { SelectionListener, SelectionService } from "../select/selection-service"; +} from '../change-bounds/movement-restrictor'; +import { SelectionListener, SelectionService } from '../select/selection-service'; import { FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, ShowChangeBoundsToolResizeFeedbackAction -} from "../tool-feedback/change-bounds-tool-feedback"; -import { applyCssClasses, CursorCSS, cursorFeedbackAction, deleteCssClasses } from "../tool-feedback/css-feedback"; -import { BaseGLSPTool } from "./base-glsp-tool"; +} from '../tool-feedback/change-bounds-tool-feedback'; +import { applyCssClasses, CursorCSS, cursorFeedbackAction, deleteCssClasses } from '../tool-feedback/css-feedback'; +import { BaseGLSPTool } from './base-glsp-tool'; /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. @@ -81,7 +81,7 @@ import { BaseGLSPTool } from "./base-glsp-tool"; */ @injectable() export class ChangeBoundsTool extends BaseGLSPTool { - static ID = "glsp.change-bounds-tool"; + static ID = 'glsp.change-bounds-tool'; @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @@ -94,7 +94,7 @@ export class ChangeBoundsTool extends BaseGLSPTool { return ChangeBoundsTool.ID; } - enable() { + enable(): void { // install feedback move mouse listener for client-side move updates this.feedbackMoveMouseListener = this.createMoveMouseListener(); this.mouseTool.register(this.feedbackMoveMouseListener); @@ -113,7 +113,7 @@ export class ChangeBoundsTool extends BaseGLSPTool { return new ChangeBoundsListener(this); } - disable() { + disable(): void { this.mouseTool.deregister(this.changeBoundsListener); this.selectionService.deregister(this.changeBoundsListener); this.mouseTool.deregister(this.feedbackMoveMouseListener); @@ -277,7 +277,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; } - protected initPosition(event: MouseEvent) { + protected initPosition(event: MouseEvent): void { this.lastDragPosition = { x: event.pageX, y: event.pageY }; if (this.activeResizeHandle) { const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); @@ -315,7 +315,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return undefined; } - protected reset() { + protected reset(): void { if (this.activeResizeElement && isResizable(this.activeResizeElement)) { this.tool.dispatchFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this); @@ -324,7 +324,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele this.resetPosition(); } - protected resetPosition() { + protected resetPosition(): void { this.activeResizeHandle = undefined; this.lastDragPosition = undefined; this.positionDelta = { x: 0, y: 0 }; @@ -435,11 +435,11 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return this.isValidSize(element, newSize) && this.isValidMove(element, newPosition); } - protected isValidSize(element: SModelElement & BoundsAware, size: Dimension) { + protected isValidSize(element: SModelElement & BoundsAware, size: Dimension): boolean { return isValidSize(element, size); } - protected isValidMove(element: SModelElement & BoundsAware, newPosition: Point) { + protected isValidMove(element: SModelElement & BoundsAware, newPosition: Point): boolean { return isValidMove(element, newPosition, this.tool.movementRestrictor); } } diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index be6e21e..ec58de2 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, EnableDefaultToolsAction, @@ -25,22 +25,22 @@ import { KeyTool, MouseListener, SModelElement -} from "sprotty"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; +} from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { DeleteElementOperation } from "../../base/operations/operation"; -import { GLSPTool } from "../../base/tool-manager/glsp-tool-manager"; -import { GLSP_TYPES } from "../../base/types"; -import { IMouseTool } from "../mouse-tool/mouse-tool"; -import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; -import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; +import { DeleteElementOperation } from '../../base/operations/operation'; +import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; +import { GLSP_TYPES } from '../../base/types'; +import { IMouseTool } from '../mouse-tool/mouse-tool'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; /** * Deletes selected elements when hitting the `Del` key. */ @injectable() export class DelKeyDeleteTool implements GLSPTool { - static ID = "glsp.delete-keyboard"; + static ID = 'glsp.delete-keyboard'; isEditTool = true; protected deleteKeyListener: DeleteKeyListener = new DeleteKeyListener(); @@ -51,11 +51,11 @@ export class DelKeyDeleteTool implements GLSPTool { return DelKeyDeleteTool.ID; } - enable() { + enable(): void { this.keytool.register(this.deleteKeyListener); } - disable() { + disable(): void { this.keytool.deregister(this.deleteKeyListener); } } @@ -79,7 +79,7 @@ export class DeleteKeyListener extends KeyListener { */ @injectable() export class MouseDeleteTool implements GLSPTool { - static ID = "glsp.delete-mouse"; + static ID = 'glsp.delete-mouse'; isEditTool = true; @@ -92,12 +92,12 @@ export class MouseDeleteTool implements GLSPTool { return MouseDeleteTool.ID; } - enable() { + enable(): void { this.mouseTool.register(this.deleteToolMouseListener); this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction(CursorCSS.ELEMENT_DELETION)]); } - disable() { + disable(): void { this.mouseTool.deregister(this.deleteToolMouseListener); this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction()]); } diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index f55a449..125ae95 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -13,21 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule, interfaces } from "inversify"; -import { configureActionHandler, configureModelElement } from "sprotty"; +import { ContainerModule, interfaces } from 'inversify'; +import { configureActionHandler, configureModelElement } from 'sprotty'; -import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; -import { MARQUEE } from "../tool-feedback/marquee-tool-feedback"; -import { ChangeBoundsTool } from "./change-bounds-tool"; -import { DelKeyDeleteTool, MouseDeleteTool } from "./delete-tool"; -import { EdgeCreationTool } from "./edge-creation-tool"; -import { EdgeEditTool } from "./edge-edit-tool"; -import { MarqueeMouseTool } from "./marquee-mouse-tool"; -import { MarqueeTool } from "./marquee-tool"; -import { MarqueeNode } from "./model"; -import { NodeCreationTool } from "./node-creation-tool"; -import { MarqueeView } from "./view"; +import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; +import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; +import { ChangeBoundsTool } from './change-bounds-tool'; +import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; +import { EdgeCreationTool } from './edge-creation-tool'; +import { EdgeEditTool } from './edge-edit-tool'; +import { MarqueeMouseTool } from './marquee-mouse-tool'; +import { MarqueeTool } from './marquee-tool'; +import { MarqueeNode } from './model'; +import { NodeCreationTool } from './node-creation-tool'; +import { MarqueeView } from './view'; const toolsModule = new ContainerModule((bind, _unbind, isBound) => { // Register default tools @@ -47,7 +47,7 @@ const toolsModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); }); -export function configureMarqueeTool(context: { bind: interfaces.Bind, isBound: interfaces.IsBound }) { +export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); context.bind(GLSP_TYPES.IDefaultTool).to(MarqueeTool); context.bind(GLSP_TYPES.ITool).to(MarqueeMouseTool); diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 0b5fd51..4a54c1e 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, AnchorComputerRegistry, @@ -25,28 +25,28 @@ import { isCtrlOrCmd, SEdge, SModelElement -} from "sprotty"; +} from 'sprotty'; -import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CreateEdgeOperation, isTriggerElementTypeCreationAction, TriggerEdgeCreationAction -} from "../../base/operations/operation"; +} from '../../base/operations/operation'; import { DrawFeedbackEdgeAction, FeedbackEdgeEndMovingMouseListener, RemoveFeedbackEdgeAction -} from "../tool-feedback/creation-tool-feedback"; -import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; -import { BaseGLSPTool } from "./base-glsp-tool"; +} from '../tool-feedback/creation-tool-feedback'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { BaseGLSPTool } from './base-glsp-tool'; /** * Tool to create connections in a Diagram, by selecting a source and target node. */ @injectable() export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { - static ID = "tool_create_edge"; + static ID = 'tool_create_edge'; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @@ -58,7 +58,7 @@ export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { return EdgeCreationTool.ID; } - enable() { + enable(): void { if (this.triggerAction === undefined) { throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); } @@ -69,7 +69,7 @@ export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { this.dispatchFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); } - disable() { + disable(): void { this.mouseTool.deregister(this.creationToolMouseListener); this.mouseTool.deregister(this.feedbackEndMovingMouseListener); this.deregisterFeedback([new RemoveFeedbackEdgeAction(), cursorFeedbackAction()]); @@ -88,7 +88,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { protected source?: string; protected target?: string; protected currentTarget?: SModelElement; - protected allowedTarget: boolean = false; + protected allowedTarget = false; protected proxyEdge: SEdge; constructor(protected triggerAction: TriggerEdgeCreationAction, protected tool: EdgeCreationTool) { @@ -97,7 +97,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.proxyEdge.type = triggerAction.elementTypeId; } - protected reinitialize() { + protected reinitialize(): void { this.source = undefined; this.target = undefined; this.currentTarget = undefined; @@ -132,11 +132,11 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { return result; } - protected isSourceSelected() { + protected isSourceSelected(): boolean { return this.source !== undefined; } - protected isTargetSelected() { + protected isTargetSelected(): boolean { return this.target !== undefined; } @@ -162,10 +162,10 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { } protected isAllowedSource(element: SModelElement | undefined): boolean { - return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, "source"); + return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, 'source'); } protected isAllowedTarget(element: SModelElement | undefined): boolean { - return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, "target"); + return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, 'target'); } } diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index b4adc14..4bab08f 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, AnchorComputerRegistry, @@ -27,22 +27,22 @@ import { SModelRoot, SRoutableElement, SRoutingHandle -} from "sprotty"; +} from 'sprotty'; -import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; -import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from "../../base/operations/operation"; -import { GLSP_TYPES } from "../../base/types"; -import { isRoutable, isRoutingHandle } from "../../utils/smodel-util"; +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from '../../base/operations/operation'; +import { GLSP_TYPES } from '../../base/types'; +import { isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle -} from "../reconnect/model"; -import { SelectionListener, SelectionService } from "../select/selection-service"; -import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from "../tool-feedback/creation-tool-feedback"; -import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; +} from '../reconnect/model'; +import { SelectionListener, SelectionService } from '../select/selection-service'; +import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from '../tool-feedback/creation-tool-feedback'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { DrawFeedbackEdgeSourceAction, FeedbackEdgeRouteMovingMouseListener, @@ -51,12 +51,12 @@ import { HideEdgeReconnectHandlesFeedbackAction, ShowEdgeReconnectHandlesFeedbackAction, SwitchRoutingModeAction -} from "../tool-feedback/edge-edit-tool-feedback"; -import { BaseGLSPTool } from "./base-glsp-tool"; +} from '../tool-feedback/edge-edit-tool-feedback'; +import { BaseGLSPTool } from './base-glsp-tool'; @injectable() export class EdgeEditTool extends BaseGLSPTool { - static ID = "glsp.edge-edit-tool"; + static ID = 'glsp.edge-edit-tool'; @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @@ -121,7 +121,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return edge !== undefined && edge.id !== feedbackEdgeId(edge.root) && isSelected(edge); } - protected setEdgeSelected(edge: SRoutableElement) { + protected setEdgeSelected(edge: SRoutableElement): void { if (this.edge && this.edge.id !== edge.id) { // reset from a previously selected edge this.reset(); @@ -143,18 +143,22 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return this.edge !== undefined && isSelected(this.edge); } - protected setReconnectHandleSelected(edge: SRoutableElement, reconnectHandle: SReconnectHandle) { + protected setReconnectHandleSelected(edge: SRoutableElement, reconnectHandle: SReconnectHandle): void { if (this.edge && this.edge.target && this.edge.source) { if (isSourceRoutingHandle(edge, reconnectHandle)) { - this.tool.dispatchFeedback([new HideEdgeReconnectHandlesFeedbackAction(), - cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), - new DrawFeedbackEdgeSourceAction(this.edge.type, this.edge.targetId)]); - this.reconnectMode = "NEW_SOURCE"; + this.tool.dispatchFeedback([ + new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), + new DrawFeedbackEdgeSourceAction(this.edge.type, this.edge.targetId) + ]); + this.reconnectMode = 'NEW_SOURCE'; } else if (isTargetRoutingHandle(edge, reconnectHandle)) { - this.tool.dispatchFeedback([new HideEdgeReconnectHandlesFeedbackAction(), - cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), - new DrawFeedbackEdgeAction(this.edge.type, this.edge.sourceId)]); - this.reconnectMode = "NEW_TARGET"; + this.tool.dispatchFeedback([ + new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), + new DrawFeedbackEdgeAction(this.edge.type, this.edge.sourceId) + ]); + this.reconnectMode = 'NEW_TARGET'; } } } @@ -164,10 +168,10 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen } protected isReconnectingNewSource(): boolean { - return this.reconnectMode === "NEW_SOURCE"; + return this.reconnectMode === 'NEW_SOURCE'; } - protected setRoutingHandleSelected(edge: SRoutableElement, routingHandle: SRoutingHandle) { + protected setRoutingHandleSelected(edge: SRoutableElement, routingHandle: SRoutingHandle): void { if (this.edge && this.edge.target && this.edge.source) { this.routingHandle = routingHandle; } @@ -177,15 +181,15 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return this.edge !== undefined && (this.edge.sourceId !== sourceId || this.edge.targetId !== targetId); } - protected setNewConnectable(connectable?: SModelElement & Connectable) { + protected setNewConnectable(connectable?: SModelElement & Connectable): void { this.newConnectable = connectable; } - protected isReadyToReconnect() { + protected isReadyToReconnect(): boolean | undefined { return this.edge && this.isReconnecting() && this.newConnectable !== undefined; } - protected isReadyToReroute() { + protected isReadyToReroute(): boolean { return this.routingHandle !== undefined; } @@ -251,8 +255,8 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen if (!this.newConnectable || currentTarget !== this.newConnectable) { this.setNewConnectable(currentTarget); if (currentTarget) { - if ((this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, "source")) || - (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, "target"))) { + if ((this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || + (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target'))) { this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); return []; @@ -293,25 +297,28 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen } } - public reset() { + public reset(): void { this.resetFeedback(); this.resetData(); } - protected resetData() { + protected resetData(): void { this.edge = undefined; this.reconnectMode = undefined; this.newConnectable = undefined; this.routingHandle = undefined; } - protected resetFeedback() { + protected resetFeedback(): void { const result: Action[] = []; if (this.edge) { result.push(new SwitchRoutingModeAction([], [this.edge.id])); } - result.push(...[new HideEdgeReconnectHandlesFeedbackAction(), - cursorFeedbackAction(), new RemoveFeedbackEdgeAction()]); + result.push(...[ + new HideEdgeReconnectHandlesFeedbackAction(), + cursorFeedbackAction(), + new RemoveFeedbackEdgeAction() + ]); this.tool.deregisterFeedback(result); this.tool.deregisterFeedbackListeners(); } diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index adb81a4..ede8f8a 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -13,18 +13,31 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, BoundsAware, EnableDefaultToolsAction, isSelectable, KeyListener, Point, SEdge, SelectAction, SModelElement, SNode, TYPES } from "sprotty"; -import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; -import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; -import { DrawMarqueeAction, RemoveMarqueeAction } from "../tool-feedback/marquee-tool-feedback"; -import { BaseGLSPTool } from "../tools/base-glsp-tool"; -import { getAbsolutePosition, toAbsoluteBounds } from "../../utils/viewpoint-util"; -import { DOMHelper } from "sprotty/lib/base/views/dom-helper"; +import { inject, injectable } from 'inversify'; +import { + Action, + BoundsAware, + EnableDefaultToolsAction, + isSelectable, + KeyListener, + Point, + SEdge, + SelectAction, + SModelElement, + SNode, + TYPES +} from 'sprotty'; +import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; + +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { DrawMarqueeAction, RemoveMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; +import { BaseGLSPTool } from '../tools/base-glsp-tool'; @injectable() export class MarqueeMouseTool extends BaseGLSPTool { - static ID = "glsp.marquee-mouse-tool"; + static ID = 'glsp.marquee-mouse-tool'; @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; @@ -58,7 +71,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { protected domHelper: DOMHelper; - protected isActive: boolean = false; + protected isActive = false; constructor(domHelper: DOMHelper) { super(); @@ -100,7 +113,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } getMarkedEdges(root: SModelElement): string[] { - const elements = Array.from(document.querySelectorAll("g")); + const elements = Array.from(document.querySelectorAll('g')); const edges = Array.from(root.index.all().filter(e => e instanceof SEdge). filter(e => isSelectable(e)).map(e => e.id)); return elements.filter(e => edges.includes(this.domHelper.findSModelIdByDOMElement(e))) @@ -108,17 +121,19 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } isEdgeMarked(element: SVGElement): boolean { - if (element.getAttribute("transform") == null) { - if (element.children[0] != null) { - const path = element.children[0].getAttribute("d"); - if (path != null) { + if (!element.getAttribute('transform')) { + if (element.children[0]) { + const path = element.children[0].getAttribute('d'); + if (path) { const points = path.split(/M|L/); for (let i = 0; i < points.length - 1; i++) { - const coord1 = points[i].split(","); - const coord2 = points[i + 1].split(","); - const point1 = { x: parseInt(coord1[0]), y: parseInt(coord1[1]) }; - const point2 = { x: parseInt(coord2[0]), y: parseInt(coord2[1]) }; - if (this.isLineMarked(point1, point2)) return true; + const coord1 = points[i].split(','); + const coord2 = points[i + 1].split(','); + const point1 = { x: parseInt(coord1[0], 10), y: parseInt(coord1[1], 10) }; + const point2 = { x: parseInt(coord2[0], 10), y: parseInt(coord2[1], 10) }; + if (this.isLineMarked(point1, point2)) { + return true; + } } } } @@ -127,11 +142,21 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } isLineMarked(point1: Point, point2: Point): boolean { - if (this.pointInRect(point1) || this.pointInRect(point2)) return true; - if (this.linesIntersect(point1, point2, this.startPoint, { x: this.startPoint.x, y: this.currentPoint.y })) return true; - if (this.linesIntersect(point1, point2, this.startPoint, { x: this.currentPoint.x, y: this.startPoint.y })) return true; - if (this.linesIntersect(point1, point2, { x: this.currentPoint.x, y: this.startPoint.y }, this.currentPoint)) return true; - if (this.linesIntersect(point1, point2, { x: this.startPoint.x, y: this.currentPoint.y }, this.currentPoint)) return true; + if (this.pointInRect(point1) || this.pointInRect(point2)) { + return true; + } + if (this.linesIntersect(point1, point2, this.startPoint, { x: this.startPoint.x, y: this.currentPoint.y })) { + return true; + } + if (this.linesIntersect(point1, point2, this.startPoint, { x: this.currentPoint.x, y: this.startPoint.y })) { + return true; + } + if (this.linesIntersect(point1, point2, { x: this.currentPoint.x, y: this.startPoint.y }, this.currentPoint)) { + return true; + } + if (this.linesIntersect(point1, point2, { x: this.startPoint.x, y: this.currentPoint.y }, this.currentPoint)) { + return true; + } return false; } @@ -142,7 +167,9 @@ export class MarqueeMouseListener extends DragAwareMouseListener { const uCount = ((p2.x - p1.x) * (p1.y - p3.y)) - ((p2.y - p1.y) * (p1.x - p3.x)); const uDenom = ((p1.x - p2.x) * (p3.y - p4.y)) - ((p1.y - p2.y) * (p3.x - p4.x)); const u = uCount / uDenom; - if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) return true; + if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) { + return true; + } return false; } @@ -161,7 +188,9 @@ export class MarqueeMouseListener extends DragAwareMouseListener { const verticallyIn = this.startPoint.y < this.currentPoint.y ? this.isElementBetweenYAxis(element, this.startPoint.y, this.currentPoint.y) : this.isElementBetweenYAxis(element, this.currentPoint.y, this.startPoint.y); - if (horizontallyIn && verticallyIn) return true; + if (horizontallyIn && verticallyIn) { + return true; + } return false; } @@ -186,7 +215,9 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } isBetween(x: number, lower: number, upper: number): boolean { - if (lower <= x && x <= upper) return true; + if (lower <= x && x <= upper) { + return true; + } return false; } diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index 5351dfd..38c18a0 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -13,14 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; -import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from "sprotty"; -import { BaseGLSPTool } from "../tools/base-glsp-tool"; -import { MarqueeMouseTool } from "./marquee-mouse-tool"; +import { inject, injectable } from 'inversify'; +import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from 'sprotty'; + +import { BaseGLSPTool } from '../tools/base-glsp-tool'; +import { MarqueeMouseTool } from './marquee-mouse-tool'; @injectable() export class MarqueeTool extends BaseGLSPTool { - static ID = "glsp.marquee-tool"; + static ID = 'glsp.marquee-tool'; protected marqueeKeyListener: MarqueeKeyListener = new MarqueeKeyListener(); diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/model.ts index 9179f3e..c57bd24 100644 --- a/packages/client/src/features/tools/model.ts +++ b/packages/client/src/features/tools/model.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -import { boundsFeature, Point, RectangularNode } from "sprotty"; +import { boundsFeature, Point, RectangularNode } from 'sprotty'; export class MarqueeNode extends RectangularNode { static readonly DEFAULT_FEATURES = [boundsFeature]; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index 875fc19..8a87fe8 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, EnableDefaultToolsAction, @@ -25,22 +25,22 @@ import { SModelElement, SNode, TYPES -} from "sprotty"; +} from 'sprotty'; -import { DragAwareMouseListener } from "../../base/drag-aware-mouse-listener"; +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CreateNodeOperation, isTriggerNodeCreationAction, TriggerNodeCreationAction -} from "../../base/operations/operation"; -import { getAbsolutePosition } from "../../utils/viewpoint-util"; -import { Containable, isContainable } from "../hints/model"; -import { CursorCSS, cursorFeedbackAction } from "../tool-feedback/css-feedback"; -import { BaseGLSPTool } from "./base-glsp-tool"; +} from '../../base/operations/operation'; +import { getAbsolutePosition } from '../../utils/viewpoint-util'; +import { Containable, isContainable } from '../hints/model'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { BaseGLSPTool } from './base-glsp-tool'; @injectable() export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { - static ID = "tool_create_node"; + static ID = 'tool_create_node'; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @@ -51,7 +51,7 @@ export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { return NodeCreationTool.ID; } - enable() { + enable(): void { if (this.triggerAction === undefined) { throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); } @@ -60,7 +60,7 @@ export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { this.dispatchFeedback([cursorFeedbackAction(CursorCSS.NODE_CREATION)]); } - disable() { + disable(): void { this.mouseTool.deregister(this.creationToolMouseListener); this.deregisterFeedback([cursorFeedbackAction()]); } @@ -82,11 +82,11 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { super(); } - protected creationAllowed(elementTypeId: string) { + protected creationAllowed(elementTypeId: string): boolean | undefined { return this.container && this.container.isContainableElement(elementTypeId); } - get elementTypeId() { + get elementTypeId(): string { return this.triggerAction.elementTypeId; } diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx index 0e027b6..9729930 100644 --- a/packages/client/src/features/tools/view.tsx +++ b/packages/client/src/features/tools/view.tsx @@ -13,13 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { VNode } from 'snabbdom/vnode'; +import { RectangularNodeView, RenderingContext } from 'sprotty'; -import { injectable } from "inversify"; -import * as snabbdom from "snabbdom-jsx"; -import { VNode } from "snabbdom/vnode"; -import { RectangularNodeView, RenderingContext } from "sprotty"; -import { MarqueeNode } from "./model"; +import { MarqueeNode } from './model'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: snabbdom.svg }; @injectable() @@ -35,4 +36,4 @@ export class MarqueeView extends RectangularNodeView {
; return graph; } -} \ No newline at end of file +} diff --git a/packages/client/src/features/undo-redo/model.ts b/packages/client/src/features/undo-redo/model.ts index ccff081..6f3120e 100644 --- a/packages/client/src/features/undo-redo/model.ts +++ b/packages/client/src/features/undo-redo/model.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Operation } from "../../base/operations/operation"; +import { Operation } from '../../base/operations/operation'; export class UndoOperation implements Operation { static readonly KIND = 'glspUndo'; diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index be59f51..42c5a5d 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from "inversify"; -import { configureCommand, TYPES } from "sprotty"; +import { ContainerModule } from 'inversify'; +import { configureCommand, TYPES } from 'sprotty'; -import { GLSP_TYPES } from "../../base/types"; +import { GLSP_TYPES } from '../../base/types'; import { LeftToRightTopToBottomComparator, MarkerNavigator, @@ -24,8 +24,8 @@ import { MarkerNavigatorKeyListener, NavigateToMarkerCommand, SModelElementComparator -} from "./marker-navigator"; -import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from "./validate"; +} from './marker-navigator'; +import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from './validate'; export const validationModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, SetMarkersCommand); diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 9a04318..805a478 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SDecoration, SIssue, SIssueMarker, SParentElement } from "sprotty"; +import { SDecoration, SIssue, SIssueMarker, SParentElement } from 'sprotty'; -import { Marker, MarkerKind } from "../../utils/marker"; +import { Marker, MarkerKind } from '../../utils/marker'; export class GIssueMarker extends SIssueMarker { constructor() { @@ -38,7 +38,7 @@ export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMar if (issueMarker === undefined) { issueMarker = new GIssueMarker(); - issueMarker.type = "marker"; + issueMarker.type = 'marker'; issueMarker.issues = new Array(); modelElement.add(issueMarker); } diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 0abd67a..d8ef1e3 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import "mocha"; -import "reflect-metadata"; +/* eslint-disable import/no-deprecated,no-unused-expressions */ +import 'mocha'; +import 'reflect-metadata'; -import { expect } from "chai"; -import { Container } from "inversify"; +import { expect } from 'chai'; +import { Container } from 'inversify'; import { BoundsAware, decorationModule, @@ -29,12 +30,12 @@ import { SNodeSchema, SParentElement, TYPES -} from "sprotty/lib"; -import defaultModule from "sprotty/lib/base/di.config"; -import { SGraphFactory } from "sprotty/lib/graph/sgraph-factory"; +} from 'sprotty/lib'; +import defaultModule from 'sprotty/lib/base/di.config'; +import { SGraphFactory } from 'sprotty/lib/graph/sgraph-factory'; -import { markerNavigatorModule } from "./di.config"; -import { MarkerNavigator } from "./marker-navigator"; +import { markerNavigatorModule } from './di.config'; +import { MarkerNavigator } from './marker-navigator'; describe('MarkerNavigator', () => { const container = new Container(); @@ -45,27 +46,27 @@ describe('MarkerNavigator', () => { const rootWithoutAnyMarkers = graphFactory.createRoot({ id: 'root', type: 'graph', children: [ - { + { id: '1', type: 'node' - } + } as SNodeSchema ] }) as SModelRoot; const rootWithMarkers = graphFactory.createRoot({ id: 'root', type: 'graph', children: [ - { + { id: 'bottom-right', type: 'node' - }, - { + } as SNodeSchema, + { id: 'top-right', type: 'node' - }, - { + } as SNodeSchema, + { id: 'top-left', type: 'node' - }, - { + } as SNodeSchema, + { id: 'bottom-left', type: 'node' - } + } as SNodeSchema ] }) as SModelRoot; @@ -170,11 +171,11 @@ describe('MarkerNavigator', () => { }); -function clearMarker(elem: SParentElement) { +function clearMarker(elem: SParentElement): void { elem.children.filter(isMarker).forEach(marker => elem.remove(marker)); } -function setIssues(elem: SParentElement, issues: SIssue[]) { +function setIssues(elem: SParentElement, issues: SIssue[]): SIssueMarker { const marker = getOrCreateMarker(elem); marker.issues = issues; return marker; @@ -196,7 +197,7 @@ function isMarker(element: SModelElement): element is SIssueMarker { return element instanceof SIssueMarker; } -function createMarker(elem: SParentElement) { +function createMarker(elem: SParentElement): SIssueMarker { const newMarker = new SIssueMarker(); newMarker.type = 'marker'; newMarker.id = `${elem.id}_marker`; diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 08a7274..b9d4204 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from "inversify"; +import { inject, injectable } from 'inversify'; import { Action, BoundsAware, @@ -29,19 +29,20 @@ import { KeyListener, MenuItem, Point, + Selectable, SelectAction, SIssueMarker, SIssueSeverity, SModelElement, SModelRoot, TYPES -} from "sprotty"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; +} from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { GLSP_TYPES } from "../../base/types"; -import { collectIssueMarkers, MarkerPredicates } from "../../utils/marker"; -import { isSelectableAndBoundsAware } from "../../utils/smodel-util"; -import { SelectCommand, SelectionService } from "../select/selection-service"; +import { GLSP_TYPES } from '../../base/types'; +import { collectIssueMarkers, MarkerPredicates } from '../../utils/marker'; +import { isSelectableAndBoundsAware } from '../../utils/smodel-util'; +import { SelectCommand, SelectionService } from '../select/selection-service'; export class NavigateToMarkerAction implements Action { static readonly KIND = 'navigateToMarker'; @@ -105,12 +106,12 @@ export class MarkerNavigator { return markers[this.getPreviousIndex(current, markers) % markers.length]; } - protected getMarkers(root: SModelRoot, predicate: (marker: SIssueMarker) => boolean) { + protected getMarkers(root: SModelRoot, predicate: (marker: SIssueMarker) => boolean): SIssueMarker[] { const markers = collectIssueMarkers(root); return markers.filter(predicate).sort(this.markerComparator.compare); } - protected getNextIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]) { + protected getNextIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]): number { for (let index = 0; index < markers.length; index++) { if (this.markerComparator.compare(markers[index], current) > 0) { return index; @@ -119,7 +120,7 @@ export class MarkerNavigator { return 0; } - protected getPreviousIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]) { + protected getPreviousIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]): number { for (let index = markers.length - 1; index >= 0; index--) { if (this.markerComparator.compare(markers[index], current) < 0) { return index; @@ -168,7 +169,7 @@ export class NavigateToMarkerCommand extends Command { return root; } - protected getSelectedElements(root: SModelRoot) { + protected getSelectedElements(root: SModelRoot): (SModelElement & Selectable)[] { let selectedIds = []; if (this.action.selectedElementIds !== undefined && this.action.selectedElementIds.length > 0) { selectedIds = this.action.selectedElementIds; @@ -178,13 +179,13 @@ export class NavigateToMarkerCommand extends Command { return selectedIds.map(id => root.index.getById(id)).filter(isSelectable); } - protected getTarget(selected: SModelElement[], root: SModelRoot) { + protected getTarget(selected: SModelElement[], root: SModelRoot): SIssueMarker | undefined { const selectedBoundsAware = selected.filter(isBoundsAware).sort(this.markerComparator.compare); const currentTopmost = selectedBoundsAware.length > 0 ? selectedBoundsAware[0] : undefined; if (this.action.direction === 'previous') { - return this.markerNavigator.previous(root, currentTopmost, (marker) => this.matchesSeverities(marker)); + return this.markerNavigator.previous(root, currentTopmost, marker => this.matchesSeverities(marker)); } else { - return this.markerNavigator.next(root, currentTopmost, (marker) => this.matchesSeverities(marker)); + return this.markerNavigator.next(root, currentTopmost, marker => this.matchesSeverities(marker)); } } @@ -217,15 +218,15 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP const hasMarkers = collectIssueMarkers(root).length > 0; return Promise.resolve([ { - id: "navigate", label: "Go to", group: "navigate", actions: [], + id: 'navigate', label: 'Go to', group: 'navigate', actions: [], children: [ { - id: "next-marker", label: "Next marker", group: "marker", + id: 'next-marker', label: 'Next marker', group: 'marker', actions: [new NavigateToMarkerAction('next', selectedElementIds)], isEnabled: () => hasMarkers }, { - id: "previous-marker", label: "Previous marker", group: "marker", + id: 'previous-marker', label: 'Previous marker', group: 'marker', actions: [new NavigateToMarkerAction('previous', selectedElementIds)], isEnabled: () => hasMarkers } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index dede197..a3ca6db 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from "inversify"; +import { inject, injectable, optional } from 'inversify'; import { Action, Command, @@ -26,17 +26,16 @@ import { SModelRoot, SParentElement, TYPES -} from "sprotty"; - -import { EditorContextService } from "../../base/editor-context"; -import { GLSP_TYPES } from "../../base/types"; -import { Marker } from "../../utils/marker"; -import { removeCssClasses } from "../../utils/smodel-util"; -import { getSeverity } from "../hover/hover"; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from "../tool-feedback/feedback-action-dispatcher"; -import { FeedbackCommand } from "../tool-feedback/model"; -import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from "./issue-marker"; +} from 'sprotty'; +import { EditorContextService } from '../../base/editor-context'; +import { GLSP_TYPES } from '../../base/types'; +import { Marker } from '../../utils/marker'; +import { removeCssClasses } from '../../utils/smodel-util'; +import { getSeverity } from '../hover/hover'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../tool-feedback/model'; +import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and @@ -51,13 +50,14 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { private registeredAction: MarkersAction; + // eslint-disable-next-line @typescript-eslint/no-empty-function private constructor() { } /** * Register the action that should be emitted for visualizing validation feedback. * @param action the action that should be emitted when the model is updated and that will visualize the model validation feedback. */ - registerValidationFeedbackAction(action: MarkersAction) { + registerValidationFeedbackAction(action: MarkersAction): void { // De-register old action responsible for applying markers and re-applying them when the model is updated this.feedbackActionDispatcher.deregisterFeedback(this, []); @@ -97,11 +97,11 @@ export abstract class ExternalMarkerManager { protected actionDispatcher?: IActionDispatcher; - connect(actionDispatcher: IActionDispatcher) { + connect(actionDispatcher: IActionDispatcher): void { this.actionDispatcher = actionDispatcher; } - removeMarkers(markers: Marker[]) { + removeMarkers(markers: Marker[]): void { if (this.actionDispatcher) { this.actionDispatcher.dispatch(new DeleteMarkersAction(markers)); } @@ -129,7 +129,9 @@ export class SetMarkersCommand extends Command { async execute(context: CommandExecutionContext): Promise { const markers: Marker[] = this.action.markers; const uri = await this.editorContextService.getSourceUri(); - if (this.externalMarkerManager) this.externalMarkerManager.setMarkers(markers, uri); + if (this.externalMarkerManager) { + this.externalMarkerManager.setMarkers(markers, uri); + } const applyMarkersAction: ApplyMarkersAction = new ApplyMarkersAction(markers); this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); return context.root; @@ -144,12 +146,11 @@ export class SetMarkersCommand extends Command { } } - /** * Action to retrieve markers for a model */ export class RequestMarkersAction implements Action { - static readonly KIND = "requestMarkers"; + static readonly KIND = 'requestMarkers'; constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) { } } @@ -173,7 +174,7 @@ export class ApplyMarkersAction implements MarkersAction { */ @injectable() export class ApplyMarkersCommand extends FeedbackCommand { - static KIND = "applyMarkers"; + static KIND = 'applyMarkers'; readonly priority = 0; constructor(@inject(TYPES.Action) protected action: ApplyMarkersAction) { @@ -203,7 +204,7 @@ export class ApplyMarkersCommand extends FeedbackCommand { } } -function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker) { +function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { const maxSeverityCSSClass = getSeverity(issueMarker); if (!modelElement.cssClasses) { @@ -214,11 +215,10 @@ function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issue } } -function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker) { +function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { removeCssClasses(modelElement, [getSeverity(issueMarker)]); } - /** * Action for clearing makers of a model */ @@ -232,7 +232,7 @@ export class DeleteMarkersAction implements MarkersAction { */ @injectable() export class DeleteMarkersCommand extends Command { - static KIND = "deleteMarkers"; + static KIND = 'deleteMarkers'; constructor(@inject(TYPES.Action) protected action: DeleteMarkersAction) { super(); @@ -272,4 +272,3 @@ export class DeleteMarkersCommand extends Command { } } - diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/di.config.ts index f81ca7c..278834c 100644 --- a/packages/client/src/features/viewport/di.config.ts +++ b/packages/client/src/features/viewport/di.config.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ContainerModule } from 'inversify'; import { CenterCommand, CenterKeyboardListener, @@ -24,11 +25,10 @@ import { GetViewportCommand, SetViewportCommand, TYPES, - ZoomMouseListener, -} from "sprotty"; -import { ContainerModule } from "inversify"; + ZoomMouseListener +} from 'sprotty'; -import { GLSPScrollMouseListener } from "./glsp-scroll-mouse-listener"; +import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, CenterCommand); diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 4195b33..9021cb4 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -13,9 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from "sprotty"; -import { injectable } from "inversify"; -import { MarqueeMouseTool } from "../tools/marquee-mouse-tool"; +import { injectable } from 'inversify'; +import { + Action, + EnableDefaultToolsAction, + EnableToolsAction, + IActionHandler, + ICommand, + ScrollMouseListener, + SModelElement +} from 'sprotty'; + +import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @injectable() export class GLSPScrollMouseListener extends ScrollMouseListener implements IActionHandler { diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index dc6ec44..e11bea0 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -13,25 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import defaultGLSPModule from "./base/di.config"; -import glspCommandPaletteModule from "./features/command-palette/di.config"; -import glspContextMenuModule from "./features/context-menu/di.config"; -import { copyPasteContextMenuModule, glspServerCopyPasteModule } from "./features/copy-paste/di.config"; -import glspDecorationModule from "./features/decoration/di.config"; -import glspEditLabelModule from "./features/edit-label/di.config"; -import modelHintsModule from "./features/hints/di.config"; -import glspHoverModule from "./features/hover/di.config"; -import layoutCommandsModule from "./features/layout/di.config"; -import modelSourceWatcherModule from "./features/model-source-watcher/di.config"; -import glspMouseToolModule from "./features/mouse-tool/di.config"; -import { navigationModule } from "./features/navigation/di.config"; -import saveModule from "./features/save/di.config"; -import glspSelectModule from "./features/select/di.config"; -import toolFeedbackModule from "./features/tool-feedback/di.config"; -import paletteModule from "./features/tool-palette/di.config"; -import toolsModule from "./features/tools/di.config"; -import { markerNavigatorModule, validationModule } from "./features/validation/di.config"; -import glspViewportModule from "./features/viewport/di.config"; +import defaultGLSPModule from './base/di.config'; +import glspCommandPaletteModule from './features/command-palette/di.config'; +import glspContextMenuModule from './features/context-menu/di.config'; +import { copyPasteContextMenuModule, glspServerCopyPasteModule } from './features/copy-paste/di.config'; +import glspDecorationModule from './features/decoration/di.config'; +import glspEditLabelModule from './features/edit-label/di.config'; +import modelHintsModule from './features/hints/di.config'; +import glspHoverModule from './features/hover/di.config'; +import layoutCommandsModule from './features/layout/di.config'; +import modelSourceWatcherModule from './features/model-source-watcher/di.config'; +import glspMouseToolModule from './features/mouse-tool/di.config'; +import { navigationModule } from './features/navigation/di.config'; +import saveModule from './features/save/di.config'; +import glspSelectModule from './features/select/di.config'; +import toolFeedbackModule from './features/tool-feedback/di.config'; +import paletteModule from './features/tool-palette/di.config'; +import toolsModule from './features/tools/di.config'; +import { markerNavigatorModule, validationModule } from './features/validation/di.config'; +import glspViewportModule from './features/viewport/di.config'; export * from 'sprotty'; export * from './base/action-dispatcher'; diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index 5f3e754..48e8a23 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { exportFeature, SGraph, SModelElement, SModelElementSchema, viewportFeature } from "sprotty/lib"; +import { exportFeature, SGraph, SModelElement, SModelElementSchema, viewportFeature } from 'sprotty/lib'; -import { Containable, containerFeature } from "../features/hints/model"; -import { Saveable, saveFeature } from "../features/save/model"; +import { Containable, containerFeature } from '../features/hints/model'; +import { Saveable, saveFeature } from '../features/save/model'; export class GLSPGraph extends SGraph implements Saveable, Containable { static readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, saveFeature, containerFeature]; - dirty: boolean = false; + dirty = false; isContainableElement(input: string | SModelElement | SModelElementSchema): boolean { return true; } diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 3f499ea..f4a0256 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GLSPClient } from "@eclipse-glsp/protocol"; -import { injectable } from "inversify"; +import { GLSPClient } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; import { Action, ActionHandlerRegistry, @@ -25,14 +25,14 @@ import { RequestModelAction, ServerStatusAction, SwitchEditModeCommand -} from "sprotty"; +} from 'sprotty'; -import { RequestContextActions } from "../base/actions/context-actions"; -import { isSetEditModeAction, SetEditModeAction } from "../base/actions/edit-mode-action"; -import { InitializeClientSessionAction } from "../base/actions/protocol-actions"; -import { SourceUriAware } from "../base/source-uri-aware"; -import { RequestTypeHintsAction } from "../features/hints/request-type-hints-action"; -import { isServerMessageAction, ServerMessageAction } from "./glsp-server-status"; +import { RequestContextActions } from '../base/actions/context-actions'; +import { isSetEditModeAction, SetEditModeAction } from '../base/actions/edit-mode-action'; +import { InitializeClientSessionAction } from '../base/actions/protocol-actions'; +import { SourceUriAware } from '../base/source-uri-aware'; +import { RequestTypeHintsAction } from '../features/hints/request-type-hints-action'; +import { isServerMessageAction, ServerMessageAction } from './glsp-server-status'; const receivedFromServerProperty = '__receivedFromServer'; @@ -63,13 +63,15 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { initialize(registry: ActionHandlerRegistry): void { registerDefaultGLSPServerActions(registry, this); - if (!this.clientId) + if (!this.clientId) { this.clientId = this.viewerOptions.baseDiv; + } } handle(action: Action): void | ICommand | Action { - if (action instanceof RequestModelAction && action.options !== undefined) - this._sourceUri = action.options.sourceUri; + if (action instanceof RequestModelAction && action.options !== undefined) { + this._sourceUri = action.options.sourceUri as string; + } return super.handle(action); } @@ -101,11 +103,11 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { } } -export function isReceivedFromServer(action: Action) { +export function isReceivedFromServer(action: Action): boolean { return (action as any)[receivedFromServerProperty] === true; } -export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer) { +export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer): void { // Register the InitializeClientSessionAction as a server action. Then, the server will // notify us about all actions it handles (Via ConfigureServerHandlersAction/Handler) registry.register(InitializeClientSessionAction.KIND, diagramServer); diff --git a/packages/client/src/model-source/glsp-server-status.ts b/packages/client/src/model-source/glsp-server-status.ts index 4967971..1b0b963 100644 --- a/packages/client/src/model-source/glsp-server-status.ts +++ b/packages/client/src/model-source/glsp-server-status.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ServerStatusAction as SprottyServerStatusAction } from "sprotty"; +import { Action, ServerStatusAction as SprottyServerStatusAction } from 'sprotty'; export class GLSPServerStatusAction extends SprottyServerStatusAction { - timeout: number = -1; + timeout = -1; } export function isGLSPServerStatusAction(serverStatusAction: SprottyServerStatusAction): serverStatusAction is GLSPServerStatusAction { - return (serverStatusAction).timeout !== undefined; + return (serverStatusAction as GLSPServerStatusAction).timeout !== undefined; } export class ServerMessageAction implements Action { @@ -29,8 +29,8 @@ export class ServerMessageAction implements Action { kind = ServerMessageAction.KIND; severity: 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL'; message: string; - details: string = ''; - timeout: number = -1; + details = ''; + timeout = -1; } export function isServerMessageAction(action: Action): action is ServerMessageAction { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index ff70ef0..e3be146 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement } from "sprotty"; +import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement } from 'sprotty'; -import { IMovementRestrictor } from "../features/change-bounds/movement-restrictor"; +import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; export function minWidth(element: SModelElement & BoundsAware): number { const layoutOptions = getLayoutOptions(element); @@ -41,25 +41,27 @@ export function getLayoutOptions(element: SModelElement): ModelLayoutOptions | u return undefined; } -export function isValidSize(element: SModelElement & BoundsAware, size: Dimension) { +export function isValidSize(element: SModelElement & BoundsAware, size: Dimension): boolean { return size.width >= minWidth(element) && size.height >= minHeight(element); } -export function isValidMove(element: SModelElement & BoundsAware, newPosition: Point, movementRestrictor?: IMovementRestrictor) { +export function isValidMove(element: SModelElement & BoundsAware, newPosition: Point, movementRestrictor?: IMovementRestrictor): boolean { if (movementRestrictor) { return movementRestrictor.validate(newPosition, element); } return true; } -export function toValidElementMove(element: SModelElement & BoundsAware, move: WriteableElementMove, movementRestrictor?: IMovementRestrictor) { +export function toValidElementMove(element: SModelElement & BoundsAware, move: WriteableElementMove, movementRestrictor?: IMovementRestrictor): WriteableElementMove | undefined { if (!isValidMove(element, move.toPosition, movementRestrictor)) { return; } return move; } -export function toValidElementAndBounds(element: SModelElement & BoundsAware, bounds: WriteableElementAndBounds, movementRestrictor?: IMovementRestrictor) { +export function toValidElementAndBounds(element: SModelElement & BoundsAware, bounds: WriteableElementAndBounds, + movementRestrictor?: IMovementRestrictor): WriteableElementAndBounds | undefined { + if (!isValidMove(element, bounds.newPosition, movementRestrictor)) { return; } @@ -85,11 +87,11 @@ export interface WriteableElementMove extends ElementMove { } export interface WriteableDimension extends Dimension { - width: number - height: number + width: number; + height: number; } export interface WriteableElementAndBounds extends ElementAndBounds { newPosition: WriteablePoint; - newSize: WriteableDimension + newSize: WriteableDimension; } diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts index 6e7bfa4..c70b3b0 100644 --- a/packages/client/src/utils/marker.ts +++ b/packages/client/src/utils/marker.ts @@ -13,21 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SIssueMarker, SParentElement } from "sprotty"; +import { SIssueMarker, SParentElement } from 'sprotty'; export namespace MarkerKind { - export const INFO = "info"; - export const WARNING = "warning"; - export const ERROR = "error"; + export const INFO = 'info'; + export const WARNING = 'warning'; + export const ERROR = 'error'; } export namespace MarkerPredicates { - export const ALL = () => true; - export const ERRORS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.ERROR); - export const WARNINGS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.WARNING); - export const INFOS = (marker: SIssueMarker) => hasIssueWithSeverity(marker, MarkerKind.INFO); + export const ALL = (): boolean => true; + export const ERRORS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.ERROR); + export const WARNINGS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.WARNING); + export const INFOS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.INFO); - export function hasIssueWithSeverity(marker: SIssueMarker, severity: 'info' | 'warning' | 'error') { + export function hasIssueWithSeverity(marker: SIssueMarker, severity: 'info' | 'warning' | 'error'): boolean { return marker.issues.find(issue => issue.severity === severity) !== undefined; } } diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index 05c5885..ebee961 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { BoundsAware, + ElementAndBounds, isBoundsAware, isMoveable, isSelectable, @@ -22,18 +23,19 @@ import { Selectable, SModelElement, SModelElementSchema, + SModelIndex, SRoutableElement, SRoutingHandle -} from "sprotty"; +} from 'sprotty'; -import { ElementAndRoutingPoints } from "../base/operations/operation"; +import { ElementAndRoutingPoints } from '../base/operations/operation'; - -export function getIndex(element: SModelElement) { +export function getIndex(element: SModelElement): SModelIndex { return element.root.index; } -export function forEachElement(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T, runnable: (element: SModelElement & T) => void) { +export function forEachElement(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T, + runnable: (element: SModelElement & T) => void): void { getIndex(element).all() .filter(predicate) .forEach(runnable); @@ -45,7 +47,7 @@ export function getMatchingElements(element: SModelElement, predicate: (eleme return matching; } -export function hasSelectedElements(element: SModelElement) { +export function hasSelectedElements(element: SModelElement): boolean { return getSelectedElementCount(element) > 0; } @@ -61,7 +63,7 @@ export function isNotUndefined(element: T | undefined): element is T { return element !== undefined; } -export function addCssClasses(root: SModelElement, cssClasses: string[]) { +export function addCssClasses(root: SModelElement, cssClasses: string[]): void { if (root.cssClasses === undefined) { root.cssClasses = []; } @@ -72,7 +74,7 @@ export function addCssClasses(root: SModelElement, cssClasses: string[]) { } } -export function removeCssClasses(root: SModelElement, cssClasses: string[]) { +export function removeCssClasses(root: SModelElement, cssClasses: string[]): void { if (root.cssClasses === undefined || root.cssClasses.length === 0) { return; } @@ -108,7 +110,7 @@ export type SelectableBoundsAware = SModelElement & BoundsAware & Selectable; export type BoundsAwareModelElement = SModelElement & BoundsAware; -export function toElementAndBounds(element: SModelElement & BoundsAware) { +export function toElementAndBounds(element: SModelElement & BoundsAware): ElementAndBounds { return { elementId: element.id, newPosition: { @@ -133,15 +135,15 @@ export function toElementAndRoutingPoints(element: SRoutableElement): ElementAnd * Checks if the model is compatible with the passed type string. * (either has the same type or a subtype of this type) */ -export function hasCompatibleType(input: SModelElement | SModelElementSchema | string, type: string) { +export function hasCompatibleType(input: SModelElement | SModelElementSchema | string, type: string): boolean { const inputType = getElementTypeId(input); - return inputType === type ? true : inputType.split(":").includes(type); + return inputType === type ? true : inputType.split(':').includes(type); } -export function getElementTypeId(input: SModelElement | SModelElementSchema | string) { +export function getElementTypeId(input: SModelElement | SModelElementSchema | string): string { if (typeof input === 'string') { - return input; + return input as string; } else { - return (input)["type"]; + return (input as any)['type'] as string; } } diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 017f059..7f5a962 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -25,7 +25,7 @@ import { SModelElement, translateBounds, Viewport -} from "sprotty"; +} from 'sprotty'; /** * Return the position corresponding to this mouse event (Browser coordinates) @@ -41,7 +41,7 @@ import { * A mouseEvent */ export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEvent): Point { - let xPos = mouseEvent.pageX, yPos = mouseEvent.pageY; + let xPos = mouseEvent.pageX; let yPos = mouseEvent.pageY; const canvasBounds = target.root.canvasBounds; xPos -= canvasBounds.x; yPos -= canvasBounds.y; @@ -56,7 +56,6 @@ export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEven xPos /= zoom; yPos /= zoom; } - xPos; return { x: xPos, diff --git a/packages/protocol/.eslintrc.js b/packages/protocol/.eslintrc.js new file mode 100644 index 0000000..bc7afd9 --- /dev/null +++ b/packages/protocol/.eslintrc.js @@ -0,0 +1,15 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/base.eslintrc.json', + '../../configs/warnings.eslintrc.json', + '../../configs/errors.eslintrc.json' + ], + ignorePatterns: [ + '**/{node_modules,lib}' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/packages/protocol/package.json b/packages/protocol/package.json index e7eaf3e..7dd94b6 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -50,14 +50,13 @@ "@types/uuid": "3.4.5", "@babel/runtime": "^7.11.2", "rimraf": "^2.6.1", - "tslint": "^5.5.0", "typescript": "^3.9.2" }, "scripts": { "prepare": "yarn run clean && yarn run build", "clean": "rimraf lib", "build": "tsc && yarn run lint", - "lint": "tslint -c ../../tslint.json --project ./tsconfig.json", + "lint": "eslint -c ./.eslintrc.js --ext .ts ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index fdcd3ee..5fbee1f 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from "sprotty"; -import * as uuid from "uuid"; +import { ActionMessage } from 'sprotty'; +import * as uuid from 'uuid'; export interface InitializeParameters<> { /** @@ -25,7 +25,7 @@ export interface InitializeParameters<> { /** * Options that can include application-specific parameters. */ - options?: any + options?: any; } export class ApplicationIdProvider { @@ -98,12 +98,12 @@ export interface GLSPClient { * @param params Initialize parameter * @returns true if the initialization was successful */ - initializeServer(params: InitializeParameters): Promise; + initializeServer(params: InitializeParameters): Promise; /** * Send a `shutdown` notification to the server. */ - shutdownServer(): void + shutdownServer(): void; /** * Stops the client and disposes any resources. During the stop procedure the client is in the `Stopping` state and will transition to either `Stopped` or `ServerError`. @@ -132,7 +132,7 @@ export namespace GLSPClient { } export function isOptions(object: any): object is Options { - return object !== undefined && "id" in object && typeof object["id"] === "string" - && "name" in object && typeof object["name"] === "string"; + return object !== undefined && 'id' in object && typeof object['id'] === 'string' + && 'name' in object && typeof object['name'] === 'string'; } } diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index dbc164a..c2bb8bf 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from "sprotty"; -import { Message, MessageConnection } from "vscode-ws-jsonrpc"; +import { ActionMessage } from 'sprotty'; +import { Message, MessageConnection } from 'vscode-ws-jsonrpc'; -import { ActionMessageHandler, ClientState, GLSPClient, InitializeParameters } from "../glsp-client"; -import { ConnectionProvider, JsonrpcGLSPClient } from "./glsp-jsonrpc-client"; +import { ActionMessageHandler, ClientState, GLSPClient, InitializeParameters } from '../glsp-client'; +import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { @@ -40,7 +40,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { } } - initializeServer(params: InitializeParameters): Promise { + initializeServer(params: InitializeParameters): Promise { if (this.checkConnectionState()) { return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); } diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts index 5b0c396..2f1f55f 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from "sprotty"; -import { MessageConnection, NotificationType, RequestType } from "vscode-jsonrpc"; +import { ActionMessage } from 'sprotty'; +import { MessageConnection, NotificationType, RequestType } from 'vscode-jsonrpc'; import { createMessageConnection, Logger, @@ -22,10 +22,9 @@ import { toSocket, WebSocketMessageReader, WebSocketMessageWriter -} from "vscode-ws-jsonrpc"; - -import { GLSPClient, InitializeParameters } from "../glsp-client"; +} from 'vscode-ws-jsonrpc'; +import { GLSPClient, InitializeParameters } from '../glsp-client'; export type MaybePromise = T | Promise | PromiseLike; export type ConnectionProvider = MessageConnection | (() => MaybePromise); @@ -36,11 +35,11 @@ export namespace JsonrpcGLSPClient { } export function isOptions(object: any): object is Options { - return GLSPClient.isOptions(object) && "connectionProvider" in object; + return GLSPClient.isOptions(object) && 'connectionProvider' in object; } export const ActionMessageNotification = new NotificationType('process'); - export const InitializeRequest = new RequestType('initialize'); + export const InitializeRequest = new RequestType('initialize'); export const ShutdownNotification = new NotificationType0('shutdown'); export const ClientNotReadyMsg = 'JsonrpcGLSPClient is not ready yet'; @@ -52,7 +51,6 @@ export namespace JsonrpcGLSPClient { return createMessageConnection(reader, writer, logger); } - export function error(message: string, ...optionalParams: any[]): void { console.error(`[JsonrpcGLSPClient] ${message}`, optionalParams); } diff --git a/packages/protocol/src/launch-util.ts b/packages/protocol/src/launch-util.ts index 835189e..cf413dd 100644 --- a/packages/protocol/src/launch-util.ts +++ b/packages/protocol/src/launch-util.ts @@ -20,7 +20,7 @@ * @param defaultPort Default port that should be returned if no (valid) port was passed via CLI */ export function getPort(argsKey: string, defaultPort?: number): number { - argsKey = `--${argsKey.replace("--", "").replace("=", "")}=`; + argsKey = `--${argsKey.replace('--', '').replace('=', '')}=`; const args = process.argv.filter(a => a.startsWith(argsKey)); if (args.length > 0) { return Number.parseInt(args[0].substring(argsKey.length), 10); From 67c17e11f80b8640a336bb45764cc132ce185335 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 15 Apr 2021 16:19:51 +0200 Subject: [PATCH 028/566] #180: Provide default model and view registrations and add views - Registrations -- Provide default types as strings -- Register elements for default types -- Register views for default types -- Ensure element and view registrations can be overwritten - Views -- Add rounded corner view that reads argument map for corner radius -- Add edge view that supports custom padding for easier mouse handling -- Move issue marker view to shared views folder - Use custom edge and rounded corner view in example for new elements Fixes https://github.com/eclipse-glsp/glsp/issues/180 Signed-off-by: Martin Fleck --- examples/workflow-glsp/src/di.config.ts | 24 +--- examples/workflow-glsp/src/workflow-views.tsx | 68 +++++----- packages/client/src/base/di.config.ts | 6 + .../client/src/base/model/model-registry.ts | 31 +++++ .../client/src/base/view/view-registry.ts | 31 +++++ .../src/features/decoration/di.config.ts | 4 +- packages/client/src/index.ts | 6 +- packages/client/src/utils/argument-utils.ts | 127 ++++++++++++++++++ packages/client/src/views/base-view-module.ts | 98 ++++++++++++++ packages/client/src/views/default-types.ts | 48 +++++++ packages/client/src/views/glsp-edge-view.tsx | 72 ++++++++++ packages/client/src/views/index.ts | 21 +++ .../view.tsx => views/issue-marker-view.tsx} | 2 +- .../client/src/views/rounded-corner-view.tsx | 90 +++++++++++++ packages/client/src/views/rounded-corner.ts | 88 ++++++++++++ 15 files changed, 661 insertions(+), 55 deletions(-) create mode 100644 packages/client/src/base/model/model-registry.ts create mode 100644 packages/client/src/base/view/view-registry.ts create mode 100644 packages/client/src/utils/argument-utils.ts create mode 100644 packages/client/src/views/base-view-module.ts create mode 100644 packages/client/src/views/default-types.ts create mode 100644 packages/client/src/views/glsp-edge-view.tsx create mode 100644 packages/client/src/views/index.ts rename packages/client/src/{features/decoration/view.tsx => views/issue-marker-view.tsx} (98%) create mode 100644 packages/client/src/views/rounded-corner-view.tsx create mode 100644 packages/client/src/views/rounded-corner.ts diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index edf2831..8b7fb89 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -20,15 +20,16 @@ import 'sprotty/css/edit-label.css'; import { boundsModule, buttonModule, + configureDefaultModelElement, configureModelElement, ConsoleLogger, defaultGLSPModule, defaultModule, + DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, edgeLayoutModule, editLabelFeature, - ExpandButtonView, expandModule, exportModule, fadeModule, @@ -37,15 +38,12 @@ import { glspContextMenuModule, glspDecorationModule, glspEditLabelModule, - GLSPGraph, glspHoverModule, glspMouseToolModule, glspSelectModule, glspServerCopyPasteModule, glspViewportModule, GridSnapper, - HtmlRoot, - HtmlRootView, labelEditUiModule, layoutCommandsModule, LogLevel, @@ -58,19 +56,13 @@ import { openModule, overrideViewerOptions, paletteModule, - PreRenderedElement, - PreRenderedView, RevealNamedElementActionProvider, routingModule, - SButton, SCompartment, SCompartmentView, SEdge, - SGraphView, SLabel, SLabelView, - SRoutingHandle, - SRoutingHandleView, toolFeedbackModule, toolsModule, TYPES, @@ -91,19 +83,15 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind bind(TYPES.ICommandPaletteActionProvider).to(RevealNamedElementActionProvider); bind(TYPES.IContextMenuItemProvider).to(DeleteElementContextMenuItemProvider); const context = { bind, unbind, isBound, rebind }; - configureModelElement(context, 'graph', GLSPGraph, SGraphView); + + configureDefaultModelElement(context); configureModelElement(context, 'task:automated', TaskNode, TaskNodeView); configureModelElement(context, 'task:manual', TaskNode, TaskNodeView); configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); configureModelElement(context, 'comp:comp', SCompartment, SCompartmentView); configureModelElement(context, 'comp:header', SCompartment, SCompartmentView); configureModelElement(context, 'label:icon', SLabel, SLabelView); - configureModelElement(context, 'html', HtmlRoot, HtmlRootView); - configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); - configureModelElement(context, 'button:expand', SButton, ExpandButtonView); - configureModelElement(context, 'routing-point', SRoutingHandle, SRoutingHandleView); - configureModelElement(context, 'volatile-routing-point', SRoutingHandle, SRoutingHandleView); - configureModelElement(context, 'edge', SEdge, WorkflowEdgeView); + configureModelElement(context, DefaultTypes.EDGE, SEdge, WorkflowEdgeView); configureModelElement(context, 'edge:weighted', WeightedEdge, WeightedEdgeView); configureModelElement(context, 'icon', Icon, IconView); configureModelElement(context, 'activityNode:merge', ActivityNode, DiamondNodeView); @@ -115,7 +103,7 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind export default function createContainer(widgetId: string): Container { const container = new Container(); - container.load(validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, glspViewportModule, toolsModule, + container.load(defaultModule, defaultGLSPModule, glspMouseToolModule, validationModule, glspSelectModule, boundsModule, glspViewportModule, toolsModule, glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, modelSourceWatcherModule, glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 9640ba4..90df5e7 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -15,17 +15,21 @@ ********************************************************************************/ import { angleOfPoint, + GEdgeView, + Hoverable, IView, Point, - PolylineEdgeView, RectangularNodeView, RenderingContext, + RoundedCornerNodeView, SEdge, + Selectable, SShapeElement, toDegrees } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; import * as snabbdom from 'snabbdom-jsx'; +import { Classes } from 'snabbdom/modules/class'; import { VNode } from 'snabbdom/vnode'; import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; @@ -34,24 +38,32 @@ import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; const JSX = { createElement: snabbdom.svg }; @injectable() -export class TaskNodeView extends RectangularNodeView { - render(node: TaskNode, context: RenderingContext): VNode { - const rcr = this.getRoundedCornerRadius(node); +export class TaskNodeView extends RoundedCornerNodeView { + protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode { + const task = node as TaskNode; + const rcr = this.getRoundedCornerRadius(task); const graph = - + {...this.additionalClasses(task, context)} + width={Math.max(0, node.bounds.width)} height={Math.max(0, node.bounds.height)} /> {context.renderChildren(node)} ; return graph; } - protected getRoundedCornerRadius(node: SShapeElement): number { + protected getRoundedCornerRadius(_node: TaskNode): number { return 5; } + + protected additionalClasses(element: Readonly, context: RenderingContext): Classes { + const node = element as TaskNode; + return { + 'class-task': true, + 'class-automated': node.taskType === 'automated', + 'class-manual': node.taskType === 'manual' + }; + } } @injectable() @@ -67,36 +79,28 @@ export class ForkOrJoinNodeView extends RectangularNodeView { } @injectable() -export class WorkflowEdgeView extends PolylineEdgeView { +export class WorkflowEdgeView extends GEdgeView { protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { + const additionals = super.renderAdditionals(edge, segments, context); const p1 = segments[segments.length - 2]; const p2 = segments[segments.length - 1]; - return [ - - ]; + const arrow = ; + additionals.push(arrow); + return additionals; } } @injectable() export class WeightedEdgeView extends WorkflowEdgeView { - render(edge: Readonly, context: RenderingContext): VNode { - const router = this.edgeRouterRegistry.get(edge.routerKind); - const route = router.route(edge); - if (route.length === 0) { - return this.renderDanglingEdge('Cannot compute route', edge, context); - } - - return - {this.renderLine(edge, route, context)} - {this.renderAdditionals(edge, route, context)} - {context.renderChildren(edge, { route })} - ; + protected addtionalClasses(edge: Readonly, _context: RenderingContext): Classes { + const wedge = edge as WeightedEdge; + return { + 'class-no-probability': !wedge.probability, + 'class-low': wedge.probability === 'low', + 'class-medium': wedge.probability === 'medium', + 'class-high': wedge.probability === 'high' + }; } } diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 94d36ca..d9bec89 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -26,10 +26,12 @@ import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context'; import { FocusTracker } from './focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; +import { GLSPModelRegistry } from './model/model-registry'; import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from './model/update-model-command'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; import { GLSP_TYPES } from './types'; +import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, _unbind, isBound, rebind }; @@ -64,6 +66,10 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = rebind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); + + // support re-registration of model elements and views + rebind(TYPES.SModelRegistry).to(GLSPModelRegistry).inSingletonScope(); + rebind(TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); }); export default defaultGLSPModule; diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts new file mode 100644 index 0000000..a918438 --- /dev/null +++ b/packages/client/src/base/model/model-registry.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { SModelElement, SModelRegistry } from 'sprotty'; + +@injectable() +export class GLSPModelRegistry extends SModelRegistry { + register(key: string, factory: (u: void) => SModelElement): void { + if (key === undefined) { + throw new Error('Key is undefined'); + } + if (this.hasKey(key)) { + // do not throw error but log overwriting + console.log(`Factory for model element '${key}' will be overwritten.`); + } + this.elements.set(key, factory); + } +} diff --git a/packages/client/src/base/view/view-registry.ts b/packages/client/src/base/view/view-registry.ts new file mode 100644 index 0000000..bb204d3 --- /dev/null +++ b/packages/client/src/base/view/view-registry.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { IView, ViewRegistry } from 'sprotty'; + +@injectable() +export class GLSPViewRegistry extends ViewRegistry { + register(key: string, instance: IView): void { + if (key === undefined) { + throw new Error('Key is undefined'); + } + if (this.hasKey(key)) { + // do not throw error but log overwriting + console.log(`View instance for type '${key}' will be overwritten.`); + } + this.elements.set(key, instance); + } +} diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts index 6019739..1f7057a 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/di.config.ts @@ -16,13 +16,11 @@ import '../../../css/decoration.css'; import { ContainerModule } from 'inversify'; -import { configureModelElement, SIssueMarker, TYPES } from 'sprotty'; +import { TYPES } from 'sprotty'; import { GlspDecorationPlacer } from './decoration-placer'; -import { GlspIssueMarkerView } from './view'; const glspDecorationModule = new ContainerModule((bind, _unbind, isBound) => { - configureModelElement({ bind, isBound }, 'marker', SIssueMarker, GlspIssueMarkerView); bind(GlspDecorationPlacer).toSelf().inSingletonScope(); bind(TYPES.IVNodePostprocessor).toService(GlspDecorationPlacer); }); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index e11bea0..843a469 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -49,11 +49,13 @@ export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context'; export * from './base/focus-tracker'; export * from './base/model-initialization-constraint'; +export * from './base/model/model-registry'; export * from './base/model/update-model-command'; export * from './base/operations/operation'; export * from './base/selection-clearing-mouse-listener'; export * from './base/source-uri-aware'; export * from './base/types'; +export * from './base/view/view-registry'; export * from './features/change-bounds/model'; export * from './features/change-bounds/movement-restrictor'; export * from './features/change-bounds/snap'; @@ -61,7 +63,6 @@ export * from './features/command-palette/server-command-palette-provider'; export * from './features/context-menu/delete-element-context-menu'; export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; -export * from './features/decoration/view'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; export * from './features/hints/model'; @@ -100,10 +101,13 @@ export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './lib/model'; export * from './model-source/glsp-server-status'; export * from './model-source/glsp-diagram-server'; +export * from './utils/argument-utils'; export * from './utils/array-utils'; export * from './utils/marker'; export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; +export * from './views'; + export { validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, diff --git a/packages/client/src/utils/argument-utils.ts b/packages/client/src/utils/argument-utils.ts new file mode 100644 index 0000000..11eac78 --- /dev/null +++ b/packages/client/src/utils/argument-utils.ts @@ -0,0 +1,127 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { SModelElement } from 'sprotty'; + +import { hasArguments } from '../base/args'; + +export namespace GArgument { + export function asNumber(argValue: (string | number | boolean)): number | undefined { + return typeof argValue === 'number' ? argValue : undefined; + } + + export function asNumbers(argValues: (string | number | boolean)[]): (number | undefined)[] { + return argValues.map(asNumber); + } + + export function asString(argValue: (string | number | boolean)): string | undefined { + return typeof argValue === 'string' ? argValue : undefined; + } + + export function asStrings(argValues: (string | number | boolean)[]): (string | undefined)[] { + return argValues.map(asString); + } + + export function asBoolean(argValue: (string | number | boolean)): boolean | undefined { + return typeof argValue === 'boolean' ? argValue : undefined; + } + + export function asBooleans(argValues: (string | number | boolean)[]): (boolean | undefined)[] { + return argValues.map(asBoolean); + } + + export function getArgument(element: SModelElement | undefined, key: string): string | number | boolean | undefined { + return hasArguments(element) ? element.args[key] : undefined; + } + + export function getNumber(element: SModelElement | undefined, key: string): number | undefined { + return hasArguments(element) ? asNumber(element.args[key]) : undefined; + } + + export function getString(element: SModelElement | undefined, key: string): string | undefined { + return hasArguments(element) ? asString(element.args[key]) : undefined; + } + + export function getBoolean(element: SModelElement | undefined, key: string): boolean | undefined { + return hasArguments(element) ? asBoolean(element.args[key]) : undefined; + } + + export function getArguments(element: SModelElement | undefined, ...keys: string[]): (number | boolean | string)[] | undefined { + if (!hasArguments(element)) { + return undefined; + } + const values = []; + for (const key of keys) { + const value = element.args[key]; + if (value) { + values.push(value); + } + } + return values; + } + + export function getNumbers(element: SModelElement | undefined, ...keys: string[]): (number | undefined)[] | undefined { + const values = getArguments(element, ...keys); + return values ? asNumbers(values) : undefined; + } + + export function getStrings(element: SModelElement | undefined, ...keys: string[]): (string | undefined)[] | undefined { + const values = getArguments(element, ...keys); + return values ? asStrings(values) : undefined; + } + + export function getBooleans(element: SModelElement | undefined, ...keys: string[]): (boolean | undefined)[] | undefined { + const values = getArguments(element, ...keys); + return values ? asBooleans(values) : undefined; + } + + export function hasNValues(values: (T | undefined)[], length: number): values is T[] { + return values.length === length && values.filter(e => e === undefined).length === 0; + } +} + +export namespace EdgePadding { + const KEY = 'edgePadding'; + + export function from(element: SModelElement | undefined): number | undefined { + return GArgument.getNumber(element, KEY); + } +} + +export class CornerRadius { + static NO_RADIUS = new CornerRadius(0); + + static KEY_RADIUS_TOP_LEFT = 'radiusTopLeft'; + static KEY_RADIUS_TOP_RIGHT = 'radiusTopRight'; + static KEY_RADIUS_BOTTOM_RIGHT = 'radiusBottomRight'; + static KEY_RADIUS_BOTTOM_LEFT = 'radiusBottomLeft'; + + constructor( + public readonly topLeft: number = 0, + public readonly topRight: number = topLeft, + public readonly bottomRight: number = topLeft, + public readonly bottomLeft: number = topRight) { + } + + static from(element: SModelElement | undefined): CornerRadius | undefined { + const radius = GArgument.getNumbers(element, this.KEY_RADIUS_TOP_LEFT, this.KEY_RADIUS_TOP_RIGHT, this.KEY_RADIUS_BOTTOM_RIGHT, this.KEY_RADIUS_BOTTOM_LEFT); + if (radius === undefined || radius[0] === undefined) { + return undefined; + } + return GArgument.hasNValues(radius, 4) + ? new CornerRadius(radius[0], radius[1], radius[2], radius[3]) + : new CornerRadius(radius[0]); + } +} diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts new file mode 100644 index 0000000..78a1d16 --- /dev/null +++ b/packages/client/src/views/base-view-module.ts @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule, interfaces } from 'inversify'; +import { + CircularNode, + CircularNodeView, + configureModelElement, + DiamondNode, + DiamondNodeView, + ExpandButtonView, + ForeignObjectElement, + ForeignObjectView, + HtmlRoot, + HtmlRootView, + PreRenderedElement, + PreRenderedView, + RectangularNode, + RectangularNodeView, + SButton, + SCompartment, + SCompartmentView, + SEdge, + SGraphView, + ShapedPreRenderedElement, + SIssueMarker, + SLabel, + SLabelView, + SNode, + SPort, + SRoutingHandle, + SRoutingHandleView, + SvgViewportView, + ViewportRootElement +} from 'sprotty'; + +import { GLSPGraph } from '../lib/model'; +import { DefaultTypes } from './default-types'; +import { GEdgeView } from './glsp-edge-view'; +import { GIssueMarkerView } from './issue-marker-view'; +import { RoundedCornerNodeView } from './rounded-corner-view'; + +interface ContainerContext { + bind: interfaces.Bind; + isBound: interfaces.IsBound; +} + +const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureDefaultModelElement(context); +}); + +export function configureDefaultModelElement(context: ContainerContext): void { + // HTML elements + configureModelElement(context, DefaultTypes.HTML, HtmlRoot, HtmlRootView); + + // generic elements + configureModelElement(context, DefaultTypes.FOREIGN_OBJECT, ForeignObjectElement, ForeignObjectView); + configureModelElement(context, DefaultTypes.PRE_RENDERED, PreRenderedElement, PreRenderedView); + configureModelElement(context, DefaultTypes.SHAPE_PRE_RENDERED, ShapedPreRenderedElement, PreRenderedView); + + // SVG elements + configureModelElement(context, DefaultTypes.SVG, ViewportRootElement, SvgViewportView); + + // graph elements + configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, SGraphView); + configureModelElement(context, DefaultTypes.NODE, SNode, RoundedCornerNodeView); + configureModelElement(context, DefaultTypes.COMPARTMENT, SCompartment, SCompartmentView); + configureModelElement(context, DefaultTypes.COMPARTMENT_HEADER, SCompartment, SCompartmentView); + configureModelElement(context, DefaultTypes.EDGE, SEdge, GEdgeView); + configureModelElement(context, DefaultTypes.PORT, SPort, RectangularNodeView); + configureModelElement(context, DefaultTypes.ROUTING_POINT, SRoutingHandle, SRoutingHandleView); + configureModelElement(context, DefaultTypes.VOLATILE_ROUTING_POINT, SRoutingHandle, SRoutingHandleView); + configureModelElement(context, DefaultTypes.LABEL, SLabel, SLabelView); + + // UI elements + configureModelElement(context, DefaultTypes.BUTTON_EXPAND, SButton, ExpandButtonView); + configureModelElement(context, DefaultTypes.ISSUE_MARKER, SIssueMarker, GIssueMarkerView); + + // shapes + configureModelElement(context, DefaultTypes.NODE_CIRCLE, CircularNode, CircularNodeView); + configureModelElement(context, DefaultTypes.NODE_DIAMOND, DiamondNode, DiamondNodeView); + configureModelElement(context, DefaultTypes.NODE_RECTANGLE, RectangularNode, RectangularNodeView); +} + +export default baseViewModule; diff --git a/packages/client/src/views/default-types.ts b/packages/client/src/views/default-types.ts new file mode 100644 index 0000000..9f327db --- /dev/null +++ b/packages/client/src/views/default-types.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export namespace DefaultTypes { + // HTML elements + export const HTML = 'html'; + + // generic types + export const FOREIGN_OBJECT = 'foreign-object'; + export const PRE_RENDERED = 'pre-rendered'; + export const SHAPE_PRE_RENDERED = 'shape-pre-rendered'; + + // SVG elements + export const SVG = 'svg'; + + // graph types + export const GRAPH = 'graph'; + export const NODE = 'node'; + export const COMPARTMENT = 'comp'; + export const COMPARTMENT_HEADER = 'comp:header'; + export const EDGE = 'edge'; + export const PORT = 'port'; + export const ROUTING_POINT = 'routing-point'; + export const VOLATILE_ROUTING_POINT = `volatile-${ROUTING_POINT}`; + export const LABEL = 'label'; + + // UI elements + export const BUTTON_EXPAND = 'button:expand'; + export const ISSUE_MARKER = 'marker'; + + // shapes + export const NODE_CIRCLE = 'node:circle'; + export const NODE_RECTANGLE = 'node:rectangle'; + export const NODE_DIAMOND = 'node:diamond'; +} diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx new file mode 100644 index 0000000..20cac04 --- /dev/null +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { Classes } from 'snabbdom/modules/class'; +import { VNode } from 'snabbdom/vnode'; +import { Point, PolylineEdgeView, RenderingContext, SEdge } from 'sprotty'; + +import { EdgePadding } from '../utils/argument-utils'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: snabbdom.svg }; + +@injectable() +export class GEdgeView extends PolylineEdgeView { + render(edge: Readonly, context: RenderingContext): VNode { + const router = this.edgeRouterRegistry.get(edge.routerKind); + const route = router.route(edge); + if (route.length === 0) { + return this.renderDanglingEdge('Cannot compute route', edge, context); + } + + return + {this.renderLine(edge, route, context)} + {this.renderAdditionals(edge, route, context)} + {context.renderChildren(edge, { route })} + ; + } + + protected addtionalClasses(_edge: Readonly, _context: RenderingContext): Classes { + return {}; + } + + protected renderLine(_edge: SEdge, segments: Point[], _context: RenderingContext): VNode { + return ; + } + + protected renderAdditionals(edge: SEdge, segments: Point[], _context: RenderingContext): VNode[] { + // for additional padding we draw another transparent path with larger stroke width + const edgePadding = EdgePadding.from(edge); + return edgePadding ? [this.renderMouseHandle(segments, edgePadding)] : []; + } + + protected renderMouseHandle(segments: Point[], padding: number): VNode { + return ; + } + + protected createPathForSegments(segments: Point[]): string { + const firstPoint = segments[0]; + let path = `M ${firstPoint.x},${firstPoint.y}`; + for (let i = 1; i < segments.length; i++) { + const p = segments[i]; + path += ` L ${p.x},${p.y}`; + } + return path; + } + +} diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts new file mode 100644 index 0000000..1d954f6 --- /dev/null +++ b/packages/client/src/views/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './base-view-module'; +export * from './default-types'; +export * from './glsp-edge-view'; +export * from './issue-marker-view'; +export * from './rounded-corner'; +export * from './rounded-corner-view'; diff --git a/packages/client/src/features/decoration/view.tsx b/packages/client/src/views/issue-marker-view.tsx similarity index 98% rename from packages/client/src/features/decoration/view.tsx rename to packages/client/src/views/issue-marker-view.tsx index 582de0c..415dbb4 100644 --- a/packages/client/src/features/decoration/view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -23,7 +23,7 @@ import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeveri const JSX = { createElement: snabbdom.svg }; @injectable() -export class GlspIssueMarkerView extends IssueMarkerView { +export class GIssueMarkerView extends IssueMarkerView { render(marker: SIssueMarker, _context: RenderingContext): VNode { const maxSeverity = super.getMaxSeverity(marker); diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx new file mode 100644 index 0000000..9eb2d01 --- /dev/null +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -0,0 +1,90 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import * as snabbdom from 'snabbdom-jsx'; +import { Classes } from 'snabbdom/modules/class'; +import { VNode } from 'snabbdom/vnode'; +import { Hoverable, RectangularNodeView, RenderingContext, Selectable, SNode, SPort, SShapeElement } from 'sprotty/lib'; + +import { CornerRadius } from '../utils/argument-utils'; +import { RoundedCornerWrapper } from './rounded-corner'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: snabbdom.svg }; + +@injectable() +export class RoundedCornerNodeView extends RectangularNodeView { + render(node: Readonly, context: RenderingContext): VNode | undefined { + const cornerRadius = CornerRadius.from(node); + if (!cornerRadius) { + return this.renderWithoutRadius(node, context); + } + + const wrapper = new RoundedCornerWrapper(node, cornerRadius); + return + + + + + + {this.renderPathNode(wrapper, context)} + {context.renderChildren(node)} + ; + } + + protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode | undefined { + return super.render(node, context); + } + + protected getClipPathInsets(): number | undefined { + return 2; + } + + protected renderPathNode(wrapper: Readonly, context: RenderingContext): VNode { + return ; + } + + protected additionalClasses(_node: Readonly, _context: RenderingContext): Classes { + return {}; + } + + protected renderPath(wrapper: Readonly, _context: RenderingContext, inset: number): string { + // Calcualte length of straight line segments + const topLineLength = Math.max(0, wrapper.size.width - wrapper.cornerRadius.topLeft - wrapper.cornerRadius.topRight); + const rightLineLength = Math.max(0, wrapper.size.height - wrapper.cornerRadius.topRight - wrapper.cornerRadius.bottomRight); + const bottomLineLength = Math.max(0, wrapper.size.width - wrapper.cornerRadius.bottomLeft - wrapper.cornerRadius.bottomRight); + + const path = `M${0 + inset},${0 + wrapper.topLeftCorner.radiusY}` + + `q${0},${-(wrapper.topLeftCorner.radiusY - inset)} ${wrapper.topLeftCorner.radiusX - inset},${-(wrapper.topLeftCorner.radiusY - inset)}` + + `h${topLineLength}` + + `q${wrapper.topRightCorner.radiusX - inset},0 ${wrapper.topRightCorner.radiusX - inset},${wrapper.topRightCorner.radiusY - inset}` + + `v${rightLineLength}` + + `q0,${wrapper.bottomRightCorner.radiusY - inset} ${-(wrapper.bottomRightCorner.radiusX - inset)},${wrapper.bottomRightCorner.radiusY - inset}` + + `h${-bottomLineLength}` + + `q${-(wrapper.bottomLeftCorner.radiusX - inset)},0 ${-(wrapper.bottomLeftCorner.radiusX - inset)},${-(wrapper.bottomLeftCorner.radiusY - inset)}` + + 'z '; + return path; + } +} + +export function toClipPathId(node: Readonly): string { + return `${node.id}_clip_path`; +} diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts new file mode 100644 index 0000000..cb6887d --- /dev/null +++ b/packages/client/src/views/rounded-corner.ts @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Dimension, Hoverable, Selectable, SShapeElement } from 'sprotty/lib'; + +import { CornerRadius } from '../utils/argument-utils'; + +export interface RoundedCorner { + radiusX: number; + radiusY: number; +} + +export class RoundedCornerWrapper { + protected _topLeftCorner: RoundedCorner; + protected _topRightCorner: RoundedCorner; + protected _bottomRightCorner: RoundedCorner; + protected _bottomLeftCorner: RoundedCorner; + + constructor(public readonly element: SShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) { + } + + get size(): Dimension { + return this.element.size; + } + + get topLeftCorner(): RoundedCorner { + if (!this._topLeftCorner) { + this._topLeftCorner = { + radiusX: scaledRadius(this.cornerRadius.topLeft, this.element.size.width / 2), + radiusY: scaledRadius(this.cornerRadius.topLeft, this.element.size.height / 2) + }; + } + return this._topLeftCorner; + } + + get topRightCorner(): RoundedCorner { + if (!this._topRightCorner) { + this._topRightCorner = { + radiusX: scaledRadius(this.cornerRadius.topRight, this.element.size.width / 2), + radiusY: scaledRadius(this.cornerRadius.topRight, this.element.size.height / 2) + }; + } + return this._topRightCorner; + } + + get bottomRightCorner(): RoundedCorner { + if (!this._bottomRightCorner) { + this._bottomRightCorner = { + radiusX: scaledRadius(this.cornerRadius.bottomRight, this.element.size.width / 2), + radiusY: scaledRadius(this.cornerRadius.bottomRight, this.element.size.height / 2) + }; + } + return this._bottomRightCorner; + } + + get bottomLeftCorner(): RoundedCorner { + if (!this._bottomLeftCorner) { + this._bottomLeftCorner = { + radiusX: scaledRadius(this.cornerRadius.bottomLeft, this.element.size.width / 2), + radiusY: scaledRadius(this.cornerRadius.bottomLeft, this.element.size.height / 2) + }; + } + return this._bottomLeftCorner; + } +} + +/* +* Scales the radius if necessary. (Percentual downscaling if the radius is bigger then the maximal allowed length) +*/ +export function scaledRadius(radius: number, maximalLength: number): number { + if (radius <= maximalLength) { + return radius; + } else { + return radius * (maximalLength / radius); + } +} From a785eda60c53aab243092a019ea7623c8d41bd41 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 20 Apr 2021 14:28:34 +0200 Subject: [PATCH 029/566] #180: Integrate review feedback --- examples/workflow-glsp/src/di.config.ts | 4 ++-- examples/workflow-glsp/src/workflow-views.tsx | 6 +++--- packages/client/src/views/base-view-module.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 8b7fb89..d4bf9b3 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -20,7 +20,7 @@ import 'sprotty/css/edit-label.css'; import { boundsModule, buttonModule, - configureDefaultModelElement, + configureDefaultModelElements, configureModelElement, ConsoleLogger, defaultGLSPModule, @@ -84,7 +84,7 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind bind(TYPES.IContextMenuItemProvider).to(DeleteElementContextMenuItemProvider); const context = { bind, unbind, isBound, rebind }; - configureDefaultModelElement(context); + configureDefaultModelElements(context); configureModelElement(context, 'task:automated', TaskNode, TaskNodeView); configureModelElement(context, 'task:manual', TaskNode, TaskNodeView); configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 90df5e7..6c02aa4 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -43,9 +43,9 @@ export class TaskNodeView extends RoundedCornerNodeView { const task = node as TaskNode; const rcr = this.getRoundedCornerRadius(task); const graph = - {context.renderChildren(node)} ; @@ -56,7 +56,7 @@ export class TaskNodeView extends RoundedCornerNodeView { return 5; } - protected additionalClasses(element: Readonly, context: RenderingContext): Classes { + protected additionalClasses(element: Readonly, _context: RenderingContext): Classes { const node = element as TaskNode; return { 'class-task': true, diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 78a1d16..8aba083 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -59,10 +59,10 @@ interface ContainerContext { const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - configureDefaultModelElement(context); + configureDefaultModelElements(context); }); -export function configureDefaultModelElement(context: ContainerContext): void { +export function configureDefaultModelElements(context: ContainerContext): void { // HTML elements configureModelElement(context, DefaultTypes.HTML, HtmlRoot, HtmlRootView); From 9f7843d5e710f279b6c3b5e05e4ebbb2c645a9f3 Mon Sep 17 00:00:00 2001 From: Lucas Koehler Date: Mon, 3 May 2021 16:25:01 +0200 Subject: [PATCH 030/566] #220 Rework workflow-example (#116) * Remove custom views that were only used to apply custom css classes * TaskNodeView * ForkOrJoinNodeView * Use default Sprotty views instead and set css classes in the backend via SModelElement.cssClasses * Adapt di config and diagram css * Adapt standalone example1.wf file to changes and to be aligned with example1.wf from the theia integration workflow example part of eclipse-glsp/glsp/issues/220 Signed-off-by: Lucas Koehler --- examples/workflow-glsp/css/diagram.css | 4 +- examples/workflow-glsp/src/di.config.ts | 16 +- examples/workflow-glsp/src/workflow-views.tsx | 62 +--- examples/workflow-standalone/app/example1.wf | 341 +++++++++++------- examples/workflow-standalone/css/diagram.css | 4 +- 5 files changed, 234 insertions(+), 193 deletions(-) diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index a827621..e9c731a 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -120,11 +120,11 @@ opacity: 0.2; } -.sprotty-node.task.automated { +.task.automated>.sprotty-node { fill: gray; } -.sprotty-node.task.manual { +.task.manual>.sprotty-node { fill: lightblue; } diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index d4bf9b3..304eb69 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -57,6 +57,7 @@ import { overrideViewerOptions, paletteModule, RevealNamedElementActionProvider, + RoundedCornerNodeView, routingModule, SCompartment, SCompartmentView, @@ -67,13 +68,14 @@ import { toolsModule, TYPES, validationModule, - zorderModule + zorderModule, + RectangularNodeView } from '@eclipse-glsp/client'; import { Container, ContainerModule } from 'inversify'; import { directTaskEditor } from './direct-task-editing/di.config'; import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; -import { ForkOrJoinNodeView, IconView, TaskNodeView, WeightedEdgeView, WorkflowEdgeView } from './workflow-views'; +import { IconView, WorkflowEdgeView } from './workflow-views'; const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -85,19 +87,19 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); - configureModelElement(context, 'task:automated', TaskNode, TaskNodeView); - configureModelElement(context, 'task:manual', TaskNode, TaskNodeView); + configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); + configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); configureModelElement(context, 'comp:comp', SCompartment, SCompartmentView); configureModelElement(context, 'comp:header', SCompartment, SCompartmentView); configureModelElement(context, 'label:icon', SLabel, SLabelView); configureModelElement(context, DefaultTypes.EDGE, SEdge, WorkflowEdgeView); - configureModelElement(context, 'edge:weighted', WeightedEdge, WeightedEdgeView); + configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); configureModelElement(context, 'icon', Icon, IconView); configureModelElement(context, 'activityNode:merge', ActivityNode, DiamondNodeView); configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); - configureModelElement(context, 'activityNode:fork', ActivityNode, ForkOrJoinNodeView); - configureModelElement(context, 'activityNode:join', ActivityNode, ForkOrJoinNodeView); + configureModelElement(context, 'activityNode:fork', ActivityNode, RectangularNodeView); + configureModelElement(context, 'activityNode:join', ActivityNode, RectangularNodeView); }); export default function createContainer(widgetId: string): Container { diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 6c02aa4..6e638fe 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -16,68 +16,21 @@ import { angleOfPoint, GEdgeView, - Hoverable, IView, Point, - RectangularNodeView, RenderingContext, - RoundedCornerNodeView, SEdge, - Selectable, - SShapeElement, toDegrees } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; import * as snabbdom from 'snabbdom-jsx'; -import { Classes } from 'snabbdom/modules/class'; import { VNode } from 'snabbdom/vnode'; -import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; +import { Icon } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: snabbdom.svg }; -@injectable() -export class TaskNodeView extends RoundedCornerNodeView { - protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode { - const task = node as TaskNode; - const rcr = this.getRoundedCornerRadius(task); - const graph = - - {context.renderChildren(node)} - ; - return graph; - } - - protected getRoundedCornerRadius(_node: TaskNode): number { - return 5; - } - - protected additionalClasses(element: Readonly, _context: RenderingContext): Classes { - const node = element as TaskNode; - return { - 'class-task': true, - 'class-automated': node.taskType === 'automated', - 'class-manual': node.taskType === 'manual' - }; - } -} - -@injectable() -export class ForkOrJoinNodeView extends RectangularNodeView { - render(node: ActivityNode, context: RenderingContext): VNode { - const graph = - - ; - return graph; - } -} - @injectable() export class WorkflowEdgeView extends GEdgeView { protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { @@ -91,19 +44,6 @@ export class WorkflowEdgeView extends GEdgeView { } } -@injectable() -export class WeightedEdgeView extends WorkflowEdgeView { - protected addtionalClasses(edge: Readonly, _context: RenderingContext): Classes { - const wedge = edge as WeightedEdge; - return { - 'class-no-probability': !wedge.probability, - 'class-low': wedge.probability === 'low', - 'class-medium': wedge.probability === 'medium', - 'class-high': wedge.probability === 'high' - }; - } -} - @injectable() export class IconView implements IView { render(element: Icon, context: RenderingContext): VNode { diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 51d2937..3d34479 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -5,6 +5,10 @@ "name": "Push", "taskType": "manual", "id": "task0", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task0_header", @@ -17,16 +21,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -50,16 +54,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { "width": 48.90625, - "height": 22.40625 + "height": 22.0 }, "text": "Push", "alignment": { "x": 24.453125, - "y": 18.40625 + "y": 18.0 } } ], @@ -84,12 +88,22 @@ "width": 101.90625, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "ManualTask1", "taskType": "manual", "id": "task1", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task1_header", @@ -102,16 +116,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -135,16 +149,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 53.5625, - "height": 22.40625 + "width": 52.5625, + "height": 22.0 }, "text": "RflWt", "alignment": { "x": 26.109375, - "y": 18.40625 + "y": 18.0 } } ], @@ -154,7 +168,7 @@ "y": 5.0 }, "size": { - "width": 96.5625, + "width": 95.5625, "height": 42.0 }, "layout": "hbox" @@ -163,18 +177,28 @@ "type": "task:manual", "position": { "x": 930.0, - "y": 110.0 + "y": 150.0 }, "size": { - "width": 106.5625, + "width": 105.5625, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "ManualTask2", "taskType": "manual", "id": "task2", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task2_header", @@ -187,16 +211,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -220,16 +244,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 62.8125, - "height": 22.40625 + "width": 62.21875, + "height": 22.0 }, "text": "ChkTp", "alignment": { "x": 31.109375, - "y": 18.40625 + "y": 18.0 } } ], @@ -239,7 +263,7 @@ "y": 5.0 }, "size": { - "width": 105.8125, + "width": 105.21875, "height": 42.0 }, "layout": "hbox" @@ -248,18 +272,28 @@ "type": "task:manual", "position": { "x": 670.0, - "y": 350.0 + "y": 320.0 }, "size": { - "width": 115.8125, + "width": 115.21875, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "ManualTask3", "taskType": "manual", "id": "task3", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task3_header", @@ -272,16 +306,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -305,16 +339,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 76.953125, - "height": 22.40625 + "width": 75.953125, + "height": 22.0 }, "text": "PreHeat", "alignment": { "x": 37.8125, - "y": 18.40625 + "y": 18.0 } } ], @@ -324,7 +358,7 @@ "y": 5.0 }, "size": { - "width": 119.953125, + "width": 118.953125, "height": 42.0 }, "layout": "hbox" @@ -332,19 +366,29 @@ ], "type": "task:manual", "position": { - "x": 920.0, - "y": 390.0 + "x": 930.0, + "y": 360.0 }, "size": { - "width": 129.953125, + "width": 128.953125, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "ManualTask4", "taskType": "manual", "id": "task4", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task4_header", @@ -357,16 +401,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -390,16 +434,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 50.15625, - "height": 22.40625 + "width": 49.359375, + "height": 22.0 }, "text": "Brew", "alignment": { "x": 24.453125, - "y": 18.40625 + "y": 18.0 } } ], @@ -409,7 +453,7 @@ "y": 5.0 }, "size": { - "width": 93.15625, + "width": 92.359375, "height": 42.0 }, "layout": "hbox" @@ -417,19 +461,29 @@ ], "type": "task:manual", "position": { - "x": 1280.0, - "y": 260.0 + "x": 1260.0, + "y": 250.0 }, "size": { - "width": 103.15625, + "width": 102.359375, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "ManualTask6", "taskType": "manual", "id": "task6", + "cssClasses": [ + "task", + "manual" + ], "children": [ { "id": "task6_header", @@ -442,16 +496,16 @@ "type": "label:icon", "position": { "x": 7.65625, - "y": 4.796875 + "y": 5.0 }, "size": { "width": 16.6875, - "height": 22.40625 + "height": 22.0 }, "text": "M", "alignment": { "x": 8.34375, - "y": 18.40625 + "y": 18.0 } } ], @@ -475,16 +529,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 73.9375, - "height": 22.40625 + "width": 73.34375, + "height": 22.0 }, "text": "KeepTp", "alignment": { "x": 36.671875, - "y": 18.40625 + "y": 18.0 } } ], @@ -494,7 +548,7 @@ "y": 5.0 }, "size": { - "width": 116.9375, + "width": 116.34375, "height": 42.0 }, "layout": "hbox" @@ -502,19 +556,29 @@ ], "type": "task:manual", "position": { - "x": 920.0, - "y": 300.0 + "x": 930.0, + "y": 290.0 }, "size": { - "width": 126.9375, + "width": 126.34375, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "AutomatedTask8", "taskType": "automated", "id": "task8", + "cssClasses": [ + "task", + "automated" + ], "children": [ { "id": "task8_header", @@ -526,17 +590,17 @@ "id": "task8_ticon", "type": "label:icon", "position": { - "x": 7.9921875, - "y": 4.796875 + "x": 8.4921875, + "y": 5.0 }, "size": { - "width": 16.015625, - "height": 22.40625 + "width": 15.015625, + "height": 22.0 }, "text": "A", "alignment": { - "x": 8.28125, - "y": 18.40625 + "x": 7.671875, + "y": 18.0 } } ], @@ -560,16 +624,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 64.671875, - "height": 22.40625 + "width": 63.671875, + "height": 22.0 }, "text": "ChkWt", "alignment": { "x": 31.671875, - "y": 18.40625 + "y": 18.0 } } ], @@ -579,7 +643,7 @@ "y": 5.0 }, "size": { - "width": 107.671875, + "width": 106.671875, "height": 42.0 }, "layout": "hbox" @@ -587,19 +651,29 @@ ], "type": "task:automated", "position": { - "x": 680.0, - "y": 160.0 + "x": 670.0, + "y": 180.0 }, "size": { - "width": 117.671875, + "width": 116.671875, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "name": "AutomatedTask9", "taskType": "automated", "id": "task9", + "cssClasses": [ + "task", + "automated" + ], "children": [ { "id": "task9_header", @@ -611,17 +685,17 @@ "id": "task9_ticon", "type": "label:icon", "position": { - "x": 7.9921875, - "y": 4.796875 + "x": 8.4921875, + "y": 5.0 }, "size": { - "width": 16.015625, - "height": 22.40625 + "width": 15.015625, + "height": 22.0 }, "text": "A", "alignment": { - "x": 8.28125, - "y": 18.40625 + "x": 7.671875, + "y": 18.0 } } ], @@ -645,16 +719,16 @@ "type": "label:heading", "position": { "x": 38.0, - "y": 9.796875 + "y": 10.0 }, "size": { - "width": 57.109375, - "height": 22.40625 + "width": 56.109375, + "height": 22.0 }, "text": "WtOK", "alignment": { - "x": 28.578125, - "y": 18.40625 + "x": 27.78125, + "y": 18.0 } } ], @@ -664,7 +738,7 @@ "y": 5.0 }, "size": { - "width": 100.109375, + "width": 99.109375, "height": 42.0 }, "layout": "hbox" @@ -673,13 +747,19 @@ "type": "task:automated", "position": { "x": 930.0, - "y": 190.0 + "y": 220.0 }, "size": { - "width": 110.109375, + "width": 109.109375, "height": 52.0 }, - "layout": "vbox" + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } }, { "nodeType": "mergeNode", @@ -687,7 +767,7 @@ "type": "activityNode:merge", "position": { "x": 1100.0, - "y": 170.0 + "y": 190.0 }, "size": { "width": 32.0, @@ -716,7 +796,7 @@ "type": "activityNode:decision", "position": { "x": 840.0, - "y": 360.0 + "y": 330.0 }, "size": { "width": 32.0, @@ -738,8 +818,8 @@ "id": "activityNode4", "type": "activityNode:merge", "position": { - "x": 1100.0, - "y": 350.0 + "x": 1110.0, + "y": 330.0 }, "size": { "width": 32.0, @@ -767,8 +847,8 @@ "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", "type": "activityNode:decision", "position": { - "x": 840.0, - "y": 170.0 + "x": 830.0, + "y": 190.0 }, "size": { "width": 32.0, @@ -781,19 +861,19 @@ }, { "id": "69d7d698-c5ce-4e85-be06-d9bd9357322d", + "cssClasses": [ + "medium" + ], "type": "edge:weighted", "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", "targetId": "task1" }, - { - "id": "9c6a2ca0-e178-4b38-abea-33b07aecad54", - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "task9" - }, { "nodeType": "forkNode", "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "cssClasses": [ + "forkOrJoin" + ], "type": "activityNode:fork", "position": { "x": 590.0, @@ -810,18 +890,6 @@ "sourceId": "task0", "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" }, - { - "id": "ce77c929-f358-4a13-97c4-62c6792a644c", - "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task8" - }, - { - "id": "e346b1f6-dec9-40a5-a838-22dae399b8e9", - "type": "edge", - "sourceId": "task8", - "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" - }, { "id": "5eeaa218-ad56-4052-8b28-bbc08d82ed34", "type": "edge", @@ -831,10 +899,13 @@ { "nodeType": "joinNode", "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "cssClasses": [ + "forkOrJoin" + ], "type": "activityNode:join", "position": { "x": 1220.0, - "y": 260.0 + "y": 250.0 }, "size": { "width": 10.0, @@ -843,12 +914,18 @@ }, { "id": "2f57af4e-2960-496e-992d-03c8b021d81a", + "cssClasses": [ + "medium" + ], "type": "edge:weighted", "sourceId": "activityNode2", "targetId": "task6" }, { "id": "2c7427b0-9743-4bfb-b04c-078ba98f8d7e", + "cssClasses": [ + "medium" + ], "type": "edge:weighted", "sourceId": "activityNode2", "targetId": "task3" @@ -870,7 +947,29 @@ "type": "edge", "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", "targetId": "task4" + }, + { + "id": "e9aa796a-aa5b-4e6d-8860-4b9c5a5b8d89", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "task9" + }, + { + "id": "f49a588c-618c-4d9f-96fc-feee776a0ead", + "type": "edge", + "sourceId": "task8", + "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" + }, + { + "id": "568d91e3-d5bf-464d-923e-9769a8a651f3", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task8" } ], - "type": "graph" + "type": "graph", + "revision": 18 } diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index ed3f49a..3fb5755 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -28,11 +28,11 @@ stroke: black; } -.sprotty-node.forkOrJoin { +.forkOrJoin>.sprotty-node { fill: black; } -.sprotty-node.forkOrJoin.selected { +.forkOrJoin>.sprotty-node.selected { stroke: rgb(87, 87, 214); } From 844fd91be593dc7ec5fdd1897b06d4c4087c247d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 4 May 2021 09:29:06 +0200 Subject: [PATCH 031/566] #227 Use concrete next version instead of tag (#117) Also add update script to root package.json. The update script can be executed with `yarn upgrade:next` Part of eclipse-glsp/glsp/issues/227 --- packages/client/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 35f132b..77f938f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -46,7 +46,7 @@ "dependencies": { "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", - "sprotty": "next" + "sprotty": "^0.10.0-next.0bb1094" }, "devDependencies": { "@babel/runtime": "^7.11.2", @@ -67,7 +67,8 @@ "build": "tsc && yarn run lint", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", - "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"" + "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", + "upgrade:next": "yarn upgrade sprotty@next" }, "publishConfig": { "access": "public" From c4ae2198d31d6d1c3e2334bd92517f190914343e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 13 May 2021 20:01:26 +0200 Subject: [PATCH 032/566] Revert #227 Revert back to using next tags after identifying issues for glsp-dependent projects that occurred with eclipse-glsp/glsp/issues/227. We still want to maintain a "yarn upgrade:next" script in all repositories. --- packages/client/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 77f938f..35f132b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -46,7 +46,7 @@ "dependencies": { "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", - "sprotty": "^0.10.0-next.0bb1094" + "sprotty": "next" }, "devDependencies": { "@babel/runtime": "^7.11.2", @@ -67,8 +67,7 @@ "build": "tsc && yarn run lint", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", - "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", - "upgrade:next": "yarn upgrade sprotty@next" + "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"" }, "publishConfig": { "access": "public" From b5f84e9a742a5bbcd1234386cc0770d519589af5 Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Thu, 20 May 2021 11:26:22 +0200 Subject: [PATCH 033/566] Keep selection when using CTRL and marquee tool (#120) Pressing CTRL now keeps the previous selection when using the marquee tool Signed-off-by: Simon Graband --- .../client/src/features/tools/marquee-mouse-tool.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index ede8f8a..c55b066 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -19,6 +19,7 @@ import { BoundsAware, EnableDefaultToolsAction, isSelectable, + isSelected, KeyListener, Point, SEdge, @@ -71,6 +72,8 @@ export class MarqueeMouseListener extends DragAwareMouseListener { protected domHelper: DOMHelper; + protected previouslySelected: string[]; + protected isActive = false; constructor(domHelper: DOMHelper) { @@ -81,6 +84,12 @@ export class MarqueeMouseListener extends DragAwareMouseListener { mouseDown(target: SModelElement, event: MouseEvent): Action[] { this.isActive = true; this.startPoint = { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }; + if(event.ctrlKey){ + this.previouslySelected = Array.from(target.root.index.all() + .map(e => e as SModelElement & BoundsAware) + .filter(e => isSelected(e)) + .map(e => e.id)); + } return []; } @@ -95,8 +104,8 @@ export class MarqueeMouseListener extends DragAwareMouseListener { const edgeIdsSelected = this.getMarkedEdges(target.root); const selected = nodeIdsSelected.concat(edgeIdsSelected); return [ - new SelectAction([], Array.from(target.index.all().map(e => e.id))), - new SelectAction(selected, []), + new SelectAction([], Array.from(target.root.index.all().map(e => e.id))), + new SelectAction(selected.concat(this.previouslySelected), []), new DrawMarqueeAction(this.startPoint, { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }) ]; From 8fa69b41492153372b7988638881cdeafb7a4bcc Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 25 May 2021 10:25:52 +0200 Subject: [PATCH 034/566] #60 #229 Cleanup and add codestyle reporting to ci (#122) - Update metadata of all packages - Add standalone eslint script `yarn lint` - Add standalone build-only script (without linting) `yarn build` - Use `mocha-jenkins-reporter` instead of `jenkins-mocha`. This plugin allows simultaneous logging of test results to console and file. - Add `test:ci` script for executing mocha tests on the Jenkins instance Jenkinsfile - Enable eslint reporting & publishing - Enable mocha test executing recording & publishing - Only trigger deployment job if public package code was changed (part of eclipse-glsp/glsp/issues/229) Part of: eclipse-glsp/glsp/issues/60 --- examples/workflow-glsp/package.json | 26 ++++++++++++------- examples/workflow-standalone/package.json | 20 ++++++++++----- packages/client/package.json | 31 ++++++++++++----------- packages/protocol/package.json | 24 +++++++++--------- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index baeec88..3818b3e 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -3,7 +3,21 @@ "version": "0.9.0", "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", - "author": "Eclipse GLSP", + "keywords": [ + "glsp", + "workflow", + "diagram", + "example" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { "name": "Philip Langer", @@ -21,12 +35,6 @@ "url": "https://www.eclipsesource.com" } ], - "keywords": [ - "sprotty", - "workflow", - "diagram", - "example" - ], "dependencies": { "@eclipse-glsp/client": "0.9.0", "balloon-css": "^0.5.0" @@ -37,9 +45,9 @@ "typescript": "^3.9.2" }, "scripts": { - "prepare": "yarn run clean && yarn run build", + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib", - "build": "tsc && yarn run lint", + "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w" }, diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index dbaffd4..c63efbf 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -3,11 +3,15 @@ "name": "workflow-standalone", "version": "0.9.0", "description": "Standalone browser-app for the Workflow example", - "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "0.9.0", - "@eclipse-glsp/client": "0.9.0" + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" }, - "author": "Eclipse GLSP", + "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { "name": "Philip Langer", @@ -25,6 +29,10 @@ "url": "https://www.eclipsesource.com" } ], + "dependencies": { + "@eclipse-glsp-examples/workflow-glsp": "0.9.0", + "@eclipse-glsp/client": "0.9.0" + }, "devDependencies": { "@types/chai": "^4.0.1", "@types/file-saver": "^0.0.1", @@ -56,10 +64,10 @@ "access": "public" }, "scripts": { - "prepare": "yarn run clean && yarn build", + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib app/bundle.js app/bundle.js.map app/css", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", - "build": "tsc && yarn lint && webpack", + "build": "tsc && webpack", "watch": "tsc -w -p ./tsconfig.json" } } diff --git a/packages/client/package.json b/packages/client/package.json index 35f132b..d7af04f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -12,16 +12,15 @@ "glsp", "diagram editor" ], - "homepage": "https://www.eclipse.org/glsp/", - "bugs": "https://github.com/eclipse-glsp/glsp-client/issues", - "files": [ - "lib", - "src", - "css" - ], "author": { "name": "Eclipse GLSP" }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { "name": "Philip Langer", @@ -39,10 +38,11 @@ "url": "https://www.eclipsesource.com" } ], - "repository": { - "type": "git", - "url": "https://github.com/eclipse-glsp/glsp-client.git" - }, + "files": [ + "lib", + "src", + "css" + ], "dependencies": { "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", @@ -54,20 +54,21 @@ "@types/mocha": "^5.2.7", "@types/node": "10.14.18", "chai": "^4.2.0", - "jenkins-mocha": "^8.0.0", "mocha": "^6.2.0", + "mocha-jenkins-reporter": "0.4.5", "reflect-metadata": "^0.1.13", "rimraf": "^2.6.1", "ts-node": "^8.3.0", "typescript": "^3.9.2" }, "scripts": { - "prepare": "yarn run clean && yarn run build", + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib", - "build": "tsc && yarn run lint", + "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", - "test": "jenkins-mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"" + "test": "mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --colors --reporter mocha-jenkins-reporter" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 7dd94b6..cc2227a 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -12,15 +12,15 @@ "glsp", "diagram editor" ], - "homepage": "https://www.eclipse.org/glsp/", - "bugs": "https://github.com/eclipse-glsp/glsp-client/issues", - "files": [ - "lib", - "src" - ], "author": { "name": "Eclipse GLSP" }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { "name": "Philip Langer", @@ -38,10 +38,10 @@ "url": "https://www.eclipsesource.com" } ], - "repository": { - "type": "git", - "url": "https://github.com/eclipse-glsp/glsp-client.git" - }, + "files": [ + "lib", + "src" + ], "dependencies": { "uuid": "7.0.3", "vscode-ws-jsonrpc": "0.2.0" @@ -53,9 +53,9 @@ "typescript": "^3.9.2" }, "scripts": { - "prepare": "yarn run clean && yarn run build", + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib", - "build": "tsc && yarn run lint", + "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts ./src", "watch": "tsc -w" }, From 6fef976d6b912ed6968a8c824f77cce4c783d661 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 25 May 2021 16:28:07 +0200 Subject: [PATCH 035/566] #224 Fix wrong feedback anchor computation for nested elements (#123) Fix feedback edge snapping for nested elements inside of a container. Compute the absolute anchor position (relative to the root graph) instead of the local anchor position (relative to the child element). This ensure that the edge snaps to the correct position. This fixes the third subissue mentioned in eclipse-glsp/glsp/issues/224 --- .../tool-feedback/creation-tool-feedback.ts | 24 +++++++++++++++---- .../tool-feedback/edge-edit-tool-feedback.ts | 23 ++++++++++++++---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index c474a18..567d0b0 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,6 +16,7 @@ import { inject, injectable } from 'inversify'; import { Action, + add, AnchorComputerRegistry, center, CommandExecutionContext, @@ -27,6 +28,7 @@ import { isConnectable, MouseListener, MoveAction, + Point, PolylineEdgeRouter, SChildElement, SConnectableElement, @@ -39,7 +41,7 @@ import { } from 'sprotty'; import { isRoutable } from '../../utils/smodel-util'; -import { getAbsolutePosition, toAbsolutePosition } from '../../utils/viewpoint-util'; +import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackCommand } from './model'; export class DrawFeedbackEdgeAction implements Action { @@ -107,8 +109,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { .find(element => isConnectable(element) && element.canConnect(edge, 'target')); if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { - const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, endAtMousePosition.anchorKind); - const anchor = anchorComputer.getAnchor(endAtMousePosition, center(edge.source.bounds)); + const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(edge.source.bounds)); if (euclideanDistance(anchor, edgeEnd.position) > 1) { return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; } @@ -118,6 +119,21 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { return []; } + + protected computeAbsoluteAnchor(element: SConnectableElement, referencePoint: Point, offset?: number): Point { + const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); + let anchor = anchorComputer.getAnchor(element, referencePoint, offset); + // The anchor is computed in the local coordinate system of the element. + // If the element is a nested child element we have to add the absolute position of its parent to the anchor. + if (element.parent !== element.root) { + const parent = findParentByFeature(element.parent, isBoundsAware); + if (parent) { + const absoluteParentPosition = toAbsoluteBounds(parent); + anchor = add(absoluteParentPosition, anchor); + } + } + return anchor; + } } export function feedbackEdgeId(root: SModelRoot): string { diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index af21f42..4de3f9c 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,6 +17,7 @@ import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom/vnode'; import { Action, + add, AnchorComputerRegistry, center, CommandExecutionContext, @@ -44,7 +45,7 @@ import { } from 'sprotty'; import { isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; -import { getAbsolutePosition } from '../../utils/viewpoint-util'; +import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; import { FeedbackEdgeEnd, @@ -172,8 +173,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { .find(e => isConnectable(e) && e.canConnect(edge, 'source')); if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { - const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, endAtMousePosition.anchorKind); - const anchor = anchorComputer.getAnchor(endAtMousePosition, center(edge.target.bounds)); + const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(edge.target.bounds)); if (euclideanDistance(anchor, edgeEnd.position) > 1) { return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; } @@ -183,6 +183,21 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { return []; } + + protected computeAbsoluteAnchor(element: SConnectableElement, referencePoint: Point, offset?: number): Point { + const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); + let anchor = anchorComputer.getAnchor(element, referencePoint, offset); + // The anchor is computed in the local coordinate system of the element. + // If the element is a nested child element we have to add the absolute position of its parent to the anchor. + if (element.parent !== element.root) { + const parent = findParentByFeature(element.parent, isBoundsAware); + if (parent) { + const absoluteParentPosition = toAbsoluteBounds(parent); + anchor = add(absoluteParentPosition, anchor); + } + } + return anchor; + } } export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { From 2ff164c04ced33c91f2365fb8af93261d177bb2f Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Tue, 8 Jun 2021 17:45:51 +0200 Subject: [PATCH 036/566] Fix copy paste Switches mime type of clipboard ID to text/plain. Prevents GLSP handling paste if the active element isn't the SVG element. Tests whether copy, cut and paste commands are supported in the browser to enable even paste in an Electron context. Fixes https://github.com/eclipse-glsp/glsp/issues/249 --- .../copy-paste/copy-paste-context-menu.ts | 45 ++++++++++++++++--- .../features/copy-paste/copy-paste-handler.ts | 16 +++++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 9b3afd7..1582237 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -49,21 +49,31 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { handle(action: Action): void { switch (action.kind) { case InvokeCopyAction.KIND: - document.execCommand('copy'); + if (supportsCopy()) { + document.execCommand('copy'); + } else { + this.notifyUserToUseShortcut('copy'); + } break; case InvokePasteAction.KIND: - // in a browser without additional permission we can't invoke the paste command - // the user needs to invoke it from the browser, so notify the user about it - this.notifyUserToUseShortcut(); + if (supportsPaste()) { + document.execCommand('paste'); + } else { + this.notifyUserToUseShortcut('paste'); + } break; case InvokeCutAction.KIND: - document.execCommand('cut'); + if (supportsCut()) { + document.execCommand('cut'); + } else { + this.notifyUserToUseShortcut('cut'); + } break; } } - protected notifyUserToUseShortcut(): void { - const message = 'Please use the browser\'s paste command or shortcut.'; + protected notifyUserToUseShortcut(operation: string): void { + const message = `Please use the browser's ${operation} command or shortcut.`; const timeout = 10000; const severity = 'WARNING'; this.dispatcher.dispatchAll([ @@ -105,3 +115,24 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide }; } } + +export function supportsCopy(): boolean { + return isNative() || document.queryCommandSupported('copy'); +} + +export function supportsCut(): boolean { + return isNative() || document.queryCommandSupported('cut'); +} + +export function supportsPaste(): boolean { + const isChrome = (userAgent().indexOf('Chrome') >= 0); + return isNative() || (!isChrome && document.queryCommandSupported('paste')); +} + +export function isNative(): boolean { + return typeof (window as any).process !== 'undefined'; +} + +function userAgent(): string { + return typeof navigator !== 'undefined' ? navigator.userAgent : ''; +} diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index b244259..bc45555 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -91,7 +91,7 @@ function getClipboardIdFromDataTransfer(dataTransfer: DataTransfer): string | un return isClipboardId(jsonObject) ? jsonObject.clipboardId : undefined; } -const CLIPBOARD_DATA_FORMAT = 'application/json'; +const CLIPBOARD_DATA_FORMAT = 'text/plain'; @injectable() export class ServerCopyPasteHandler implements ICopyPasteHandler { @@ -126,7 +126,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { } handlePaste(event: ClipboardEvent): void { - if (event.clipboardData) { + if (event.clipboardData && this.shouldPaste(event)) { const clipboardId = getClipboardIdFromDataTransfer(event.clipboardData); const clipboardData = this.clipboadService.get(clipboardId); if (clipboardData) { @@ -137,8 +137,16 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { } protected shouldCopy(_event: ClipboardEvent): boolean | null { - return this.editorContext.get().selectedElementIds.length > 0 && document.activeElement instanceof SVGElement - && document.activeElement.parentElement && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; + return this.editorContext.get().selectedElementIds.length > 0 && this.isDiagramActive(); } + protected shouldPaste(_event: ClipboardEvent): boolean | null { + return this.isDiagramActive(); + } + + private isDiagramActive(): boolean | null { + return document.activeElement instanceof SVGElement + && document.activeElement.parentElement + && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; + } } From cebee95e4663c7fbcbfe91e20cbb50cf8783de2e Mon Sep 17 00:00:00 2001 From: Nina Doschek <47414588+ndoschek@users.noreply.github.com> Date: Wed, 7 Jul 2021 08:46:24 +0200 Subject: [PATCH 037/566] #253 Align operation definitions for ChangeContainerOperation (#127) --- packages/client/src/base/operations/operation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index d335110..822f5ee 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -82,7 +82,7 @@ export class ChangeContainerOperation implements Operation { static readonly KIND = 'changeContainer'; constructor(public readonly elementId: string, public readonly targetContainerId: string, - public readonly location?: string, + public readonly location?: Point, public readonly kind: string = ChangeContainerOperation.KIND) { } } From e54ac48dcc18eccce2db1a7b1e4857a75748a36e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 12 Jul 2021 09:45:10 +0200 Subject: [PATCH 038/566] #246 Use generic stand-in contributor (#128) Part of eclipse-glsp/glsp/issues/246 --- examples/workflow-glsp/package.json | 16 +++------------- examples/workflow-standalone/package.json | 16 +++------------- packages/client/package.json | 16 +++------------- packages/protocol/package.json | 16 +++------------- 4 files changed, 12 insertions(+), 52 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 3818b3e..1219111 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -20,19 +20,9 @@ "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { - "name": "Philip Langer", - "email": "planger@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Tobias Ortmayr", - "email": "tortmayr@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Martin Fleck", - "email": "mfleck@eclipsesource.com", - "url": "https://www.eclipsesource.com" + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], "dependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index c63efbf..bc6a160 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -14,19 +14,9 @@ "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { - "name": "Philip Langer", - "email": "planger@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Tobias Ortmayr", - "email": "tortmayr@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Martin Fleck", - "email": "mfleck@eclipsesource.com", - "url": "https://www.eclipsesource.com" + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], "dependencies": { diff --git a/packages/client/package.json b/packages/client/package.json index d7af04f..6a45e2f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -23,19 +23,9 @@ "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { - "name": "Philip Langer", - "email": "planger@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Tobias Ortmayr", - "email": "tortmayr@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Martin Fleck", - "email": "mfleck@eclipsesource.com", - "url": "https://www.eclipsesource.com" + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], "files": [ diff --git a/packages/protocol/package.json b/packages/protocol/package.json index cc2227a..11d1b82 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -23,19 +23,9 @@ "bugs": "https://github.com/eclipse-glsp/glsp/issues", "contributors": [ { - "name": "Philip Langer", - "email": "planger@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Tobias Ortmayr", - "email": "tortmayr@eclipsesource.com", - "url": "https://www.eclipsesource.com" - }, - { - "name": "Martin Fleck", - "email": "mfleck@eclipsesource.com", - "url": "https://www.eclipsesource.com" + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], "files": [ From 1f528cace4f810c618b7d755d4e21b01aa2e8b43 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Aug 2021 13:28:21 +0200 Subject: [PATCH 039/566] #258 Remove name property from GLSPClient (#130) A client is already uniquely identified via its id, there is no need for an additional name property. In our current implementing projects the name property is always equal to the id and therefore redundant. Part of eclipse-glsp/glsp/issues/258 --- examples/workflow-standalone/src/main.ts | 5 ++--- packages/protocol/src/glsp-client.ts | 11 ++--------- .../protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts | 3 +-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/main.ts index 78f6195..84ba03c 100644 --- a/examples/workflow-standalone/src/main.ts +++ b/examples/workflow-standalone/src/main.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,7 +29,6 @@ import createContainer from './di.config'; const port = 8081; const id = 'workflow'; -const name = 'workflow'; const websocket = new WebSocket(`ws://localhost:${port}/${id}`); const container = createContainer(); @@ -44,7 +43,7 @@ const actionDispatcher = container.get(TYPES.IActionDispatche websocket.onopen = () => { const connectionProvider = JsonrpcGLSPClient.createWebsocketConnectionProvider(websocket); - const glspClient = new BaseJsonrpcGLSPClient({ id, name, connectionProvider }); + const glspClient = new BaseJsonrpcGLSPClient({ id, connectionProvider }); diagramServer.connect(glspClient).then(client => { client.initializeServer({ applicationId: ApplicationIdProvider.get() }); actionDispatcher.dispatch(new InitializeClientSessionAction(diagramServer.clientId)); diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index 5fbee1f..2afa7b4 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -77,11 +77,6 @@ export interface GLSPClient { */ readonly id: string; - /** - * Client name. - */ - readonly name: string; - /** * Current client state. */ @@ -128,11 +123,9 @@ export interface GLSPClient { export namespace GLSPClient { export interface Options { id: string; - name: string; } export function isOptions(object: any): object is Options { - return object !== undefined && 'id' in object && typeof object['id'] === 'string' - && 'name' in object && typeof object['name'] === 'string'; + return object !== undefined && 'id' in object && typeof object['id'] === 'string'; } } diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index c2bb8bf..cd42063 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,7 +21,6 @@ import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { - readonly name: string; readonly id: string; protected readonly connectionProvider: ConnectionProvider; protected connectionPromise?: Promise; From fcdba279f3ba34f4514feef7f75a419604907b0f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 16 Aug 2021 15:34:34 +0200 Subject: [PATCH 040/566] #315 Refactor base protocol (#132) Refactor the base communication protocol to support initializing and disposing a client session for a given language. The method parameters are encapsulated in dedicated parameter objects to provide a stable API. Refactor the existing `initializeServer` method to return an `InitializeResult` instead of a boolean and use a generic args map for custom arguments instead of a dedicated object. The `InitializeResult` also provides the information about which actionKinds are handled by the server. In addition, the protocol version that is implemented by the client is now passed as part of the `InitializeParameters` Using the new initialize result the server action handlers can be configured BEFORE the first action is dispatched in a diagram container, which means its no longer necessary to register certain early actions directly in the diagram server. Refactor the standalone example to properly implement the new protocol. Also: - Remove no longer needs protocol actions for initializing/disposing a client session. - Update webpack config of standalone example to properly support typescript source maps. Part of eclipse-glsp/glsp/issues/315 --- examples/workflow-glsp/src/di.config.ts | 6 +- examples/workflow-standalone/package.json | 4 +- examples/workflow-standalone/src/di.config.ts | 3 +- examples/workflow-standalone/src/main.ts | 39 ++++--- .../workflow-standalone/webpack.config.js | 19 ++- .../src/base/actions/protocol-actions.ts | 82 ------------- packages/client/src/base/di.config.ts | 35 ++++-- .../src/features/tool-palette/di.config.ts | 2 +- packages/client/src/index.ts | 10 +- .../src/model-source/glsp-diagram-server.ts | 16 +-- packages/protocol/src/glsp-client.ts | 109 ++++++++++++++++-- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 30 ++++- .../src/jsonrpc/glsp-jsonrpc-client.ts | 15 ++- 13 files changed, 212 insertions(+), 158 deletions(-) delete mode 100644 packages/client/src/base/actions/protocol-actions.ts diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 304eb69..f8f923e 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -56,6 +56,7 @@ import { openModule, overrideViewerOptions, paletteModule, + RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, routingModule, @@ -68,8 +69,7 @@ import { toolsModule, TYPES, validationModule, - zorderModule, - RectangularNodeView + zorderModule } from '@eclipse-glsp/client'; import { Container, ContainerModule } from 'inversify'; diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index bc6a160..224b67b 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -59,5 +59,7 @@ "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "build": "tsc && webpack", "watch": "tsc -w -p ./tsconfig.json" - } + }, + "types": "lib", + "main": "lib" } diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 1d183fd..3f53fb9 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -22,7 +22,8 @@ import { ConsoleLogger, LogLevel, TYPES } from 'sprotty'; export default function createContainer(): Container { const container = createWorkflowDiagramContainer('sprotty'); - container.bind(TYPES.ModelSource).to(GLSPDiagramServer).inSingletonScope(); + container.bind(GLSPDiagramServer).toSelf().inSingletonScope(); + container.bind(TYPES.ModelSource).toService(GLSPDiagramServer); container.rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); container.rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); return container; diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/main.ts index 84ba03c..d10713f 100644 --- a/examples/workflow-standalone/src/main.ts +++ b/examples/workflow-standalone/src/main.ts @@ -16,12 +16,12 @@ import 'reflect-metadata'; import { + configureServerActions, EnableToolPaletteAction, GLSPDiagramServer, - InitializeClientSessionAction, RequestTypeHintsAction } from '@eclipse-glsp/client'; -import { ApplicationIdProvider, BaseJsonrpcGLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; +import { ApplicationIdProvider, BaseJsonrpcGLSPClient, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; import { join, resolve } from 'path'; import { IActionDispatcher, RequestModelAction, TYPES } from 'sprotty'; @@ -29,32 +29,39 @@ import createContainer from './di.config'; const port = 8081; const id = 'workflow'; +const diagramType = 'workflow-diagram'; const websocket = new WebSocket(`ws://localhost:${port}/${id}`); -const container = createContainer(); const loc = window.location.pathname; const currentDir = loc.substring(0, loc.lastIndexOf('/')); const examplePath = resolve(join(currentDir, '..', 'app', 'example1.wf')); +const clientId = ApplicationIdProvider.get() + '_' + examplePath; +const container = createContainer(); const diagramServer = container.get(TYPES.ModelSource); -diagramServer.clientId = ApplicationIdProvider.get() + '_' + examplePath; - -const actionDispatcher = container.get(TYPES.IActionDispatcher); +diagramServer.clientId = clientId; websocket.onopen = () => { const connectionProvider = JsonrpcGLSPClient.createWebsocketConnectionProvider(websocket); const glspClient = new BaseJsonrpcGLSPClient({ id, connectionProvider }); - diagramServer.connect(glspClient).then(client => { - client.initializeServer({ applicationId: ApplicationIdProvider.get() }); - actionDispatcher.dispatch(new InitializeClientSessionAction(diagramServer.clientId)); - actionDispatcher.dispatch(new RequestModelAction({ - sourceUri: `file://${examplePath}`, - diagramType: 'workflow-diagram' - })); - actionDispatcher.dispatch(new RequestTypeHintsAction('workflow-diagram')); - actionDispatcher.dispatch(new EnableToolPaletteAction()); - }); + initialize(glspClient); }; +async function initialize(client: GLSPClient): Promise { + await diagramServer.connect(client); + const result = await client.initializeServer({ applicationId: ApplicationIdProvider.get(), protocolVersion: GLSPClient.protocolVersion }); + await configureServerActions(result, diagramType, container); + + const actionDispatcher = container.get(TYPES.IActionDispatcher); + + await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); + actionDispatcher.dispatch(new RequestModelAction({ + sourceUri: `file://${examplePath}`, + diagramType + })); + actionDispatcher.dispatch(new RequestTypeHintsAction(diagramType)); + actionDispatcher.dispatch(new EnableToolPaletteAction()); +} + websocket.onerror = ev => alert('Connection to server errored. Please make sure that the server is running'); diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index f5ff8a1..14640ef 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017 TypeFox & others + * Copyright (c) 2017-2021 TypeFox & others * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -34,27 +34,26 @@ module.exports = { path: appRoot }, mode: 'development', - devtool: 'source-map', + devtool: 'eval-source-map', resolve: { // Add `.ts` and `.tsx` as a resolvable extension. extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js'] }, module: { rules: [ - // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` { test: /\.tsx?$/, - use: [{ - loader: 'ts-loader', - options: { - configFile: path.resolve(__dirname, 'examples.tsconfig.json') - } - }] + use: ['ts-loader'] + }, + { + test: /\.js$/, + use: ['source-map-loader'], + enforce: 'pre' }, { test: /\.css$/, exclude: /\.useable\.css$/, - loader: 'style-loader!css-loader' + use: ['style-loader', 'css-loader'] } ] }, diff --git a/packages/client/src/base/actions/protocol-actions.ts b/packages/client/src/base/actions/protocol-actions.ts deleted file mode 100644 index 37d171b..0000000 --- a/packages/client/src/base/actions/protocol-actions.ts +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { Action, ActionHandlerRegistry, IActionHandler, ModelSource, TYPES } from 'sprotty'; - -/** - * Initializes the graphical representation (diagram) for a specific client session. - * Each individual diagram on the client side counts as one session and has to provide - * a unique clientId. - */ -@injectable() -export class InitializeClientSessionAction implements Action { - static readonly KIND = 'initializeClientSession'; - constructor(public readonly clientId: string, public readonly kind: string = InitializeClientSessionAction.KIND) { } -} - -export function initializeClientSessionAction(action: Action): action is InitializeClientSessionAction { - return action.kind === InitializeClientSessionAction.KIND; -} - -/** - * Sent to the server if the graphical representation (diagram) for a specific client session - * is no longer needed. e.g. the tab containing the diagram widget has been closed. - */ -@injectable() -export class DisposeClientSessionAction implements Action { - static readonly KIND = 'disposeClientSession'; - constructor(public readonly clientId: string, public readonly kind: string = DisposeClientSessionAction.KIND) { } -} - -export function isDisposeClientSessionAction(action: Action): action is DisposeClientSessionAction { - return action.kind === DisposeClientSessionAction.KIND; -} - -/** - * Sent by the server after ClientSessionInitialization, to indicate - * all the action kinds that the server can handle. - */ -@injectable() -export class ConfigureServerHandlersAction implements Action { - static readonly KIND = 'configureServerHandlers'; - readonly kind = ConfigureServerHandlersAction.KIND; - - constructor(readonly actionKinds: string[]) { } -} - -export function isConfigureServerHandlersAction(action: Action): action is ConfigureServerHandlersAction { - return action.kind === ConfigureServerHandlersAction.KIND; -} - -@injectable() -export class ConfigureServerHandlersActionHandler implements IActionHandler { - - @inject(TYPES.ModelSource) - protected diagramServer: ModelSource; - - @inject(TYPES.ActionHandlerRegistryProvider) - actionHandlerRegistryProvider: () => Promise; - - handle(action: Action): void { - if (isConfigureServerHandlersAction(action)) { - this.actionHandlerRegistryProvider().then(registry => { - for (const actionKind of action.actionKinds) { - registry.register(actionKind, this.diagramServer); - } - }); - } - } -} diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index d9bec89..f387dd0 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,13 +15,20 @@ ********************************************************************************/ import '../../css/glsp-sprotty.css'; -import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureCommand, SetModelCommand, TYPES } from 'sprotty'; +import { InitializeResult } from '@eclipse-glsp/protocol'; +import { Container, ContainerModule } from 'inversify'; +import { + ActionHandlerRegistry, + configureActionHandler, + configureCommand, + ModelSource, + SetModelCommand, + TYPES +} from 'sprotty'; import { GLSPActionDispatcher } from './action-dispatcher'; import { SetEditModeAction } from './actions/edit-mode-action'; import { FocusStateChangedAction } from './actions/focus-change-action'; -import { ConfigureServerHandlersAction, ConfigureServerHandlersActionHandler } from './actions/protocol-actions'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context'; import { FocusTracker } from './focus-tracker'; @@ -53,9 +60,6 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = configureCommand(context, FeedbackAwareUpdateModelCommand); configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); - // Dynamically register all server-side action/operation handlers - configureActionHandler(context, ConfigureServerHandlersAction.KIND, ConfigureServerHandlersActionHandler); - bind(TYPES.MouseListener).to(SelectionClearingMouseListener); rebind(TYPES.ICommandStack).to(GLSPCommandStack); @@ -73,3 +77,20 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = }); export default defaultGLSPModule; + +/** + * Utility function to configure the {@link ModelSource}, i.e. the `DiagramServer`, as action handler for all server actions for the given diagramType. + * @param result A promise that resolves after all server actions have been registered. + * @param diagramType The diagram type. + * @param container The di container. + */ +export async function configureServerActions(result: InitializeResult, diagramType: string, container: Container): Promise { + const modelSource = container.get(TYPES.ModelSource); + const actionHandlerRegistry = container.get(ActionHandlerRegistry); + const serverActions = result.serverActions[diagramType]; + if (serverActions.length === 0) { + throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${diagramType}!`); + } + serverActions.forEach(actionKind => actionHandlerRegistry.register(actionKind, modelSource)); +} + diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts index be30fef..9dc7372 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -20,7 +20,7 @@ import { configureActionHandler, EnableDefaultToolsAction, TYPES } from 'sprotty import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; -const toolPaletteModule = new ContainerModule((bind, _unbind, isBound) => { +const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { bind(ToolPalette).toSelf().inSingletonScope(); bind(TYPES.IUIExtension).toService(ToolPalette); configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 843a469..6aa8401 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -39,12 +39,12 @@ export * from './base/actions/context-actions'; export * from './base/actions/edit-mode-action'; export * from './base/actions/edit-validation-actions'; export * from './base/actions/focus-change-action'; -export * from './base/actions/protocol-actions'; export * from './base/args'; export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; export * from './base/command-stack'; +export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context'; export * from './base/focus-tracker'; @@ -95,21 +95,21 @@ export * from './features/tools/edge-edit-tool'; export * from './features/tools/node-creation-tool'; export * from './features/undo-redo/model'; export * from './features/validation/issue-marker'; -export * from './features/validation/validate'; export * from './features/validation/marker-navigator'; +export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './lib/model'; -export * from './model-source/glsp-server-status'; export * from './model-source/glsp-diagram-server'; +export * from './model-source/glsp-server-status'; export * from './utils/argument-utils'; export * from './utils/array-utils'; export * from './utils/marker'; export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; - export { validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule, modelSourceWatcherModule, glspViewportModule }; + diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index f4a0256..5b43c80 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,11 +27,8 @@ import { SwitchEditModeCommand } from 'sprotty'; -import { RequestContextActions } from '../base/actions/context-actions'; import { isSetEditModeAction, SetEditModeAction } from '../base/actions/edit-mode-action'; -import { InitializeClientSessionAction } from '../base/actions/protocol-actions'; import { SourceUriAware } from '../base/source-uri-aware'; -import { RequestTypeHintsAction } from '../features/hints/request-type-hints-action'; import { isServerMessageAction, ServerMessageAction } from './glsp-server-status'; const receivedFromServerProperty = '__receivedFromServer'; @@ -108,16 +105,7 @@ export function isReceivedFromServer(action: Action): boolean { } export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer): void { - // Register the InitializeClientSessionAction as a server action. Then, the server will - // notify us about all actions it handles (Via ConfigureServerHandlersAction/Handler) - registry.register(InitializeClientSessionAction.KIND, diagramServer); - - // Register some additional early actions, that will happen before the server can tell us - // which actions it handles - registry.register(RequestTypeHintsAction.KIND, diagramServer); - registry.register(RequestModelAction.KIND, diagramServer); - registry.register(RequestContextActions.KIND, diagramServer); - + registry.register(ServerMessageAction.KIND, diagramServer); registry.register(ServerStatusAction.KIND, diagramServer); // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index 2afa7b4..0b55485 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -16,16 +16,76 @@ import { ActionMessage } from 'sprotty'; import * as uuid from 'uuid'; -export interface InitializeParameters<> { +/** + * A key-value pair structure for primitive typed custom arguments. + */ +export interface Args { [key: string]: string | number | boolean } + +/** + * A key-value pair structure to map a diagramType to its server-handled action kinds. + */ +export interface ServerActions { [key: string]: string[] } + +export interface InitializeParameters { /** * Unique identifier for the current client application. */ applicationId: string; /** - * Options that can include application-specific parameters. + * GLSP protocol version that this client is implementing. + */ + protocolVersion: string; + + /** + * Additional custom arguments e.g. application specific parameters. + */ + args?: Args; +} + +export interface InitializeResult { + + /** + * GLSP protocol version that the server is implementing. + */ + protocolVersion: string; + + /** + * The actions (grouped by diagramType) that the server can handle. + */ + serverActions: ServerActions; +} + +/** + * Known server actions i.e. action kinds that the server can handle for a specific diagram type. + */ +export interface InitializeClientSessionParameters { + /** + * Unique identifier for the new client session. + */ + clientSessionId: string; + + /** + * Unique identifier of the diagram type for which the session should be configured. + */ + diagramType: string; + + /** + * Additional custom arguments. + */ + args?: Args; +} + +export interface DisposeClientSessionParameters { + /** + * Unique identifier of the client session that should be disposed. */ - options?: any; + clientSessionId: string; + + /** + * Additional custom arguments. + */ + args?: Args; } export class ApplicationIdProvider { @@ -80,20 +140,43 @@ export interface GLSPClient { /** * Current client state. */ - currentState(): ClientState; + readonly currentState: ClientState; /** - * Initializes the client and the server connection. During the start procedure the client is in the `Starting` state and will transition to either `Running` or `StartFailed`. + * Initializes the client and the server connection. During the start procedure the client is in the + * `Starting` state and will transition to either `Running` or `StartFailed`. + * + * @returns A promise that resolves if the startup was successful. */ start(): Promise; /** - * Send an `initialize` request to the server. The server needs to be initialized in order to accept and process action messages. + * Send an `initialize` request to the server. The server needs to be initialized in order to accept and + * process other requests and notifications. + * + * @param params Initialize parameters + * @returns A promise of the {@link InitializeResult}. + */ + initializeServer(params: InitializeParameters): Promise; + + /** + * Send an `initializeClientSession` request to the server. One client application may open several session. + * Each individual diagram on the client side counts as one session and has to provide + * a unique clientId. + * + * @param params InitializeClientSession parameters + * @returns A promise that resolves if the initialization was successful + */ + initializeClientSession(params: InitializeClientSessionParameters): Promise; + + /** + * Sends a `disposeClientSession` request to the server. This request has to be sent at the end of client session lifecycle + * e.g. when an editor widget is closed. * - * @param params Initialize parameter - * @returns true if the initialization was successful + * @param params DisposeClientSession parameters + * @returns A promise that resolves if the disposal was successful */ - initializeServer(params: InitializeParameters): Promise; + disposeClientSession(params: DisposeClientSessionParameters): Promise; /** * Send a `shutdown` notification to the server. @@ -101,7 +184,10 @@ export interface GLSPClient { shutdownServer(): void; /** - * Stops the client and disposes any resources. During the stop procedure the client is in the `Stopping` state and will transition to either `Stopped` or `ServerError`. + * Stops the client and disposes any resources. During the stop procedure the client is in the `Stopping` state and will + * transition to either `Stopped` or `ServerError`. + * + * @returns A promise that resolves after the server was stopped and disposed. */ stop(): Promise; @@ -119,7 +205,6 @@ export interface GLSPClient { */ onActionMessage(handler: ActionMessageHandler): void; } - export namespace GLSPClient { export interface Options { id: string; @@ -128,4 +213,6 @@ export namespace GLSPClient { export function isOptions(object: any): object is Options { return object !== undefined && 'id' in object && typeof object['id'] === 'string'; } + + export const protocolVersion = '0.9.0'; } diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index cd42063..1924be1 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -16,7 +16,15 @@ import { ActionMessage } from 'sprotty'; import { Message, MessageConnection } from 'vscode-ws-jsonrpc'; -import { ActionMessageHandler, ClientState, GLSPClient, InitializeParameters } from '../glsp-client'; +import { + ActionMessageHandler, + ClientState, + DisposeClientSessionParameters, + GLSPClient, + InitializeClientSessionParameters, + InitializeParameters, + InitializeResult +} from '../glsp-client'; import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { @@ -39,11 +47,25 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { } } - initializeServer(params: InitializeParameters): Promise { + initializeServer(params: InitializeParameters): Promise { if (this.checkConnectionState()) { return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); } - return Promise.resolve(false); + return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); + } + + initializeClientSession(params: InitializeClientSessionParameters): Promise { + if (this.checkConnectionState()) { + return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, params); + } + return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); + } + + disposeClientSession(params: DisposeClientSessionParameters): Promise { + if (this.checkConnectionState()) { + return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, params); + } + return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); } onActionMessage(handler: ActionMessageHandler): void { @@ -138,7 +160,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.state === ClientState.Running && !!this.resolvedConnection; } - currentState(): ClientState { + get currentState(): ClientState { return this.state; } } diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts index 2f1f55f..7256cbc 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -24,7 +24,13 @@ import { WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; -import { GLSPClient, InitializeParameters } from '../glsp-client'; +import { + DisposeClientSessionParameters, + GLSPClient, + InitializeClientSessionParameters, + InitializeParameters, + InitializeResult +} from '../glsp-client'; export type MaybePromise = T | Promise | PromiseLike; export type ConnectionProvider = MessageConnection | (() => MaybePromise); @@ -39,7 +45,10 @@ export namespace JsonrpcGLSPClient { } export const ActionMessageNotification = new NotificationType('process'); - export const InitializeRequest = new RequestType('initialize'); + export const InitializeRequest = new RequestType('initialize'); + export const InitializeClientSessionRequest = new RequestType('initializeClientSession'); + export const DisposeClientSessionRequest = new RequestType('disposeClientSession'); + export const ShutdownNotification = new NotificationType0('shutdown'); export const ClientNotReadyMsg = 'JsonrpcGLSPClient is not ready yet'; From 2ea4aab3f71cbfacf1b1ad1361bcdb4dd1da4180 Mon Sep 17 00:00:00 2001 From: Nina Doschek <47414588+ndoschek@users.noreply.github.com> Date: Fri, 20 Aug 2021 10:21:45 +0200 Subject: [PATCH 041/566] #376 Element dragging without selection might not work with Firefox (#134) - Fix element movement without selection for Firefox by avoiding reset of startDragPosition on mouse entering (similar to other Firefox fixes in sprotty) --- .../src/features/tool-feedback/change-bounds-tool-feedback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 254ae0c..01a5401 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -214,7 +214,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } mouseEnter(target: SModelElement, event: MouseEvent): Action[] { - if (target instanceof SModelRoot && event.buttons === 0) { + if (target instanceof SModelRoot && event.buttons === 0 && !this.startDragPosition) { this.mouseUp(target, event); } return []; From f5c26af78112fe60bf8a5ce64cb73a4c09c76d35 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 20 Sep 2021 23:53:18 +0200 Subject: [PATCH 042/566] Upgrade to most recent Sprotty version and ES2017 (#137) --- examples/workflow-glsp/src/di.config.ts | 3 +- examples/workflow-glsp/src/workflow-views.tsx | 10 +++---- .../src/features/mouse-tool/mouse-tool.ts | 30 ++++++++++--------- .../change-bounds-tool-feedback.ts | 2 +- .../tool-feedback/edge-edit-tool-feedback.ts | 2 +- .../src/features/tool-feedback/view.tsx | 7 ++--- packages/client/src/features/tools/view.tsx | 7 ++--- packages/client/src/views/glsp-edge-view.tsx | 8 ++--- .../client/src/views/issue-marker-view.tsx | 7 ++--- .../client/src/views/rounded-corner-view.tsx | 8 ++--- 10 files changed, 40 insertions(+), 44 deletions(-) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index f8f923e..110b499 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -28,6 +28,7 @@ import { DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, + edgeIntersectionModule, edgeLayoutModule, editLabelFeature, expandModule, @@ -108,7 +109,7 @@ export default function createContainer(widgetId: string): Container { container.load(defaultModule, defaultGLSPModule, glspMouseToolModule, validationModule, glspSelectModule, boundsModule, glspViewportModule, toolsModule, glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, modelSourceWatcherModule, - glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, + glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, edgeIntersectionModule, layoutCommandsModule, directTaskEditor, navigationModule, markerNavigatorModule); overrideViewerOptions(container, { diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 6e638fe..372d923 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -15,24 +15,24 @@ ********************************************************************************/ import { angleOfPoint, - GEdgeView, IView, Point, + PolylineEdgeViewWithGapsOnIntersections, RenderingContext, SEdge, toDegrees } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { VNode } from 'snabbdom/vnode'; +import { VNode } from 'snabbdom'; +import { svg } from 'sprotty'; import { Icon } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; @injectable() -export class WorkflowEdgeView extends GEdgeView { +export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { const additionals = super.renderAdditionals(edge, segments, context); const p1 = segments[segments.length - 2]; diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index 02304ce..85722ff 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, multiInject, optional } from 'inversify'; -import { VNode, VNodeData } from 'snabbdom/vnode'; +import { On, VNode, VNodeData } from 'snabbdom'; import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; import { getRank } from '../rank/model'; @@ -46,18 +46,16 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { decorate(vnode: VNode, element: SModelElement): VNode { // we need to overwrite the existing event handlers registered by the original mouse tool if (element instanceof SModelRoot) { - overwriteOn(vnode, 'mouseover', this.mouseOver.bind(this), element); - overwriteOn(vnode, 'mouseout', this.mouseOut.bind(this), element); - overwriteOn(vnode, 'mouseenter', this.mouseEnter.bind(this), element); - overwriteOn(vnode, 'mouseleave', this.mouseLeave.bind(this), element); - overwriteOn(vnode, 'mousedown', this.mouseDown.bind(this), element); - overwriteOn(vnode, 'mouseup', this.mouseUp.bind(this), element); - overwriteOn(vnode, 'mousemove', this.mouseMove.bind(this), element); - overwriteOn(vnode, 'wheel', this.wheel.bind(this), element); - overwriteOn(vnode, 'contextmenu', (_target: SModelElement, event: any) => { - event.preventDefault(); - }, element); - overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this), element); + overwriteOn(vnode, 'mouseover', this.mouseOver.bind(this, element), element); + overwriteOn(vnode, 'mouseout', this.mouseOut.bind(this, element), element); + overwriteOn(vnode, 'mouseenter', this.mouseEnter.bind(this, element), element); + overwriteOn(vnode, 'mouseleave', this.mouseLeave.bind(this, element), element); + overwriteOn(vnode, 'mousedown', this.mouseDown.bind(this, element), element); + overwriteOn(vnode, 'mouseup', this.mouseUp.bind(this, element), element); + overwriteOn(vnode, 'mousemove', this.mouseMove.bind(this, element), element); + overwriteOn(vnode, 'wheel', this.wheel.bind(this, element), element); + overwriteOn(vnode, 'contextmenu', this.contextMenu.bind(this, element), element); + overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this, element), element); } vnode = this.mouseListeners.reduce( (n: VNode, listener: MouseListener) => listener.decorate(n, element), @@ -65,6 +63,10 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { return vnode; } + contextMenu(model: SModelRoot, event: MouseEvent): void { + event.preventDefault(); + } + protected handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { this.focusOnMouseEvent(methodName, model); const element = this.getTargetElement(model, event); @@ -121,7 +123,7 @@ function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElemen (val as any)[event] = [listener, element]; } -function getOn(vnode: VNode): any { +function getOn(vnode: VNode): On { const data = getData(vnode); if (!data.on) { data.on = {}; diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 01a5401..8ccad0a 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { VNode } from 'snabbdom/vnode'; +import { VNode } from 'snabbdom'; import { Action, CommandExecutionContext, diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 4de3f9c..aaba85f 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { VNode } from 'snabbdom/vnode'; +import { VNode } from 'snabbdom'; import { Action, add, diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index f3aba9a..265add0 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -14,14 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { VNode } from 'snabbdom/vnode'; -import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement } from 'sprotty'; +import { VNode } from 'snabbdom'; +import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; /** * This view is used for the invisible end of the feedback edge. diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx index 9729930..5b47143 100644 --- a/packages/client/src/features/tools/view.tsx +++ b/packages/client/src/features/tools/view.tsx @@ -14,14 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { VNode } from 'snabbdom/vnode'; -import { RectangularNodeView, RenderingContext } from 'sprotty'; +import { VNode } from 'snabbdom'; +import { RectangularNodeView, RenderingContext, svg } from 'sprotty'; import { MarqueeNode } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; @injectable() export class MarqueeView extends RectangularNodeView { diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index 20cac04..b02020e 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -14,15 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { Classes } from 'snabbdom/modules/class'; -import { VNode } from 'snabbdom/vnode'; -import { Point, PolylineEdgeView, RenderingContext, SEdge } from 'sprotty'; +import { Classes, VNode } from 'snabbdom'; +import { Point, PolylineEdgeView, RenderingContext, SEdge, svg } from 'sprotty'; import { EdgePadding } from '../utils/argument-utils'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; @injectable() export class GEdgeView extends PolylineEdgeView { diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 415dbb4..f3071d9 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -15,12 +15,11 @@ ********************************************************************************/ /* eslint-disable max-len */ import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { VNode } from 'snabbdom/vnode'; -import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity } from 'sprotty'; +import { VNode } from 'snabbdom'; +import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity, svg } from 'sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; @injectable() export class GIssueMarkerView extends IssueMarkerView { diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 9eb2d01..3210824 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -14,16 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import * as snabbdom from 'snabbdom-jsx'; -import { Classes } from 'snabbdom/modules/class'; -import { VNode } from 'snabbdom/vnode'; -import { Hoverable, RectangularNodeView, RenderingContext, Selectable, SNode, SPort, SShapeElement } from 'sprotty/lib'; +import { Classes, VNode } from 'snabbdom'; +import { Hoverable, RectangularNodeView, RenderingContext, Selectable, SNode, SPort, SShapeElement, svg } from 'sprotty'; import { CornerRadius } from '../utils/argument-utils'; import { RoundedCornerWrapper } from './rounded-corner'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: snabbdom.svg }; +const JSX = { createElement: svg }; @injectable() export class RoundedCornerNodeView extends RectangularNodeView { From f1c475a3669cf34567c022369a381f31820c16f7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 1 Oct 2021 17:07:15 +0200 Subject: [PATCH 043/566] #377 Switch to glsp-configuration package (#139) * #377 Switch to glsp-configuration package - Use remote configuration instead of project-specific configuration. - Use prettier instead of the typescript-hero/editor-config/css-formatter extension set - Reformat code with prettier Part of eclipse-glsp/glsp/issues/377 * Update .settings * Remove unnecessary consecutive lines Co-authored-by: Philip Langer --- examples/workflow-glsp/.eslintrc.js | 9 +- examples/workflow-glsp/src/di.config.ts | 41 +++++- .../direct-task-editing/direct-task-editor.ts | 20 +-- examples/workflow-glsp/src/index.ts | 3 +- examples/workflow-glsp/src/model.ts | 18 ++- examples/workflow-glsp/src/workflow-views.tsx | 24 +++- examples/workflow-glsp/tsconfig.json | 4 +- examples/workflow-standalone/.eslintrc.js | 9 +- examples/workflow-standalone/src/main.ts | 23 ++- examples/workflow-standalone/tsconfig.json | 4 +- packages/client/.eslintrc.js | 9 +- packages/client/mocha.opts | 6 + packages/client/package.json | 4 +- packages/client/src/base/action-dispatcher.ts | 3 +- .../src/base/actions/context-actions.ts | 12 +- .../src/base/actions/edit-mode-action.ts | 7 +- .../base/actions/edit-validation-actions.ts | 40 ++++-- .../src/base/actions/focus-change-action.ts | 2 +- packages/client/src/base/args.ts | 6 +- .../auto-complete/auto-complete-widget.ts | 55 ++++--- .../auto-complete/validation-decorator.ts | 10 +- packages/client/src/base/command-stack.ts | 4 +- packages/client/src/base/di.config.ts | 27 ++-- .../src/base/drag-aware-mouse-listener.ts | 4 +- packages/client/src/base/editor-context.ts | 6 +- packages/client/src/base/focus-tracker.ts | 2 - .../src/base/model/update-model-command.ts | 9 +- .../client/src/base/operations/operation.ts | 81 +++++++---- .../src/features/change-bounds/model.ts | 12 +- .../change-bounds/movement-restrictor.ts | 22 ++- .../server-command-palette-provider.ts | 13 +- .../src/features/context-menu/di.config.ts | 19 +-- ...rvice-aware-context-menu-mouse-listener.ts | 7 +- .../server-context-menu-provider.ts | 10 +- .../features/copy-paste/copy-paste-actions.ts | 19 +-- .../copy-paste/copy-paste-context-menu.ts | 31 ++-- .../features/copy-paste/copy-paste-handler.ts | 11 +- .../features/decoration/decoration-placer.ts | 15 +- .../edit-label/edit-label-validator.ts | 23 ++- packages/client/src/features/hints/model.ts | 4 +- .../hints/request-type-hints-action.ts | 15 +- .../client/src/features/hints/type-hints.ts | 56 ++++---- packages/client/src/features/hover/hover.ts | 3 +- .../features/layout/layout-commands.spec.ts | 135 ++++++++++-------- .../src/features/layout/layout-commands.ts | 73 ++++++---- .../model-source-changed-action-handler.ts | 6 +- .../model-source-changed-action.ts | 4 +- .../src/features/mouse-tool/mouse-tool.ts | 23 +-- .../navigation/navigation-action-handler.ts | 54 ++++--- .../navigation-target-resolver.spec.ts | 4 +- .../navigation/navigation-target-resolver.ts | 26 ++-- packages/client/src/features/rank/model.ts | 4 +- .../client/src/features/reconnect/model.ts | 5 +- .../client/src/features/save/di.config.ts | 3 +- packages/client/src/features/save/save.ts | 4 +- .../features/select/select-feedback-action.ts | 10 +- .../features/select/selection-service.spec.ts | 13 +- .../src/features/select/selection-service.ts | 18 ++- .../change-bounds-tool-feedback.ts | 17 +-- .../tool-feedback/creation-tool-feedback.ts | 23 ++- .../features/tool-feedback/css-feedback.ts | 7 +- .../src/features/tool-feedback/di.config.ts | 7 +- .../tool-feedback/edge-edit-tool-feedback.ts | 31 ++-- .../feedback-action-dispatcher.ts | 14 +- .../tool-feedback/marquee-tool-feedback.ts | 5 +- .../src/features/tool-feedback/model.ts | 3 +- .../src/features/tool-feedback/view.tsx | 19 ++- .../src/features/tool-palette/palette-item.ts | 6 +- .../src/features/tool-palette/tool-palette.ts | 26 ++-- .../src/features/tools/change-bounds-tool.ts | 46 +++--- .../client/src/features/tools/delete-tool.ts | 11 +- .../src/features/tools/edge-creation-tool.ts | 13 +- .../src/features/tools/edge-edit-tool.ts | 23 +-- .../src/features/tools/marquee-mouse-tool.ts | 94 +++++++----- .../client/src/features/tools/marquee-tool.ts | 1 - .../src/features/tools/node-creation-tool.ts | 13 +- packages/client/src/features/tools/view.tsx | 22 +-- .../client/src/features/undo-redo/model.ts | 6 +- .../validation/marker-navigator.spec.ts | 24 ++-- .../features/validation/marker-navigator.ts | 31 ++-- .../src/features/validation/validate.ts | 22 ++- .../viewport/glsp-scroll-mouse-listener.ts | 11 +- packages/client/src/index.ts | 25 +++- packages/client/src/utils/argument-utils.ts | 22 +-- packages/client/src/utils/array-utils.ts | 6 +- packages/client/src/utils/layout-utils.ts | 16 ++- packages/client/src/utils/smodel-util.ts | 24 ++-- packages/client/src/utils/viewpoint-util.ts | 5 +- packages/client/src/views/glsp-edge-view.tsx | 25 ++-- .../client/src/views/issue-marker-view.tsx | 16 +-- .../client/src/views/rounded-corner-view.tsx | 55 ++++--- packages/client/src/views/rounded-corner.ts | 7 +- packages/client/tsconfig.json | 4 +- packages/protocol/.eslintrc.js | 9 +- packages/protocol/src/glsp-client.ts | 23 +-- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 5 +- .../src/jsonrpc/glsp-jsonrpc-client.ts | 6 +- packages/protocol/src/launch-util.ts | 3 +- packages/protocol/tsconfig.json | 4 +- 99 files changed, 1033 insertions(+), 781 deletions(-) create mode 100644 packages/client/mocha.opts diff --git a/examples/workflow-glsp/.eslintrc.js b/examples/workflow-glsp/.eslintrc.js index 09bded5..727bf62 100644 --- a/examples/workflow-glsp/.eslintrc.js +++ b/examples/workflow-glsp/.eslintrc.js @@ -1,13 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: [ - '../../configs/base.eslintrc.json', - '../../configs/warnings.eslintrc.json', - '../../configs/errors.eslintrc.json' - ], - ignorePatterns: [ - '**/{css,node_modules,lib,scripts}' - ], + extends: '@eclipse-glsp', parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 110b499..e4a0075 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -106,11 +106,42 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind export default function createContainer(widgetId: string): Container { const container = new Container(); - container.load(defaultModule, defaultGLSPModule, glspMouseToolModule, validationModule, glspSelectModule, boundsModule, glspViewportModule, toolsModule, - glspHoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditUiModule, glspEditLabelModule, - workflowDiagramModule, toolFeedbackModule, modelHintsModule, glspContextMenuModule, glspServerCopyPasteModule, modelSourceWatcherModule, - glspCommandPaletteModule, paletteModule, routingModule, glspDecorationModule, edgeLayoutModule, zorderModule, edgeIntersectionModule, - layoutCommandsModule, directTaskEditor, navigationModule, markerNavigatorModule); + container.load( + defaultModule, + defaultGLSPModule, + glspMouseToolModule, + validationModule, + glspSelectModule, + boundsModule, + glspViewportModule, + toolsModule, + glspHoverModule, + fadeModule, + exportModule, + expandModule, + openModule, + buttonModule, + modelSourceModule, + labelEditUiModule, + glspEditLabelModule, + workflowDiagramModule, + toolFeedbackModule, + modelHintsModule, + glspContextMenuModule, + glspServerCopyPasteModule, + modelSourceWatcherModule, + glspCommandPaletteModule, + paletteModule, + routingModule, + glspDecorationModule, + edgeLayoutModule, + zorderModule, + edgeIntersectionModule, + layoutCommandsModule, + directTaskEditor, + navigationModule, + markerNavigatorModule + ); overrideViewerOptions(container, { baseDiv: widgetId, diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 740197d..78e5b33 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -42,12 +42,11 @@ import { isTaskNode, TaskNode } from '../model'; export class ApplyTaskEditOperation implements Action { static readonly KIND = 'applyTaskEdit'; readonly kind = ApplyTaskEditOperation.KIND; - constructor(readonly taskId: string, readonly expression: string) { } + constructor(readonly taskId: string, readonly expression: string) {} } @injectable() export class TaskEditor extends AbstractUIExtension { - static readonly ID = 'task-editor'; readonly autoSuggestionSettings = { noSuggestionsMessage: 'No suggestions available', @@ -86,14 +85,16 @@ export class TaskEditor extends AbstractUIExtension { this.autoSuggestionSettings, { provideSuggestions: input => this.retrieveSuggestions(input) }, { executeFromSuggestion: input => this.executeFromSuggestion(input) }, - () => this.hide(), this.logger); + () => this.hide(), + this.logger + ); this.autoSuggestion.configureValidation( { validate: input => this.validateInput(input) }, new ValidationDecorator(containerElement) ); - this.autoSuggestion.configureTextSubmitHandler( - { executeFromTextOnlyInput: (input: string) => this.executeFromTextOnlyInput(input) } - ); + this.autoSuggestion.configureTextSubmitHandler({ + executeFromTextOnlyInput: (input: string) => this.executeFromTextOnlyInput(input) + }); this.autoSuggestion.initialize(containerElement); } @@ -124,8 +125,9 @@ export class TaskEditor extends AbstractUIExtension { } protected async retrieveSuggestions(input: string): Promise { - const response = await this.actionDispatcher.request(new RequestContextActions(TaskEditor.ID, - this.editorContextService.get({ ['text']: input }))); + const response = await this.actionDispatcher.request( + new RequestContextActions(TaskEditor.ID, this.editorContextService.get({ ['text']: input })) + ); if (isSetContextActionsAction(response)) { return response.actions; } diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index 37d541c..76e7aff 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,4 +19,3 @@ export * from './model'; export * from './workflow-views'; export * from './direct-task-editing/direct-task-editor'; export { createWorkflowDiagramContainer }; - diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index cbf0a6e..13257a7 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -40,8 +40,19 @@ import { } from '@eclipse-glsp/client'; export class TaskNode extends RectangularNode implements Nameable, WithEditableLabel { - static readonly DEFAULT_FEATURES = [connectableFeature, deletableFeature, selectFeature, boundsFeature, - moveFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature, nameFeature, withEditLabelFeature]; + static readonly DEFAULT_FEATURES = [ + connectableFeature, + deletableFeature, + selectFeature, + boundsFeature, + moveFeature, + layoutContainerFeature, + fadeFeature, + hoverFeedbackFeature, + popupFeature, + nameFeature, + withEditLabelFeature + ]; name = ''; duration?: number; taskType?: string; @@ -76,7 +87,6 @@ export class ActivityNode extends DiamondNode { strokeWidth = 1; } -// eslint-disable-next-line no-redeclare export namespace ActivityNode { export namespace Type { export const INITIAL = 'initalNode'; diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 372d923..03a7691 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -37,8 +37,16 @@ export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { const additionals = super.renderAdditionals(edge, segments, context); const p1 = segments[segments.length - 2]; const p2 = segments[segments.length - 1]; - const arrow = ; + const arrow = ( + + ); additionals.push(arrow); return additionals; } @@ -48,10 +56,12 @@ export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { export class IconView implements IView { render(element: Icon, context: RenderingContext): VNode { const radius = this.getRadius(); - return - - {context.renderChildren(element)} - ; + return ( + + + {context.renderChildren(element)} + + ); } getRadius(): number { diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index e940dd2..9bc6df5 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../configs/base.tsconfig", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib", @@ -10,4 +10,4 @@ "include": [ "src" ], -} +} \ No newline at end of file diff --git a/examples/workflow-standalone/.eslintrc.js b/examples/workflow-standalone/.eslintrc.js index c7982ca..727bf62 100644 --- a/examples/workflow-standalone/.eslintrc.js +++ b/examples/workflow-standalone/.eslintrc.js @@ -1,13 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: [ - '../../configs/base.eslintrc.json', - '../../configs/warnings.eslintrc.json', - '../../configs/errors.eslintrc.json' - ], - ignorePatterns: [ - '**/{css,node_modules,lib}' - ], + extends: '@eclipse-glsp', parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/main.ts index d10713f..b3eceae 100644 --- a/examples/workflow-standalone/src/main.ts +++ b/examples/workflow-standalone/src/main.ts @@ -15,12 +15,7 @@ ********************************************************************************/ import 'reflect-metadata'; -import { - configureServerActions, - EnableToolPaletteAction, - GLSPDiagramServer, - RequestTypeHintsAction -} from '@eclipse-glsp/client'; +import { configureServerActions, EnableToolPaletteAction, GLSPDiagramServer, RequestTypeHintsAction } from '@eclipse-glsp/client'; import { ApplicationIdProvider, BaseJsonrpcGLSPClient, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; import { join, resolve } from 'path'; import { IActionDispatcher, RequestModelAction, TYPES } from 'sprotty'; @@ -49,19 +44,23 @@ websocket.onopen = () => { async function initialize(client: GLSPClient): Promise { await diagramServer.connect(client); - const result = await client.initializeServer({ applicationId: ApplicationIdProvider.get(), protocolVersion: GLSPClient.protocolVersion }); + const result = await client.initializeServer({ + applicationId: ApplicationIdProvider.get(), + protocolVersion: GLSPClient.protocolVersion + }); await configureServerActions(result, diagramType, container); const actionDispatcher = container.get(TYPES.IActionDispatcher); await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); - actionDispatcher.dispatch(new RequestModelAction({ - sourceUri: `file://${examplePath}`, - diagramType - })); + actionDispatcher.dispatch( + new RequestModelAction({ + sourceUri: `file://${examplePath}`, + diagramType + }) + ); actionDispatcher.dispatch(new RequestTypeHintsAction(diagramType)); actionDispatcher.dispatch(new EnableToolPaletteAction()); } websocket.onerror = ev => alert('Connection to server errored. Please make sure that the server is running'); - diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json index 2c83567..7b23021 100644 --- a/examples/workflow-standalone/tsconfig.json +++ b/examples/workflow-standalone/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../configs/base.tsconfig", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib", @@ -12,4 +12,4 @@ "css", "app" ] -} +} \ No newline at end of file diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index c7982ca..727bf62 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -1,13 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: [ - '../../configs/base.eslintrc.json', - '../../configs/warnings.eslintrc.json', - '../../configs/errors.eslintrc.json' - ], - ignorePatterns: [ - '**/{css,node_modules,lib}' - ], + extends: '@eclipse-glsp', parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' diff --git a/packages/client/mocha.opts b/packages/client/mocha.opts new file mode 100644 index 0000000..f1715af --- /dev/null +++ b/packages/client/mocha.opts @@ -0,0 +1,6 @@ +--require ts-node/register +--require reflect-metadata/Reflect +--reporter spec +--colors +--watch-extensions ts,tsx +--timeout 10000 diff --git a/packages/client/package.json b/packages/client/package.json index 6a45e2f..2b7d1a4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -57,7 +57,7 @@ "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", - "test": "mocha --opts ../../configs/mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", + "test": "mocha --opts ./mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --colors --reporter mocha-jenkins-reporter" }, "publishConfig": { @@ -65,4 +65,4 @@ }, "main": "lib/index", "types": "lib/index" -} +} \ No newline at end of file diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index c98c86e..0359778 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,6 @@ import { Action, ActionDispatcher, isResponseAction, RequestAction, ResponseActi import { ModelInitializationConstraint } from './model-initialization-constraint'; export class GLSPActionDispatcher extends ActionDispatcher { - protected readonly timeouts: Map = new Map(); @inject(ModelInitializationConstraint) protected initiailizationConstraint: ModelInitializationConstraint; diff --git a/packages/client/src/base/actions/context-actions.ts b/packages/client/src/base/actions/context-actions.ts index 44d9103..6d48de2 100644 --- a/packages/client/src/base/actions/context-actions.ts +++ b/packages/client/src/base/actions/context-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -24,7 +24,8 @@ export class RequestContextActions implements RequestAction { public readonly contextId: string, public readonly editorContext: EditorContext, public readonly requestId: string = generateRequestId(), - public readonly kind: string = RequestContextActions.KIND) { } + public readonly kind: string = RequestContextActions.KIND + ) {} } export class SetContextActions implements ResponseAction { @@ -33,11 +34,10 @@ export class SetContextActions implements ResponseAction { public readonly actions: LabeledAction[], public readonly responseId: string = '', public readonly args?: Args, - public readonly kind: string = SetContextActions.KIND) { - } + public readonly kind: string = SetContextActions.KIND + ) {} } export function isSetContextActionsAction(action: Action): action is SetContextActions { - return action !== undefined && (action.kind === SetContextActions.KIND) - && (action as SetContextActions).actions !== undefined; + return action !== undefined && action.kind === SetContextActions.KIND && (action as SetContextActions).actions !== undefined; } diff --git a/packages/client/src/base/actions/edit-mode-action.ts b/packages/client/src/base/actions/edit-mode-action.ts index dc9cd16..ab30ca8 100644 --- a/packages/client/src/base/actions/edit-mode-action.ts +++ b/packages/client/src/base/actions/edit-mode-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,12 +19,11 @@ import { Action } from 'sprotty'; @injectable() export class SetEditModeAction implements Action { static readonly KIND = 'setEditMode'; - constructor(public readonly editMode: string = EditMode.EDITABLE, public readonly kind: string = SetEditModeAction.KIND) { } + constructor(public readonly editMode: string = EditMode.EDITABLE, public readonly kind: string = SetEditModeAction.KIND) {} } export function isSetEditModeAction(action: Action): action is SetEditModeAction { - return action !== undefined && (action.kind === SetEditModeAction.KIND) - && 'editMode' in action && typeof action['editMode'] === 'string'; + return action !== undefined && action.kind === SetEditModeAction.KIND && 'editMode' in action && typeof action['editMode'] === 'string'; } export namespace EditMode { diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts index df6b223..915567a 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Action, generateRequestId, RequestAction, ResponseAction } from 'sprotty'; - import { Args } from '../args'; export class RequestEditValidationAction implements RequestAction { @@ -24,7 +23,8 @@ export class RequestEditValidationAction implements RequestAction void = () => { }, + protected notifyClose: () => void = () => {}, protected logger?: ILogger - ) { } + ) {} - configureValidation(inputValidator: InputValidator, - validationDecorator: IValidationDecorator = IValidationDecorator.NO_DECORATION): void { + configureValidation( + inputValidator: InputValidator, + validationDecorator: IValidationDecorator = IValidationDecorator.NO_DECORATION + ): void { this.inputValidator = inputValidator; this.validationDecorator = validationDecorator; } @@ -154,25 +156,33 @@ export class AutoCompleteWidget { showOnFocus: this.autoSuggestionSettings.showOnFocus, debounceWaitMs: this.autoSuggestionSettings.debounceWaitMs, minLength: -1, - fetch: (text: string, update: (items: LabeledAction[]) => void) => - this.updateSuggestions(update, text, root), + fetch: (text: string, update: (items: LabeledAction[]) => void) => this.updateSuggestions(update, text, root), onSelect: (item: LabeledAction) => this.onSelect(item), - render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => - this.renderSuggestions(item, currentValue), + render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => this.renderSuggestions(item, currentValue), customize: (input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) => { this.customizeInputElement(input, inputRect, container, maxHeight); } }; } - protected customizeInputElement(input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number): void { + protected customizeInputElement( + input: HTMLInputElement, + inputRect: ClientRect | DOMRect, + container: HTMLDivElement, + maxHeight: number + ): void { // move container into our UIExtension container as this is already positioned correctly if (this.containerElement) { this.containerElement.appendChild(container); } } - protected updateSuggestions(update: (items: LabeledAction[]) => void, text: string, root: Readonly, ...contextElementIds: string[]): void { + protected updateSuggestions( + update: (items: LabeledAction[]) => void, + text: string, + root: Readonly, + ...contextElementIds: string[] + ): void { this.onLoading(); this.doUpdateSuggestions(text, root) .then(actions => { @@ -230,11 +240,13 @@ export class AutoCompleteWidget { } protected filterActions(filterText: string, actions: LabeledAction[]): LabeledAction[] { - return toArray(actions.filter(action => { - const label = action.label.toLowerCase(); - const searchWords = filterText.split(' '); - return searchWords.every(word => label.indexOf(word.toLowerCase()) !== -1); - })); + return toArray( + actions.filter(action => { + const label = action.label.toLowerCase(); + const searchWords = filterText.split(' '); + return searchWords.every(word => label.indexOf(word.toLowerCase()) !== -1); + }) + ); } protected onSelect(item: LabeledAction): void { @@ -257,10 +269,10 @@ export class AutoCompleteWidget { } private isNoOrExactlyOneMatchingContextAction(): boolean | undefined { - return !this.isSuggestionAvailable() - || (this.contextActions - && this.contextActions.length === 1 - && this.inputElement.value.endsWith(this.contextActions[0].label)); + return ( + !this.isSuggestionAvailable() || + (this.contextActions && this.contextActions.length === 1 && this.inputElement.value.endsWith(this.contextActions[0].label)) + ); } protected isSuggestionAvailable(): boolean | undefined { @@ -269,7 +281,8 @@ export class AutoCompleteWidget { validateInput(): void { if (this.inputValidator) { - this.inputValidator.validate(this.inputElement.value) + this.inputValidator + .validate(this.inputElement.value) .then(result => this.validationDecorator.decorateValidationResult(result)) .catch(error => this.handleErrorDuringValidation(error)); } diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index 97cbd4b..dac291f 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,14 +25,14 @@ export interface IValidationDecorator { export namespace IValidationDecorator { export const NO_DECORATION: IValidationDecorator = { // eslint-disable-next-line @typescript-eslint/no-empty-function - decorateValidationResult(_status: ValidationStatus) { }, + decorateValidationResult(_status: ValidationStatus) {}, isValidatedOk(): boolean { return false; }, // eslint-disable-next-line @typescript-eslint/no-empty-function - invalidate() { }, + invalidate() {}, // eslint-disable-next-line @typescript-eslint/no-empty-function - dispose() { } + dispose() {} }; } @@ -47,7 +47,7 @@ export class ValidationDecorator implements IValidationDecorator { protected decorationDiv?: HTMLDivElement; - constructor(protected containerElement: HTMLElement) { } + constructor(protected containerElement: HTMLElement) {} decorateValidationResult(status: ValidationStatus): void { if (ValidationStatus.isError(status)) { diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index c88a2f4..854d958 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,7 +20,6 @@ import { RedoOperation, UndoOperation } from '../features/undo-redo/model'; @injectable() export class GLSPCommandStack extends CommandStack { - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; undo(): Promise { @@ -33,4 +32,3 @@ export class GLSPCommandStack extends CommandStack { return this.thenUpdate(); } } - diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index f387dd0..f56cfff 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -17,14 +17,7 @@ import '../../css/glsp-sprotty.css'; import { InitializeResult } from '@eclipse-glsp/protocol'; import { Container, ContainerModule } from 'inversify'; -import { - ActionHandlerRegistry, - configureActionHandler, - configureCommand, - ModelSource, - SetModelCommand, - TYPES -} from 'sprotty'; +import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand, TYPES } from 'sprotty'; import { GLSPActionDispatcher } from './action-dispatcher'; import { SetEditModeAction } from './actions/edit-mode-action'; @@ -43,13 +36,16 @@ import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, _unbind, isBound, rebind }; bind(EditorContextService).toSelf().inSingletonScope(); - bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider(ctx => () => new Promise((resolve, reject) => { - if (ctx.container.isBound(EditorContextService)) { - resolve(ctx.container.get(EditorContextService)); - } else { - reject(); - } - })); + bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider( + ctx => () => + new Promise((resolve, reject) => { + if (ctx.container.isBound(EditorContextService)) { + resolve(ctx.container.get(EditorContextService)); + } else { + reject(); + } + }) + ); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); @@ -93,4 +89,3 @@ export async function configureServerActions(result: InitializeResult, diagramTy } serverActions.forEach(actionKind => actionHandlerRegistry.register(actionKind, modelSource)); } - diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index 1b7fcac..e19dea4 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -24,7 +24,6 @@ import { Action, MouseListener, SModelElement } from 'sprotty'; * of mouse up events. */ export class DragAwareMouseListener extends MouseListener { - private _isMouseDown = false; private _isMouseDrag = false; @@ -65,5 +64,4 @@ export class DragAwareMouseListener extends MouseListener { get isMouseDown(): boolean { return this._isMouseDown; } - } diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context.ts index f3a40e9..9b818fc 100644 --- a/packages/client/src/base/editor-context.ts +++ b/packages/client/src/base/editor-context.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -51,13 +51,12 @@ export interface EditModeListener { @injectable() export class EditorContextService implements IActionHandler { - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; @inject(TYPES.ModelSourceProvider) protected modelSource: () => Promise; protected _editMode: string; - constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) { } + constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) {} register(editModeListener: EditModeListener): void { distinctAdd(this.editModeListeners, editModeListener); @@ -121,4 +120,3 @@ export class EditorContextService implements IActionHandler { } export type EditorContextServiceProvider = () => Promise; - diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts index a1002f2..c7c71e1 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus-tracker.ts @@ -20,7 +20,6 @@ import { isFocusStateChangedAction } from './actions/focus-change-action'; @injectable() export class FocusTracker implements IActionHandler { - protected inActiveCssClass = 'inactive'; protected _hasFocus = true; @@ -46,5 +45,4 @@ export class FocusTracker implements IActionHandler { } } } - } diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index 584c9a4..676c540 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -35,7 +35,7 @@ import { FeedbackCommand } from '../../features/tool-feedback/model'; import { GLSP_TYPES } from '../types'; /* ActionHandler that transforms a SetModelAction into an (feedback-aware) UpdateModelAction. This can be done because in sprotty -* UpdateModel behaves the same as SetModel if no model is present yet.*/ + * UpdateModel behaves the same as SetModel if no model is present yet.*/ @injectable() export class SetModelActionHandler implements IActionHandler { handle(action: Action): Action | void { @@ -46,8 +46,7 @@ export class SetModelActionHandler implements IActionHandler { } export function isSetModelAction(action: Action): action is SetModelAction { - return action !== undefined && (action.kind === SetModelCommand.KIND) - && (action as SetModelAction).newRoot !== undefined; + return action !== undefined && action.kind === SetModelCommand.KIND && (action as SetModelAction).newRoot !== undefined; } export interface SModelRootListener { @@ -73,7 +72,7 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { @postConstruct() protected initialize(): void { - this.actionHandlerRegistryProvider().then(registry => this.actionHandlerRegistry = registry); + this.actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } protected performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts index 822f5ee..a50da5b 100644 --- a/packages/client/src/base/operations/operation.ts +++ b/packages/client/src/base/operations/operation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,7 +21,7 @@ import { Args } from '../args'; * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the server. * After a successful modification, the server sends the updated model back to the client using the `UpdateModelAction`. */ -export interface Operation extends Action { } +export interface Operation extends Action {} export interface CreateOperation extends Operation { elementTypeId: string; @@ -35,11 +35,13 @@ export function isCreateOperation(object?: any): object is CreateOperation { export class CreateNodeOperation implements CreateOperation { static readonly KIND = 'createNode'; - constructor(public readonly elementTypeId: string, + constructor( + public readonly elementTypeId: string, public location?: Point, public containerId?: string, public args?: Args, - public readonly kind: string = CreateNodeOperation.KIND) { } + public readonly kind: string = CreateNodeOperation.KIND + ) {} } export function isCreateNodeOperation(object?: any): object is CreateNodeOperation { @@ -49,11 +51,13 @@ export function isCreateNodeOperation(object?: any): object is CreateNodeOperati export class CreateEdgeOperation implements CreateOperation { static readonly KIND = 'createEdge'; - constructor(public readonly elementTypeId: string, + constructor( + public readonly elementTypeId: string, public sourceElementId: string, public targetElementId: string, public args?: Args, - public readonly kind: string = CreateEdgeOperation.KIND) { } + public readonly kind: string = CreateEdgeOperation.KIND + ) {} } export function isCreateConnectionOperation(object?: any): object is CreateEdgeOperation { @@ -62,7 +66,7 @@ export function isCreateConnectionOperation(object?: any): object is CreateEdgeO export class DeleteElementOperation implements Operation { static readonly KIND = 'deleteElement'; - constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) { } + constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) {} } export function isDeleteElementOperation(object?: any): object is DeleteElementOperation { @@ -71,7 +75,7 @@ export function isDeleteElementOperation(object?: any): object is DeleteElementO export class ChangeBoundsOperation implements Operation { static readonly KIND = 'changeBounds'; - constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) { } + constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) {} } export function isChangeBoundsOperation(object?: any): object is ChangeBoundsOperation { @@ -80,46 +84,65 @@ export function isChangeBoundsOperation(object?: any): object is ChangeBoundsOpe export class ChangeContainerOperation implements Operation { static readonly KIND = 'changeContainer'; - constructor(public readonly elementId: string, + constructor( + public readonly elementId: string, public readonly targetContainerId: string, public readonly location?: Point, - public readonly kind: string = ChangeContainerOperation.KIND) { } + public readonly kind: string = ChangeContainerOperation.KIND + ) {} } export function isChangeContainerOperation(object?: any): object is ChangeContainerOperation { - return isAction(object) && object.kind === ChangeContainerOperation.KIND - && 'elementId' in object && typeof object['elementId'] === 'string' - && 'targetContainerId' in object && typeof object['targetContainerId'] === 'string'; + return ( + isAction(object) && + object.kind === ChangeContainerOperation.KIND && + 'elementId' in object && + typeof object['elementId'] === 'string' && + 'targetContainerId' in object && + typeof object['targetContainerId'] === 'string' + ); } export class ReconnectEdgeOperation implements Operation { static readonly KIND = 'reconnectEdge'; - constructor(public readonly edgeElementId: string, + constructor( + public readonly edgeElementId: string, public readonly sourceElementId: string, public readonly targetElementId: string, - public readonly kind: string = ReconnectEdgeOperation.KIND) { } + public readonly kind: string = ReconnectEdgeOperation.KIND + ) {} } export function isReconnectEdgeOperation(object?: any): object is ReconnectEdgeOperation { - return isAction(object) && object.kind === ReconnectEdgeOperation.KIND - && 'edgeElementId' in object && typeof object['edgeElementId'] === 'string' - && 'sourceElementId' in object && typeof object['sourceElementId'] === 'string' - && 'targetElementId' in object && typeof object['targetElementId'] === 'string'; + return ( + isAction(object) && + object.kind === ReconnectEdgeOperation.KIND && + 'edgeElementId' in object && + typeof object['edgeElementId'] === 'string' && + 'sourceElementId' in object && + typeof object['sourceElementId'] === 'string' && + 'targetElementId' in object && + typeof object['targetElementId'] === 'string' + ); } export class ChangeRoutingPointsOperation implements Operation { static readonly KIND = 'changeRoutingPoints'; - constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) { } + constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) {} } export function isChangeRoutingsPointsOperation(object?: any): object is ChangeRoutingPointsOperation { - return isAction(object) && object.kind === ChangeRoutingPointsOperation.KIND - && 'newRoutingPoints' in object && typeof object['newRoutingPoints'] === 'string'; + return ( + isAction(object) && + object.kind === ChangeRoutingPointsOperation.KIND && + 'newRoutingPoints' in object && + typeof object['newRoutingPoints'] === 'string' + ); } export class CompoundOperation implements Operation { static readonly KIND = 'compound'; - constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) { } + constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) {} } export function isCompoundOperation(object?: any): object is CompoundOperation { @@ -142,7 +165,7 @@ export interface ElementAndRoutingPoints { } export abstract class TriggerElementCreationAction implements Action { - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = 'unknown') { } + constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = 'unknown') {} } export class TriggerNodeCreationAction extends TriggerElementCreationAction { @@ -156,7 +179,11 @@ export class TriggerNodeCreationAction extends TriggerElementCreationAction { export class TriggerEdgeCreationAction extends TriggerElementCreationAction { static readonly KIND = 'triggerEdgeCreation'; - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = TriggerEdgeCreationAction.KIND) { + constructor( + public readonly elementTypeId: string, + readonly args?: Args, + public readonly kind: string = TriggerEdgeCreationAction.KIND + ) { super(elementTypeId, args, kind); } } @@ -173,6 +200,6 @@ export function isTriggerEdgeCreationAction(object?: any): object is TriggerEdge return isTriggerElementTypeCreationAction(object) && object.kind === TriggerEdgeCreationAction.KIND; } -export class LayoutOperation extends LayoutAction implements Operation { } +export class LayoutOperation extends LayoutAction implements Operation {} -export class ApplyLabelEditOperation extends ApplyLabelEditAction implements Operation { } +export class ApplyLabelEditOperation extends ApplyLabelEditAction implements Operation {} diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index e51b250..f196b3c 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,13 +29,13 @@ import { export const resizeFeature = Symbol('resizeFeature'); -export interface Resizable extends BoundsAware, Selectable { -} +export interface Resizable extends BoundsAware, Selectable { } export function isResizable(element: SModelElement): element is SParentElement & Resizable { return isBoundsAware(element) && isSelectable(element) && element instanceof SParentElement && element.hasFeature(resizeFeature); } +// eslint-disable-next-line no-shadow export enum ResizeHandleLocation { TopLeft = 'top-left', TopRight = 'top-right', @@ -50,9 +50,11 @@ export function isBoundsAwareMoveable(element: SModelElement): element is SModel export class SResizeHandle extends SChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; - constructor(public readonly location?: ResizeHandleLocation, + constructor( + public readonly location?: ResizeHandleLocation, public readonly type: string = SResizeHandle.TYPE, - public readonly hoverFeedback: boolean = false) { + public readonly hoverFeedback: boolean = false + ) { super(); } diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index c461623..5cdf3b3 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,16 +25,21 @@ export interface IMovementRestrictor { cssClasses?: string[]; } -export function createMovementRestrictionFeedback(element: SModelElement, movementRestrictor: IMovementRestrictor): ModifyCSSFeedbackAction { +export function createMovementRestrictionFeedback( + element: SModelElement, + movementRestrictor: IMovementRestrictor +): ModifyCSSFeedbackAction { const elements: SModelElement[] = [element]; if (element instanceof SParentElement) { element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } return new ModifyCSSFeedbackAction(elements, movementRestrictor.cssClasses); - } -export function removeMovementRestrictionFeedback(element: SModelElement, movementRestrictor: IMovementRestrictor): ModifyCSSFeedbackAction { +export function removeMovementRestrictionFeedback( + element: SModelElement, + movementRestrictor: IMovementRestrictor +): ModifyCSSFeedbackAction { const elements: SModelElement[] = [element]; if (element instanceof SParentElement) { element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); @@ -61,8 +66,12 @@ export class NoOverlapMovmentRestrictor implements IMovementRestrictor { }; ghostElement.type = 'Ghost'; ghostElement.id = element.id; - return !Array.from(element.root.index.all().filter(e => e.id !== ghostElement.id && e !== ghostElement.root && (e instanceof SNode)) - .map(e => e as SModelElement & BoundsAware)).some(e => areOverlapping(e, ghostElement)); + return !Array.from( + element.root.index + .all() + .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof SNode) + .map(e => e as SModelElement & BoundsAware) + ).some(e => areOverlapping(e, ghostElement)); } } @@ -85,5 +94,4 @@ export function areOverlapping(element1: SModelElement & BoundsAware, element2: } return true; - } diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 53484ac..5a9911c 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -32,10 +32,13 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction @inject(EditorContextService) protected editorContext: EditorContextService; getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { - const requestAction = new RequestContextActions(ServerCommandPalette.CONTEXT_ID, this.editorContext.get({ - [ServerCommandPalette.TEXT]: text, - [ServerCommandPalette.INDEX]: index ? index : 0 - })); + const requestAction = new RequestContextActions( + ServerCommandPalette.CONTEXT_ID, + this.editorContext.get({ + [ServerCommandPalette.TEXT]: text, + [ServerCommandPalette.INDEX]: index ? index : 0 + }) + ); return this.actionDispatcher.requestUntil(requestAction).then(response => this.getPaletteActionsFromResponse(response)); } diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts index 62d47d2..14a41ad 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,13 +20,16 @@ import { SelectionServiceAwareContextMenuMouseListener } from './selection-servi import { ServerContextMenuItemProvider } from './server-context-menu-provider'; const glspContextMenuModule = new ContainerModule(bind => { - bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => () => new Promise((resolve, reject) => { - if (ctx.container.isBound(TYPES.IContextMenuService)) { - resolve(ctx.container.get(TYPES.IContextMenuService)); - } else { - reject(); - } - })); + bind(TYPES.IContextMenuServiceProvider).toProvider( + ctx => () => + new Promise((resolve, reject) => { + if (ctx.container.isBound(TYPES.IContextMenuService)) { + resolve(ctx.container.get(TYPES.IContextMenuService)); + } else { + reject(); + } + }) + ); bind(TYPES.MouseListener).to(SelectionServiceAwareContextMenuMouseListener); bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); bind(TYPES.IContextMenuItemProvider).to(ServerContextMenuItemProvider); diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 4b7d5b2..bc9fe32 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -42,8 +42,9 @@ export class SelectionServiceAwareContextMenuMouseListener extends MouseListener selectableTarget.selected = true; this.selectionService.updateSelection(target.root, [selectableTarget.id], []); } - Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]) - .then(([menuService, menuItems]) => menuService.show(menuItems, mousePosition)); + Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]).then( + ([menuService, menuItems]) => menuService.show(menuItems, mousePosition) + ); } return []; } diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 8db45e7..a790136 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,12 +26,16 @@ export namespace ServerContextMenu { @injectable() export class ServerContextMenuItemProvider implements IContextMenuItemProvider { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; getItems(root: Readonly, lastMousePosition?: Point): Promise { - const selectedElementIds = Array.from(root.index.all().filter(isSelected).map(e => e.id)); + const selectedElementIds = Array.from( + root.index + .all() + .filter(isSelected) + .map(e => e.id) + ); const context = this.editorContext.getWithSelection(selectedElementIds); const requestAction = new RequestContextActions(ServerContextMenu.CONTEXT_ID, context); return this.actionDispatcher.requestUntil(requestAction).then(response => this.getContextActionsFromResponse(response)); diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/client/src/features/copy-paste/copy-paste-actions.ts index d504ca3..a44de87 100644 --- a/packages/client/src/features/copy-paste/copy-paste-actions.ts +++ b/packages/client/src/features/copy-paste/copy-paste-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,9 +21,7 @@ import { Operation } from '../../base/operations/operation'; export class CutOperation implements Operation { static readonly KIND = 'cut'; - constructor( - public readonly editorContext: EditorContext, - public readonly kind: string = CutOperation.KIND) { } + constructor(public readonly editorContext: EditorContext, public readonly kind: string = CutOperation.KIND) {} } export class PasteOperation implements Operation { @@ -32,7 +30,8 @@ export class PasteOperation implements Operation { constructor( public readonly clipboardData: ClipboardData, public readonly editorContext: EditorContext, - public readonly kind: string = PasteOperation.KIND) { } + public readonly kind: string = PasteOperation.KIND + ) {} } export class RequestClipboardDataAction implements RequestAction { @@ -41,14 +40,17 @@ export class RequestClipboardDataAction implements RequestAction { return new RequestClipboardDataAction(editorContext); } } -export interface ClipboardData { [format: string]: string } +export interface ClipboardData { + [format: string]: string; +} export class SetClipboardDataAction implements ResponseAction { static readonly KIND = 'setClipboardData'; @@ -56,5 +58,6 @@ export class SetClipboardDataAction implements ResponseAction { constructor( public readonly clipboardData: ClipboardData, public readonly responseId: string = '', - public readonly kind: string = SetClipboardDataAction.KIND) { } + public readonly kind: string = SetClipboardDataAction.KIND + ) {} } diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 1582237..44657db 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,17 +30,17 @@ import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/ export class InvokeCopyAction implements Action { static readonly KIND = 'invoke-copy'; - constructor(public readonly kind = InvokeCopyAction.KIND) { } + constructor(public readonly kind = InvokeCopyAction.KIND) {} } export class InvokePasteAction implements Action { static readonly KIND = 'invoke-paste'; - constructor(public readonly kind = InvokePasteAction.KIND) { } + constructor(public readonly kind = InvokePasteAction.KIND) {} } export class InvokeCutAction implements Action { static readonly KIND = 'invoke-cut'; - constructor(public readonly kind = InvokeCutAction.KIND) { } + constructor(public readonly kind = InvokeCutAction.KIND) {} } @injectable() @@ -96,22 +96,31 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide protected createPasteMenuItem(): MenuItem { return { - id: 'paste', label: 'Paste', group: 'copy-paste', - actions: [new InvokePasteAction()], isEnabled: () => true + id: 'paste', + label: 'Paste', + group: 'copy-paste', + actions: [new InvokePasteAction()], + isEnabled: () => true }; } protected createCutMenuItem(hasSelectedElements: boolean): MenuItem { return { - id: 'cut', label: 'Cut', group: 'copy-paste', - actions: [new InvokeCutAction()], isEnabled: () => hasSelectedElements + id: 'cut', + label: 'Cut', + group: 'copy-paste', + actions: [new InvokeCutAction()], + isEnabled: () => hasSelectedElements }; } protected createCopyMenuItem(hasSelectedElements: boolean): MenuItem { return { - id: 'copy', label: 'Copy', group: 'copy-paste', - actions: [new InvokeCopyAction()], isEnabled: () => hasSelectedElements + id: 'copy', + label: 'Copy', + group: 'copy-paste', + actions: [new InvokeCopyAction()], + isEnabled: () => hasSelectedElements }; } } @@ -125,7 +134,7 @@ export function supportsCut(): boolean { } export function supportsPaste(): boolean { - const isChrome = (userAgent().indexOf('Chrome') >= 0); + const isChrome = userAgent().indexOf('Chrome') >= 0; return isNative() || (!isChrome && document.queryCommandSupported('paste')); } diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index bc45555..86c2a1e 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -95,7 +95,6 @@ const CLIPBOARD_DATA_FORMAT = 'text/plain'; @injectable() export class ServerCopyPasteHandler implements ICopyPasteHandler { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; @inject(GLSP_TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; @@ -145,8 +144,10 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { } private isDiagramActive(): boolean | null { - return document.activeElement instanceof SVGElement - && document.activeElement.parentElement - && document.activeElement.parentElement.id === this.viewerOptions.baseDiv; + return ( + document.activeElement instanceof SVGElement && + document.activeElement.parentElement && + document.activeElement.parentElement.id === this.viewerOptions.baseDiv + ); } } diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index b15a187..8ee0c23 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,20 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { - Decoration, - DecorationPlacer, - isSizeable, - ORIGIN_POINT, - Point, - SChildElement, - SModelElement, - SRoutableElement -} from 'sprotty'; +import { Decoration, DecorationPlacer, isSizeable, ORIGIN_POINT, Point, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { - protected static readonly DECORATION_OFFSET: Point = { x: 12, y: 10 }; protected getPosition(element: SModelElement & Decoration): Point { @@ -42,5 +32,4 @@ export class GlspDecorationPlacer extends DecorationPlacer { } return ORIGIN_POINT; } - } diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index 2c6eff0..cd92f48 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,11 +26,7 @@ import { } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { - isSetEditValidationResultAction, - RequestEditValidationAction, - ValidationStatus -} from '../../base/actions/edit-validation-actions'; +import { isSetEditValidationResultAction, RequestEditValidationAction, ValidationStatus } from '../../base/actions/edit-validation-actions'; export namespace LabelEditValidation { export const CONTEXT_ID = 'label-edit'; @@ -55,7 +51,6 @@ export class ValidateLabelEditAction extends RequestEditValidationAction { @injectable() export class ServerEditLabelValidator implements IEditLabelValidator { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; validate(value: string, label: EditableLabel & SModelElement): Promise { @@ -69,12 +64,10 @@ export class ServerEditLabelValidator implements IEditLabelValidator { } return { severity: 'ok' as Severity }; } - } @injectable() export class BalloonLabelValidationDecorator implements IEditLabelValidationDecorator { - decorate(input: HTMLInputElement, result: EditLabelValidationResult): void { const containerElement = input.parentElement; if (!containerElement) { @@ -86,9 +79,15 @@ export class BalloonLabelValidationDecorator implements IEditLabelValidationDeco containerElement.setAttribute('data-balloon-visible', 'true'); } switch (result.severity) { - case 'ok': containerElement.classList.add('validation-ok'); break; - case 'warning': containerElement.classList.add('validation-warning'); break; - case 'error': containerElement.classList.add('validation-error'); break; + case 'ok': + containerElement.classList.add('validation-ok'); + break; + case 'warning': + containerElement.classList.add('validation-warning'); + break; + case 'error': + containerElement.classList.add('validation-error'); + break; } } diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index d346e8c..8f713ef 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,7 +27,7 @@ export function isContainable(element: SModelElement): element is SModelElement export const reparentFeature = Symbol('reparentFeature'); -export interface Reparentable extends SModelExtension { } +export interface Reparentable extends SModelExtension {} export function isReparentable(element: SModelElement): element is SModelElement & Reparentable { return element.hasFeature(reparentFeature); diff --git a/packages/client/src/features/hints/request-type-hints-action.ts b/packages/client/src/features/hints/request-type-hints-action.ts index b6ea9ea..ee2203b 100644 --- a/packages/client/src/features/hints/request-type-hints-action.ts +++ b/packages/client/src/features/hints/request-type-hints-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,7 @@ import { EdgeTypeHint, ShapeTypeHint } from './type-hints'; export class RequestTypeHintsAction implements Action { static readonly KIND = 'requestTypeHints'; - constructor(public readonly diagramType?: string, public readonly kind: string = RequestTypeHintsAction.KIND) { } + constructor(public readonly diagramType?: string, public readonly kind: string = RequestTypeHintsAction.KIND) {} } export class SetTypeHintsAction implements Action { @@ -27,10 +27,15 @@ export class SetTypeHintsAction implements Action { constructor( public readonly shapeHints: ShapeTypeHint[], public readonly edgeHints: EdgeTypeHint[], - public readonly kind: string = SetTypeHintsAction.KIND) { } + public readonly kind: string = SetTypeHintsAction.KIND + ) {} } export function isSetTypeHintsAction(action: Action): action is SetTypeHintsAction { - return action !== undefined && (action.kind === SetTypeHintsAction.KIND) - && (action as SetTypeHintsAction).shapeHints !== undefined && (action as SetTypeHintsAction).edgeHints !== undefined; + return ( + action !== undefined && + action.kind === SetTypeHintsAction.KIND && + (action as SetTypeHintsAction).shapeHints !== undefined && + (action as SetTypeHintsAction).edgeHints !== undefined + ); } diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 385b0c0..5654f64 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -50,8 +50,8 @@ export abstract class TypeHint { readonly elementTypeId: string; /** - * Specifies whether the element can be relocated. - */ + * Specifies whether the element can be relocated. + */ readonly repositionable: boolean; /** @@ -89,14 +89,13 @@ export class EdgeTypeHint extends TypeHint { readonly sourceElementTypeIds: string[]; /** - * Allowed targe element types for this edge type - */ + * Allowed targe element types for this edge type + */ readonly targetElementTypeIds: string[]; isAllowedSource(input: SModelElement | SModelElementSchema | string): boolean { const type = getElementTypeId(input); return this.sourceElementTypeIds.includes(type); - } isAllowedTarget(input: SModelElement | SModelElementSchema | string): boolean { const type = getElementTypeId(input); @@ -111,7 +110,6 @@ export class ApplyTypeHintsAction implements Action { @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { - public static KIND = 'applyTypeHints'; public readonly priority = 10; @@ -122,15 +120,13 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { } execute(context: CommandExecutionContext): CommandReturn { - context.root.index.all() - .forEach(element => { - if (element instanceof SShapeElement || element instanceof SModelRoot) { - this.applyShapeTypeHint(element); - - } else if (element instanceof SEdge) { - return this.applyEdgeTypeHint(element); - } - }); + context.root.index.all().forEach(element => { + if (element instanceof SShapeElement || element instanceof SModelRoot) { + this.applyShapeTypeHint(element); + } else if (element instanceof SEdge) { + return this.applyEdgeTypeHint(element); + } + }); return context.root; } @@ -167,18 +163,14 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { function createConnectable(validSourceEdges: string[], validTargetEdges: string[]): Connectable { return { canConnect: (routable, role) => - role === 'source' ? - validSourceEdges.includes(routable.type) : - validTargetEdges.includes(routable.type) + role === 'source' ? validSourceEdges.includes(routable.type) : validTargetEdges.includes(routable.type) }; } function createContainable(hint: ShapeTypeHint): Containable { return { isContainableElement: element => - hint.containableElementTypeIds ? - hint.containableElementTypeIds.includes(getElementTypeId(element)) : - false + hint.containableElementTypeIds ? hint.containableElementTypeIds.includes(getElementTypeId(element)) : false }; } @@ -202,12 +194,11 @@ export interface ITypeHintProvider { @injectable() export class TypeHintProvider implements IActionHandler, ITypeHintProvider { - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; - protected shapeHints: Map = new Map; - protected edgeHints: Map = new Map; + protected shapeHints: Map = new Map(); + protected edgeHints: Map = new Map(); handle(action: Action): ICommand | Action | void { if (isSetTypeHintsAction(action)) { @@ -222,13 +213,19 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider { if (role === 'source') { return Array.from( Array.from(this.edgeHints.values()) - .filter(hint => hint.sourceElementTypeIds.some(sourceElementTypeId => hasCompatibleType(elementTypeId, sourceElementTypeId))) - .map(hint => hint.elementTypeId)); + .filter(hint => + hint.sourceElementTypeIds.some(sourceElementTypeId => hasCompatibleType(elementTypeId, sourceElementTypeId)) + ) + .map(hint => hint.elementTypeId) + ); } else { return Array.from( Array.from(this.edgeHints.values()) - .filter(hint => hint.targetElementTypeIds.some(targetElementTypeId => hasCompatibleType(elementTypeId, targetElementTypeId))) - .map(hint => hint.elementTypeId)); + .filter(hint => + hint.targetElementTypeIds.some(targetElementTypeId => hasCompatibleType(elementTypeId, targetElementTypeId)) + ) + .map(hint => hint.elementTypeId) + ); } } @@ -254,4 +251,3 @@ function getTypeHint(input: SModelElement | SModelElement | } return hint; } - diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index d215aeb..532a2fa 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,7 +33,6 @@ import { GIssueMarker } from '../validation/issue-marker'; @injectable() export class GlspHoverMouseListener extends HoverMouseListener { - protected startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { this.stopMouseOverTimer(); return new Promise(resolve => { diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-commands.spec.ts index aca3557..a77a800 100644 --- a/packages/client/src/features/layout/layout-commands.spec.ts +++ b/packages/client/src/features/layout/layout-commands.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -58,7 +58,7 @@ import { } from './layout-commands'; class MockActionDispatcher implements IActionDispatcher { - constructor(public dispatchedActions: Action[] = []) { } + constructor(public dispatchedActions: Action[] = []) {} dispatch(action: Action): Promise { this.dispatchedActions.push(action); return Promise.resolve(); @@ -84,15 +84,18 @@ const selectionService = container.get(GLSP_TYPES.SelectionSer const actionDispatcher = new MockActionDispatcher(); const node1 = { - id: 'node1', type: 'node:circle', + id: 'node1', + type: 'node:circle', selected: true }; const node2 = { - id: 'node2', type: 'node:circle', + id: 'node2', + type: 'node:circle', selected: true }; const node3 = { - id: 'node3', type: 'node:circle', + id: 'node3', + type: 'node:circle', selected: true }; const model = createModel(); @@ -123,7 +126,6 @@ const context: CommandExecutionContext = { const defaultSize = { height: 10, width: 10 }; describe('AlignElementsCommand', () => { - it('should align all elements left', () => { actionDispatcher.dispatchedActions = []; const newModel = initModel([ @@ -135,11 +137,13 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); it('should align all elements right', () => { @@ -153,11 +157,13 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); it('should align all elements center', () => { @@ -171,11 +177,13 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); it('should align all elements top', () => { @@ -189,11 +197,13 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 111, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 111, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); it('should align all elements bottom', () => { @@ -207,11 +217,13 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 111, y: 333, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 333, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 333, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); it('should align all elements middle', () => { @@ -225,17 +237,17 @@ describe('AlignElementsCommand', () => { const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); - assertAllBounds(new Map([ - ['node1', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], - ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], - ['node3', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }] - ])); + assertAllBounds( + new Map([ + ['node1', { x: 111, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node2', { x: 222, y: 222, width: defaultSize.width, height: defaultSize.height }], + ['node3', { x: 333, y: 222, width: defaultSize.width, height: defaultSize.height }] + ]) + ); }); - }); describe('ResizeElementsCommand', () => { - it('should make same width as last', () => { actionDispatcher.dispatchedActions = []; const newModel = initModel([ @@ -248,11 +260,13 @@ describe('ResizeElementsCommand', () => { command.execute(newContext(newModel)); // resize is keeping the center, so the X moves by diff / 2 - assertAllBoundsInChangeBounds(new Map([ - ['node1', { x: 90, y: 100, height: 10, width: 30 }], - ['node2', { x: 95, y: 200, height: 20, width: 30 }], - ['node3', { x: 100, y: 300, height: 30, width: 30 }] - ])); + assertAllBoundsInChangeBounds( + new Map([ + ['node1', { x: 90, y: 100, height: 10, width: 30 }], + ['node2', { x: 95, y: 200, height: 20, width: 30 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] + ]) + ); }); it('should make same height as last', () => { @@ -267,11 +281,13 @@ describe('ResizeElementsCommand', () => { command.execute(newContext(newModel)); // resize is keeping the center, so the Y moves by diff / 2 - assertAllBoundsInChangeBounds(new Map([ - ['node1', { x: 100, y: 90, height: 30, width: 10 }], - ['node2', { x: 100, y: 195, height: 30, width: 20 }], - ['node3', { x: 100, y: 300, height: 30, width: 30 }] - ])); + assertAllBoundsInChangeBounds( + new Map([ + ['node1', { x: 100, y: 90, height: 30, width: 10 }], + ['node2', { x: 100, y: 195, height: 30, width: 20 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] + ]) + ); }); it('should make same width and height as last', () => { @@ -286,13 +302,14 @@ describe('ResizeElementsCommand', () => { command.execute(newContext(newModel)); // resize is keeping the center, so the Y moves by diff / 2 - assertAllBoundsInChangeBounds(new Map([ - ['node1', { x: 90, y: 90, height: 30, width: 30 }], - ['node2', { x: 95, y: 195, height: 30, width: 30 }], - ['node3', { x: 100, y: 300, height: 30, width: 30 }] - ])); + assertAllBoundsInChangeBounds( + new Map([ + ['node1', { x: 90, y: 90, height: 30, width: 30 }], + ['node2', { x: 95, y: 195, height: 30, width: 30 }], + ['node3', { x: 100, y: 300, height: 30, width: 30 }] + ]) + ); }); - }); function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { @@ -350,11 +367,17 @@ function getElementAndBoundsById(id: string, elementAndBounds: ElementAndBounds[ } function dispatchedElementMoves(): ElementMove[] { - return actionDispatcher.dispatchedActions.filter(isMoveAction).map(a => a.moves).reduce((acc, val) => acc.concat(val), []); + return actionDispatcher.dispatchedActions + .filter(isMoveAction) + .map(a => a.moves) + .reduce((acc, val) => acc.concat(val), []); } function dispatchedChangeBounds(): ElementAndBounds[] { - return actionDispatcher.dispatchedActions.filter(isChangeBounds).map(a => a.newBounds).reduce((acc, val) => acc.concat(val), []); + return actionDispatcher.dispatchedActions + .filter(isChangeBounds) + .map(a => a.newBounds) + .reduce((acc, val) => acc.concat(val), []); } function isMoveAction(action: Action): action is MoveAction { diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index ba83093..6fdfdb5 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,24 +27,18 @@ import { SModelElement, TYPES } from 'sprotty'; - import { ChangeBoundsOperation } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; -import { - toValidElementAndBounds, - toValidElementMove, - WriteableElementAndBounds, - WriteableElementMove -} from '../../utils/layout-utils'; +import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; import { SelectionService } from '../select/selection-service'; +// eslint-disable-next-line no-shadow export enum ResizeDimension { Width, Height, - // eslint-disable-next-line @typescript-eslint/camelcase Width_And_Height } @@ -84,10 +78,11 @@ export class ResizeElementsAction implements Action { * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. */ public readonly reductionFunction: (...values: number[]) => number, - public readonly kind: string = ResizeElementsCommand.KIND) { - } + public readonly kind: string = ResizeElementsCommand.KIND + ) {} } +// eslint-disable-next-line no-shadow export enum Alignment { Left, Center, @@ -125,16 +120,18 @@ export class AlignElementsAction implements Action { * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. */ public readonly selectionFunction: (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[] = Select.all, - public readonly kind = AlignElementsCommand.KIND) { - } + public readonly kind = AlignElementsCommand.KIND + ) {} } @injectable() abstract class LayoutElementsCommand extends Command { - constructor(@inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction, + constructor( + @inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction, @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + ) { super(); } @@ -170,10 +167,12 @@ abstract class LayoutElementsCommand extends Command { export class ResizeElementsCommand extends LayoutElementsCommand { static readonly KIND = 'layout:resize'; - constructor(@inject(TYPES.Action) protected action: ResizeElementsAction, + constructor( + @inject(TYPES.Action) protected action: ResizeElementsAction, @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + ) { super(action, actionDispatcher, selectionService, movementRestrictor); } @@ -233,7 +232,10 @@ export class ResizeElementsCommand extends LayoutElementsCommand { }); } - dispatchResizeActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void): void { + dispatchResizeActions( + elements: BoundsAwareModelElement[], + change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void + ): void { const elementAndBounds: ElementAndBounds[] = []; // client- and server-side resize elements.forEach(element => { const elementChange = this.createElementAndBounds(element, change); @@ -245,9 +247,10 @@ export class ResizeElementsCommand extends LayoutElementsCommand { this.dispatchActions([new SetBoundsAction(elementAndBounds), new ChangeBoundsOperation(elementAndBounds)]); } - createElementAndBounds(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, - bounds: WriteableElementAndBounds) => void): WriteableElementAndBounds | undefined { - + createElementAndBounds( + element: BoundsAwareModelElement, + change: (_element: BoundsAwareModelElement, _bounds: WriteableElementAndBounds) => void + ): WriteableElementAndBounds | undefined { const bounds: WriteableElementAndBounds = { elementId: element.id, newPosition: { @@ -278,10 +281,12 @@ export class ResizeElementsCommand extends LayoutElementsCommand { export class AlignElementsCommand extends LayoutElementsCommand { static readonly KIND = 'layout:align'; - constructor(@inject(TYPES.Action) protected action: AlignElementsAction, + constructor( + @inject(TYPES.Action) protected action: AlignElementsAction, @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor) { + @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + ) { super(action, actionDispatcher, selectionService, movementRestrictor); } @@ -319,7 +324,7 @@ export class AlignElementsCommand extends LayoutElementsCommand { alignLeft(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); - this.dispatchAlignActions(elements, (_, move) => move.toPosition.x = minX); + this.dispatchAlignActions(elements, (_, move) => (move.toPosition.x = minX)); } alignCenter(elements: BoundsAwareModelElement[]): void { @@ -328,19 +333,19 @@ export class AlignElementsCommand extends LayoutElementsCommand { const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); const diffX = maxX - minX; const centerX = minX + 0.5 * diffX; - this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = centerX - 0.5 * element.bounds.width); + this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = centerX - 0.5 * element.bounds.width)); } alignRight(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); - this.dispatchAlignActions(elements, (element, move) => move.toPosition.x = maxX - element.bounds.width); + this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = maxX - element.bounds.width)); } alignTop(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); - this.dispatchAlignActions(elements, (_, move) => move.toPosition.y = minY); + this.dispatchAlignActions(elements, (_, move) => (move.toPosition.y = minY)); } alignMiddle(elements: BoundsAwareModelElement[]): void { @@ -349,16 +354,19 @@ export class AlignElementsCommand extends LayoutElementsCommand { const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); const diffY = maxY - minY; const middleY = minY + 0.5 * diffY; - this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = middleY - 0.5 * element.bounds.height); + this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = middleY - 0.5 * element.bounds.height)); } alignBottom(elements: BoundsAwareModelElement[]): void { const calculationElements = this.action.selectionFunction(elements); const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); - this.dispatchAlignActions(elements, (element, move) => move.toPosition.y = maxY - element.bounds.height); + this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = maxY - element.bounds.height)); } - dispatchAlignActions(elements: BoundsAwareModelElement[], change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void): void { + dispatchAlignActions( + elements: BoundsAwareModelElement[], + change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void + ): void { const moves: ElementMove[] = []; // client-side move const elementAndBounds: ElementAndBounds[] = []; // server-side move elements.forEach(element => { @@ -373,7 +381,10 @@ export class AlignElementsCommand extends LayoutElementsCommand { this.dispatchActions([new MoveAction(moves), new ChangeBoundsOperation(elementAndBounds)]); } - createElementMove(element: BoundsAwareModelElement, change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void): WriteableElementMove | undefined { + createElementMove( + element: BoundsAwareModelElement, + change: (_element: BoundsAwareModelElement, _move: WriteableElementMove) => void + ): WriteableElementMove | undefined { const move: WriteableElementMove = { elementId: element.id, fromPosition: { diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts index ef1b752..e87b5fe 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -36,14 +36,14 @@ export abstract class ExternalModelSourceChangedHandler { @injectable() export class ModelSourceChangedActionHandler implements IActionHandler { - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; @inject(TYPES.ViewerOptions) protected options: ViewerOptions; - @inject(ExternalModelSourceChangedHandler) @optional() + @inject(ExternalModelSourceChangedHandler) + @optional() protected externalModelSourceChangedHandler?: ExternalModelSourceChangedHandler; handle(action: Action): void { diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts index 9804dd8..03fda9e 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { Action } from 'sprotty'; export class ModelSourceChangedAction implements Action { static KIND = 'modelSourceChanged'; readonly kind = ModelSourceChangedAction.KIND; - constructor(public readonly modelSourceName: string) { } + constructor(public readonly modelSourceName: string) {} } export function isModelSourceChangedAction(action: Action): action is ModelSourceChangedAction { diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index 85722ff..c1e75f5 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,6 @@ import { injectable, multiInject, optional } from 'inversify'; import { On, VNode, VNodeData } from 'snabbdom'; import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; - import { getRank } from '../rank/model'; export interface IMouseTool { @@ -57,9 +56,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { overwriteOn(vnode, 'contextmenu', this.contextMenu.bind(this, element), element); overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this, element), element); } - vnode = this.mouseListeners.reduce( - (n: VNode, listener: MouseListener) => listener.decorate(n, element), - vnode); + vnode = this.mouseListeners.reduce((n: VNode, listener: MouseListener) => listener.decorate(n, element), vnode); return vnode; } @@ -76,13 +73,23 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { this.notifyListenersByRank(element, methodName, model, event); } - async notifyListenersByRank(element: SModelElement, methodName: K, model: SModelRoot, event: MouseEvent): Promise { + async notifyListenersByRank( + element: SModelElement, + methodName: K, + model: SModelRoot, + event: MouseEvent + ): Promise { for (const rank of this.rankedMouseListeners) { await this.dispatchActions(rank[1], methodName, element, event); } } - async dispatchActions(mouseListeners: MouseListener[], methodName: K, element: SModelElement, event: MouseEvent): Promise { + async dispatchActions( + mouseListeners: MouseListener[], + methodName: K, + element: SModelElement, + event: MouseEvent + ): Promise { const actions = mouseListeners .map(listener => listener[methodName].apply(listener, [element, event])) .reduce((a, b) => a.concat(b)); @@ -117,7 +124,7 @@ function groupBy(array: Array, keyFunction: (x: T) => K): Map { return new Map([...unsortedMap.entries()].sort()); } -function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, event: Event) => void, element: SModelElement): void { +function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, _event: Event) => void, element: SModelElement): void { const val = getOn(vnode); // ignore any previous val[event] registrations (val as any)[event] = [listener, element]; diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 190a2bc..1b25888 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -50,55 +50,58 @@ import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTarget export class NavigateAction implements Action { static readonly KIND = 'navigate'; readonly kind = NavigateAction.KIND; - constructor(readonly targetTypeId: string, readonly args?: Args) { } + constructor(readonly targetTypeId: string, readonly args?: Args) {} } export function isNavigateAction(action: Action): action is NavigateAction { - return action !== undefined && (action.kind === NavigateAction.KIND) - && (action as NavigateAction).targetTypeId !== undefined; + return action !== undefined && action.kind === NavigateAction.KIND && (action as NavigateAction).targetTypeId !== undefined; } /** Action that is usually sent to the server to request navigation targets for a navigation type. */ export class RequestNavigationTargetsAction implements RequestAction { static readonly KIND = 'requestNavigationTargets'; kind = RequestNavigationTargetsAction.KIND; - constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, - readonly requestId: string = generateRequestId()) { } + constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, readonly requestId: string = generateRequestId()) {} } /** Action that is usually sent from the server to the client as a repsonse to a `RequestNavigationTargetsAction`. */ export class SetNavigationTargetsAction implements ResponseAction { static readonly KIND = 'setNavigationTargets'; kind = SetNavigationTargetsAction.KIND; - constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) { } + constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) {} } export function isSetNavigationTargetsAction(action: Action): action is SetNavigationTargetsAction { - return action !== undefined && (action.kind === SetNavigationTargetsAction.KIND) - && (action as SetNavigationTargetsAction).targets !== undefined; + return ( + action !== undefined && + action.kind === SetNavigationTargetsAction.KIND && + (action as SetNavigationTargetsAction).targets !== undefined + ); } /** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ export class NavigateToTargetAction implements Action { static readonly KIND = 'navigateToTarget'; readonly kind = NavigateToTargetAction.KIND; - constructor(readonly target: NavigationTarget) { } + constructor(readonly target: NavigationTarget) {} } export function isNavigateToTargetAction(action: Action): action is NavigateToTargetAction { - return action !== undefined && (action.kind === NavigateToTargetAction.KIND) - && (action as NavigateToTargetAction).target !== undefined; + return action !== undefined && action.kind === NavigateToTargetAction.KIND && (action as NavigateToTargetAction).target !== undefined; } export class NavigateToExternalTargetAction implements Action { static readonly KIND = 'navigateToExternalTarget'; readonly kind = NavigateToExternalTargetAction.KIND; - constructor(readonly target: NavigationTarget) { } + constructor(readonly target: NavigationTarget) {} } export function isNavigateToExternalTargetAction(action: Action): action is NavigateToExternalTargetAction { - return action !== undefined && (action.kind === NavigateToExternalTargetAction.KIND) - && (action as NavigateToExternalTargetAction).target !== undefined; + return ( + action !== undefined && + action.kind === NavigateToExternalTargetAction.KIND && + (action as NavigateToExternalTargetAction).target !== undefined + ); } /** Action to trigger the processing of additional navigation arguments. @@ -112,12 +115,15 @@ export function isNavigateToExternalTargetAction(action: Action): action is Navi export class ProcessNavigationArgumentsAction implements Action { static readonly KIND = 'processNavigationArguments'; readonly kind = ProcessNavigationArgumentsAction.KIND; - constructor(readonly args: Args) { } + constructor(readonly args: Args) {} } export function isProcessNavigationArgumentsAction(action: Action): action is ProcessNavigationArgumentsAction { - return action !== undefined && (action.kind === ProcessNavigationArgumentsAction.KIND) - && (action as ProcessNavigationArgumentsAction).args !== undefined; + return ( + action !== undefined && + action.kind === ProcessNavigationArgumentsAction.KIND && + (action as ProcessNavigationArgumentsAction).args !== undefined + ); } /** @@ -144,7 +150,6 @@ export function isProcessNavigationArgumentsAction(action: Action): action is Pr */ @injectable() export class NavigationActionHandler implements IActionHandler { - readonly notificationTimeout = 5000; @inject(TYPES.ILogger) protected logger: ILogger; @@ -172,8 +177,11 @@ export class NavigationActionHandler implements IActionHandler { const response = await this.dispatcher.request(new RequestNavigationTargetsAction(action.targetTypeId, context)); if (isSetNavigationTargetsAction(response) && response.targets && response.targets.length === 1) { if (response.targets.length > 1) { - this.logger.warn(this, 'Processing of multiple targets is not supported yet. ' + - 'Only the first is being processed.', response.targets); + this.logger.warn( + this, + 'Processing of multiple targets is not supported yet. ' + 'Only the first is being processed.', + response.targets + ); } return this.dispatcher.dispatch(new NavigateToTargetAction(response.targets[0])); } @@ -203,7 +211,9 @@ export class NavigationActionHandler implements IActionHandler { return this.resolver.resolve(action.target); } - protected containsElementIdsOrArguments(target: SetResolvedNavigationTargetAction | undefined): target is SetResolvedNavigationTargetAction { + protected containsElementIdsOrArguments( + target: SetResolvedNavigationTargetAction | undefined + ): target is SetResolvedNavigationTargetAction { return target !== undefined && (this.containsElementIds(target.elementIds) || this.containsArguments(target.args)); } diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index bec24c1..dc88192 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -22,7 +22,6 @@ import { expect } from 'chai'; import { NavigationTarget } from './navigation-target-resolver'; describe('NavigationTarget', () => { - it('should be able to set and get element IDs', () => { const navigationTarget: NavigationTarget = { uri: 'uri' }; NavigationTarget.setElementIds(navigationTarget, ['id1', 'id2']); @@ -57,5 +56,4 @@ describe('NavigationTarget', () => { NavigationTarget.setTextPosition(navigationTarget, { line: 1, character: 2 }); expect(NavigationTarget.hasArguments(navigationTarget)).to.be.true; }); - }); diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 81f3f63..029b83c 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,7 +27,6 @@ export interface NavigationTarget { } export namespace NavigationTarget { - export const ELEMENT_IDS = 'elementIds'; export const ELEMENT_IDS_SEPARATOR = '&'; export const TEXT_LINE = 'line'; @@ -56,7 +55,7 @@ export namespace NavigationTarget { if (target.args === undefined) { target.args = {}; } - return target.args[NavigationTarget.ELEMENT_IDS] = elementIds.join(NavigationTarget.ELEMENT_IDS_SEPARATOR); + return (target.args[NavigationTarget.ELEMENT_IDS] = elementIds.join(NavigationTarget.ELEMENT_IDS_SEPARATOR)); } export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void { @@ -70,9 +69,11 @@ export namespace NavigationTarget { } export function getTextPosition(target: NavigationTarget): TextPosition | undefined { - if (target.args === undefined - || target.args[NavigationTarget.TEXT_LINE] === undefined - || target.args[NavigationTarget.TEXT_COLUMN] === undefined) { + if ( + target.args === undefined || + target.args[NavigationTarget.TEXT_LINE] === undefined || + target.args[NavigationTarget.TEXT_COLUMN] === undefined + ) { return undefined; } return { @@ -90,17 +91,17 @@ export interface TextPosition { export class ResolveNavigationTargetAction implements RequestAction { static readonly KIND = 'resolveNavigationTarget'; kind = ResolveNavigationTargetAction.KIND; - constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) { } + constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) {} } export class SetResolvedNavigationTargetAction implements ResponseAction { static readonly KIND = 'setResolvedNavigationTarget'; kind = SetResolvedNavigationTargetAction.KIND; - constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') { } + constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') {} } export function isSetResolvedNavigationTargets(action: Action): action is SetResolvedNavigationTargetAction { - return action !== undefined && (action.kind === SetResolvedNavigationTargetAction.KIND); + return action !== undefined && action.kind === SetResolvedNavigationTargetAction.KIND; } /** @@ -111,7 +112,6 @@ export function isSetResolvedNavigationTargets(action: Action): action is SetRes */ @injectable() export class NavigationTargetResolver { - @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; @inject(TYPES.ILogger) protected readonly logger: ILogger; @@ -122,7 +122,10 @@ export class NavigationTargetResolver { return this.resolveWithSourceUri(sourceUri, navigationTarget); } - async resolveWithSourceUri(sourceUri: string | undefined, target: NavigationTarget): Promise { + async resolveWithSourceUri( + sourceUri: string | undefined, + target: NavigationTarget + ): Promise { const targetUri = decodeURIComponent(target.uri); if (sourceUri && sourceUri !== targetUri && `file://${sourceUri}` !== targetUri) { // different URI, so we can't resolve it locally @@ -143,4 +146,3 @@ export class NavigationTargetResolver { return this.dispatcher.request(new ResolveNavigationTargetAction(target)); } } - diff --git a/packages/client/src/features/rank/model.ts b/packages/client/src/features/rank/model.ts index 1827e21..2718db3 100644 --- a/packages/client/src/features/rank/model.ts +++ b/packages/client/src/features/rank/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,7 +21,7 @@ export interface Ranked { } export function isRanked(arg: any): arg is Ranked { - return arg !== undefined && arg.rank !== undefined && typeof (arg.rank) === 'number'; + return arg !== undefined && arg.rank !== undefined && typeof arg.rank === 'number'; } export function getRank(arg: any): number { diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index ed9e390..b192d32 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,8 +26,7 @@ import { export const reconnectFeature = Symbol('reconnectFeature'); -export interface Reconnectable extends SModelExtension { -} +export interface Reconnectable extends SModelExtension {} export function isReconnectable(element: SModelElement): element is SRoutableElement & Reconnectable { return element instanceof SRoutableElement && element.hasFeature(reconnectFeature); diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index 7545e5b..651f471 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -24,7 +24,6 @@ import { SaveModelKeyboardListener } from './save'; */ const saveModule = new ContainerModule(bind => { bind(TYPES.KeyListener).to(SaveModelKeyboardListener); - }); export default saveModule; diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save.ts index e73f7d9..5a972a7 100644 --- a/packages/client/src/features/save/save.ts +++ b/packages/client/src/features/save/save.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; export class SaveModelAction implements Action { static readonly KIND = 'saveModel'; - constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) { } + constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) {} } export function isSaveModelAction(action: Action): action is SaveModelAction { diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 6035753..417f5b6 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,16 +27,15 @@ export class SelectFeedbackAction { constructor( public readonly selectedElementsIDs: string[] = [], public readonly deselectedElementsIDs: string[] = [], - public readonly kind: string = SelectFeedbackCommand.KIND) { - } + public readonly kind: string = SelectFeedbackCommand.KIND + ) {} } export class SelectAllFeedbackAction { /** * If `select` is true, all elements are selected, othewise they are deselected. */ - constructor(public readonly select: boolean = true, public readonly kind: string = SelectFeedbackCommand.KIND) { - } + constructor(public readonly select: boolean = true, public readonly kind: string = SelectFeedbackCommand.KIND) {} } @injectable() @@ -84,4 +83,3 @@ export class SelectAllFeedbackCommand extends Command { return this.sprottySelectAllCommand.redo(context); } } - diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index a7e2585..1e0afa4 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,7 +25,7 @@ import { SelectionListener, SelectionService } from './selection-service'; @injectable() class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { - protected feedbackEmitters: Map = new Map; + protected feedbackEmitters: Map = new Map(); registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { this.feedbackEmitters.set(feedbackEmitter, actions); @@ -43,7 +43,7 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { getSingleFeedbackAction(): SelectFeedbackAction | undefined { const actions = this.getRegisteredFeedback(); - return actions.length === 1 ? actions[0] as SelectFeedbackAction : undefined; + return actions.length === 1 ? (actions[0] as SelectFeedbackAction) : undefined; } } @@ -363,7 +363,12 @@ describe('SelectionService', () => { } } - function assertListener(listener: MockSelectionListener, expectedRoot: SModelRoot | undefined, expectedSelection: string[], expectedCalled: number): void { + function assertListener( + listener: MockSelectionListener, + expectedRoot: SModelRoot | undefined, + expectedSelection: string[], + expectedCalled: number + ): void { expect(listener.getRoot()).to.equal(expectedRoot); expect(listener.getSelectedElements()).to.have.lengthOf(expectedSelection.length); expect(listener.getSelectedElements()).to.have.ordered.members(expectedSelection); diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index ab6118f..d1a9a30 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -50,7 +50,7 @@ export class SelectionService implements SModelRootListener { @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.ILogger) protected logger: ILogger; - constructor(@multiInject(GLSP_TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) { } + constructor(@multiInject(GLSP_TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) {} register(selectionListener: SelectionListener): void { distinctAdd(this.selectionListeners, selectionListener); @@ -99,7 +99,9 @@ export class SelectionService implements SModelRootListener { } // only send out changes if there actually are changes, i.e., the root or the selected elements changed - const selectionChanged = prevSelectedElementIDs.size !== this.selectedElementIDs.size || ![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value)); + const selectionChanged = + prevSelectedElementIDs.size !== this.selectedElementIDs.size || + ![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value)); if (selectionChanged) { // aggregate to feedback action handling all elements as only the last feedback is restored this.dispatchFeedback([new SelectFeedbackAction([...this.selectedElementIDs], [...deselectedElementIDs])]); @@ -156,7 +158,10 @@ export class SelectCommand extends Command { protected selected: SModelElement[] = []; protected deselected: SModelElement[] = []; - constructor(@inject(TYPES.Action) public action: SelectAction, @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService) { + constructor( + @inject(TYPES.Action) public action: SelectAction, + @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService + ) { super(); } @@ -197,7 +202,10 @@ export class SelectAllCommand extends Command { static readonly KIND = SprottySelectAllCommand.KIND; protected previousSelection: Map = new Map(); - constructor(@inject(TYPES.Action) public action: SelectAllAction, @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService) { + constructor( + @inject(TYPES.Action) public action: SelectAllAction, + @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService + ) { super(); } diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 8ccad0a..5bcf658 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -39,11 +39,11 @@ import { ChangeBoundsTool } from '../tools/change-bounds-tool'; import { FeedbackCommand } from './model'; export class ShowChangeBoundsToolResizeFeedbackAction implements Action { - constructor(readonly elementId?: string, public readonly kind: string = ShowChangeBoundsToolResizeFeedbackCommand.KIND) { } + constructor(readonly elementId?: string, public readonly kind: string = ShowChangeBoundsToolResizeFeedbackCommand.KIND) {} } export class HideChangeBoundsToolResizeFeedbackAction implements Action { - constructor(public readonly kind: string = HideChangeBoundsToolResizeFeedbackCommand.KIND) { } + constructor(public readonly kind: string = HideChangeBoundsToolResizeFeedbackCommand.KIND) {} } @injectable() @@ -54,10 +54,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index - .all() - .filter(isResizable) - .forEach(removeResizeHandles); + index.all().filter(isResizable).forEach(removeResizeHandles); if (isNotUndefined(this.action.elementId)) { const resizeElement = index.getById(this.action.elementId); @@ -77,10 +74,7 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index - .all() - .filter(isResizable) - .forEach(removeResizeHandles); + index.all().filter(isResizable).forEach(removeResizeHandles); return context.root; } } @@ -230,7 +224,6 @@ export class FeedbackMoveMouseListener extends MouseListener { if (this.tool.movementRestrictor) { this.tool.deregisterFeedback([removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)], this); } - } this.hasDragged = false; this.startDragPosition = undefined; diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 567d0b0..1cc5be7 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -49,7 +49,8 @@ export class DrawFeedbackEdgeAction implements Action { public readonly elementTypeId: string, public readonly sourceId: string, public readonly edgeSchema?: SEdgeSchema, - public readonly kind: string = DrawFeedbackEdgeCommand.KIND) { } + public readonly kind: string = DrawFeedbackEdgeCommand.KIND + ) {} } @injectable() @@ -68,7 +69,7 @@ export class DrawFeedbackEdgeCommand extends FeedbackCommand { } export class RemoveFeedbackEdgeAction implements Action { - constructor(public readonly kind: string = RemoveFeedbackEdgeCommand.KIND) { } + constructor(public readonly kind: string = RemoveFeedbackEdgeCommand.KIND) {} } @injectable() @@ -83,10 +84,12 @@ export class RemoveFeedbackEdgeCommand extends FeedbackCommand { export class FeedbackEdgeEnd extends SDanglingAnchor { static readonly TYPE = 'feedback-edge-end'; - constructor(readonly sourceId: string, + constructor( + readonly sourceId: string, readonly elementTypeId: string, public feedbackEdge: SRoutableElement | undefined = undefined, - public readonly type: string = FeedbackEdgeEnd.TYPE) { + public readonly type: string = FeedbackEdgeEnd.TYPE + ) { super(); } } @@ -105,8 +108,9 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { const edge = edgeEnd.feedbackEdge; const position = getAbsolutePosition(edgeEnd, event); - const endAtMousePosition = findChildrenAtPosition(target.root, position) - .find(element => isConnectable(element) && element.canConnect(edge, 'target')); + const endAtMousePosition = findChildrenAtPosition(target.root, position).find( + element => isConnectable(element) && element.canConnect(edge, 'target') + ); if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(edge.source.bounds)); @@ -149,7 +153,12 @@ export const defaultFeedbackEdgeSchema: SEdgeSchema = { opacity: 0.3 } as SEdgeSchema; -export function drawFeedbackEdge(context: CommandExecutionContext, sourceId: string, elementTypeId: string, feedbackEdgeSchema: SEdgeSchema): void { +export function drawFeedbackEdge( + context: CommandExecutionContext, + sourceId: string, + elementTypeId: string, + feedbackEdgeSchema: SEdgeSchema +): void { const root = context.root; const sourceChild = root.index.getById(sourceId); if (!sourceChild) { diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 7ce7df7..64b45ac 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,6 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from 'sprotty'; - import { isStringArray } from '../../utils/array-utils'; import { addCssClasses, removeCssClasses } from '../../utils/smodel-util'; import { FeedbackCommand } from './model'; @@ -27,7 +26,8 @@ export class ModifyCSSFeedbackAction implements Action { public readonly input?: string[] | SModelElement[], public readonly addClasses?: string[], public readonly removeClasses?: string[], - public kind = ModifyCssFeedbackCommand.KIND) { + public kind = ModifyCssFeedbackCommand.KIND + ) { if (input) { this.elementIds = isStringArray(input) ? input : input.map(element => element.id); } @@ -68,6 +68,7 @@ function exists(elt?: SModelElement): elt is SModelElement { return elt !== undefined; } +// eslint-disable-next-line no-shadow export enum CursorCSS { DEFAULT = 'default-mode', OVERLAP_FORBIDDEN = 'overlap-forbidden-mode', diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index f261fe8..43973af 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,10 +18,7 @@ import { configureCommand, configureView, LocationPostprocessor, MoveCommand, TY import { GLSP_TYPES } from '../../base/types'; import { SResizeHandle } from '../change-bounds/model'; -import { - HideChangeBoundsToolResizeFeedbackCommand, - ShowChangeBoundsToolResizeFeedbackCommand -} from './change-bounds-tool-feedback'; +import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from './creation-tool-feedback'; import { ModifyCssFeedbackCommand } from './css-feedback'; import { diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index aaba85f..fe8c35e 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -47,12 +47,7 @@ import { import { isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; -import { - FeedbackEdgeEnd, - feedbackEdgeEndId, - FeedbackEdgeEndMovingMouseListener, - feedbackEdgeId -} from './creation-tool-feedback'; +import { FeedbackEdgeEnd, feedbackEdgeEndId, FeedbackEdgeEndMovingMouseListener, feedbackEdgeId } from './creation-tool-feedback'; import { FeedbackCommand } from './model'; /** @@ -60,11 +55,11 @@ import { FeedbackCommand } from './model'; */ export class ShowEdgeReconnectHandlesFeedbackAction implements Action { - constructor(readonly elementId?: string, public readonly kind: string = ShowEdgeReconnectHandlesFeedbackCommand.KIND) { } + constructor(readonly elementId?: string, public readonly kind: string = ShowEdgeReconnectHandlesFeedbackCommand.KIND) {} } export class HideEdgeReconnectHandlesFeedbackAction implements Action { - constructor(public readonly kind: string = HideEdgeReconnectHandlesFeedbackCommand.KIND) { } + constructor(public readonly kind: string = HideEdgeReconnectHandlesFeedbackCommand.KIND) {} } @injectable() @@ -108,9 +103,11 @@ export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { */ export class SwitchRoutingModeAction extends SwitchEditModeAction { - constructor(public readonly elementsToActivate: string[] = [], + constructor( + public readonly elementsToActivate: string[] = [], public readonly elementsToDeactivate: string[] = [], - public readonly kind: string = SwitchRoutingModeCommand.KIND) { + public readonly kind: string = SwitchRoutingModeCommand.KIND + ) { super(elementsToActivate, elementsToDeactivate); } } @@ -128,7 +125,11 @@ export class SwitchRoutingModeCommand extends SwitchEditModeCommand { */ export class DrawFeedbackEdgeSourceAction implements Action { - constructor(readonly elementTypeId: string, readonly targetId: string, public readonly kind: string = DrawFeedbackEdgeSourceCommand.KIND) { } + constructor( + readonly elementTypeId: string, + readonly targetId: string, + public readonly kind: string = DrawFeedbackEdgeSourceCommand.KIND + ) {} } @injectable() @@ -169,8 +170,9 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { const edge = edgeEnd.feedbackEdge; const position = getAbsolutePosition(edgeEnd, event); - const endAtMousePosition = findChildrenAtPosition(target.root, position) - .find(e => isConnectable(e) && e.canConnect(edge, 'source')); + const endAtMousePosition = findChildrenAtPosition(target.root, position).find( + e => isConnectable(e) && e.canConnect(edge, 'source') + ); if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(edge.target.bounds)); @@ -234,7 +236,8 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { const dx = (event.pageX - this.lastDragPosition.x) / zoom; const dy = (event.pageY - this.lastDragPosition.y) / zoom; const handleMoves: ElementMove[] = []; - target.root.index.all() + target.root.index + .all() .filter(element => isSelected(element)) .forEach(element => { if (isRoutingHandle(element)) { diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index 1bd8d4e..2eee4f0 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { inject, injectable } from 'inversify'; import { Action, IActionDispatcher, ILogger, TYPES } from 'sprotty'; -export interface IFeedbackEmitter { } +export interface IFeedbackEmitter {} /** * Dispatcher for actions that are meant to show visual feedback on @@ -52,14 +52,14 @@ export interface IFeedbackActionDispatcher { deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void; /** - * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. - */ + * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. + */ getRegisteredFeedback(): Action[]; } @injectable() export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { - protected feedbackEmitters: Map = new Map; + protected feedbackEmitters: Map = new Map(); @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; @inject(TYPES.ILogger) protected logger: ILogger; @@ -93,9 +93,7 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { if (value.find(a => a === action)) { result.push(key); } - } - ); + }); return result; } - } diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index 90fad14..81d2f0a 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -33,7 +33,8 @@ export class DrawMarqueeAction implements Action { constructor( public readonly startPoint: Point, public readonly endPoint: Point, - public readonly kind: string = DrawMarqueeCommand.KIND) { } + public readonly kind: string = DrawMarqueeCommand.KIND + ) {} } @injectable() @@ -51,7 +52,7 @@ export class DrawMarqueeCommand extends FeedbackCommand { } export class RemoveMarqueeAction implements Action { - constructor(public readonly kind: string = RemoveMarqueeCommand.KIND) { } + constructor(public readonly kind: string = RemoveMarqueeCommand.KIND) {} } @injectable() diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts index a308472..cdf9783 100644 --- a/packages/client/src/features/tool-feedback/model.ts +++ b/packages/client/src/features/tool-feedback/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,4 +29,3 @@ export abstract class FeedbackCommand extends Command { return context.root; } } - diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index 265add0..31840b0 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,9 +23,9 @@ import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-boun const JSX = { createElement: svg }; /** -* This view is used for the invisible end of the feedback edge. -* A feedback edge is shown as a visual feedback when creating edges. -*/ + * This view is used for the invisible end of the feedback edge. + * A feedback edge is shown as a visual feedback when creating edges. + */ @injectable() export class FeedbackEdgeEndView implements IView { render(model: Readonly, context: RenderingContext): VNode { @@ -39,8 +39,15 @@ export class SResizeHandleView implements IView { render(handle: SResizeHandle, context: RenderingContext): VNode { const position = this.getPosition(handle); if (position !== undefined) { - const node = ; + const node = ( + + ); setAttr(node, 'data-kind', handle.location); return node; } diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts index 9a32eb1..9cf5802 100644 --- a/packages/client/src/features/tool-palette/palette-item.ts +++ b/packages/client/src/features/tool-palette/palette-item.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,11 +30,11 @@ export function isPaletteItem(object?: any): object is PaletteItem { export namespace PaletteItem { export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { if (item) { - const initiAction = item.actions.filter(a => isTriggerElementTypeCreationAction(a)) + const initiAction = item.actions + .filter(a => isTriggerElementTypeCreationAction(a)) .map(action => action as TriggerElementCreationAction); return initiAction.length > 0 ? initiAction[0] : undefined; } return undefined; } } - diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 4b23db5..9b74c7f 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -135,16 +135,15 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const bodyDiv = document.createElement('div'); bodyDiv.classList.add('palette-body'); let tabIndex = 0; - this.paletteItems.sort(compare) - .forEach(item => { - if (item.children) { - const group = createToolGroup(item); - item.children.sort(compare).forEach(child => group.appendChild(this.createToolButton(child, tabIndex++))); - bodyDiv.appendChild(group); - } else { - bodyDiv.appendChild(this.createToolButton(item, tabIndex++)); - } - }); + this.paletteItems.sort(compare).forEach(item => { + if (item.children) { + const group = createToolGroup(item); + item.children.sort(compare).forEach(child => group.appendChild(this.createToolButton(child, tabIndex++))); + bodyDiv.appendChild(group); + } else { + bodyDiv.appendChild(this.createToolButton(item, tabIndex++)); + } + }); if (this.paletteItems.length === 0) { const noResultsDiv = document.createElement('div'); noResultsDiv.innerText = 'No results found.'; @@ -165,7 +164,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, headerCompartment.classList.add('palette-header'); headerCompartment.append(this.createHeaderTitle()); headerCompartment.appendChild(this.createHeaderTools()); - headerCompartment.appendChild(this.searchField = this.createHeaderSearchField()); + headerCompartment.appendChild((this.searchField = this.createHeaderSearchField())); this.containerElement.appendChild(headerCompartment); } @@ -411,6 +410,5 @@ export function createToolGroup(item: PaletteItem): HTMLElement { } export function changeCSSClass(element: Element, css: string): void { - element.classList.contains(css) ? element.classList.remove(css) : - element.classList.add(css); + element.classList.contains(css) ? element.classList.remove(css) : element.classList.add(css); } diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 2222319..9ce7915 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -118,7 +118,7 @@ export class ChangeBoundsTool extends BaseGLSPTool { this.selectionService.deregister(this.changeBoundsListener); this.mouseTool.deregister(this.feedbackMoveMouseListener); this.deregisterFeedback([], this.feedbackMoveMouseListener); - this.deregisterFeedback([new HideChangeBoundsToolResizeFeedbackAction], this.changeBoundsListener); + this.deregisterFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this.changeBoundsListener); } } @@ -162,7 +162,10 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele if (this.isMouseDrag && this.activeResizeHandle) { // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements // consider resize handles ourselves - const actions: Action[] = [cursorFeedbackAction(CursorCSS.RESIZE), applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE)]; + const actions: Action[] = [ + cursorFeedbackAction(CursorCSS.RESIZE), + applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE) + ]; const positionUpdate = this.updatePosition(target, event); if (positionUpdate) { const resizeActions = this.handleResizeOnClient(positionUpdate); @@ -281,7 +284,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele this.lastDragPosition = { x: event.pageX, y: event.pageY }; if (this.activeResizeHandle) { const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); - this.initialBounds = { x: resizeElement!.bounds.x, y: resizeElement!.bounds.y, width: resizeElement!.bounds.width, height: resizeElement!.bounds.height }; + this.initialBounds = { + x: resizeElement!.bounds.x, + y: resizeElement!.bounds.y, + width: resizeElement!.bounds.width, + height: resizeElement!.bounds.height + }; } } @@ -318,7 +326,6 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected reset(): void { if (this.activeResizeElement && isResizable(this.activeResizeElement)) { this.tool.dispatchFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this); - } this.tool.dispatchActions([cursorFeedbackAction(CursorCSS.DEFAULT)]); this.resetPosition(); @@ -352,35 +359,43 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } protected handleTopLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { - return this.createSetBoundsAction(resizeElement, + return this.createSetBoundsAction( + resizeElement, resizeElement.bounds.x + positionUpdate.x, resizeElement.bounds.y + positionUpdate.y, resizeElement.bounds.width - positionUpdate.x, - resizeElement.bounds.height - positionUpdate.y); + resizeElement.bounds.height - positionUpdate.y + ); } protected handleTopRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { - return this.createSetBoundsAction(resizeElement, + return this.createSetBoundsAction( + resizeElement, resizeElement.bounds.x, resizeElement.bounds.y + positionUpdate.y, resizeElement.bounds.width + positionUpdate.x, - resizeElement.bounds.height - positionUpdate.y); + resizeElement.bounds.height - positionUpdate.y + ); } protected handleBottomLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { - return this.createSetBoundsAction(resizeElement, + return this.createSetBoundsAction( + resizeElement, resizeElement.bounds.x + positionUpdate.x, resizeElement.bounds.y, resizeElement.bounds.width - positionUpdate.x, - resizeElement.bounds.height + positionUpdate.y); + resizeElement.bounds.height + positionUpdate.y + ); } protected handleBottomRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { - return this.createSetBoundsAction(resizeElement, + return this.createSetBoundsAction( + resizeElement, resizeElement.bounds.x, resizeElement.bounds.y, resizeElement.bounds.width + positionUpdate.x, - resizeElement.bounds.height + positionUpdate.y); + resizeElement.bounds.height + positionUpdate.y + ); } protected createChangeBoundsAction(element: SModelElement & BoundsAware): Action[] { @@ -414,7 +429,6 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele result.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } result.push(new SetBoundsAction([{ elementId: element.id, newPosition, newSize }])); - } else if (this.isValidSize(element, newSize)) { if (this.tool.movementRestrictor) { result.push(createMovementRestrictionFeedback(element, this.tool.movementRestrictor)); @@ -426,9 +440,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { - return isSnap && this.tool.snapper - ? this.tool.snapper.snap(position, element) - : { x: position.x, y: position.y }; + return isSnap && this.tool.snapper ? this.tool.snapper.snap(position, element) : { x: position.x, y: position.y }; } protected isValidBoundChange(element: SModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index ec58de2..7b7600d 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -64,8 +64,13 @@ export class DelKeyDeleteTool implements GLSPTool { export class DeleteKeyListener extends KeyListener { keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Delete')) { - const deleteElementIds = Array.from(element.root.index.all().filter(e => isDeletable(e) && isSelectable(e) && e.selected) - .filter(e => e.id !== e.root.id).map(e => e.id)); + const deleteElementIds = Array.from( + element.root.index + .all() + .filter(e => isDeletable(e) && isSelectable(e) && e.selected) + .filter(e => e.id !== e.root.id) + .map(e => e.id) + ); if (deleteElementIds.length > 0) { return [new DeleteElementOperation(deleteElementIds)]; } diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 4a54c1e..6de4647 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,11 +28,7 @@ import { } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { - CreateEdgeOperation, - isTriggerElementTypeCreationAction, - TriggerEdgeCreationAction -} from '../../base/operations/operation'; +import { CreateEdgeOperation, isTriggerElementTypeCreationAction, TriggerEdgeCreationAction } from '../../base/operations/operation'; import { DrawFeedbackEdgeAction, FeedbackEdgeEndMovingMouseListener, @@ -151,8 +147,9 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.allowedTarget = this.isAllowedTarget(newCurrentTarget); } if (this.allowedTarget) { - const action = !this.isSourceSelected() ? cursorFeedbackAction(CursorCSS.EDGE_CREATION_SOURCE) : - cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET); + const action = !this.isSourceSelected() + ? cursorFeedbackAction(CursorCSS.EDGE_CREATION_SOURCE) + : cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET); return [action]; } } diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 4bab08f..c0874e3 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,13 +33,7 @@ import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { isRoutable, isRoutingHandle } from '../../utils/smodel-util'; -import { - isReconnectable, - isReconnectHandle, - isSourceRoutingHandle, - isTargetRoutingHandle, - SReconnectHandle -} from '../reconnect/model'; +import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle } from '../reconnect/model'; import { SelectionListener, SelectionService } from '../select/selection-service'; import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from '../tool-feedback/creation-tool-feedback'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; @@ -255,9 +249,10 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen if (!this.newConnectable || currentTarget !== this.newConnectable) { this.setNewConnectable(currentTarget); if (currentTarget) { - if ((this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || - (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target'))) { - + if ( + (this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || + (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target')) + ) { this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); return []; } @@ -314,11 +309,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen if (this.edge) { result.push(new SwitchRoutingModeAction([], [this.edge.id])); } - result.push(...[ - new HideEdgeReconnectHandlesFeedbackAction(), - cursorFeedbackAction(), - new RemoveFeedbackEdgeAction() - ]); + result.push(...[new HideEdgeReconnectHandlesFeedbackAction(), cursorFeedbackAction(), new RemoveFeedbackEdgeAction()]); this.tool.deregisterFeedback(result); this.tool.deregisterFeedbackListeners(); } diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index c55b066..1635918 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -65,7 +65,6 @@ export class MarqueeMouseTool extends BaseGLSPTool { @injectable() export class MarqueeMouseListener extends DragAwareMouseListener { - protected startPoint: Point; protected currentPoint: Point; @@ -84,11 +83,14 @@ export class MarqueeMouseListener extends DragAwareMouseListener { mouseDown(target: SModelElement, event: MouseEvent): Action[] { this.isActive = true; this.startPoint = { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }; - if(event.ctrlKey){ - this.previouslySelected = Array.from(target.root.index.all() - .map(e => e as SModelElement & BoundsAware) - .filter(e => isSelected(e)) - .map(e => e.id)); + if (event.ctrlKey) { + this.previouslySelected = Array.from( + target.root.index + .all() + .map(e => e as SModelElement & BoundsAware) + .filter(e => isSelected(e)) + .map(e => e.id) + ); } return []; } @@ -96,18 +98,21 @@ export class MarqueeMouseListener extends DragAwareMouseListener { mouseMove(target: SModelElement, event: MouseEvent): Action[] { this.currentPoint = { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }; if (this.isActive) { - const nodeIdsSelected = Array.from(target.root.index.all() - .map(e => e as SModelElement & BoundsAware) - .filter(e => isSelectable(e)) - .filter(e => e instanceof SNode) - .filter(e => this.isNodeMarked(e)).map(e => e.id)); + const nodeIdsSelected = Array.from( + target.root.index + .all() + .map(e => e as SModelElement & BoundsAware) + .filter(e => isSelectable(e)) + .filter(e => e instanceof SNode) + .filter(e => this.isNodeMarked(e)) + .map(e => e.id) + ); const edgeIdsSelected = this.getMarkedEdges(target.root); const selected = nodeIdsSelected.concat(edgeIdsSelected); return [ new SelectAction([], Array.from(target.root.index.all().map(e => e.id))), new SelectAction(selected.concat(this.previouslySelected), []), - new DrawMarqueeAction(this.startPoint, - { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }) + new DrawMarqueeAction(this.startPoint, { x: getAbsolutePosition(target, event).x, y: getAbsolutePosition(target, event).y }) ]; } return []; @@ -123,10 +128,17 @@ export class MarqueeMouseListener extends DragAwareMouseListener { getMarkedEdges(root: SModelElement): string[] { const elements = Array.from(document.querySelectorAll('g')); - const edges = Array.from(root.index.all().filter(e => e instanceof SEdge). - filter(e => isSelectable(e)).map(e => e.id)); - return elements.filter(e => edges.includes(this.domHelper.findSModelIdByDOMElement(e))) - .filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e)); + const edges = Array.from( + root.index + .all() + .filter(e => e instanceof SEdge) + .filter(e => isSelectable(e)) + .map(e => e.id) + ); + return elements + .filter(e => edges.includes(this.domHelper.findSModelIdByDOMElement(e))) + .filter(e => this.isEdgeMarked(e)) + .map(e => this.domHelper.findSModelIdByDOMElement(e)); } isEdgeMarked(element: SVGElement): boolean { @@ -170,11 +182,11 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } linesIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean { - const tCount = ((p1.x - p3.x) * (p3.y - p4.y)) - ((p1.y - p3.y) * (p3.x - p4.x)); - const tDenom = ((p1.x - p2.x) * (p3.y - p4.y)) - ((p1.y - p2.y) * (p3.x - p4.x)); + const tCount = (p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x); + const tDenom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x); const t = tCount / tDenom; - const uCount = ((p2.x - p1.x) * (p1.y - p3.y)) - ((p2.y - p1.y) * (p1.x - p3.x)); - const uDenom = ((p1.x - p2.x) * (p3.y - p4.y)) - ((p1.y - p2.y) * (p3.x - p4.x)); + const uCount = (p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x); + const uDenom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x); const u = uCount / uDenom; if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) { return true; @@ -183,20 +195,26 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } pointInRect(point: Point): boolean { - const boolX = this.startPoint.x <= this.currentPoint.x ? - this.isBetween(point.x, this.startPoint.x, this.currentPoint.x) : this.isBetween(point.x, this.currentPoint.x, this.startPoint.x); - const boolY = this.startPoint.y <= this.currentPoint.y ? - this.isBetween(point.y, this.startPoint.y, this.currentPoint.y) : this.isBetween(point.y, this.currentPoint.y, this.startPoint.y); + const boolX = + this.startPoint.x <= this.currentPoint.x + ? this.isBetween(point.x, this.startPoint.x, this.currentPoint.x) + : this.isBetween(point.x, this.currentPoint.x, this.startPoint.x); + const boolY = + this.startPoint.y <= this.currentPoint.y + ? this.isBetween(point.y, this.startPoint.y, this.currentPoint.y) + : this.isBetween(point.y, this.currentPoint.y, this.startPoint.y); return boolX && boolY; } isNodeMarked(element: SModelElement & BoundsAware): boolean { - const horizontallyIn = this.startPoint.x < this.currentPoint.x ? - this.isElementBetweenXAxis(element, this.startPoint.x, this.currentPoint.x) : - this.isElementBetweenXAxis(element, this.currentPoint.x, this.startPoint.x); - const verticallyIn = this.startPoint.y < this.currentPoint.y ? - this.isElementBetweenYAxis(element, this.startPoint.y, this.currentPoint.y) : - this.isElementBetweenYAxis(element, this.currentPoint.y, this.startPoint.y); + const horizontallyIn = + this.startPoint.x < this.currentPoint.x + ? this.isElementBetweenXAxis(element, this.startPoint.x, this.currentPoint.x) + : this.isElementBetweenXAxis(element, this.currentPoint.x, this.startPoint.x); + const verticallyIn = + this.startPoint.y < this.currentPoint.y + ? this.isElementBetweenYAxis(element, this.startPoint.y, this.currentPoint.y) + : this.isElementBetweenYAxis(element, this.currentPoint.y, this.startPoint.y); if (horizontallyIn && verticallyIn) { return true; } @@ -204,8 +222,10 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } isElementBetweenXAxis(element: SModelElement & BoundsAware, marqueeLeft: number, marqueeRight: number): boolean { - if (this.isBetween(marqueeLeft, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width) - || this.isBetween(marqueeRight, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width)) { + if ( + this.isBetween(marqueeLeft, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width) || + this.isBetween(marqueeRight, toAbsoluteBounds(element).x, toAbsoluteBounds(element).x + toAbsoluteBounds(element).width) + ) { return true; } const leftEdge = this.isBetween(toAbsoluteBounds(element).x, marqueeLeft, marqueeRight); @@ -214,8 +234,10 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } isElementBetweenYAxis(element: SModelElement & BoundsAware, marqueeTop: number, marqueeBottom: number): boolean { - if (this.isBetween(marqueeTop, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height) - || this.isBetween(marqueeBottom, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height)) { + if ( + this.isBetween(marqueeTop, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height) || + this.isBetween(marqueeBottom, toAbsoluteBounds(element).y, toAbsoluteBounds(element).y + toAbsoluteBounds(element).height) + ) { return true; } const topEdge = this.isBetween(toAbsoluteBounds(element).y, marqueeTop, marqueeBottom); @@ -229,12 +251,10 @@ export class MarqueeMouseListener extends DragAwareMouseListener { } return false; } - } @injectable() export class ShiftKeyListener extends KeyListener { - keyUp(element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return []; diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index 38c18a0..674dd21 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -42,7 +42,6 @@ export class MarqueeTool extends BaseGLSPTool { @injectable() export class MarqueeKeyListener extends KeyListener { - keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return [new EnableToolsAction([MarqueeMouseTool.ID])]; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index 8a87fe8..20bca83 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,11 +28,7 @@ import { } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { - CreateNodeOperation, - isTriggerNodeCreationAction, - TriggerNodeCreationAction -} from '../../base/operations/operation'; +import { CreateNodeOperation, isTriggerNodeCreationAction, TriggerNodeCreationAction } from '../../base/operations/operation'; import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { Containable, isContainable } from '../hints/model'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; @@ -75,7 +71,6 @@ export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { @injectable() export class NodeCreationToolMouseListener extends DragAwareMouseListener { - protected container?: SModelElement & Containable; constructor(protected triggerAction: TriggerNodeCreationAction, protected tool: NodeCreationTool) { @@ -114,8 +109,8 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { if (!this.container || currentContainer !== this.container) { this.container = currentContainer; const feedback = this.creationAllowed(this.elementTypeId) - ? cursorFeedbackAction(CursorCSS.NODE_CREATION) : - cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED); + ? cursorFeedbackAction(CursorCSS.NODE_CREATION) + : cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED); this.tool.dispatchFeedback([feedback]); } return []; diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx index 5b47143..4c56d03 100644 --- a/packages/client/src/features/tools/view.tsx +++ b/packages/client/src/features/tools/view.tsx @@ -25,14 +25,20 @@ const JSX = { createElement: svg }; @injectable() export class MarqueeView extends RectangularNodeView { render(node: MarqueeNode, context: RenderingContext): VNode { - const graph = - - ; + const graph = ( + + + + ); return graph; } } diff --git a/packages/client/src/features/undo-redo/model.ts b/packages/client/src/features/undo-redo/model.ts index 6f3120e..8495248 100644 --- a/packages/client/src/features/undo-redo/model.ts +++ b/packages/client/src/features/undo-redo/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,10 +17,10 @@ import { Operation } from '../../base/operations/operation'; export class UndoOperation implements Operation { static readonly KIND = 'glspUndo'; - constructor(public readonly kind = UndoOperation.KIND) { } + constructor(public readonly kind = UndoOperation.KIND) {} } export class RedoOperation implements Operation { static readonly KIND = 'glspRedo'; - constructor(public readonly kind = RedoOperation.KIND) { } + constructor(public readonly kind = RedoOperation.KIND) {} } diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index d8ef1e3..d786406 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -45,7 +45,9 @@ describe('MarkerNavigator', () => { const markerNavigator = container.get(MarkerNavigator); const rootWithoutAnyMarkers = graphFactory.createRoot({ - id: 'root', type: 'graph', children: [ + id: 'root', + type: 'graph', + children: [ { id: '1', type: 'node' @@ -54,18 +56,24 @@ describe('MarkerNavigator', () => { }) as SModelRoot; const rootWithMarkers = graphFactory.createRoot({ - id: 'root', type: 'graph', children: [ + id: 'root', + type: 'graph', + children: [ { - id: 'bottom-right', type: 'node' + id: 'bottom-right', + type: 'node' } as SNodeSchema, { - id: 'top-right', type: 'node' + id: 'top-right', + type: 'node' } as SNodeSchema, { - id: 'top-left', type: 'node' + id: 'top-left', + type: 'node' } as SNodeSchema, { - id: 'bottom-left', type: 'node' + id: 'bottom-left', + type: 'node' } as SNodeSchema ] }) as SModelRoot; @@ -168,7 +176,6 @@ describe('MarkerNavigator', () => { expect(found4).to.eql(marker2); expect(found5).to.eql(marker1); }); - }); function clearMarker(elem: SParentElement): void { @@ -204,4 +211,3 @@ function createMarker(elem: SParentElement): SIssueMarker { elem.add(newMarker); return newMarker; } - diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index b9d4204..75b7028 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -51,7 +51,8 @@ export class NavigateToMarkerAction implements Action { public readonly direction: 'next' | 'previous' = 'next', public readonly selectedElementIds?: string[], public readonly severities: SIssueSeverity[] = MarkerNavigator.ALL_SEVERITIES, - public kind = NavigateToMarkerAction.KIND) { } + public kind = NavigateToMarkerAction.KIND + ) {} } export class SModelElementComparator { @@ -90,7 +91,11 @@ export class MarkerNavigator { @inject(SModelElementComparator) protected markerComparator: SModelElementComparator; - next(root: SModelRoot, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL): SIssueMarker | undefined { + next( + root: SModelRoot, + current?: SModelElement & BoundsAware, + predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL + ): SIssueMarker | undefined { const markers = this.getMarkers(root, predicate); if (current === undefined) { return markers.length > 0 ? markers[0] : undefined; @@ -98,7 +103,11 @@ export class MarkerNavigator { return markers[this.getNextIndex(current, markers) % markers.length]; } - previous(root: SModelRoot, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL): SIssueMarker | undefined { + previous( + root: SModelRoot, + current?: SModelElement & BoundsAware, + predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL + ): SIssueMarker | undefined { const markers = this.getMarkers(root, predicate); if (current === undefined) { return markers.length > 0 ? markers[0] : undefined; @@ -206,7 +215,6 @@ export class NavigateToMarkerCommand extends Command { } return this.centerCommand ? this.centerCommand.redo(context) : context.root; } - } @injectable() @@ -218,15 +226,22 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP const hasMarkers = collectIssueMarkers(root).length > 0; return Promise.resolve([ { - id: 'navigate', label: 'Go to', group: 'navigate', actions: [], + id: 'navigate', + label: 'Go to', + group: 'navigate', + actions: [], children: [ { - id: 'next-marker', label: 'Next marker', group: 'marker', + id: 'next-marker', + label: 'Next marker', + group: 'marker', actions: [new NavigateToMarkerAction('next', selectedElementIds)], isEnabled: () => hasMarkers }, { - id: 'previous-marker', label: 'Previous marker', group: 'marker', + id: 'previous-marker', + label: 'Previous marker', + group: 'marker', actions: [new NavigateToMarkerAction('previous', selectedElementIds)], isEnabled: () => hasMarkers } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index a3ca6db..ca4b8b4 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -43,7 +43,6 @@ import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from './issue- */ @injectable() export class ValidationFeedbackEmitter implements IFeedbackEmitter { - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; @@ -51,7 +50,7 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { private registeredAction: MarkersAction; // eslint-disable-next-line @typescript-eslint/no-empty-function - private constructor() { } + private constructor() {} /** * Register the action that should be emitted for visualizing validation feedback. @@ -77,11 +76,11 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { * Action to set markers for a model */ export class SetMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = SetMarkersCommand.KIND) { } + constructor(public readonly markers: Marker[], public readonly kind = SetMarkersCommand.KIND) {} } export function isSetMarkersAction(action: Action): action is SetMarkersAction { - return SetMarkersCommand.KIND === action.kind && ('markers' in action); + return SetMarkersCommand.KIND === action.kind && 'markers' in action; } /** @@ -92,7 +91,6 @@ export function isSetMarkersAction(action: Action): action is SetMarkersAction { */ @injectable() export abstract class ExternalMarkerManager { - languageLabel: string; protected actionDispatcher?: IActionDispatcher; @@ -115,7 +113,6 @@ export abstract class ExternalMarkerManager { */ @injectable() export class SetMarkersCommand extends Command { - @inject(ValidationFeedbackEmitter) protected validationFeedbackEmitter: ValidationFeedbackEmitter; @inject(ExternalMarkerManager) @optional() protected externalMarkerManager?: ExternalMarkerManager; @inject(EditorContextService) protected editorContextService: EditorContextService; @@ -147,11 +144,11 @@ export class SetMarkersCommand extends Command { } /** -* Action to retrieve markers for a model -*/ + * Action to retrieve markers for a model + */ export class RequestMarkersAction implements Action { static readonly KIND = 'requestMarkers'; - constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) { } + constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) {} } /** @@ -166,7 +163,7 @@ export interface MarkersAction extends Action { */ @injectable() export class ApplyMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = ApplyMarkersCommand.KIND) { } + constructor(public readonly markers: Marker[], public readonly kind = ApplyMarkersCommand.KIND) {} } /** @@ -224,7 +221,7 @@ function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker */ @injectable() export class DeleteMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = DeleteMarkersCommand.KIND) { } + constructor(public readonly markers: Marker[], public readonly kind = DeleteMarkersCommand.KIND) {} } /** @@ -271,4 +268,3 @@ export class DeleteMarkersCommand extends Command { return this.execute(context); } } - diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 9021cb4..46c87f5 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -14,21 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { - Action, - EnableDefaultToolsAction, - EnableToolsAction, - IActionHandler, - ICommand, - ScrollMouseListener, - SModelElement -} from 'sprotty'; +import { Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from 'sprotty'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @injectable() export class GLSPScrollMouseListener extends ScrollMouseListener implements IActionHandler { - preventScrolling = false; handle(action: Action): void | Action | ICommand { diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6aa8401..7e6a655 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -108,8 +108,25 @@ export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; export { - validationModule, saveModule, paletteModule, toolFeedbackModule, defaultGLSPModule, modelHintsModule, glspCommandPaletteModule, - glspContextMenuModule, glspServerCopyPasteModule, copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, layoutCommandsModule, glspEditLabelModule, - glspHoverModule, toolsModule, navigationModule, markerNavigatorModule, glspDecorationModule, modelSourceWatcherModule, glspViewportModule + validationModule, + saveModule, + paletteModule, + toolFeedbackModule, + defaultGLSPModule, + modelHintsModule, + glspCommandPaletteModule, + glspContextMenuModule, + glspServerCopyPasteModule, + copyPasteContextMenuModule, + glspSelectModule, + glspMouseToolModule, + layoutCommandsModule, + glspEditLabelModule, + glspHoverModule, + toolsModule, + navigationModule, + markerNavigatorModule, + glspDecorationModule, + modelSourceWatcherModule, + glspViewportModule }; - diff --git a/packages/client/src/utils/argument-utils.ts b/packages/client/src/utils/argument-utils.ts index 11eac78..0186784 100644 --- a/packages/client/src/utils/argument-utils.ts +++ b/packages/client/src/utils/argument-utils.ts @@ -18,7 +18,7 @@ import { SModelElement } from 'sprotty'; import { hasArguments } from '../base/args'; export namespace GArgument { - export function asNumber(argValue: (string | number | boolean)): number | undefined { + export function asNumber(argValue: string | number | boolean): number | undefined { return typeof argValue === 'number' ? argValue : undefined; } @@ -26,7 +26,7 @@ export namespace GArgument { return argValues.map(asNumber); } - export function asString(argValue: (string | number | boolean)): string | undefined { + export function asString(argValue: string | number | boolean): string | undefined { return typeof argValue === 'string' ? argValue : undefined; } @@ -34,7 +34,7 @@ export namespace GArgument { return argValues.map(asString); } - export function asBoolean(argValue: (string | number | boolean)): boolean | undefined { + export function asBoolean(argValue: string | number | boolean): boolean | undefined { return typeof argValue === 'boolean' ? argValue : undefined; } @@ -112,16 +112,20 @@ export class CornerRadius { public readonly topLeft: number = 0, public readonly topRight: number = topLeft, public readonly bottomRight: number = topLeft, - public readonly bottomLeft: number = topRight) { - } + public readonly bottomLeft: number = topRight + ) {} static from(element: SModelElement | undefined): CornerRadius | undefined { - const radius = GArgument.getNumbers(element, this.KEY_RADIUS_TOP_LEFT, this.KEY_RADIUS_TOP_RIGHT, this.KEY_RADIUS_BOTTOM_RIGHT, this.KEY_RADIUS_BOTTOM_LEFT); + const radius = GArgument.getNumbers( + element, + this.KEY_RADIUS_TOP_LEFT, + this.KEY_RADIUS_TOP_RIGHT, + this.KEY_RADIUS_BOTTOM_RIGHT, + this.KEY_RADIUS_BOTTOM_LEFT + ); if (radius === undefined || radius[0] === undefined) { return undefined; } - return GArgument.hasNValues(radius, 4) - ? new CornerRadius(radius[0], radius[1], radius[2], radius[3]) - : new CornerRadius(radius[0]); + return GArgument.hasNValues(radius, 4) ? new CornerRadius(radius[0], radius[1], radius[2], radius[3]) : new CornerRadius(radius[0]); } } diff --git a/packages/client/src/utils/array-utils.ts b/packages/client/src/utils/array-utils.ts index d3c0f14..c4b9a96 100644 --- a/packages/client/src/utils/array-utils.ts +++ b/packages/client/src/utils/array-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -31,7 +31,9 @@ export function distinctAdd(array: T[], value: T): boolean { return false; } -interface Constructor { new(...args: any[]): T } +interface Constructor { + new (...args: any[]): T; +} type PrimitiveType = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'; export function isArrayOfType(object: any, typeGuard: (elem: any) => elem is T, supportEmpty = false): object is T[] { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index e3be146..06585e4 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -52,16 +52,22 @@ export function isValidMove(element: SModelElement & BoundsAware, newPosition: P return true; } -export function toValidElementMove(element: SModelElement & BoundsAware, move: WriteableElementMove, movementRestrictor?: IMovementRestrictor): WriteableElementMove | undefined { +export function toValidElementMove( + element: SModelElement & BoundsAware, + move: WriteableElementMove, + movementRestrictor?: IMovementRestrictor +): WriteableElementMove | undefined { if (!isValidMove(element, move.toPosition, movementRestrictor)) { return; } return move; } -export function toValidElementAndBounds(element: SModelElement & BoundsAware, bounds: WriteableElementAndBounds, - movementRestrictor?: IMovementRestrictor): WriteableElementAndBounds | undefined { - +export function toValidElementAndBounds( + element: SModelElement & BoundsAware, + bounds: WriteableElementAndBounds, + movementRestrictor?: IMovementRestrictor +): WriteableElementAndBounds | undefined { if (!isValidMove(element, bounds.newPosition, movementRestrictor)) { return; } diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index ebee961..ef1a39c 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,21 +27,24 @@ import { SRoutableElement, SRoutingHandle } from 'sprotty'; - import { ElementAndRoutingPoints } from '../base/operations/operation'; export function getIndex(element: SModelElement): SModelIndex { return element.root.index; } -export function forEachElement(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T, - runnable: (element: SModelElement & T) => void): void { - getIndex(element).all() - .filter(predicate) - .forEach(runnable); +export function forEachElement( + element: SModelElement, + predicate: (modelElement: SModelElement) => modelElement is SModelElement & T, + runnable: (modelElement: SModelElement & T) => void +): void { + getIndex(element).all().filter(predicate).forEach(runnable); } -export function getMatchingElements(element: SModelElement, predicate: (element: SModelElement) => element is SModelElement & T): (SModelElement & T)[] { +export function getMatchingElements( + element: SModelElement, + predicate: (modelElement: SModelElement) => modelElement is SModelElement & T +): (SModelElement & T)[] { const matching: (SModelElement & T)[] = []; forEachElement(element, predicate, item => matching.push(item)); return matching; @@ -53,9 +56,10 @@ export function hasSelectedElements(element: SModelElement): boolean { export function getSelectedElementCount(element: SModelElement): number { let selected = 0; - getIndex(element).all() + getIndex(element) + .all() .filter(isSelected) - .forEach(e => selected = selected + 1); + .forEach(e => (selected = selected + 1)); return selected; } diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 7f5a962..6ba4fc4 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -41,7 +41,8 @@ import { * A mouseEvent */ export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEvent): Point { - let xPos = mouseEvent.pageX; let yPos = mouseEvent.pageY; + let xPos = mouseEvent.pageX; + let yPos = mouseEvent.pageY; const canvasBounds = target.root.canvasBounds; xPos -= canvasBounds.x; yPos -= canvasBounds.y; diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index b02020e..8490527 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -31,11 +31,13 @@ export class GEdgeView extends PolylineEdgeView { return this.renderDanglingEdge('Cannot compute route', edge, context); } - return - {this.renderLine(edge, route, context)} - {this.renderAdditionals(edge, route, context)} - {context.renderChildren(edge, { route })} - ; + return ( + + {this.renderLine(edge, route, context)} + {this.renderAdditionals(edge, route, context)} + {context.renderChildren(edge, { route })} + + ); } protected addtionalClasses(_edge: Readonly, _context: RenderingContext): Classes { @@ -53,8 +55,16 @@ export class GEdgeView extends PolylineEdgeView { } protected renderMouseHandle(segments: Point[], padding: number): VNode { - return ; + return ( + + ); } protected createPathForSegments(segments: Point[]): string { @@ -66,5 +76,4 @@ export class GEdgeView extends PolylineEdgeView { } return path; } - } diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index f3071d9..f57b757 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,15 +23,16 @@ const JSX = { createElement: svg }; @injectable() export class GIssueMarkerView extends IssueMarkerView { - render(marker: SIssueMarker, _context: RenderingContext): VNode { const maxSeverity = super.getMaxSeverity(marker); - const group = - - - + const group = ( + + + + + - ; + ); setClass(group, 'sprotty-' + maxSeverity, true); return group; } @@ -51,5 +52,4 @@ export class GIssueMarkerView extends IssueMarkerView { return 'M 8.0000004,0 C 3.5820324,0 0,3.58332 0,8 0,12.41926 3.5820324,16 8.0000004,16 12.417968,16 16,12.41926 16,8 16,3.58332 12.417968,0 8.0000004,0 Z m 0,3.54839 c 0.748258,0 1.354839,0.60658 1.354839,1.35484 0,0.74825 -0.606581,1.35483 -1.354839,1.35483 -0.748258,0 -1.354839,-0.60658 -1.354839,-1.35483 0,-0.74826 0.606581,-1.35484 1.354839,-1.35484 z m 1.806452,8.19355 c 0,0.21377 -0.173323,0.38709 -0.387097,0.38709 h -2.83871 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.38709 v -0.7742 c 0,-0.21377 0.173323,-0.38709 0.387097,-0.38709 h 0.387097 V 8.51613 h -0.387097 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.3871 V 7.35484 c 0,-0.21378 0.173323,-0.3871 0.387097,-0.3871 h 2.064516 c 0.213774,0 0.387097,0.17332 0.387097,0.3871 v 3.22581 h 0.387097 c 0.213774,0 0.387097,0.17332 0.387097,0.38709 z'; } } - } diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 3210824..2faf97d 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -32,15 +32,17 @@ export class RoundedCornerNodeView extends RectangularNodeView { } const wrapper = new RoundedCornerWrapper(node, cornerRadius); - return - - - - - - {this.renderPathNode(wrapper, context)} - {context.renderChildren(node)} - ; + return ( + + + + + + + {this.renderPathNode(wrapper, context)} + {context.renderChildren(node)} + + ); } protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode | undefined { @@ -52,12 +54,16 @@ export class RoundedCornerNodeView extends RectangularNodeView { } protected renderPathNode(wrapper: Readonly, context: RenderingContext): VNode { - return ; + return ( + + ); } protected additionalClasses(_node: Readonly, _context: RenderingContext): Classes { @@ -70,14 +76,23 @@ export class RoundedCornerNodeView extends RectangularNodeView { const rightLineLength = Math.max(0, wrapper.size.height - wrapper.cornerRadius.topRight - wrapper.cornerRadius.bottomRight); const bottomLineLength = Math.max(0, wrapper.size.width - wrapper.cornerRadius.bottomLeft - wrapper.cornerRadius.bottomRight); - const path = `M${0 + inset},${0 + wrapper.topLeftCorner.radiusY}` + - `q${0},${-(wrapper.topLeftCorner.radiusY - inset)} ${wrapper.topLeftCorner.radiusX - inset},${-(wrapper.topLeftCorner.radiusY - inset)}` + + const path = + `M${0 + inset},${0 + wrapper.topLeftCorner.radiusY}` + + `q${0},${-(wrapper.topLeftCorner.radiusY - inset)} ${wrapper.topLeftCorner.radiusX - inset},${-( + wrapper.topLeftCorner.radiusY - inset + )}` + `h${topLineLength}` + - `q${wrapper.topRightCorner.radiusX - inset},0 ${wrapper.topRightCorner.radiusX - inset},${wrapper.topRightCorner.radiusY - inset}` + + `q${wrapper.topRightCorner.radiusX - inset},0 ${wrapper.topRightCorner.radiusX - inset},${ + wrapper.topRightCorner.radiusY - inset + }` + `v${rightLineLength}` + - `q0,${wrapper.bottomRightCorner.radiusY - inset} ${-(wrapper.bottomRightCorner.radiusX - inset)},${wrapper.bottomRightCorner.radiusY - inset}` + + `q0,${wrapper.bottomRightCorner.radiusY - inset} ${-(wrapper.bottomRightCorner.radiusX - inset)},${ + wrapper.bottomRightCorner.radiusY - inset + }` + `h${-bottomLineLength}` + - `q${-(wrapper.bottomLeftCorner.radiusX - inset)},0 ${-(wrapper.bottomLeftCorner.radiusX - inset)},${-(wrapper.bottomLeftCorner.radiusY - inset)}` + + `q${-(wrapper.bottomLeftCorner.radiusX - inset)},0 ${-(wrapper.bottomLeftCorner.radiusX - inset)},${-( + wrapper.bottomLeftCorner.radiusY - inset + )}` + 'z '; return path; } diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index cb6887d..5966326 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -28,8 +28,7 @@ export class RoundedCornerWrapper { protected _bottomRightCorner: RoundedCorner; protected _bottomLeftCorner: RoundedCorner; - constructor(public readonly element: SShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) { - } + constructor(public readonly element: SShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) {} get size(): Dimension { return this.element.size; @@ -77,8 +76,8 @@ export class RoundedCornerWrapper { } /* -* Scales the radius if necessary. (Percentual downscaling if the radius is bigger then the maximal allowed length) -*/ + * Scales the radius if necessary. (Percentual downscaling if the radius is bigger then the maximal allowed length) + */ export function scaledRadius(radius: number, maximalLength: number): number { if (radius <= maximalLength) { return radius; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index e0b4d40..e438834 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../configs/base.tsconfig", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib", @@ -9,4 +9,4 @@ "include": [ "src" ] -} +} \ No newline at end of file diff --git a/packages/protocol/.eslintrc.js b/packages/protocol/.eslintrc.js index bc7afd9..727bf62 100644 --- a/packages/protocol/.eslintrc.js +++ b/packages/protocol/.eslintrc.js @@ -1,13 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: [ - '../../configs/base.eslintrc.json', - '../../configs/warnings.eslintrc.json', - '../../configs/errors.eslintrc.json' - ], - ignorePatterns: [ - '**/{node_modules,lib}' - ], + extends: '@eclipse-glsp', parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index 0b55485..e2a786e 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -13,18 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/naming-convention */ + import { ActionMessage } from 'sprotty'; import * as uuid from 'uuid'; /** * A key-value pair structure for primitive typed custom arguments. */ -export interface Args { [key: string]: string | number | boolean } +export interface Args { + [key: string]: string | number | boolean; +} /** * A key-value pair structure to map a diagramType to its server-handled action kinds. */ -export interface ServerActions { [key: string]: string[] } +export interface ServerActions { + [key: string]: string[]; +} export interface InitializeParameters { /** @@ -44,10 +50,9 @@ export interface InitializeParameters { } export interface InitializeResult { - /** - * GLSP protocol version that the server is implementing. - */ + * GLSP protocol version that the server is implementing. + */ protocolVersion: string; /** @@ -83,8 +88,8 @@ export interface DisposeClientSessionParameters { clientSessionId: string; /** - * Additional custom arguments. - */ + * Additional custom arguments. + */ args?: Args; } @@ -100,6 +105,7 @@ export class ApplicationIdProvider { export type ActionMessageHandler = (message: ActionMessage) => void; +// eslint-disable-next-line no-shadow export enum ClientState { /** * The client has been created. @@ -210,8 +216,9 @@ export namespace GLSPClient { id: string; } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function isOptions(object: any): object is Options { - return object !== undefined && 'id' in object && typeof object['id'] === 'string'; + return typeof object === 'object' && 'id' in object && typeof object['id'] === 'string'; } export const protocolVersion = '0.9.0'; diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index 1924be1..bc86e90 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -28,7 +28,6 @@ import { import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { - readonly id: string; protected readonly connectionProvider: ConnectionProvider; protected connectionPromise?: Promise; @@ -109,13 +108,13 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.onStop; } this.state = ClientState.Stopping; - return this.onStop = this.resolveConnection().then(connection => { + return (this.onStop = this.resolveConnection().then(connection => { connection.dispose(); this.state = ClientState.Stopped; this.onStop = undefined; this.connectionPromise = undefined; this.resolvedConnection = undefined; - }); + })); } private resolveConnection(): Promise { diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts index 7256cbc..47461e7 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -23,7 +23,6 @@ import { WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; - import { DisposeClientSessionParameters, GLSPClient, @@ -40,13 +39,16 @@ export namespace JsonrpcGLSPClient { connectionProvider: ConnectionProvider; } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function isOptions(object: any): object is Options { return GLSPClient.isOptions(object) && 'connectionProvider' in object; } export const ActionMessageNotification = new NotificationType('process'); export const InitializeRequest = new RequestType('initialize'); - export const InitializeClientSessionRequest = new RequestType('initializeClientSession'); + export const InitializeClientSessionRequest = new RequestType( + 'initializeClientSession' + ); export const DisposeClientSessionRequest = new RequestType('disposeClientSession'); export const ShutdownNotification = new NotificationType0('shutdown'); diff --git a/packages/protocol/src/launch-util.ts b/packages/protocol/src/launch-util.ts index cf413dd..510282c 100644 --- a/packages/protocol/src/launch-util.ts +++ b/packages/protocol/src/launch-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,4 +27,3 @@ export function getPort(argsKey: string, defaultPort?: number): number { } return defaultPort ? defaultPort : NaN; } - diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index e0b4d40..e438834 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../configs/base.tsconfig", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib", @@ -9,4 +9,4 @@ "include": [ "src" ] -} +} \ No newline at end of file From bf4ea8fc3bc45e3586466c3561b4751395bcafbe Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 4 Oct 2021 16:11:45 +0200 Subject: [PATCH 044/566] #377 Update to latest glsp-config version Update to latest glsp-config version to consume https://github.com/eclipse-glsp/glsp/pull/405 Also: - Align yarn version range with Theia - Update vscode settings. - Add prettier as default formatter for "javascriptreact" (.jsx files). - Fix minor formatting inconsistencies --- .../client/src/base/actions/edit-validation-actions.ts | 4 ++-- packages/client/src/base/di.config.ts | 7 +++---- packages/client/src/base/model/update-model-command.ts | 5 ++--- packages/client/src/base/operations/operation.ts | 5 ++--- packages/client/src/features/change-bounds/model.ts | 2 +- packages/client/src/features/context-menu/di.config.ts | 1 - packages/client/src/features/tools/change-bounds-tool.ts | 5 ++--- packages/client/src/features/tools/edge-edit-tool.ts | 4 ++-- 8 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/client/src/base/actions/edit-validation-actions.ts index 915567a..3d4ef9b 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/client/src/base/actions/edit-validation-actions.ts @@ -24,7 +24,7 @@ export class RequestEditValidationAction implements RequestAction Date: Thu, 7 Oct 2021 16:16:50 +0200 Subject: [PATCH 045/566] #377 Update prettier config and fix reflect-metadata error Update to latest @eclipse-glsp/config version. This version ships with prettier as dependency which enables to configure a workspace-local prettier version for the vscode prettier extension. This ensures that all developers are using the same prettier version. .settings: - Remove prettier as general default formatter to avoid unintended usage of prettier for languages like java. - Configure prettier as default formatter for CSS - Set the prettier path to "node_modules/prettier" Workflow-standalone-example: Fix issue with reflect-metadata polyfill: Extract import of "reflect-metadata" in dedicated "index.ts" file to ensure that the polyfill is loaded before any dependent sprotty or glsp modules are imported. Reformat all code: - Reformat code with "yarn prettier --config .prettierrc ./ --write" in root to ensure that all files for which prettier is set as default formatter are properly formatted. --- examples/workflow-glsp/README.md | 3 +- examples/workflow-glsp/css/diagram.css | 54 +++++++++------- examples/workflow-glsp/scripts/download.ts | 35 +++++----- examples/workflow-glsp/tsconfig.json | 6 +- examples/workflow-standalone/app/diagram.html | 64 +++++++++---------- examples/workflow-standalone/css/diagram.css | 21 +++++- .../src/{main.ts => app.ts} | 3 - examples/workflow-standalone/src/di.config.ts | 3 +- examples/workflow-standalone/src/index.ts | 17 +++++ examples/workflow-standalone/tsconfig.json | 9 +-- .../workflow-standalone/webpack.config.js | 26 +++----- packages/client/README.md | 3 +- packages/client/css/command-palette.css | 11 ++-- packages/client/css/glsp-sprotty.css | 11 ++-- packages/client/css/tool-palette.css | 28 ++++---- packages/client/package.json | 2 +- packages/client/tsconfig.json | 6 +- packages/protocol/README.md | 2 - packages/protocol/tsconfig.json | 6 +- 19 files changed, 163 insertions(+), 147 deletions(-) rename examples/workflow-standalone/src/{main.ts => app.ts} (99%) create mode 100644 examples/workflow-standalone/src/index.ts diff --git a/examples/workflow-glsp/README.md b/examples/workflow-glsp/README.md index 0a688cb..8b698ad 100644 --- a/examples/workflow-glsp/README.md +++ b/examples/workflow-glsp/README.md @@ -3,12 +3,11 @@ This package contains the configuration for the GLSP diagrams of the Workflow example language. It contains the base code that is independent from the actual application framework and integration variant. Separating this base configuration and the actual integration code means that it can be reused for various different integration variants of the GLSP Workflow example language e.g in a Theia application, Standalone application, VS Code plugin or integrated into the Eclipse IDE. ## Building -This package is built with `yarn` and is available from npm via [@eclipse-glsp-examples/workflow-glsp](https://www.npmjs.com/package/@eclipse-glsp-examples/worfklow-glsp) +This package is built with `yarn` and is available from npm via [@eclipse-glsp-examples/workflow-glsp](https://www.npmjs.com/package/@eclipse-glsp-examples/worfklow-glsp) ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) - diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index e9c731a..3d724d4 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,7 +26,7 @@ .sprotty-text { font-size: 12pt; - text-anchor: middle + text-anchor: middle; } .sprotty-node { @@ -83,22 +83,22 @@ stroke-width: 3px; } -.sprotty-edge>.sprotty-routing-handle { +.sprotty-edge > .sprotty-routing-handle { r: 7px; fill: #884; stroke: none; z-index: 1000; } -.sprotty-edge>.sprotty-routing-handle[data-kind='line'] { +.sprotty-edge > .sprotty-routing-handle[data-kind='line'] { opacity: 0.35; } -.sprotty-edge>.sprotty-routing-handle.selected { +.sprotty-edge > .sprotty-routing-handle.selected { fill: #66a; } -.sprotty-edge>.sprotty-routing-handle.mouseover { +.sprotty-edge > .sprotty-routing-handle.mouseover { stroke: #112; stroke-width: 1; } @@ -120,11 +120,11 @@ opacity: 0.2; } -.task.automated>.sprotty-node { +.task.automated > .sprotty-node { fill: gray; } -.task.manual>.sprotty-node { +.task.manual > .sprotty-node { fill: lightblue; } @@ -137,24 +137,30 @@ text { text-anchor: middle; } -.sprotty-edge.weighted.low, .sprotty-edge.weighted.low .arrow { - stroke: lightblue +.sprotty-edge.weighted.low, +.sprotty-edge.weighted.low .arrow { + stroke: lightblue; } .sprotty-edge.weighted.low .arrow { fill: lightblue; } -.sprotty-edge.weighted, .sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium, .sprotty-edge.weighted.medium .arrow { +.sprotty-edge.weighted, +.sprotty-edge.weighted .arrow, +.sprotty-edge.weighted.medium, +.sprotty-edge.weighted.medium .arrow { stroke: rgb(53, 53, 226); } -.sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium .arrow { +.sprotty-edge.weighted .arrow, +.sprotty-edge.weighted.medium .arrow { fill: rgb(53, 53, 226); } -.sprotty-edge.weighted.high, .sprotty-edge.weighted.high .arrow { - stroke: darkblue +.sprotty-edge.weighted.high, +.sprotty-edge.weighted.high .arrow { + stroke: darkblue; } .sprotty-edge.weighted.high .arrow { @@ -181,23 +187,23 @@ svg { margin-left: -5px; } -g .movement-not-allowed>.sprotty-node { - stroke: var(--glsp-error-foreground)!important; +g .movement-not-allowed > .sprotty-node { + stroke: var(--glsp-error-foreground) !important; } .sprotty-resize-handle.movement-not-allowed { - stroke: var(--glsp-error-foreground)!important; - fill: var(--glsp-error-foreground)!important; + stroke: var(--glsp-error-foreground) !important; + fill: var(--glsp-error-foreground) !important; } -.error>.sprotty-node { - stroke: var(--glsp-error-foreground)!important; +.error > .sprotty-node { + stroke: var(--glsp-error-foreground) !important; } -.warning>.sprotty-node { - stroke: var(--glsp-warning-foreground)!important; +.warning > .sprotty-node { + stroke: var(--glsp-warning-foreground) !important; } -.info>.sprotty-node { - stroke: var(--glsp-info-foreground)!important; +.info > .sprotty-node { + stroke: var(--glsp-info-foreground) !important; } diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index 1f071fd..10ba3c9 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,20 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import download from "mvn-artifact-download"; -import { join, resolve } from "path"; +import download from 'mvn-artifact-download'; +import { join, resolve } from 'path'; -const downloadDir = resolve(join(__dirname)) + "/../../.."; -const mavenRepository = "https://oss.sonatype.org/content/repositories/snapshots/"; -const groupId = "org.eclipse.glsp.example"; -const artifactId = "org.eclipse.glsp.example.workflow"; -const version = "0.9.0"; -const classifier = "glsp"; +const downloadDir = resolve(join(__dirname)) + '/../../..'; +const mavenRepository = 'https://oss.sonatype.org/content/repositories/snapshots/'; +const groupId = 'org.eclipse.glsp.example'; +const artifactId = 'org.eclipse.glsp.example.workflow'; +const version = '0.9.0'; +const classifier = 'glsp'; -console.log("Downloading latest version of the Workflow Example Java Server from the maven repository..."); -download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository) - .then(() => console.log("Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-" - + version + "-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n" - + "After starting the server, access the following file locally in your browser to see the running example:\n" - + "./examples/workflow-standalone/app/diagram.html" - )); +console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); +download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository).then(() => + console.log( + 'Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-' + + version + + '-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n' + + 'After starting the server, access the following file locally in your browser to see the running example:\n' + + './examples/workflow-standalone/app/diagram.html' + ) +); diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index 9bc6df5..9ac8a4f 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -7,7 +7,5 @@ "reactNamespace": "JSX", "skipLibCheck": true }, - "include": [ - "src" - ], -} \ No newline at end of file + "include": ["src"] +} diff --git a/examples/workflow-standalone/app/diagram.html b/examples/workflow-standalone/app/diagram.html index 9a0199d..0ea5c71 100644 --- a/examples/workflow-standalone/app/diagram.html +++ b/examples/workflow-standalone/app/diagram.html @@ -1,38 +1,38 @@ + + + + + - - - - - -
- - + svg { + width: 100%; + height: 100%; + overflow: hidden; + } + + + + + +
+ + diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 3fb5755..6d52ff7 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -1,3 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2019-2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ .sprotty-graph { background: rgb(179, 196, 202); } @@ -19,7 +34,7 @@ stroke: #844; } -.sprotty-edge.selected>.arrow { +.sprotty-edge.selected > .arrow { fill: #844; stroke: #844; } @@ -28,11 +43,11 @@ stroke: black; } -.forkOrJoin>.sprotty-node { +.forkOrJoin > .sprotty-node { fill: black; } -.forkOrJoin>.sprotty-node.selected { +.forkOrJoin > .sprotty-node.selected { stroke: rgb(87, 87, 214); } diff --git a/examples/workflow-standalone/src/main.ts b/examples/workflow-standalone/src/app.ts similarity index 99% rename from examples/workflow-standalone/src/main.ts rename to examples/workflow-standalone/src/app.ts index b3eceae..6016319 100644 --- a/examples/workflow-standalone/src/main.ts +++ b/examples/workflow-standalone/src/app.ts @@ -13,13 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import 'reflect-metadata'; - import { configureServerActions, EnableToolPaletteAction, GLSPDiagramServer, RequestTypeHintsAction } from '@eclipse-glsp/client'; import { ApplicationIdProvider, BaseJsonrpcGLSPClient, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; import { join, resolve } from 'path'; import { IActionDispatcher, RequestModelAction, TYPES } from 'sprotty'; - import createContainer from './di.config'; const port = 8081; diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 3f53fb9..74be488 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '../css/diagram.css'; - import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp/lib'; import { GLSPDiagramServer } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import { ConsoleLogger, LogLevel, TYPES } from 'sprotty'; +import '../css/diagram.css'; export default function createContainer(): Container { const container = createWorkflowDiagramContainer('sprotty'); diff --git a/examples/workflow-standalone/src/index.ts b/examples/workflow-standalone/src/index.ts new file mode 100644 index 0000000..b2d6201 --- /dev/null +++ b/examples/workflow-standalone/src/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import 'reflect-metadata'; +import './app'; diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json index 7b23021..f76247e 100644 --- a/examples/workflow-standalone/tsconfig.json +++ b/examples/workflow-standalone/tsconfig.json @@ -6,10 +6,5 @@ "baseUrl": ".", "noImplicitAny": false }, - "include": [ - "src", - "lib", - "css", - "app" - ] -} \ No newline at end of file + "include": ["src", "lib", "css", "app"] +} diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index 14640ef..469f268 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -20,20 +20,13 @@ const buildRoot = path.resolve(__dirname, 'lib'); const appRoot = path.resolve(__dirname, 'app'); var CircularDependencyPlugin = require('circular-dependency-plugin'); - module.exports = { - entry: [ - 'core-js/es6/map', - 'core-js/es6/promise', - 'core-js/es6/string', - 'core-js/es6/symbol', - path.resolve(buildRoot, 'main') - ], - output: { - filename: 'bundle.js', - path: appRoot - }, - mode: 'development', + entry: ['core-js/es6/map', 'core-js/es6/promise', 'core-js/es6/string', 'core-js/es6/symbol', path.resolve(buildRoot, 'index')], + output: { + filename: 'bundle.js', + path: appRoot + }, + mode: 'development', devtool: 'eval-source-map', resolve: { // Add `.ts` and `.tsx` as a resolvable extension. @@ -57,15 +50,12 @@ module.exports = { } ] }, - node : { fs: 'empty', net: 'empty' }, + node: { fs: 'empty', net: 'empty' }, plugins: [ new CircularDependencyPlugin({ exclude: /(node_modules|examples)\/./, failOnError: false }), - new webpack.WatchIgnorePlugin([ - /\.js$/, - /\.d\.ts$/ - ]) + new webpack.WatchIgnorePlugin([/\.js$/, /\.d\.ts$/]) ] }; diff --git a/packages/client/README.md b/packages/client/README.md index fac2f42..445897a 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -2,11 +2,12 @@ A web-based diagram client framework for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) based on [Eclipse Sprotty](https://github.com/eclipse/sprotty). - ## Building + This project is built with `yarn` and is available from npm via [@eclipse-glsp/client](https://www.npmjs.com/package/@eclipse-glsp/client). ## More information + For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/client/css/command-palette.css b/packages/client/css/command-palette.css index 3498d66..80ee32a 100644 --- a/packages/client/css/command-palette.css +++ b/packages/client/css/command-palette.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -37,7 +37,7 @@ font-style: normal; } -.command-palette-suggestions>div { +.command-palette-suggestions > div { padding: 0 4px; } @@ -45,14 +45,15 @@ background: #eee; } -.command-palette-suggestions>div:hover:not(.group), .command-palette-suggestions>div.selected { +.command-palette-suggestions > div:hover:not(.group), +.command-palette-suggestions > div.selected { cursor: pointer; } -.command-palette-suggestions>div:hover:not(.group) { +.command-palette-suggestions > div:hover:not(.group) { background: #e0e0e0; } -.command-palette-suggestions>div.selected { +.command-palette-suggestions > div.selected { background: #bbdefb; } diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 2079028..1065793 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ .sprotty { padding: 0px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } .sprotty svg text::selection { @@ -31,7 +31,7 @@ } .sprotty-popup { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; position: absolute; background: white; border-radius: 5px; @@ -40,7 +40,7 @@ min-width: 100px; } -.sprotty-popup>div { +.sprotty-popup > div { margin: 10px; } @@ -89,7 +89,8 @@ cursor: pointer; } -.edge-creation-select-target-mode, .edge-reconnect-select-target-mode { +.edge-creation-select-target-mode, +.edge-reconnect-select-target-mode { cursor: crosshair; } diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css index c3a870b..2faf972 100644 --- a/packages/client/css/tool-palette.css +++ b/packages/client/css/tool-palette.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -@import url("https://use.fontawesome.com/releases/v5.6.3/css/all.css"); +@import url('https://use.fontawesome.com/releases/v5.6.3/css/all.css'); /* Css for main container */ .tool-palette { @@ -49,27 +49,27 @@ text-align: left; font-size: 1.15em; font-weight: bold; - background: #CCCCCC; + background: #cccccc; border: 1px solid rgba(60, 60, 60, 0.6); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .header-icon i { - margin-right: 0.2em + margin-right: 0.2em; } .header-tools i { - border: 1px solid #CCCCCC; + border: 1px solid #cccccc; padding: 0.2em; margin-right: 0.2em; } .header-tools i:hover { - background: #DFDFDF; + background: #dfdfdf; } .header-tools .clicked { - background: #BDDAEF; + background: #bddaef; border: 1px solid rgba(60, 60, 60, 0.6); } @@ -83,16 +83,16 @@ .tool-group { text-align: left; - background: #EDEDEE; + background: #ededee; } .group-header { font-weight: bold; - background: #CCCCCC; + background: #cccccc; } .group-header:hover { - background: #AAAAAA; + background: #aaaaaa; } .group-header i { @@ -100,16 +100,16 @@ } .tool-button { - background: #EDEDEE; + background: #ededee; padding: 0.4em; } .tool-button:hover { - background: #DFDFDF; + background: #dfdfdf; } .tool-button.clicked { - background: #BDDAEF; + background: #bddaef; } .tool-button.collapsed { @@ -129,7 +129,7 @@ } .search-input { - background: #DFDFDF; + background: #dfdfdf; color: black; border: v#BDDAEF; padding-left: 3px; diff --git a/packages/client/package.json b/packages/client/package.json index 2b7d1a4..253f183 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -65,4 +65,4 @@ }, "main": "lib/index", "types": "lib/index" -} \ No newline at end of file +} diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index e438834..3fed2b4 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -6,7 +6,5 @@ "reactNamespace": "JSX", "baseUrl": "." }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/packages/protocol/README.md b/packages/protocol/README.md index f1004a8..925030f 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -2,10 +2,8 @@ The generic client-server communication protocol for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) and a json-rpc based default implementation. - This project is built with `yarn` and is available from npm via [@eclipse-glsp/protocol](https://www.npmjs.com/package/@eclipse-glsp/protocol). - ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index e438834..3fed2b4 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -6,7 +6,5 @@ "reactNamespace": "JSX", "baseUrl": "." }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} From 0bf91dd2a6e4e95372799f75b4ed7bdca43a6799 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 8 Oct 2021 11:42:55 +0200 Subject: [PATCH 046/566] #400: Ensure snap follows mouse cursor on resize, fix cursor feedback - Don't let mouse cursor and feedback get out of sync - Provide proper mouse cursor feedback based for move and resize - Use 'Alt' for free resize instead of 'Shift' due to marquee conflict Signed-off-by: Martin Fleck --- packages/client/css/glsp-sprotty.css | 14 +++++++++++--- .../client/src/features/change-bounds/model.ts | 9 +++++++++ .../tool-feedback/change-bounds-tool-feedback.ts | 4 +++- .../src/features/tool-feedback/css-feedback.ts | 4 +++- .../src/features/tools/change-bounds-tool.ts | 10 +++++----- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 1065793..28d4b53 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -94,8 +94,16 @@ cursor: crosshair; } -.resize-mode { - cursor: none; +.move-mode { + cursor: move; +} + +.resize-nesw-mode { + cursor: nesw-resize; +} + +.resize-nwse-mode { + cursor: nwse-resize; } .element-deletion-mode { @@ -104,4 +112,4 @@ .marquee-mode { cursor: crosshair; -} +} \ No newline at end of file diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 79c6cd9..bc47522 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -61,6 +61,15 @@ export class SResizeHandle extends SChildElement implements Hoverable { hasFeature(feature: symbol): boolean { return feature === hoverFeedbackFeature; } + + isNwSeResize(): boolean { + return this.location === ResizeHandleLocation.TopLeft || this.location === ResizeHandleLocation.BottomRight; + } + + isNeSwResize(): boolean { + return this.location === ResizeHandleLocation.TopRight || this.location === ResizeHandleLocation.BottomLeft; + } + } export function addResizeHandles(element: SParentElement): void { diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 5bcf658..6e8f0cf 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -31,10 +31,10 @@ import { SModelRoot, TYPES } from 'sprotty'; - import { isNotUndefined } from '../../utils/smodel-util'; import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from '../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; +import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { ChangeBoundsTool } from '../tools/change-bounds-tool'; import { FeedbackCommand } from './model'; @@ -120,6 +120,7 @@ export class FeedbackMoveMouseListener extends MouseListener { const moveAction = this.getElementMoves(target, event, false); if (moveAction) { result.push(moveAction); + result.push(cursorFeedbackAction(CursorCSS.MOVE)); } } return result; @@ -224,6 +225,7 @@ export class FeedbackMoveMouseListener extends MouseListener { if (this.tool.movementRestrictor) { this.tool.deregisterFeedback([removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)], this); } + result.push(cursorFeedbackAction(CursorCSS.DEFAULT)); } this.hasDragged = false; this.startDragPosition = undefined; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 64b45ac..5964451 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -78,7 +78,9 @@ export enum CursorCSS { EDGE_RECONNECT = 'edge-reconnect-select-target-mode', OPERATION_NOT_ALLOWED = 'edge-modification-not-allowed-mode', ELEMENT_DELETION = 'element-deletion-mode', - RESIZE = 'resize-mode', + RESIZE_NESW = 'resize-nesw-mode', + RESIZE_NWSE = 'resize-nwse-mode', + MOVE = 'move-mode', MARQUEE = 'marquee-mode' } diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 4ffaf2c..9a7220b 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -162,7 +162,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements // consider resize handles ourselves const actions: Action[] = [ - cursorFeedbackAction(CursorCSS.RESIZE), + cursorFeedbackAction(this.activeResizeHandle.isNwSeResize() ? CursorCSS.RESIZE_NWSE : CursorCSS.RESIZE_NESW), applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE) ]; const positionUpdate = this.updatePosition(target, event); @@ -309,14 +309,14 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele // snap our delta and only send update if the position actually changes // otherwise accumulate delta until we do snap to an update - const positionUpdate = this.snap(this.positionDelta, target, !event.shiftKey); + const positionUpdate = this.snap(this.positionDelta, target, !event.altKey); if (positionUpdate.x === 0 && positionUpdate.y === 0) { return undefined; } - // we update our position so we need to reset our delta - this.positionDelta.x = 0; - this.positionDelta.y = 0; + // we update our position so we update our delta by the snapped position + this.positionDelta.x -= positionUpdate.x; + this.positionDelta.y -= positionUpdate.y; return positionUpdate; } return undefined; From 2b887325c4081bfaf5e38d0b7227bc217e3e1719 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 15 Oct 2021 18:44:57 +0200 Subject: [PATCH 047/566] Allow creating container with default modules --- examples/workflow-glsp/src/di.config.ts | 84 ++--------------- packages/client/src/base/container-modules.ts | 92 +++++++++++++++++++ packages/client/src/index.ts | 1 + 3 files changed, 99 insertions(+), 78 deletions(-) create mode 100644 packages/client/src/base/container-modules.ts diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index e4a0075..cd0c915 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -13,67 +13,34 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '../css/diagram.css'; -import 'balloon-css/balloon.min.css'; -import 'sprotty/css/edit-label.css'; - import { - boundsModule, - buttonModule, configureDefaultModelElements, configureModelElement, ConsoleLogger, - defaultGLSPModule, - defaultModule, + createClientContainer, DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, - edgeIntersectionModule, - edgeLayoutModule, editLabelFeature, - expandModule, - exportModule, - fadeModule, GLSP_TYPES, - glspCommandPaletteModule, - glspContextMenuModule, - glspDecorationModule, - glspEditLabelModule, - glspHoverModule, - glspMouseToolModule, - glspSelectModule, - glspServerCopyPasteModule, - glspViewportModule, GridSnapper, - labelEditUiModule, - layoutCommandsModule, LogLevel, - markerNavigatorModule, - modelHintsModule, - modelSourceModule, - modelSourceWatcherModule, - navigationModule, NoOverlapMovmentRestrictor, - openModule, overrideViewerOptions, - paletteModule, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, - routingModule, SCompartment, SCompartmentView, SEdge, SLabel, SLabelView, - toolFeedbackModule, - toolsModule, - TYPES, - validationModule, - zorderModule + TYPES } from '@eclipse-glsp/client'; +import 'balloon-css/balloon.min.css'; import { Container, ContainerModule } from 'inversify'; - +import 'sprotty/css/edit-label.css'; +import '../css/diagram.css'; import { directTaskEditor } from './direct-task-editing/di.config'; import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; import { IconView, WorkflowEdgeView } from './workflow-views'; @@ -104,49 +71,10 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind }); export default function createContainer(widgetId: string): Container { - const container = new Container(); - - container.load( - defaultModule, - defaultGLSPModule, - glspMouseToolModule, - validationModule, - glspSelectModule, - boundsModule, - glspViewportModule, - toolsModule, - glspHoverModule, - fadeModule, - exportModule, - expandModule, - openModule, - buttonModule, - modelSourceModule, - labelEditUiModule, - glspEditLabelModule, - workflowDiagramModule, - toolFeedbackModule, - modelHintsModule, - glspContextMenuModule, - glspServerCopyPasteModule, - modelSourceWatcherModule, - glspCommandPaletteModule, - paletteModule, - routingModule, - glspDecorationModule, - edgeLayoutModule, - zorderModule, - edgeIntersectionModule, - layoutCommandsModule, - directTaskEditor, - navigationModule, - markerNavigatorModule - ); - + const container = createClientContainer(workflowDiagramModule, directTaskEditor); overrideViewerOptions(container, { baseDiv: widgetId, hiddenDiv: widgetId + '_hidden' }); - return container; } diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts new file mode 100644 index 0000000..8a23251 --- /dev/null +++ b/packages/client/src/base/container-modules.ts @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Container, ContainerModule } from 'inversify'; +import { defaultModule } from 'sprotty'; +import { + boundsModule, + buttonModule, + edgeIntersectionModule, + edgeLayoutModule, + expandModule, + exportModule, + fadeModule, + glspCommandPaletteModule, + glspContextMenuModule, + glspDecorationModule, + glspEditLabelModule, + glspHoverModule, + glspMouseToolModule, + glspSelectModule, + glspServerCopyPasteModule, + glspViewportModule, + labelEditUiModule, + layoutCommandsModule, + markerNavigatorModule, + modelHintsModule, + modelSourceModule, + modelSourceWatcherModule, + navigationModule, + openModule, + paletteModule, + routingModule, + toolFeedbackModule, + toolsModule, + validationModule, + zorderModule +} from '../'; +import defaultGLSPModule from './di.config'; + +export const DEFAULT_MODULES = [ + defaultModule, + defaultGLSPModule, + boundsModule, + buttonModule, + edgeIntersectionModule, + edgeLayoutModule, + expandModule, + exportModule, + fadeModule, + glspCommandPaletteModule, + glspContextMenuModule, + glspDecorationModule, + glspEditLabelModule, + glspHoverModule, + glspMouseToolModule, + glspSelectModule, + glspServerCopyPasteModule, + glspViewportModule, + labelEditUiModule, + layoutCommandsModule, + markerNavigatorModule, + modelHintsModule, + modelSourceModule, + modelSourceWatcherModule, + navigationModule, + openModule, + paletteModule, + routingModule, + toolFeedbackModule, + toolsModule, + validationModule, + zorderModule +]; + +export function createClientContainer(...modules: ContainerModule[]): Container { + const container = new Container(); + container.load(...DEFAULT_MODULES, ...modules); + return container; +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7e6a655..3af82ea 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -44,6 +44,7 @@ export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; export * from './base/command-stack'; +export * from './base/container-modules'; export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context'; From 885c3264d873dfb73e76714136a0f98f18c5021f Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 18 Oct 2021 08:58:09 +0200 Subject: [PATCH 048/566] Add typescript doc --- packages/client/src/base/container-modules.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 8a23251..7f58f77 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -85,6 +85,27 @@ export const DEFAULT_MODULES = [ zorderModule ]; +/** + * Creates a GLSP Client container with the GLSP default modules and the specified custom `modules`. + * + * You can still customize the default modules in two ways. + * + * First, you can unload default modules and load them again with your custom code. + * + * ```typescript + * const container = createClientContainer(myModule1, myModule2); + * container.unload(modelSourceWatcherModule); + * container.load(myModelSourceWatcherModule); + * ``` + * + * Second, you can unbind or rebind implementations that are originally bound in one of the default modules. + * + * ```typescript + * rebind(NavigationTargetResolver).to(MyNavigationTargetResolver); + * ``` + * @param modules Custom modules to be loaded in addition to the default modules. + * @returns The created container. + */ export function createClientContainer(...modules: ContainerModule[]): Container { const container = new Container(); container.load(...DEFAULT_MODULES, ...modules); From 8cf29bf4959f69f5614f91a9a7dd7b4954552599 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Fri, 23 Jul 2021 13:35:22 +0200 Subject: [PATCH 049/566] 225: Moving a container and a contained node moves the node twice Fixes eclipse-glsp/glsp/issues/225 Signed-off-by: Camille Letavernier --- .../change-bounds-tool-feedback.ts | 18 +++++++++++++++-- .../src/features/tools/change-bounds-tool.ts | 20 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 6e8f0cf..6ac7779 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -27,6 +27,7 @@ import { MouseListener, MoveAction, Point, + SChildElement, SModelElement, SModelRoot, TYPES @@ -127,9 +128,12 @@ export class FeedbackMoveMouseListener extends MouseListener { } protected collectStartPositions(root: SModelRoot): void { - root.index + const selectedElements = root.index .all() - .filter(element => isSelectable(element) && element.selected) + .filter(element => isSelectable(element) && element.selected); + const elementsSet = new Set(selectedElements); + selectedElements + .filter(element => !this.isChildOfSelected(elementsSet, element)) .forEach(element => { if (isMoveable(element)) { this.elementId2startPos.set(element.id, element.position); @@ -137,6 +141,16 @@ export class FeedbackMoveMouseListener extends MouseListener { }); } + protected isChildOfSelected(selectedElements: Set, element: SModelElement): boolean { + while (element instanceof SChildElement) { + element = element.parent; + if (selectedElements.has(element)) { + return true; + } + } + return false; + } + protected getElementMoves(target: SModelElement, event: MouseEvent, isFinished: boolean): MoveAction | undefined { if (!this.startDragPosition) { return undefined; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 9a7220b..1b1a85a 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -27,6 +27,7 @@ import { isViewport, MouseListener, Point, + SChildElement, SConnectableElement, SetBoundsAction, SModelElement, @@ -207,15 +208,32 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected handleMoveElementsOnServer(target: SModelElement): Action[] { const result: Operation[] = []; const newBounds: ElementAndBounds[] = []; + const selectedElements: (SModelElement & BoundsAware)[] = []; forEachElement(target, isNonRoutableSelectedMovableBoundsAware, element => { - this.createElementAndBounds(element).forEach(bounds => newBounds.push(bounds)); + selectedElements.push(element); }); + + const selectionSet: Set = new Set(selectedElements); + selectedElements.filter(element => !this.isChildOfSelected(selectionSet, element)) + .map(element => this.createElementAndBounds(element)) + .forEach(bounds => newBounds.push(...bounds)); + if (newBounds.length > 0) { result.push(new ChangeBoundsOperation(newBounds)); } return result; } + protected isChildOfSelected(selectedElements: Set, element: SModelElement): boolean { + while (element instanceof SChildElement) { + element = element.parent; + if (selectedElements.has(element)) { + return true; + } + } + return false; + } + protected handleMoveRoutingPointsOnServer(target: SModelElement): Action[] { const result: Operation[] = []; const newRoutingPoints: ElementAndRoutingPoints[] = []; From 73de4af50371434ba9d09c71c757c92966ae5b2d Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Tue, 19 Oct 2021 16:46:59 +0200 Subject: [PATCH 050/566] 392: Add structured nodes to the Workflow example (#136) * 392: Add structured nodes to the Workflow example - Introduce Struct compartment with an X/YLayout (Freeform) - Add a custom VBoxLayout that supports both automatic (based on content) and manual resizing - Disable the collision movement restrictor in Workflow Example (Issue #394) Fixes eclipse-glsp/glsp/issues/392 Signed-off-by: Camille Letavernier Co-authored-by: Martin Fleck --- examples/workflow-glsp/css/diagram.css | 13 + examples/workflow-glsp/src/di.config.ts | 8 +- examples/workflow-glsp/src/model.ts | 15 ++ packages/client/src/base/types.ts | 5 +- .../client/src/features/layout/di.config.ts | 35 ++- .../src/features/layout/freeform-layout.ts | 132 ++++++++++ .../client/src/features/layout/vbox-layout.ts | 241 ++++++++++++++++++ packages/client/src/views/compartments.tsx | 37 +++ packages/client/src/views/index.ts | 1 + 9 files changed, 476 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/features/layout/freeform-layout.ts create mode 100644 packages/client/src/features/layout/vbox-layout.ts create mode 100644 packages/client/src/views/compartments.tsx diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index 3d724d4..8efe124 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -128,6 +128,10 @@ fill: lightblue; } +.category>.sprotty-node { + fill: lightblue; +} + text { stroke-width: 0; stroke: #000; @@ -176,6 +180,15 @@ polygon.sprotty-node { stroke-width: 4; } +.sprotty-comp { + stroke: transparent; + fill: transparent; +} + +.sprotty-comp.debug { + stroke: deepskyblue; +} + svg { border-style: solid; border-width: 1px; diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index cd0c915..e8ff399 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -22,10 +22,8 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, editLabelFeature, - GLSP_TYPES, GridSnapper, LogLevel, - NoOverlapMovmentRestrictor, overrideViewerOptions, RectangularNodeView, RevealNamedElementActionProvider, @@ -42,13 +40,12 @@ import { Container, ContainerModule } from 'inversify'; import 'sprotty/css/edit-label.css'; import '../css/diagram.css'; import { directTaskEditor } from './direct-task-editing/di.config'; -import { ActivityNode, Icon, TaskNode, WeightedEdge } from './model'; +import { ActivityNode, CategoryNode, Icon, TaskNode, WeightedEdge } from './model'; import { IconView, WorkflowEdgeView } from './workflow-views'; const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); - bind(GLSP_TYPES.IMovementRestrictor).to(NoOverlapMovmentRestrictor).inSingletonScope(); bind(TYPES.ISnapper).to(GridSnapper); bind(TYPES.ICommandPaletteActionProvider).to(RevealNamedElementActionProvider); bind(TYPES.IContextMenuItemProvider).to(DeleteElementContextMenuItemProvider); @@ -68,6 +65,9 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); configureModelElement(context, 'activityNode:fork', ActivityNode, RectangularNodeView); configureModelElement(context, 'activityNode:join', ActivityNode, RectangularNodeView); + + configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); + configureModelElement(context, 'struct', SCompartment, StructureCompartmentView); }); export default function createContainer(widgetId: string): Container { diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index 13257a7..2d9f967 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -109,3 +109,18 @@ export class Icon extends SShapeElement implements LayoutContainer { height: 32 }; } + +export class CategoryNode extends RectangularNode implements Nameable, WithEditableLabel { + static readonly DEFAULT_FEATURES = [deletableFeature, selectFeature, boundsFeature, + moveFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature, nameFeature, withEditLabelFeature]; + + name = ''; + + get editableLabel(): (SChildElement & EditableLabel) | undefined { + const label = this.children.find(element => element.type === 'label:heading'); + if (label && isEditableLabel(label)) { + return label; + } + return undefined; + } +} diff --git a/packages/client/src/base/types.ts b/packages/client/src/base/types.ts index 59f79bd..e9a0dc8 100644 --- a/packages/client/src/base/types.ts +++ b/packages/client/src/base/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,5 +33,6 @@ export const GLSP_TYPES = { IGLSPToolManager: Symbol.for('IGLSPToolManager'), ITool: Symbol.for('ITool'), IDefaultTool: Symbol.for('IDefaultTool'), - IEditModeListener: Symbol.for('IEditModeListener') + IEditModeListener: Symbol.for('IEditModeListener'), + LayoutRegistration: Symbol.for('LayoutRegistration') }; diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 0cc4d29..1eee1f2 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,39 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { configureCommand } from 'sprotty'; - +import { ContainerModule, inject, injectable, multiInject, optional } from 'inversify'; +import { configureCommand, configureLayout, ILogger, LayoutRegistration, LayoutRegistry, TYPES } from 'sprotty'; +import { FreeFormLayouter } from './freeform-layout'; import { AlignElementsCommand, ResizeElementsCommand } from './layout-commands'; +import { VBoxLayouterExt } from './vbox-layout'; -const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound) => { +const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { configureCommand({ bind, isBound }, ResizeElementsCommand); configureCommand({ bind, isBound }, AlignElementsCommand); + + configureLayout({ bind, isBound }, VBoxLayouterExt.KIND, VBoxLayouterExt); + configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); + + bind(OverridableLayoutRegistry).toSelf().inSingletonScope(); + rebind(TYPES.LayoutRegistry).toService(OverridableLayoutRegistry); }); export default layoutCommandsModule; + +@injectable() +export class OverridableLayoutRegistry extends LayoutRegistry { + + // ensure logger is already used in constructor as otherwise not usable + constructor(@multiInject(TYPES.LayoutRegistration) @optional() layouts: (LayoutRegistration)[] = [], + @inject(TYPES.ILogger) logger: ILogger) { + super(); + layouts.forEach(layout => { + if (this.hasKey(layout.layoutKind)) { + logger.warn('Layout kind is already defined and will be overridden: ', layout.layoutKind); + this.deregister(layout.layoutKind); + } + // allow overriding an existing layout kind + this.register(layout.layoutKind, layout.factory()); + }); + } +} diff --git a/packages/client/src/features/layout/freeform-layout.ts b/packages/client/src/features/layout/freeform-layout.ts new file mode 100644 index 0000000..202af37 --- /dev/null +++ b/packages/client/src/features/layout/freeform-layout.ts @@ -0,0 +1,132 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { + Bounds, + BoundsData, + Dimension, + isValidDimension, + LayoutContainer, + Point, + SChildElement, + SParentElement, + StatefulLayouter +} from 'sprotty'; +import { AbstractLayout } from 'sprotty/lib/features/bounds/abstract-layout'; +import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; + +/** + * Layouts children of a container with explit X/Y positions + */ + @injectable() + export class FreeFormLayouter extends AbstractLayout { + + static KIND = 'freeform'; + + layout(container: SParentElement & LayoutContainer, + layouter: StatefulLayouter): void { + const boundsData = layouter.getBoundsData(container); + const options = this.getLayoutOptions(container); + const childrenSize = this.getChildrenSize(container, options, layouter); + + // The real size of the compartment will be determined by the parent node layout; + // we only need to compute the bounds required to layout the children. + + const maxWidth = childrenSize.width > 0 ? childrenSize.width + options.paddingLeft + options.paddingRight : 0; + const maxHeight = childrenSize.height > 0 ? childrenSize.height + options.paddingTop + options.paddingBottom : 0; + if (maxWidth > 0 && maxHeight > 0) { + const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, maxWidth, maxHeight); + boundsData.boundsChanged = true; + } else { + boundsData.bounds = { x: boundsData.bounds!.x, y: boundsData.bounds!.y, width: 0, height: 0 }; + boundsData.boundsChanged = true; + } + } + + protected getChildrenSize(container: SParentElement & LayoutContainer, + containerOptions: AbstractLayoutOptions, + layouter: StatefulLayouter): Dimension { + let maxX = 0; + let maxY = 0; + container.children.forEach( + child => { + const bounds = layouter.getBoundsData(child).bounds; + if (bounds !== undefined && isValidDimension(bounds)) { + const childMaxX = bounds.x + bounds.width; + const childMaxY = bounds.y + bounds.height; + maxX = Math.max(maxX, childMaxX); + maxY = Math.max(maxY, childMaxY); + } + } + ); + return { + width: maxX, + height: maxY + }; + } + + protected layoutChild(child: SChildElement, + boundsData: BoundsData, + bounds: Bounds, + childOptions: AbstractLayoutOptions, + containerOptions: AbstractLayoutOptions, + currentOffset: Point, + maxWidth: number, + maxHeight: number): Point { + boundsData.bounds = { + x: (child as any).bounds.x, + y: (child as any).bounds.y, + width: bounds.width, + height: bounds.height + }; + boundsData.boundsChanged = true; + return currentOffset; + } + + protected getFinalContainerBounds(container: SParentElement & LayoutContainer, + lastOffset: Point, + options: AbstractLayoutOptions, + maxWidth: number, + maxHeight: number): Bounds { + const result = { + x: container.bounds.x, + y: container.bounds.y, + width: Math.max(options.minWidth, maxWidth + options.paddingLeft + options.paddingRight), + height: Math.max(options.minHeight, maxHeight + options.paddingTop + options.paddingBottom) + }; + + return result; + } + + protected getDefaultLayoutOptions(): AbstractLayoutOptions { + return { + resizeContainer: true, + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, + paddingRight: 0, + paddingFactor: 1, + minWidth: 0, + minHeight: 0 + }; + } + + protected spread(a: AbstractLayoutOptions, b: AbstractLayoutOptions): AbstractLayoutOptions { + return { ...a, ...b }; + } + +} diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/layout/vbox-layout.ts new file mode 100644 index 0000000..de3e3c8 --- /dev/null +++ b/packages/client/src/features/layout/vbox-layout.ts @@ -0,0 +1,241 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { + Bounds, + BoundsData, + Dimension, + EMPTY_BOUNDS, + isBoundsAware, + isLayoutableChild, + isValidDimension, + LayoutContainer, + Point, + SChildElement, + SModelElement, + SParentElement, + StatefulLayouter, + VBoxLayouter, + VBoxLayoutOptions +} from 'sprotty'; + +export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { + hGrab: boolean; + vGrab: boolean; + prefWidth: number | null; + prefHeight: number | null; +} + +/** + * Extends VBoxLayouter to support additional layout options + */ + @injectable() + export class VBoxLayouterExt extends VBoxLayouter { + + static KIND = VBoxLayouter.KIND; + + layout(container: SParentElement & LayoutContainer, + layouter: StatefulLayouter): void { + + const boundsData = layouter.getBoundsData(container); + const options = this.getLayoutOptions(container); + const childrenSize = this.getChildrenSize(container, options, layouter); + + const fixedSize = this.getFixedContainerBounds(container, options, layouter); + + const maxWidth = options.paddingFactor * ( + options.resizeContainer + ? Math.max(fixedSize.width - options.paddingLeft - options.paddingRight, childrenSize.width) + : Math.max(0, fixedSize.width - options.paddingLeft - options.paddingRight)); + const maxHeight = options.paddingFactor * ( + options.resizeContainer + ? Math.max(fixedSize.height - options.paddingTop - options.paddingBottom, childrenSize.height) + : Math.max(0, fixedSize.height - options.paddingTop - options.paddingBottom)); + + // Remaining size that can be grabbed by children with the vGrab option + const grabHeight: number = maxHeight - childrenSize.height; + // Number of children that request vGrab + // FIXME: This approach works fine when only 1 child uses VGrab, but may cause rounding issues + // when the grabHeight can't be equally shared by all children. + const grabbingChildren = container.children + .map(child => this.getChildLayoutOptions(child, options)) + .filter(opt => opt.vGrab) + .length; + + if (maxWidth > 0 && maxHeight > 0) { + const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight, grabHeight, grabbingChildren); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); + boundsData.boundsChanged = true; + } + } + + protected getChildrenSize(container: SParentElement & LayoutContainer, + containerOptions: VBoxLayoutOptionsExt, + layouter: StatefulLayouter): Dimension { + let maxWidth = -1; + let maxHeight = 0; + let isFirst = true; + container.children.forEach( + child => { + if (isLayoutableChild(child)) { + const bounds = layouter.getBoundsData(child).bounds; + if (bounds !== undefined && isValidDimension(bounds)) { + maxHeight += bounds.height; + if (isFirst) { + isFirst = false; + } else { + maxHeight += containerOptions.vGap; + } + maxWidth = Math.max(maxWidth, bounds.width); + } + } + } + ); + const result = { + width: maxWidth, + height: maxHeight + }; + return result; + } + + protected layoutChildren(container: SParentElement & LayoutContainer, + layouter: StatefulLayouter, + containerOptions: VBoxLayoutOptionsExt, + maxWidth: number, + maxHeight: number, grabHeight?: number, grabbingChildren?: number): Point { + let currentOffset: Point = { + x: containerOptions.paddingLeft + 0.5 * (maxWidth - (maxWidth / containerOptions.paddingFactor)), + y: containerOptions.paddingTop + 0.5 * (maxHeight - (maxHeight / containerOptions.paddingFactor)) + }; + + container.children.forEach( + child => { + if (isLayoutableChild(child)) { + const boundsData = layouter.getBoundsData(child); + const bounds = boundsData.bounds; + const childOptions = this.getChildLayoutOptions(child, containerOptions); + if (bounds !== undefined && isValidDimension(bounds)) { + currentOffset = this.layoutChild(child, boundsData, bounds, + childOptions, containerOptions, currentOffset, + maxWidth, maxHeight, grabHeight, grabbingChildren); + } + } + } + ); + return currentOffset; + } + + protected layoutChild(child: SChildElement, + boundsData: BoundsData, + bounds: Bounds, + childOptions: VBoxLayoutOptionsExt, + containerOptions: VBoxLayoutOptionsExt, + currentOffset: Point, + maxWidth: number, + maxHeight: number, + grabHeight?: number, + grabbingChildren?: number): Point { + let offset = super.layoutChild(child, boundsData, bounds, childOptions, containerOptions, currentOffset, maxWidth, maxHeight); + if (childOptions.hGrab) { + boundsData.bounds = { + x: boundsData.bounds!.x, + y: boundsData.bounds!.y, + width: maxWidth, + height: boundsData.bounds!.height + }; + boundsData.boundsChanged = true; + } + if (childOptions.vGrab && grabHeight && grabbingChildren) { + const height = boundsData.bounds!.height + (grabHeight / grabbingChildren); + boundsData.bounds = { + x: boundsData.bounds!.x, + y: boundsData.bounds!.y, + width: boundsData.bounds!.width, + height: height + }; + boundsData.boundsChanged = true; + offset = { x: currentOffset.x, y: currentOffset.y + height }; + } + return offset; + } + + protected getFixedContainerBounds(container: SModelElement, layoutOptions: VBoxLayoutOptionsExt, layouter: StatefulLayouter): Bounds { + const currentContainer = container; + // eslint-disable-next-line no-constant-condition + if (isBoundsAware(currentContainer)) { + const bounds = currentContainer.bounds; + const elementOptions = this.getElementLayoutOptions(currentContainer); + const width = elementOptions?.prefWidth ?? 0; + const height = elementOptions?.prefHeight ?? 0; + return { ...bounds, width, height }; + } + return EMPTY_BOUNDS; + } + + protected getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + return super.getChildLayoutOptions(child, containerOptions) as VBoxLayoutOptionsExt; + } + + protected getLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt { + return super.getLayoutOptions(element) as VBoxLayoutOptionsExt; + } + + protected getElementLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt | undefined { + return (element as any).layoutOptions; + } + + protected getFinalContainerBounds(container: SParentElement & LayoutContainer, + lastOffset: Point, options: VBoxLayoutOptionsExt, maxWidth: number, maxHeight: number): Bounds { + + const elementOptions = this.getElementLayoutOptions(container); + const width = elementOptions?.prefWidth ?? options.minWidth; + const height = elementOptions?.prefHeight ?? options.minHeight; + + const result = { + x: container.bounds.x, + y: container.bounds.y, + width: Math.max(width, maxWidth + options.paddingLeft + options.paddingRight), + height: Math.max(height, maxHeight + options.paddingTop + options.paddingBottom) + }; + + return result; + } + + protected getDefaultLayoutOptions(): VBoxLayoutOptionsExt { + return { + resizeContainer: true, + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 5, + paddingRight: 5, + paddingFactor: 1, + vGap: 1, + hAlign: 'center', + minWidth: 0, + minHeight: 0, + hGrab: false, + vGrab: false, + // eslint-disable-next-line no-null/no-null + prefHeight: null, + // eslint-disable-next-line no-null/no-null + prefWidth: null + }; + } + + protected spread(a: VBoxLayoutOptionsExt, b: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + return { ...a, ...b }; + } +} diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx new file mode 100644 index 0000000..5ca21d9 --- /dev/null +++ b/packages/client/src/views/compartments.tsx @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { BoundsAware, RenderingContext, SChildElement, SCompartment, ShapeView, svg } from 'sprotty/lib'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: svg }; + +@injectable() +export class StructureCompartmentView extends ShapeView { + + render(model: Readonly, context: RenderingContext): VNode | undefined { + if (!this.isVisible(model, context)) { + return undefined; + } + + return + + {context.renderChildren(model)} + ; + } + +} diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index 1d954f6..37f494f 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -19,3 +19,4 @@ export * from './glsp-edge-view'; export * from './issue-marker-view'; export * from './rounded-corner'; export * from './rounded-corner-view'; +export * from './compartments'; From 6d178f76c6dc73252db1247d2de887134bda5f83 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 19 Oct 2021 17:11:47 +0200 Subject: [PATCH 051/566] Fix for #392 --- examples/workflow-glsp/src/di.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index e8ff399..543e505 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -33,6 +33,7 @@ import { SEdge, SLabel, SLabelView, + StructureCompartmentView, TYPES } from '@eclipse-glsp/client'; import 'balloon-css/balloon.min.css'; From 69953ffb4a3760f0f3addc90d41368e2d424ac70 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 18 Oct 2021 16:55:39 +0200 Subject: [PATCH 052/566] #406 Update to Theia 1.18.0 and switch to Codicons - Upgrade sprotty version - Replace FontAwesome icons with codicons - Adapt issue marker svgs to match codicons - Adapt workflow example styling - Update documentation GIF - Add mocha option to ignore style imports (@font-face) Fixes eclipse-glsp/glsp/issues/406 --- examples/workflow-glsp/css/diagram.css | 29 +++++----- examples/workflow-glsp/src/workflow-views.tsx | 5 +- examples/workflow-standalone/app/diagram.html | 2 +- examples/workflow-standalone/package.json | 1 + .../workflow-standalone/webpack.config.js | 8 +++ packages/client/css/command-palette.css | 3 ++ packages/client/css/decoration.css | 5 +- packages/client/css/tool-palette.css | 45 +++++++++++----- packages/client/mocha.opts | 1 + packages/client/package.json | 4 +- .../auto-complete/auto-complete-widget.ts | 4 +- .../auto-complete/validation-decorator.ts | 11 ++-- packages/client/src/base/di.config.ts | 1 + .../src/features/tool-palette/tool-palette.ts | 53 +++++++++++-------- .../viewport/glsp-scroll-mouse-listener.ts | 3 +- .../client/src/views/issue-marker-view.tsx | 26 ++++++--- 16 files changed, 126 insertions(+), 75 deletions(-) diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index 8efe124..0b2952c 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -35,12 +35,11 @@ } .sprotty-node.mouseover:not(.selected) { - stroke-width: 3; + stroke-width: 2; } .sprotty-comp { line-height: 3px; - font-weight: normal; text-align: left; } @@ -53,16 +52,18 @@ } .sprotty-label.heading { - font-weight: bold; + font-weight: normal; } .sprotty-icon { - fill: black; - stroke-width: 0; + stroke: black; + stroke-width: 0.5; + fill: #ffffff; + opacity: 0.5; } .sprotty-label.icon { - fill: #fff; + fill: black; } .sprotty-button { @@ -72,15 +73,15 @@ .sprotty-edge { fill: none; - stroke-width: 2px; + stroke-width: 1px; } .sprotty-edge.selected { - stroke-width: 4px; + stroke-width: 3px; } .sprotty-edge.mouseover:not(.selected) { - stroke-width: 3px; + stroke-width: 2px; } .sprotty-edge > .sprotty-routing-handle { @@ -116,12 +117,12 @@ } .sprotty-node.marquee { - fill: grey; + fill: lightgray; opacity: 0.2; } .task.automated > .sprotty-node { - fill: gray; + fill: lightgray; } .task.manual > .sprotty-node { @@ -154,12 +155,12 @@ text { .sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium, .sprotty-edge.weighted.medium .arrow { - stroke: rgb(53, 53, 226); + stroke: #007fd4; } .sprotty-edge.weighted .arrow, .sprotty-edge.weighted.medium .arrow { - fill: rgb(53, 53, 226); + fill: #007fd4; } .sprotty-edge.weighted.high, @@ -177,7 +178,7 @@ polygon.sprotty-node { } .sprotty-node.selected { - stroke-width: 4; + stroke-width: 3px; } .sprotty-comp { diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 03a7691..7146d94 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -25,7 +25,6 @@ import { import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { svg } from 'sprotty'; - import { Icon } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -58,13 +57,13 @@ export class IconView implements IView { const radius = this.getRadius(); return ( - + {context.renderChildren(element)} ); } getRadius(): number { - return 16; + return 14; } } diff --git a/examples/workflow-standalone/app/diagram.html b/examples/workflow-standalone/app/diagram.html index 0ea5c71..c5c2128 100644 --- a/examples/workflow-standalone/app/diagram.html +++ b/examples/workflow-standalone/app/diagram.html @@ -9,6 +9,7 @@ body { width: 100%; height: 100%; + overflow: hidden; } #sprotty { @@ -22,7 +23,6 @@ svg { width: 100%; height: 100%; - overflow: hidden; } diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 224b67b..e8704d4 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -32,6 +32,7 @@ "circular-dependency-plugin": "^2.0.0", "core-js": "^2.5.1", "css-loader": "^2.1.0", + "file-loader": "^6.2.0", "http-server": "^0.11.1", "jenkins-mocha": "^6.0.0", "jsdom": "9.12.0", diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index 469f268..6beb7ed 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -47,6 +47,14 @@ module.exports = { test: /\.css$/, exclude: /\.useable\.css$/, use: ['style-loader', 'css-loader'] + }, + { + test: /\.(ttf)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]', + esModule: false + } } ] }, diff --git a/packages/client/css/command-palette.css b/packages/client/css/command-palette.css index 80ee32a..8b14674 100644 --- a/packages/client/css/command-palette.css +++ b/packages/client/css/command-palette.css @@ -30,6 +30,8 @@ .command-palette-suggestions .icon { padding-right: 0.3em; + display: flex; + align-self: center; } .command-palette-suggestions em { @@ -39,6 +41,7 @@ .command-palette-suggestions > div { padding: 0 4px; + display: flex; } .command-palette-suggestions .group { diff --git a/packages/client/css/decoration.css b/packages/client/css/decoration.css index d127923..297087c 100644 --- a/packages/client/css/decoration.css +++ b/packages/client/css/decoration.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,10 +18,11 @@ --glsp-error-foreground: red; --glsp-warning-foreground: yellow; --glsp-info-foreground: lightblue; + --glsp-issue-background: rgb(255 255 255 / 85%); } .sprotty-issue-background { - fill: none; + fill: var(--glsp-issue-background); } .sprotty-issue.sprotty-error { diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css index 2faf972..3314923 100644 --- a/packages/client/css/tool-palette.css +++ b/packages/client/css/tool-palette.css @@ -14,15 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -@import url('https://use.fontawesome.com/releases/v5.6.3/css/all.css'); /* Css for main container */ - .tool-palette { position: absolute; - right: 25px; + right: 30px; top: 25px; text-align: center; - width: 180px; + width: 225px; display: block; z-index: 1000; border-style: solid; @@ -47,21 +45,33 @@ .palette-header { padding: 0.4em; text-align: left; - font-size: 1.15em; - font-weight: bold; background: #cccccc; border: 1px solid rgba(60, 60, 60, 0.6); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; +} + +.header-icon { + display: flex; + align-items: center; } .header-icon i { margin-right: 0.2em; } +.header-tools { + display: flex; + align-items: center; +} + .header-tools i { border: 1px solid #cccccc; - padding: 0.2em; - margin-right: 0.2em; + padding: 0.15em; + margin-right: 0.15em; } .header-tools i:hover { @@ -87,8 +97,9 @@ } .group-header { - font-weight: bold; background: #cccccc; + display: flex; + align-items: center; } .group-header:hover { @@ -102,6 +113,12 @@ .tool-button { background: #ededee; padding: 0.4em; + display: flex; + align-items: center; +} + +.tool-button i { + margin-right: 0.4em; } .tool-button:hover { @@ -123,16 +140,16 @@ .minimize-palette-button { position: absolute; - right: 12px; - top: 25px; + right: 10px; + top: 35px; z-index: 1000; } .search-input { background: #dfdfdf; color: black; - border: v#BDDAEF; + border: #bddaef; padding-left: 3px; - width: 150px; - margin-left: 5px; + width: 100%; + margin: 3px; } diff --git a/packages/client/mocha.opts b/packages/client/mocha.opts index f1715af..4af313b 100644 --- a/packages/client/mocha.opts +++ b/packages/client/mocha.opts @@ -1,5 +1,6 @@ --require ts-node/register --require reflect-metadata/Reflect +--require ignore-styles --reporter spec --colors --watch-extensions ts,tsx diff --git a/packages/client/package.json b/packages/client/package.json index 253f183..da3aab8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -43,7 +43,9 @@ "@types/chai": "4.1.3", "@types/mocha": "^5.2.7", "@types/node": "10.14.18", + "@vscode/codicons": "^0.0.25", "chai": "^4.2.0", + "ignore-styles": "^5.0.1", "mocha": "^6.2.0", "mocha-jenkins-reporter": "0.4.5", "reflect-metadata": "^0.1.13", @@ -58,7 +60,7 @@ "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", "test": "mocha --opts ./mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", - "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --colors --reporter mocha-jenkins-reporter" + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter" }, "publishConfig": { "access": "public" diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 2fd9605..6f85284 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -15,9 +15,9 @@ ********************************************************************************/ import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; import { Action, ILogger, isAction, isLabeledAction, LabeledAction, SModelRoot } from 'sprotty/lib'; +import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; import { toArray } from 'sprotty/lib/utils/iterable'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; - import { ValidationStatus } from '../actions/edit-validation-actions'; import { isSetAutoCompleteValueAction } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; @@ -53,7 +53,7 @@ export interface TextSubmitHandler { const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); export class AutoCompleteWidget { - loadingIndicatorClasses = ['loading', 'fa', 'fa-spinner', 'fa-pulse', 'fa-3x', 'fa-fw']; + loadingIndicatorClasses = codiconCSSClasses('loading', false, true, ['loading']); protected containerElement: HTMLElement; protected inputElement: HTMLInputElement; diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index dac291f..5d9201d 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { codiconCSSString } from 'sprotty/lib/utils/codicon'; import { ValidationStatus } from '../actions/edit-validation-actions'; export interface IValidationDecorator { @@ -38,9 +39,9 @@ export namespace IValidationDecorator { export class ValidationDecorator implements IValidationDecorator { warningClasses = ['warning']; - warningIconClasses = ['fa', 'fa-question-circle']; + warningIconClasses = codiconCSSString('warning'); errorClasses = ['error']; - errorIconClasses = ['fa', 'fa-exclamation-circle']; + errorIconClasses = codiconCSSString('error'); isValidated = false; hasValidationError = false; @@ -67,7 +68,7 @@ export class ValidationDecorator implements IValidationDecorator { this.switchCssClasses(this.containerElement, this.errorClasses); const div = this.createDecorationDiv(); this.switchCssClasses(div, this.errorClasses); - div.innerHTML = ` ${message}`; + div.innerHTML = ` ${message}`; this.adjustPosition(); } @@ -75,7 +76,7 @@ export class ValidationDecorator implements IValidationDecorator { this.switchCssClasses(this.containerElement, this.warningClasses); const div = this.createDecorationDiv(); this.switchCssClasses(div, this.warningClasses); - div.innerHTML = ` ${message}`; + div.innerHTML = ` ${message}`; this.adjustPosition(); } @@ -96,7 +97,7 @@ export class ValidationDecorator implements IValidationDecorator { } protected decorationContainerWidth(): number { - return this.containerElement.clientWidth - 22; + return this.containerElement.clientWidth - 5; } protected adjustPosition(): void { diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index ef1fef4..bf6957b 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { InitializeResult } from '@eclipse-glsp/protocol'; +import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand, TYPES } from 'sprotty'; import '../../css/glsp-sprotty.css'; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 9b74c7f..1e6108a 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -26,8 +26,8 @@ import { SModelRoot, TYPES } from 'sprotty'; +import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; - import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { isSetContextActionsAction, RequestContextActions, SetContextActions } from '../../base/actions/context-actions'; import { EditModeListener, EditorContextService } from '../../base/editor-context'; @@ -37,9 +37,9 @@ import { RequestMarkersAction } from '../validation/validate'; import { PaletteItem } from './palette-item'; const CLICKED_CSS_CLASS = 'clicked'; -const SEARCH_ICON = 'fa-search'; -const PALETTE_ICON = 'fa-palette'; -const CHEVRON_DOWN = 'fa-chevron-down'; +const SEARCH_ICON_ID = 'search'; +const PALETTE_ICON_ID = 'symbol-color'; +const CHEVRON_DOWN_ICON_ID = 'chevron-right'; const PALETTE_HEIGHT = '500px'; @injectable() @@ -101,7 +101,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.containerElement.classList.add('collapsible-palette'); if (baseDiv) { const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); - const minimizeIcon = createIcon(['fas', CHEVRON_DOWN]); + const minimizeIcon = createIcon(CHEVRON_DOWN_ICON_ID); this.updateMinimizePaletteButtonTooltip(minPaletteDiv); minimizeIcon.onclick = _event => { if (this.isPaletteMaximized()) { @@ -110,10 +110,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.containerElement.style.maxHeight = PALETTE_HEIGHT; } this.updateMinimizePaletteButtonTooltip(minPaletteDiv); - changeCSSClass(minimizeIcon, PALETTE_ICON); - changeCSSClass(minimizeIcon, 'fa'); - changeCSSClass(minimizeIcon, 'fas'); - changeCSSClass(minimizeIcon, CHEVRON_DOWN); + changeCodiconClass(minimizeIcon, PALETTE_ICON_ID); + changeCodiconClass(minimizeIcon, CHEVRON_DOWN_ICON_ID); }; insertedDiv.appendChild(minimizeIcon); } @@ -192,7 +190,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } protected createDefaultToolButton(): HTMLElement { - const button = createIcon(['fas', 'fa-mouse-pointer', 'fa-xs', 'clicked']); + const button = createIcon('inspect'); button.id = 'btn_default_tools'; button.title = 'Enable selection tool'; button.onclick = this.onClickStaticToolButton(this.defaultToolsButton); @@ -200,21 +198,21 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } protected createMouseDeleteToolButton(): HTMLElement { - const deleteToolButton = createIcon(['fas', 'fa-eraser', 'fa-xs']); + const deleteToolButton = createIcon('chrome-close'); deleteToolButton.title = 'Enable deletion tool'; deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); return deleteToolButton; } protected createMarqueeToolButton(): HTMLElement { - const marqueeToolButton = createIcon(['far', 'fa-object-group', 'fa-xs']); + const marqueeToolButton = createIcon('screen-full'); marqueeToolButton.title = 'Enable marquee tool'; marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID); return marqueeToolButton; } protected createValidateButton(): HTMLElement { - const validateActionButton = createIcon(['fas', 'fa-check-square', 'fa-xs']); + const validateActionButton = createIcon('pass'); validateActionButton.title = 'Validate model'; validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; @@ -224,15 +222,15 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } protected createSearchButton(): HTMLElement { - const searchIcon = createIcon(['fas', SEARCH_ICON, 'state-icon', 'fa-xs']); + const searchIcon = createIcon(SEARCH_ICON_ID); searchIcon.onclick = _ev => { const searchField = document.getElementById(this.containerElement.id + '_search_field'); if (searchField) { - if (searchField.style.display === 'inline') { - searchField.style.display = 'none'; - } else { - searchField.style.display = 'inline'; + if (searchField.style.display === 'none') { + searchField.style.display = ''; searchField.focus(); + } else { + searchField.style.display = 'none'; } } }; @@ -256,7 +254,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected createHeaderTitle(): HTMLElement { const header = document.createElement('div'); header.classList.add('header-icon'); - header.appendChild(createIcon(['fa', 'fa-palette'])); + header.appendChild(createIcon(PALETTE_ICON_ID)); header.insertAdjacentText('beforeend', 'Palette'); return header; } @@ -265,7 +263,10 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const button = document.createElement('div'); button.tabIndex = index; button.classList.add('tool-button'); - button.innerHTML = item.label; + if (item.icon) { + button.appendChild(createIcon(item.icon)); + } + button.insertAdjacentText('beforeend', item.label); button.onclick = this.onClickCreateToolButton(button, item); button.onkeydown = ev => this.clearToolOnEscape(ev); return button; @@ -382,9 +383,9 @@ export function compare(a: PaletteItem, b: PaletteItem): number { return a.label.localeCompare(b.label); } -export function createIcon(cssClasses: string[]): HTMLElement { +export function createIcon(codiconId: string): HTMLElement { const icon = document.createElement('i'); - icon.classList.add(...cssClasses); + icon.classList.add(...codiconCSSClasses(codiconId)); return icon; } @@ -395,7 +396,7 @@ export function createToolGroup(item: PaletteItem): HTMLElement { const header = document.createElement('div'); header.classList.add('group-header'); if (item.icon) { - header.appendChild(createIcon(['fas', item.icon])); + header.appendChild(createIcon(item.icon)); } header.insertAdjacentText('beforeend', item.label); header.ondblclick = _ev => { @@ -412,3 +413,9 @@ export function createToolGroup(item: PaletteItem): HTMLElement { export function changeCSSClass(element: Element, css: string): void { element.classList.contains(css) ? element.classList.remove(css) : element.classList.add(css); } + +export function changeCodiconClass(element: Element, codiconId: string): void { + element.classList.contains(codiconCSSClasses(codiconId)[1]) + ? element.classList.remove(codiconCSSClasses(codiconId)[1]) + : element.classList.add(codiconCSSClasses(codiconId)[1]); +} diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 46c87f5..ca5f964 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from 'sprotty'; - import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @injectable() @@ -32,7 +31,7 @@ export class GLSPScrollMouseListener extends ScrollMouseListener implements IAct } } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { if (this.preventScrolling) { return []; } diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index f57b757..7d712b1 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -28,7 +28,7 @@ export class GIssueMarkerView extends IssueMarkerView { const group = ( - + {this.getGlspIssueMarkerBackground(maxSeverity)} @@ -37,19 +37,29 @@ export class GIssueMarkerView extends IssueMarkerView { return group; } + protected getGlspIssueMarkerBackground(severity: SIssueSeverity): VNode { + switch (severity) { + case 'warning': + return ; + case 'error': + case 'info': + return ; + } + } + protected get radius(): number { return 8; // var(--theia-icon-size)=16px => 16/2=8 } protected getGlspIssueMarkerPath(severity: SIssueSeverity): string { switch (severity) { - // paths used here are svg versions of FontAwesome icons, resized to var(--theia-icon-size) 16px - case 'error': // 'fa-times-circle' - return 'M 8,0 C 3.5806452,0 0,3.58065 0,8 c 0,4.41935 3.5806452,8 8,8 4.419355,0 8,-3.58065 8,-8 C 16,3.58065 12.419355,0 8,0 Z m 3.922581,10.1 c 0.151613,0.15161 0.151613,0.39677 0,0.54839 l -1.27742,1.27419 c -0.151613,0.15161 -0.396774,0.15161 -0.548387,0 L 8,9.80645 5.9,11.92258 c -0.1516129,0.15161 -0.3967742,0.15161 -0.5483871,0 L 4.0774194,10.64516 c -0.1516129,-0.15161 -0.1516129,-0.39677 0,-0.54839 L 6.1935484,8 4.0774194,5.9 c -0.1516129,-0.15161 -0.1516129,-0.39677 0,-0.54839 L 5.3548387,4.07419 c 0.1516129,-0.15161 0.3967742,-0.15161 0.5483871,0 L 8,6.19355 10.1,4.07742 c 0.151613,-0.15161 0.396774,-0.15161 0.548387,0 l 1.277419,1.27742 c 0.151613,0.15161 0.151613,0.39677 0,0.54839 L 9.8064516,8 Z'; - case 'warning': // 'fa-exclamation-circle' - return 'm 16,8 c 0,4.41926 -3.582032,8 -8,8 C 3.5820323,16 0,12.41926 0,8 0,3.58332 3.5820323,0 8,0 c 4.417968,0 8,3.58332 8,8 z M 8,9.6129 c -0.8195161,0 -1.483871,0.66436 -1.483871,1.48387 0,0.81952 0.6643549,1.48388 1.483871,1.48388 0.8195161,0 1.483871,-0.66436 1.483871,-1.48388 C 9.483871,10.27726 8.8195161,9.6129 8,9.6129 Z M 6.5911935,4.27916 6.8304839,8.66626 c 0.011194,0.20529 0.1809355,0.366 0.3865161,0.366 h 1.566 c 0.2055806,0 0.3753226,-0.16071 0.3865161,-0.366 L 9.4088065,4.27916 C 9.4209035,4.05742 9.2443548,3.87097 9.0222903,3.87097 H 6.9776774 c -0.2220645,0 -0.3985806,0.18645 -0.3864839,0.40819 z'; - case 'info': // 'fa-info-circle information' - return 'M 8.0000004,0 C 3.5820324,0 0,3.58332 0,8 0,12.41926 3.5820324,16 8.0000004,16 12.417968,16 16,12.41926 16,8 16,3.58332 12.417968,0 8.0000004,0 Z m 0,3.54839 c 0.748258,0 1.354839,0.60658 1.354839,1.35484 0,0.74825 -0.606581,1.35483 -1.354839,1.35483 -0.748258,0 -1.354839,-0.60658 -1.354839,-1.35483 0,-0.74826 0.606581,-1.35484 1.354839,-1.35484 z m 1.806452,8.19355 c 0,0.21377 -0.173323,0.38709 -0.387097,0.38709 h -2.83871 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.38709 v -0.7742 c 0,-0.21377 0.173323,-0.38709 0.387097,-0.38709 h 0.387097 V 8.51613 h -0.387097 c -0.213774,0 -0.387097,-0.17332 -0.387097,-0.3871 V 7.35484 c 0,-0.21378 0.173323,-0.3871 0.387097,-0.3871 h 2.064516 c 0.213774,0 0.387097,0.17332 0.387097,0.3871 v 3.22581 h 0.387097 c 0.213774,0 0.387097,0.17332 0.387097,0.38709 z'; + // paths used here are svg versions of codicons, resized to var(--theia-icon-size) 16px + case 'error': // 'codicon-error' + return 'm 8.8527788,0.00699568 c 1.8523692,0.115773 3.5889642,1.04195762 4.8624692,2.31546152 1.505048,1.620823 2.31546,3.5889653 2.31546,5.9044268 0,1.852369 -0.694637,3.588965 -1.852367,5.094015 -1.157733,1.389277 -2.778554,2.431235 -4.6309235,2.778554 C 7.6950478,16.446772 5.8426784,16.215226 4.2218552,15.289042 2.6010319,14.362857 1.3275281,12.97358 0.63288942,11.236984 -0.06174907,9.5003878 -0.17752216,7.5322456 0.4013433,5.7956494 0.98020876,3.9432802 2.0221665,2.4382302 3.6429898,1.3962725 5.14804,0.35431486 7.0004094,-0.10877742 8.8527788,0.00699568 Z M 9.4316445,14.941722 c 1.5050505,-0.347319 2.8943275,-1.15773 3.9362845,-2.431234 0.926185,-1.273504 1.50505,-2.778554 1.389277,-4.3993771 0,-1.8523692 -0.69464,-3.7047384 -1.968142,-4.9782422 C 11.631331,1.9751379 10.242054,1.2804995 8.6212324,1.1647264 7.1161824,1.0489533 5.4953592,1.3962725 4.2218552,2.3224572 2.9483511,3.2486418 2.0221665,4.5221456 1.5590742,6.1429686 c -0.4630923,1.50505 -0.4630923,3.1258731 0.2315462,4.6309234 0.6946384,1.50505 1.7365963,2.66278 3.1258734,3.473192 1.3892769,0.810411 3.0101002,1.041958 4.5151507,0.694638 z M 8.042367,7.5322456 10.82092,4.6379187 11.631331,5.4483302 8.8527788,8.3426571 11.631331,11.236984 10.82092,12.047395 8.042367,9.1530686 5.2638128,12.047395 4.4534013,11.236984 7.2319555,8.3426571 4.4534013,5.4483302 5.2638128,4.6379187 Z'; + case 'warning': // 'codicon-warning' + return 'M 7.5587159,0.61312771 H 8.565869 L 16.050847,14.644601 15.54727,15.491525 H 0.55442396 L 0.05084746,14.644601 Z M 8.0622924,2.0780776 1.5157974,14.347033 H 14.585897 Z M 8.7776,13.202541 V 12.058049 H 7.3469848 v 1.144492 z M 7.3469848,10.913557 V 6.3355883 H 8.7776 v 4.5779687 z'; + case 'info': // 'codicon-info' + return 'M 8.7624795,0.01729008 A 7.8878137,7.8878137 0 0 1 13.625085,2.3592747 8.1894066,8.1894066 0 0 1 14.158672,13.251418 7.9458123,7.9458123 0 0 1 4.2061071,15.26977 8.1198082,8.1198082 0 0 1 0.58699265,11.209866 8.259005,8.259005 0 0 1 0.32019895,5.7463948 8.1198082,8.1198082 0 0 1 3.533323,1.3500986 7.8878137,7.8878137 0 0 1 8.7624795,0.01729008 Z M 9.3099868,14.921778 A 6.8322386,6.8322386 0 0 0 13.265493,12.520635 7.0410337,7.0410337 0 0 0 12.801504,3.1712556 6.7510406,6.7510406 0 0 0 4.1829077,2.3128758 7.029434,7.029434 0 0 0 4.762894,14.248994 6.7394408,6.7394408 0 0 0 9.3099868,14.921778 Z M 7.3786322,5.7811939 H 8.8285979 V 4.6212213 H 7.3786322 Z M 8.8285979,6.9411665 V 11.581057 H 7.3786322 V 6.9411665 Z'; } } } From 23a37776a4bc21b20efed3ee91bf60f36d68b79f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 20 Oct 2021 09:07:26 +0200 Subject: [PATCH 053/566] #256 Migrate action definitions to protocol package (#141) * #256 Migrate action definitions to protocol package Migrate action definitions and other common types into the protocol package to remove the dependency to the sprotty/glsp client. This enables general reuse of the protocol without sprotty (e.g. in the vscode-glsp-integration) and also eases the implementation of alternative glsp clients or servers in the future. eclipse-glsp/glsp/issues/256 Contributed on behalf of STMicroelectronics --- examples/workflow-glsp/css/diagram.css | 2 +- examples/workflow-glsp/src/model.ts | 14 +- packages/client/css/glsp-sprotty.css | 2 +- packages/client/src/base/action-dispatcher.ts | 4 +- .../src/base/actions/focus-change-action.ts | 2 +- .../src/base/{args.ts => argumentable.ts} | 5 +- .../auto-complete/auto-complete-actions.ts | 2 +- .../auto-complete/auto-complete-widget.ts | 5 +- .../auto-complete/validation-decorator.ts | 2 +- packages/client/src/base/command-stack.ts | 3 +- packages/client/src/base/di.config.ts | 5 +- .../src/base/drag-aware-mouse-listener.ts | 3 +- ...r-context.ts => editor-context-service.ts} | 29 +-- packages/client/src/base/focus-tracker.ts | 4 +- .../base/model-initialization-constraint.ts | 5 +- .../src/base/model/update-model-command.ts | 12 +- .../client/src/base/operations/operation.ts | 204 ------------------ .../base/tool-manager/glsp-tool-manager.ts | 8 +- .../src/features/change-bounds/model.ts | 1 - .../change-bounds/movement-restrictor.ts | 4 +- .../client/src/features/change-bounds/snap.ts | 5 +- .../server-command-palette-provider.ts | 7 +- .../delete-element-context-menu.ts | 7 +- .../server-context-menu-provider.ts | 7 +- .../copy-paste/copy-paste-context-menu.ts | 15 +- .../features/copy-paste/copy-paste-handler.ts | 7 +- .../src/features/copy-paste/di.config.ts | 5 +- .../features/decoration/decoration-placer.ts | 3 +- .../edit-label/edit-label-validator.ts | 2 +- .../client/src/features/hints/di.config.ts | 5 +- packages/client/src/features/hints/model.ts | 3 +- .../hints/request-type-hints-action.ts | 41 ---- .../client/src/features/hints/type-hints.ts | 65 +----- .../client/src/features/hover/di.config.ts | 3 +- packages/client/src/features/hover/hover.ts | 6 +- .../client/src/features/layout/di.config.ts | 7 +- .../src/features/layout/freeform-layout.ts | 45 ++-- .../features/layout/layout-commands.spec.ts | 11 +- .../src/features/layout/layout-commands.ts | 4 +- .../client/src/features/layout/vbox-layout.ts | 117 +++++----- .../model-source-watcher/di.config.ts | 7 +- .../model-source-changed-action-handler.ts | 12 +- .../src/features/mouse-tool/mouse-tool.ts | 3 +- .../src/features/navigation/di.config.ts | 14 +- .../navigation/navigation-action-handler.ts | 78 ++----- .../navigation-target-resolver.spec.ts | 6 +- .../navigation/navigation-target-resolver.ts | 96 +-------- .../client/src/features/save/di.config.ts | 2 +- .../save/{save.ts => save-keylistener.ts} | 12 +- .../features/select/selection-service.spec.ts | 4 +- .../src/features/select/selection-service.ts | 6 +- .../change-bounds-tool-feedback.ts | 7 +- .../tool-feedback/creation-tool-feedback.ts | 4 +- .../features/tool-feedback/css-feedback.ts | 4 +- .../tool-feedback/edge-edit-tool-feedback.ts | 4 +- .../feedback-action-dispatcher.ts | 3 +- .../tool-feedback/marquee-tool-feedback.ts | 4 +- .../src/features/tool-feedback/view.tsx | 4 +- .../src/features/tool-palette/palette-item.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 5 +- .../src/features/tools/base-glsp-tool.ts | 8 +- .../src/features/tools/change-bounds-tool.ts | 21 +- .../client/src/features/tools/delete-tool.ts | 4 +- .../client/src/features/tools/di.config.ts | 5 +- .../src/features/tools/edge-creation-tool.ts | 4 +- .../src/features/tools/edge-edit-tool.ts | 3 +- .../src/features/tools/marquee-mouse-tool.ts | 17 +- .../client/src/features/tools/marquee-tool.ts | 4 +- .../src/features/tools/node-creation-tool.ts | 4 +- .../src/features/validation/issue-marker.ts | 5 +- .../validation/marker-navigator.spec.ts | 8 +- .../features/validation/marker-navigator.ts | 5 +- .../src/features/validation/validate.ts | 37 +--- .../viewport/glsp-scroll-mouse-listener.ts | 3 +- packages/client/src/index.ts | 16 +- .../src/model-source/glsp-diagram-server.ts | 24 +-- .../src/model-source/glsp-server-status.ts | 38 ---- packages/client/src/protocol-reexport.ts | 64 ++++++ packages/client/src/utils/argument-utils.ts | 3 +- packages/client/src/utils/layout-utils.ts | 4 +- packages/client/src/utils/marker.ts | 28 +-- packages/client/src/utils/smodel-util.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 3 +- packages/client/src/views/compartments.tsx | 12 +- packages/client/src/views/glsp-edge-view.tsx | 4 +- packages/client/src/views/rounded-corner.ts | 4 +- packages/protocol/package.json | 3 +- .../src/action-protocol/base-protocol.ts | 122 +++++++++++ .../action-protocol/client-notification.ts | 95 ++++++++ .../src/action-protocol/clipboard.ts} | 74 +++++-- .../src/action-protocol/contexts.ts} | 27 ++- .../src/action-protocol/edge-modification.ts | 54 +++++ .../src/action-protocol/element-creation.ts | 82 +++++++ .../src/action-protocol/element-hover.ts | 59 +++++ .../src/action-protocol/element-navigation.ts | 176 +++++++++++++++ .../src/action-protocol/element-selection.ts | 50 +++++ .../action-protocol/element-text-editing.ts} | 37 +++- .../src/action-protocol/element-type-hints.ts | 99 +++++++++ .../src/action-protocol/element-validation.ts | 83 +++++++ .../protocol/src/action-protocol/index.ts | 37 ++++ .../src/action-protocol/mode-layout.ts | 75 +++++++ .../src/action-protocol/model-data.ts | 88 ++++++++ .../src/action-protocol/model-edit-mode.ts} | 4 +- .../src/action-protocol/model-saving.ts | 54 +++++ .../src/action-protocol/model-structure.ts} | 26 ++- .../src/action-protocol/node-modification.ts | 50 +++++ .../src/action-protocol/tool-palette.ts | 63 ++++++ .../protocol/src/action-protocol/types.ts | 107 +++++++++ .../src/action-protocol/undo-redo.ts} | 19 +- .../protocol/src/action-protocol/viewport.ts | 53 +++++ packages/protocol/src/glsp-client.ts | 11 +- packages/protocol/src/index.ts | 9 +- .../src/utils/array-util.ts} | 0 .../protocol/src/{ => utils}/launch-util.ts | 0 packages/protocol/src/utils/typeguard-util.ts | 65 ++++++ 115 files changed, 1926 insertions(+), 986 deletions(-) rename packages/client/src/base/{args.ts => argumentable.ts} (93%) rename packages/client/src/base/{editor-context.ts => editor-context-service.ts} (80%) delete mode 100644 packages/client/src/base/operations/operation.ts delete mode 100644 packages/client/src/features/hints/request-type-hints-action.ts rename packages/client/src/features/save/{save.ts => save-keylistener.ts} (75%) delete mode 100644 packages/client/src/model-source/glsp-server-status.ts create mode 100644 packages/client/src/protocol-reexport.ts create mode 100644 packages/protocol/src/action-protocol/base-protocol.ts create mode 100644 packages/protocol/src/action-protocol/client-notification.ts rename packages/{client/src/features/copy-paste/copy-paste-actions.ts => protocol/src/action-protocol/clipboard.ts} (55%) rename packages/{client/src/base/actions/context-actions.ts => protocol/src/action-protocol/contexts.ts} (58%) create mode 100644 packages/protocol/src/action-protocol/edge-modification.ts create mode 100644 packages/protocol/src/action-protocol/element-creation.ts create mode 100644 packages/protocol/src/action-protocol/element-hover.ts create mode 100644 packages/protocol/src/action-protocol/element-navigation.ts create mode 100644 packages/protocol/src/action-protocol/element-selection.ts rename packages/{client/src/base/actions/edit-validation-actions.ts => protocol/src/action-protocol/element-text-editing.ts} (72%) create mode 100644 packages/protocol/src/action-protocol/element-type-hints.ts create mode 100644 packages/protocol/src/action-protocol/element-validation.ts create mode 100644 packages/protocol/src/action-protocol/index.ts create mode 100644 packages/protocol/src/action-protocol/mode-layout.ts create mode 100644 packages/protocol/src/action-protocol/model-data.ts rename packages/{client/src/base/actions/edit-mode-action.ts => protocol/src/action-protocol/model-edit-mode.ts} (87%) create mode 100644 packages/protocol/src/action-protocol/model-saving.ts rename packages/{client/src/features/model-source-watcher/model-source-changed-action.ts => protocol/src/action-protocol/model-structure.ts} (55%) create mode 100644 packages/protocol/src/action-protocol/node-modification.ts create mode 100644 packages/protocol/src/action-protocol/tool-palette.ts create mode 100644 packages/protocol/src/action-protocol/types.ts rename packages/{client/src/features/undo-redo/model.ts => protocol/src/action-protocol/undo-redo.ts} (69%) create mode 100644 packages/protocol/src/action-protocol/viewport.ts rename packages/{client/src/utils/array-utils.ts => protocol/src/utils/array-util.ts} (100%) rename packages/protocol/src/{ => utils}/launch-util.ts (100%) create mode 100644 packages/protocol/src/utils/typeguard-util.ts diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index 0b2952c..13369aa 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -129,7 +129,7 @@ fill: lightblue; } -.category>.sprotty-node { +.category > .sprotty-node { fill: lightblue; } diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index 2d9f967..2bdb785 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -111,8 +111,18 @@ export class Icon extends SShapeElement implements LayoutContainer { } export class CategoryNode extends RectangularNode implements Nameable, WithEditableLabel { - static readonly DEFAULT_FEATURES = [deletableFeature, selectFeature, boundsFeature, - moveFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature, nameFeature, withEditLabelFeature]; + static readonly DEFAULT_FEATURES = [ + deletableFeature, + selectFeature, + boundsFeature, + moveFeature, + layoutContainerFeature, + fadeFeature, + hoverFeedbackFeature, + popupFeature, + nameFeature, + withEditLabelFeature + ]; name = ''; diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 28d4b53..59111c9 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -112,4 +112,4 @@ .marquee-mode { cursor: crosshair; -} \ No newline at end of file +} diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 0359778..25d7f89 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isResponseAction, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { inject } from 'inversify'; -import { Action, ActionDispatcher, isResponseAction, RequestAction, ResponseAction } from 'sprotty'; - +import { ActionDispatcher } from 'sprotty'; import { ModelInitializationConstraint } from './model-initialization-constraint'; export class GLSPActionDispatcher extends ActionDispatcher { diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/actions/focus-change-action.ts index bd0d1ef..e6f2bfe 100644 --- a/packages/client/src/base/actions/focus-change-action.ts +++ b/packages/client/src/base/actions/focus-change-action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from 'sprotty'; +import { Action } from '@eclipse-glsp/protocol'; export class FocusStateChangedAction implements Action { static KIND = 'focusStateChanged'; diff --git a/packages/client/src/base/args.ts b/packages/client/src/base/argumentable.ts similarity index 93% rename from packages/client/src/base/args.ts rename to packages/client/src/base/argumentable.ts index eb334fe..ce40175 100644 --- a/packages/client/src/base/args.ts +++ b/packages/client/src/base/argumentable.ts @@ -13,12 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; import { SModelElement } from 'sprotty'; -export interface Args { - [key: string]: string | number | boolean; -} - export interface SArgumentable { args: Args; } diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts index 2532a02..faf499a 100644 --- a/packages/client/src/base/auto-complete/auto-complete-actions.ts +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LabeledAction } from 'sprotty/lib'; +import { LabeledAction } from '@eclipse-glsp/protocol'; export interface SetAutoCompleteValueAction extends LabeledAction { readonly text: string; diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 6f85284..05ef531 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isAction, isLabeledAction, LabeledAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; -import { Action, ILogger, isAction, isLabeledAction, LabeledAction, SModelRoot } from 'sprotty/lib'; -import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; +import { codiconCSSClasses, ILogger, SModelRoot } from 'sprotty'; import { toArray } from 'sprotty/lib/utils/iterable'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { ValidationStatus } from '../actions/edit-validation-actions'; import { isSetAutoCompleteValueAction } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index 5d9201d..cca79e1 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { codiconCSSString } from 'sprotty/lib/utils/codicon'; -import { ValidationStatus } from '../actions/edit-validation-actions'; +import { ValidationStatus } from '@eclipse-glsp/protocol'; export interface IValidationDecorator { decorateValidationResult(status: ValidationStatus): void; diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 854d958..ed5a70f 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { RedoOperation, UndoOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from 'sprotty'; -import { RedoOperation, UndoOperation } from '../features/undo-redo/model'; - @injectable() export class GLSPCommandStack extends CommandStack { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index bf6957b..76951dd 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -13,16 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { InitializeResult } from '@eclipse-glsp/protocol'; +import { InitializeResult, SetEditModeAction } from '@eclipse-glsp/protocol'; import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand, TYPES } from 'sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; -import { SetEditModeAction } from './actions/edit-mode-action'; import { FocusStateChangedAction } from './actions/focus-change-action'; import { GLSPCommandStack } from './command-stack'; -import { EditorContextService } from './editor-context'; +import { EditorContextService } from './editor-context-service'; import { FocusTracker } from './focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; import { GLSPModelRegistry } from './model/model-registry'; diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index e19dea4..43a1fa4 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MouseListener, SModelElement } from 'sprotty'; +import { Action } from '@eclipse-glsp/protocol'; +import { MouseListener, SModelElement } from 'sprotty'; /** * A mouse listener that is aware of prior mouse dragging. diff --git a/packages/client/src/base/editor-context.ts b/packages/client/src/base/editor-context-service.ts similarity index 80% rename from packages/client/src/base/editor-context.ts rename to packages/client/src/base/editor-context-service.ts index 9b818fc..11740f9 100644 --- a/packages/client/src/base/editor-context.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -13,38 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Args, distinctAdd, EditMode, EditorContext, isSetEditModeAction, remove } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; -import { Action, IActionHandler, ModelSource, MousePositionTracker, Point, SModelElement, SModelRoot, TYPES } from 'sprotty'; - +import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot, TYPES } from 'sprotty'; import { SelectionService } from '../features/select/selection-service'; -import { distinctAdd, remove } from '../utils/array-utils'; -import { EditMode, isSetEditModeAction } from './actions/edit-mode-action'; -import { Args } from './args'; import { isSourceUriAware } from './source-uri-aware'; import { GLSP_TYPES } from './types'; -/** - * The `EditorContext` may be used to represent the current state of the editor for particular actions. - * It encompasses the last recorded mouse position, the list of selected elements, and may contain - * custom arguments to encode additional state information. - */ -export interface EditorContext { - /** - * The list of selected element identifiers. - */ - readonly selectedElementIds: string[]; - - /** - * The last recorded mouse position. - */ - readonly lastMousePosition?: Point; - - /** - * Custom arguments. - */ - readonly args?: Args; -} - export interface EditModeListener { editModeChanged(newValue: string, oldvalue: string): void; } diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts index c7c71e1..827f044 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus-tracker.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from 'sprotty'; - +import { IActionHandler, ICommand, TYPES, ViewerOptions } from 'sprotty'; import { isFocusStateChangedAction } from './actions/focus-change-action'; @injectable() diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts index 30c433b..fd320de 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, UpdateModelAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Action, InitializeCanvasBoundsAction, UpdateModelAction } from 'sprotty'; +import { InitializeCanvasBoundsAction } from 'sprotty'; import { Deferred } from 'sprotty/lib/utils/async'; /** diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index a2278ac..b187d18 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isSetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { - Action, ActionHandlerRegistry, Command, CommandActionHandler, @@ -23,12 +23,10 @@ import { CommandReturn, IActionHandler, ILogger, - SetModelAction, - SetModelCommand, SModelRoot, - TYPES + TYPES, + UpdateModelCommand } from 'sprotty'; -import { UpdateModelAction, UpdateModelCommand } from 'sprotty/lib/features/update/update-model'; import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../features/tool-feedback/model'; import { GLSP_TYPES } from '../types'; @@ -44,10 +42,6 @@ export class SetModelActionHandler implements IActionHandler { } } -export function isSetModelAction(action: Action): action is SetModelAction { - return action !== undefined && action.kind === SetModelCommand.KIND && (action as SetModelAction).newRoot !== undefined; -} - export interface SModelRootListener { modelRootChanged(root: Readonly): void; } diff --git a/packages/client/src/base/operations/operation.ts b/packages/client/src/base/operations/operation.ts deleted file mode 100644 index d0b197c..0000000 --- a/packages/client/src/base/operations/operation.ts +++ /dev/null @@ -1,204 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Action, ApplyLabelEditAction, ElementAndBounds, isAction, LayoutAction, Point } from 'sprotty'; -import { Args } from '../args'; - -/** - * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the - * server. After a successful modification, the server sends the updated model back to the client using the `UpdateModelAction`. - */ -export interface Operation extends Action {} - -export interface CreateOperation extends Operation { - elementTypeId: string; - args?: Args; -} - -export function isCreateOperation(object?: any): object is CreateOperation { - return isAction(object) && 'elementTypeId' in object; -} - -export class CreateNodeOperation implements CreateOperation { - static readonly KIND = 'createNode'; - - constructor( - public readonly elementTypeId: string, - public location?: Point, - public containerId?: string, - public args?: Args, - public readonly kind: string = CreateNodeOperation.KIND - ) {} -} - -export function isCreateNodeOperation(object?: any): object is CreateNodeOperation { - return isCreateOperation() && object.kind === CreateNodeOperation.KIND; -} - -export class CreateEdgeOperation implements CreateOperation { - static readonly KIND = 'createEdge'; - - constructor( - public readonly elementTypeId: string, - public sourceElementId: string, - public targetElementId: string, - public args?: Args, - public readonly kind: string = CreateEdgeOperation.KIND - ) {} -} - -export function isCreateConnectionOperation(object?: any): object is CreateEdgeOperation { - return isCreateOperation() && object.kind === CreateEdgeOperation.KIND; -} - -export class DeleteElementOperation implements Operation { - static readonly KIND = 'deleteElement'; - constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) {} -} - -export function isDeleteElementOperation(object?: any): object is DeleteElementOperation { - return isAction(object) && object.kind === DeleteElementOperation.KIND && 'elementIds' in object; -} - -export class ChangeBoundsOperation implements Operation { - static readonly KIND = 'changeBounds'; - constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) {} -} - -export function isChangeBoundsOperation(object?: any): object is ChangeBoundsOperation { - return isAction(object) && object.kind === ChangeBoundsOperation.KIND && 'newBounds' in object; -} - -export class ChangeContainerOperation implements Operation { - static readonly KIND = 'changeContainer'; - constructor( - public readonly elementId: string, - public readonly targetContainerId: string, - public readonly location?: Point, - public readonly kind: string = ChangeContainerOperation.KIND - ) {} -} - -export function isChangeContainerOperation(object?: any): object is ChangeContainerOperation { - return ( - isAction(object) && - object.kind === ChangeContainerOperation.KIND && - 'elementId' in object && - typeof object['elementId'] === 'string' && - 'targetContainerId' in object && - typeof object['targetContainerId'] === 'string' - ); -} - -export class ReconnectEdgeOperation implements Operation { - static readonly KIND = 'reconnectEdge'; - constructor( - public readonly edgeElementId: string, - public readonly sourceElementId: string, - public readonly targetElementId: string, - public readonly kind: string = ReconnectEdgeOperation.KIND - ) {} -} - -export function isReconnectEdgeOperation(object?: any): object is ReconnectEdgeOperation { - return ( - isAction(object) && - object.kind === ReconnectEdgeOperation.KIND && - 'edgeElementId' in object && - typeof object['edgeElementId'] === 'string' && - 'sourceElementId' in object && - typeof object['sourceElementId'] === 'string' && - 'targetElementId' in object && - typeof object['targetElementId'] === 'string' - ); -} - -export class ChangeRoutingPointsOperation implements Operation { - static readonly KIND = 'changeRoutingPoints'; - constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) {} -} - -export function isChangeRoutingsPointsOperation(object?: any): object is ChangeRoutingPointsOperation { - return ( - isAction(object) && - object.kind === ChangeRoutingPointsOperation.KIND && - 'newRoutingPoints' in object && - typeof object['newRoutingPoints'] === 'string' - ); -} - -export class CompoundOperation implements Operation { - static readonly KIND = 'compound'; - constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) {} -} - -export function isCompoundOperation(object?: any): object is CompoundOperation { - return isAction(object) && object.kind === CompoundOperation.KIND && 'operationList' in object; -} - -/** - * The `ElementAndRoutingPoints` type is used to associate an edge with specific routing points. - */ -export interface ElementAndRoutingPoints { - /** - * The identifier of an element. - */ - elementId: string; - - /** - * The new list of routing points. - */ - newRoutingPoints?: Point[]; -} - -export abstract class TriggerElementCreationAction implements Action { - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = 'unknown') {} -} - -export class TriggerNodeCreationAction extends TriggerElementCreationAction { - static readonly KIND = 'triggerNodeCreation'; - - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind = TriggerNodeCreationAction.KIND) { - super(elementTypeId, args, kind); - } -} - -export class TriggerEdgeCreationAction extends TriggerElementCreationAction { - static readonly KIND = 'triggerEdgeCreation'; - - constructor( - public readonly elementTypeId: string, - readonly args?: Args, - public readonly kind: string = TriggerEdgeCreationAction.KIND - ) { - super(elementTypeId, args, kind); - } -} - -export function isTriggerElementTypeCreationAction(object?: any): object is TriggerElementCreationAction { - return isAction(object) && 'elementTypeId' in object; -} - -export function isTriggerNodeCreationAction(object?: any): object is TriggerNodeCreationAction { - return isTriggerElementTypeCreationAction(object) && object.kind === TriggerNodeCreationAction.KIND; -} - -export function isTriggerEdgeCreationAction(object?: any): object is TriggerEdgeCreationAction { - return isTriggerElementTypeCreationAction(object) && object.kind === TriggerEdgeCreationAction.KIND; -} - -export class LayoutOperation extends LayoutAction implements Operation {} - -export class ApplyLabelEditOperation extends ApplyLabelEditAction implements Operation {} diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index 8c4f34d..28a0a28 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { distinctAdd, EditMode } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { Tool, ToolManager } from 'sprotty'; - -import { distinctAdd } from '../../utils/array-utils'; -import { EditMode } from '../actions/edit-mode-action'; -import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context'; +import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; import { GLSP_TYPES } from '../types'; export interface IGLSPToolManager extends ToolManager { diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index bc47522..0561ad8 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -69,7 +69,6 @@ export class SResizeHandle extends SChildElement implements Hoverable { isNeSwResize(): boolean { return this.location === ResizeHandleLocation.TopRight || this.location === ResizeHandleLocation.BottomLeft; } - } export function addResizeHandles(element: SParentElement): void { diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 5cdf3b3..5ec458a 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { BoundsAware, Point, SModelElement, SNode, SParentElement } from 'sprotty'; - +import { BoundsAware, SModelElement, SNode, SParentElement } from 'sprotty'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { ModifyCSSFeedbackAction } from '../tool-feedback/css-feedback'; import { isBoundsAwareMoveable, SResizeHandle } from './model'; diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 403ca02..cb34768 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { ISnapper, Point, SModelElement } from 'sprotty'; +import { ISnapper, SModelElement } from 'sprotty'; @injectable() export class GridSnapper implements ISnapper { diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 5a9911c..648887b 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isSetContextActionsAction, LabeledAction, Point, RequestContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, ICommandPaletteActionProvider, LabeledAction, Point, SModelElement, TYPES } from 'sprotty'; - +import { ICommandPaletteActionProvider, SModelElement, TYPES } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { isSetContextActionsAction, RequestContextActions } from '../../base/actions/context-actions'; -import { EditorContextService } from '../../base/editor-context'; +import { EditorContextService } from '../../base/editor-context-service'; export namespace ServerCommandPalette { export const CONTEXT_ID = 'command-palette'; diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index f13f358..3d04bec 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from 'sprotty'; - -import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context'; -import { DeleteElementOperation } from '../../base/operations/operation'; +import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; import { GLSP_TYPES } from '../../base/types'; @injectable() diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index a790136..42974a3 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isSetContextActionsAction, LabeledAction, Point, RequestContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, IContextMenuItemProvider, isSelected, LabeledAction, Point, SModelElement, TYPES } from 'sprotty'; - +import { IContextMenuItemProvider, isSelected, SModelElement, TYPES } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { isSetContextActionsAction, RequestContextActions } from '../../base/actions/context-actions'; -import { EditorContextService } from '../../base/editor-context'; +import { EditorContextService } from '../../base/editor-context-service'; export namespace ServerContextMenu { export const CONTEXT_ID = 'context-menu'; diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 44657db..3dab595 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -13,20 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GLSPServerStatusAction, Point, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { - Action, - IActionDispatcher, - IActionHandler, - IContextMenuItemProvider, - isSelected, - MenuItem, - Point, - SModelRoot, - TYPES -} from 'sprotty/lib'; - -import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/glsp-server-status'; +import { IActionDispatcher, IActionHandler, IContextMenuItemProvider, isSelected, MenuItem, SModelRoot, TYPES } from 'sprotty'; export class InvokeCopyAction implements Action { static readonly KIND = 'invoke-copy'; diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 86c2a1e..e0681e2 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -13,14 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction, SetClipboardDataAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { TYPES, ViewerOptions } from 'sprotty'; import { v4 as uuid } from 'uuid'; - import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { EditorContextService } from '../../base/editor-context'; +import { EditorContextService } from '../../base/editor-context-service'; import { GLSP_TYPES } from '../../base/types'; -import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction } from './copy-paste-actions'; export interface ICopyPasteHandler { handleCopy(event: ClipboardEvent): void; @@ -105,7 +104,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { const clipboardId = uuid(); event.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); this.actionDispatcher - .request(RequestClipboardDataAction.create(this.editorContext.get())) + .request(RequestClipboardDataAction.create(this.editorContext.get())) .then(action => this.clipboadService.put(action.clipboardData, clipboardId)); event.preventDefault(); } else { diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/di.config.ts index 2004287..bb7f846 100644 --- a/packages/client/src/features/copy-paste/di.config.ts +++ b/packages/client/src/features/copy-paste/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty/lib'; - +import { configureActionHandler } from 'sprotty'; import { GLSP_TYPES } from '../../base/types'; import { CopyPasteContextMenuItemProvider, diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 8ee0c23..5970820 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Decoration, DecorationPlacer, isSizeable, ORIGIN_POINT, Point, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; +import { Decoration, DecorationPlacer, isSizeable, ORIGIN_POINT, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index cd92f48..8377a6b 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { isSetEditValidationResultAction, RequestEditValidationAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { Action, @@ -26,7 +27,6 @@ import { } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { isSetEditValidationResultAction, RequestEditValidationAction, ValidationStatus } from '../../base/actions/edit-validation-actions'; export namespace LabelEditValidation { export const CONTEXT_ID = 'label-edit'; diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/di.config.ts index 1b51668..dc30533 100644 --- a/packages/client/src/features/hints/di.config.ts +++ b/packages/client/src/features/hints/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { SetTypeHintsAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureActionHandler, configureCommand } from 'sprotty'; - import { GLSP_TYPES } from '../../base/types'; -import { SetTypeHintsAction } from './request-type-hints-action'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 8f713ef..168cb31 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelElementSchema, SModelExtension } from 'sprotty'; +import { SModelElementSchema } from '@eclipse-glsp/protocol'; +import { SModelElement, SModelExtension } from 'sprotty'; export const containerFeature = Symbol('containable'); diff --git a/packages/client/src/features/hints/request-type-hints-action.ts b/packages/client/src/features/hints/request-type-hints-action.ts deleted file mode 100644 index ee2203b..0000000 --- a/packages/client/src/features/hints/request-type-hints-action.ts +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Action } from 'sprotty'; - -import { EdgeTypeHint, ShapeTypeHint } from './type-hints'; - -export class RequestTypeHintsAction implements Action { - static readonly KIND = 'requestTypeHints'; - constructor(public readonly diagramType?: string, public readonly kind: string = RequestTypeHintsAction.KIND) {} -} - -export class SetTypeHintsAction implements Action { - static readonly KIND = 'setTypeHints'; - constructor( - public readonly shapeHints: ShapeTypeHint[], - public readonly edgeHints: EdgeTypeHint[], - public readonly kind: string = SetTypeHintsAction.KIND - ) {} -} - -export function isSetTypeHintsAction(action: Action): action is SetTypeHintsAction { - return ( - action !== undefined && - action.kind === SetTypeHintsAction.KIND && - (action as SetTypeHintsAction).shapeHints !== undefined && - (action as SetTypeHintsAction).edgeHints !== undefined - ); -} diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 5654f64..4b6bdd1 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, EdgeTypeHint, isSetTypeHintsAction, ShapeTypeHint, TypeHint } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, CommandExecutionContext, CommandReturn, Connectable, @@ -28,12 +28,10 @@ import { moveFeature, SEdge, SModelElement, - SModelElementSchema, SModelRoot, SShapeElement, TYPES } from 'sprotty'; - import { GLSP_TYPES } from '../../base/types'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; @@ -41,67 +39,6 @@ import { reconnectFeature } from '../reconnect/model'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../tool-feedback/model'; import { Containable, containerFeature, reparentFeature } from './model'; -import { isSetTypeHintsAction } from './request-type-hints-action'; - -export abstract class TypeHint { - /** - The id of the element. - */ - readonly elementTypeId: string; - - /** - * Specifies whether the element can be relocated. - */ - readonly repositionable: boolean; - - /** - * Specifices wheter the element can be deleted - */ - readonly deletable: boolean; -} - -export class ShapeTypeHint extends TypeHint { - /** - * Specifies whether the element can be resized. - */ - readonly resizable: boolean; - - /** - * Specifies whether the element can be moved to another parent - */ - readonly reparentable: boolean; - - /** - * The types of elements that can be contained by this element (if any) - */ - readonly containableElementTypeIds?: string[]; -} - -export class EdgeTypeHint extends TypeHint { - /** - * Specifies whether the routing of this element can be changed. - */ - readonly routable: boolean; - - /** - * Allowed source element types for this edge type - */ - readonly sourceElementTypeIds: string[]; - - /** - * Allowed targe element types for this edge type - */ - readonly targetElementTypeIds: string[]; - - isAllowedSource(input: SModelElement | SModelElementSchema | string): boolean { - const type = getElementTypeId(input); - return this.sourceElementTypeIds.includes(type); - } - isAllowedTarget(input: SModelElement | SModelElementSchema | string): boolean { - const type = getElementTypeId(input); - return this.targetElementTypeIds.includes(type); - } -} @injectable() export class ApplyTypeHintsAction implements Action { diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index 8ab32da..e4ec655 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,7 +30,6 @@ import { TYPES } from 'sprotty'; import { PopupPositionUpdater } from 'sprotty/lib/features/hover/popup-position-updater'; - import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { GlspHoverMouseListener } from './hover'; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index 532a2fa..8cf030f 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -13,22 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Bounds, RequestPopupModelAction, SetPopupModelAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { - Action, - Bounds, EMPTY_ROOT, HoverMouseListener, PreRenderedElementSchema, - RequestPopupModelAction, - SetPopupModelAction, SIssueMarker, SIssueSeverity, SModelElement, SModelElementSchema, SModelRootSchema } from 'sprotty'; - import { GIssueMarker } from '../validation/issue-marker'; @injectable() diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 1eee1f2..04a7595 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -34,10 +34,11 @@ export default layoutCommandsModule; @injectable() export class OverridableLayoutRegistry extends LayoutRegistry { - // ensure logger is already used in constructor as otherwise not usable - constructor(@multiInject(TYPES.LayoutRegistration) @optional() layouts: (LayoutRegistration)[] = [], - @inject(TYPES.ILogger) logger: ILogger) { + constructor( + @multiInject(TYPES.LayoutRegistration) @optional() layouts: LayoutRegistration[] = [], + @inject(TYPES.ILogger) logger: ILogger + ) { super(); layouts.forEach(layout => { if (this.hasKey(layout.layoutKind)) { diff --git a/packages/client/src/features/layout/freeform-layout.ts b/packages/client/src/features/layout/freeform-layout.ts index 202af37..ccb83cc 100644 --- a/packages/client/src/features/layout/freeform-layout.ts +++ b/packages/client/src/features/layout/freeform-layout.ts @@ -31,13 +31,11 @@ import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-option /** * Layouts children of a container with explit X/Y positions */ - @injectable() - export class FreeFormLayouter extends AbstractLayout { - +@injectable() +export class FreeFormLayouter extends AbstractLayout { static KIND = 'freeform'; - layout(container: SParentElement & LayoutContainer, - layouter: StatefulLayouter): void { + layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -57,36 +55,38 @@ import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-option } } - protected getChildrenSize(container: SParentElement & LayoutContainer, + protected getChildrenSize( + container: SParentElement & LayoutContainer, containerOptions: AbstractLayoutOptions, - layouter: StatefulLayouter): Dimension { + layouter: StatefulLayouter + ): Dimension { let maxX = 0; let maxY = 0; - container.children.forEach( - child => { - const bounds = layouter.getBoundsData(child).bounds; - if (bounds !== undefined && isValidDimension(bounds)) { - const childMaxX = bounds.x + bounds.width; - const childMaxY = bounds.y + bounds.height; - maxX = Math.max(maxX, childMaxX); - maxY = Math.max(maxY, childMaxY); - } + container.children.forEach(child => { + const bounds = layouter.getBoundsData(child).bounds; + if (bounds !== undefined && isValidDimension(bounds)) { + const childMaxX = bounds.x + bounds.width; + const childMaxY = bounds.y + bounds.height; + maxX = Math.max(maxX, childMaxX); + maxY = Math.max(maxY, childMaxY); } - ); + }); return { width: maxX, height: maxY }; } - protected layoutChild(child: SChildElement, + protected layoutChild( + child: SChildElement, boundsData: BoundsData, bounds: Bounds, childOptions: AbstractLayoutOptions, containerOptions: AbstractLayoutOptions, currentOffset: Point, maxWidth: number, - maxHeight: number): Point { + maxHeight: number + ): Point { boundsData.bounds = { x: (child as any).bounds.x, y: (child as any).bounds.y, @@ -97,11 +97,13 @@ import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-option return currentOffset; } - protected getFinalContainerBounds(container: SParentElement & LayoutContainer, + protected getFinalContainerBounds( + container: SParentElement & LayoutContainer, lastOffset: Point, options: AbstractLayoutOptions, maxWidth: number, - maxHeight: number): Bounds { + maxHeight: number + ): Bounds { const result = { x: container.bounds.x, y: container.bounds.y, @@ -128,5 +130,4 @@ import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-option protected spread(a: AbstractLayoutOptions, b: AbstractLayoutOptions): AbstractLayoutOptions { return { ...a, ...b }; } - } diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-commands.spec.ts index a77a800..da4d28c 100644 --- a/packages/client/src/features/layout/layout-commands.spec.ts +++ b/packages/client/src/features/layout/layout-commands.spec.ts @@ -14,19 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ -import 'mocha'; -import 'reflect-metadata'; - +import { Action, Bounds, ChangeBoundsOperation, ElementAndBounds } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; +import 'mocha'; +import 'reflect-metadata'; import { - Action, AnimationFrameSyncer, - Bounds, CommandExecutionContext, ConsoleLogger, defaultModule, - ElementAndBounds, ElementMove, FeatureSet, IActionDispatcher, @@ -41,8 +38,6 @@ import { SModelRoot, TYPES } from 'sprotty'; - -import { ChangeBoundsOperation } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { resizeFeature } from '../change-bounds/model'; import { SelectionService } from '../select/selection-service'; diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index 6fdfdb5..bb621d6 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -13,13 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, ChangeBoundsOperation, ElementAndBounds } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { - Action, Command, CommandExecutionContext, CommandReturn, - ElementAndBounds, ElementMove, IActionDispatcher, MoveAction, @@ -27,7 +26,6 @@ import { SModelElement, TYPES } from 'sprotty'; -import { ChangeBoundsOperation } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement } from '../../utils/smodel-util'; diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/layout/vbox-layout.ts index de3e3c8..995f210 100644 --- a/packages/client/src/features/layout/vbox-layout.ts +++ b/packages/client/src/features/layout/vbox-layout.ts @@ -40,28 +40,27 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { } /** - * Extends VBoxLayouter to support additional layout options - */ - @injectable() - export class VBoxLayouterExt extends VBoxLayouter { - + * Extends VBoxLayouter to support additional layout options + */ +@injectable() +export class VBoxLayouterExt extends VBoxLayouter { static KIND = VBoxLayouter.KIND; - layout(container: SParentElement & LayoutContainer, - layouter: StatefulLayouter): void { - + layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); const fixedSize = this.getFixedContainerBounds(container, options, layouter); - const maxWidth = options.paddingFactor * ( - options.resizeContainer + const maxWidth = + options.paddingFactor * + (options.resizeContainer ? Math.max(fixedSize.width - options.paddingLeft - options.paddingRight, childrenSize.width) : Math.max(0, fixedSize.width - options.paddingLeft - options.paddingRight)); - const maxHeight = options.paddingFactor * ( - options.resizeContainer + const maxHeight = + options.paddingFactor * + (options.resizeContainer ? Math.max(fixedSize.height - options.paddingTop - options.paddingBottom, childrenSize.height) : Math.max(0, fixedSize.height - options.paddingTop - options.paddingBottom)); @@ -72,8 +71,7 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { // when the grabHeight can't be equally shared by all children. const grabbingChildren = container.children .map(child => this.getChildLayoutOptions(child, options)) - .filter(opt => opt.vGrab) - .length; + .filter(opt => opt.vGrab).length; if (maxWidth > 0 && maxHeight > 0) { const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight, grabHeight, grabbingChildren); @@ -82,28 +80,28 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { } } - protected getChildrenSize(container: SParentElement & LayoutContainer, + protected getChildrenSize( + container: SParentElement & LayoutContainer, containerOptions: VBoxLayoutOptionsExt, - layouter: StatefulLayouter): Dimension { + layouter: StatefulLayouter + ): Dimension { let maxWidth = -1; let maxHeight = 0; let isFirst = true; - container.children.forEach( - child => { - if (isLayoutableChild(child)) { - const bounds = layouter.getBoundsData(child).bounds; - if (bounds !== undefined && isValidDimension(bounds)) { - maxHeight += bounds.height; - if (isFirst) { - isFirst = false; - } else { - maxHeight += containerOptions.vGap; - } - maxWidth = Math.max(maxWidth, bounds.width); + container.children.forEach(child => { + if (isLayoutableChild(child)) { + const bounds = layouter.getBoundsData(child).bounds; + if (bounds !== undefined && isValidDimension(bounds)) { + maxHeight += bounds.height; + if (isFirst) { + isFirst = false; + } else { + maxHeight += containerOptions.vGap; } + maxWidth = Math.max(maxWidth, bounds.width); } } - ); + }); const result = { width: maxWidth, height: maxHeight @@ -111,34 +109,46 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { return result; } - protected layoutChildren(container: SParentElement & LayoutContainer, + protected layoutChildren( + container: SParentElement & LayoutContainer, layouter: StatefulLayouter, containerOptions: VBoxLayoutOptionsExt, maxWidth: number, - maxHeight: number, grabHeight?: number, grabbingChildren?: number): Point { + maxHeight: number, + grabHeight?: number, + grabbingChildren?: number + ): Point { let currentOffset: Point = { - x: containerOptions.paddingLeft + 0.5 * (maxWidth - (maxWidth / containerOptions.paddingFactor)), - y: containerOptions.paddingTop + 0.5 * (maxHeight - (maxHeight / containerOptions.paddingFactor)) + x: containerOptions.paddingLeft + 0.5 * (maxWidth - maxWidth / containerOptions.paddingFactor), + y: containerOptions.paddingTop + 0.5 * (maxHeight - maxHeight / containerOptions.paddingFactor) }; - container.children.forEach( - child => { - if (isLayoutableChild(child)) { - const boundsData = layouter.getBoundsData(child); - const bounds = boundsData.bounds; - const childOptions = this.getChildLayoutOptions(child, containerOptions); - if (bounds !== undefined && isValidDimension(bounds)) { - currentOffset = this.layoutChild(child, boundsData, bounds, - childOptions, containerOptions, currentOffset, - maxWidth, maxHeight, grabHeight, grabbingChildren); - } + container.children.forEach(child => { + if (isLayoutableChild(child)) { + const boundsData = layouter.getBoundsData(child); + const bounds = boundsData.bounds; + const childOptions = this.getChildLayoutOptions(child, containerOptions); + if (bounds !== undefined && isValidDimension(bounds)) { + currentOffset = this.layoutChild( + child, + boundsData, + bounds, + childOptions, + containerOptions, + currentOffset, + maxWidth, + maxHeight, + grabHeight, + grabbingChildren + ); } } - ); + }); return currentOffset; } - protected layoutChild(child: SChildElement, + protected layoutChild( + child: SChildElement, boundsData: BoundsData, bounds: Bounds, childOptions: VBoxLayoutOptionsExt, @@ -147,7 +157,8 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { maxWidth: number, maxHeight: number, grabHeight?: number, - grabbingChildren?: number): Point { + grabbingChildren?: number + ): Point { let offset = super.layoutChild(child, boundsData, bounds, childOptions, containerOptions, currentOffset, maxWidth, maxHeight); if (childOptions.hGrab) { boundsData.bounds = { @@ -159,7 +170,7 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { boundsData.boundsChanged = true; } if (childOptions.vGrab && grabHeight && grabbingChildren) { - const height = boundsData.bounds!.height + (grabHeight / grabbingChildren); + const height = boundsData.bounds!.height + grabHeight / grabbingChildren; boundsData.bounds = { x: boundsData.bounds!.x, y: boundsData.bounds!.y, @@ -197,9 +208,13 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { return (element as any).layoutOptions; } - protected getFinalContainerBounds(container: SParentElement & LayoutContainer, - lastOffset: Point, options: VBoxLayoutOptionsExt, maxWidth: number, maxHeight: number): Bounds { - + protected getFinalContainerBounds( + container: SParentElement & LayoutContainer, + lastOffset: Point, + options: VBoxLayoutOptionsExt, + maxWidth: number, + maxHeight: number + ): Bounds { const elementOptions = this.getElementLayoutOptions(container); const width = elementOptions?.prefWidth ?? options.minWidth; const height = elementOptions?.prefHeight ?? options.minHeight; diff --git a/packages/client/src/features/model-source-watcher/di.config.ts b/packages/client/src/features/model-source-watcher/di.config.ts index 5b359ce..026a92a 100644 --- a/packages/client/src/features/model-source-watcher/di.config.ts +++ b/packages/client/src/features/model-source-watcher/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ModelSourceChangedAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty/lib'; - -import { ModelSourceChangedAction } from './model-source-changed-action'; +import { configureActionHandler } from 'sprotty'; import { ModelSourceChangedActionHandler } from './model-source-changed-action-handler'; const modelSourceWatcherModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts index e87b5fe..7315cc5 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts @@ -13,11 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + Action, + GLSPServerStatusAction, + isModelSourceChangedAction, + ModelSourceChangedAction, + ServerMessageAction +} from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { Action, IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from 'sprotty/lib'; - -import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/glsp-server-status'; -import { isModelSourceChangedAction, ModelSourceChangedAction } from './model-source-changed-action'; +import { IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from 'sprotty'; /** * An external handler of the model source change event. diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index c1e75f5..e854bbe 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -13,9 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isAction } from '@eclipse-glsp/protocol'; import { injectable, multiInject, optional } from 'inversify'; import { On, VNode, VNodeData } from 'snabbdom'; -import { Action, isAction, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; +import { MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; import { getRank } from '../rank/model'; export interface IMouseTool { diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts index 79e2f28..ff9376f 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,16 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { NavigateToExternalTargetAction, NavigateToTargetAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty/lib'; - -import { - NavigateAction, - NavigateToExternalTargetAction, - NavigateToTargetAction, - NavigationActionHandler, - ProcessNavigationArgumentsAction -} from './navigation-action-handler'; +import { configureActionHandler } from 'sprotty'; +import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 1b25888..ab85cf2 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -13,28 +13,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, - ActionHandlerRegistry, + Args, CenterAction, - generateRequestId, - IActionDispatcher, - IActionHandler, - ICommand, - ILogger, - RequestAction, - ResponseAction, + GLSPServerStatusAction, + isNavigateToExternalTargetAction, + isNavigateToTargetAction, + isSetNavigationTargetsAction, + NavigateToExternalTargetAction, + NavigateToTargetAction, + NavigationTarget, + RequestNavigationTargetsAction, SelectAction, SelectAllAction, - TYPES -} from 'sprotty'; + ServerMessageAction, + SetResolvedNavigationTargetAction +} from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandlerRegistry, IActionDispatcher, IActionHandler, ICommand, ILogger, TYPES } from 'sprotty'; -import { Args } from '../../base/args'; -import { EditorContext, EditorContextServiceProvider } from '../../base/editor-context'; +import { EditorContextServiceProvider } from '../../base/editor-context-service'; import { GLSP_TYPES } from '../../base/types'; -import { GLSPServerStatusAction, ServerMessageAction } from '../../model-source/glsp-server-status'; -import { NavigationTarget, NavigationTargetResolver, SetResolvedNavigationTargetAction } from './navigation-target-resolver'; +import { NavigationTargetResolver } from './navigation-target-resolver'; /** * Action for triggering a navigation of a certain target type. @@ -57,53 +58,6 @@ export function isNavigateAction(action: Action): action is NavigateAction { return action !== undefined && action.kind === NavigateAction.KIND && (action as NavigateAction).targetTypeId !== undefined; } -/** Action that is usually sent to the server to request navigation targets for a navigation type. */ -export class RequestNavigationTargetsAction implements RequestAction { - static readonly KIND = 'requestNavigationTargets'; - kind = RequestNavigationTargetsAction.KIND; - constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, readonly requestId: string = generateRequestId()) {} -} - -/** Action that is usually sent from the server to the client as a repsonse to a `RequestNavigationTargetsAction`. */ -export class SetNavigationTargetsAction implements ResponseAction { - static readonly KIND = 'setNavigationTargets'; - kind = SetNavigationTargetsAction.KIND; - constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) {} -} - -export function isSetNavigationTargetsAction(action: Action): action is SetNavigationTargetsAction { - return ( - action !== undefined && - action.kind === SetNavigationTargetsAction.KIND && - (action as SetNavigationTargetsAction).targets !== undefined - ); -} - -/** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ -export class NavigateToTargetAction implements Action { - static readonly KIND = 'navigateToTarget'; - readonly kind = NavigateToTargetAction.KIND; - constructor(readonly target: NavigationTarget) {} -} - -export function isNavigateToTargetAction(action: Action): action is NavigateToTargetAction { - return action !== undefined && action.kind === NavigateToTargetAction.KIND && (action as NavigateToTargetAction).target !== undefined; -} - -export class NavigateToExternalTargetAction implements Action { - static readonly KIND = 'navigateToExternalTarget'; - readonly kind = NavigateToExternalTargetAction.KIND; - constructor(readonly target: NavigationTarget) {} -} - -export function isNavigateToExternalTargetAction(action: Action): action is NavigateToExternalTargetAction { - return ( - action !== undefined && - action.kind === NavigateToExternalTargetAction.KIND && - (action as NavigateToExternalTargetAction).target !== undefined - ); -} - /** Action to trigger the processing of additional navigation arguments. * * The resolution of a `NavigationTarget` may entail additional arguments. In this case, this action is diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index dc88192..2832269 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -14,13 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable no-unused-expressions */ +import { NavigationTarget } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { expect } from 'chai'; - -import { NavigationTarget } from './navigation-target-resolver'; - describe('NavigationTarget', () => { it('should be able to set and get element IDs', () => { const navigationTarget: NavigationTarget = { uri: 'uri' }; diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 029b83c..8a3c879 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -13,97 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + isSetResolvedNavigationTargets, + NavigationTarget, + ResolveNavigationTargetAction, + SetResolvedNavigationTargetAction +} from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, generateRequestId, IActionDispatcher, ILogger, RequestAction, ResponseAction, TYPES } from 'sprotty'; - -import { Args } from '../../base/args'; -import { EditorContextServiceProvider } from '../../base/editor-context'; +import { IActionDispatcher, ILogger, ResponseAction, TYPES } from 'sprotty'; +import { EditorContextServiceProvider } from '../../base/editor-context-service'; import { GLSP_TYPES } from '../../base/types'; -export interface NavigationTarget { - uri: string; - label?: string; - args?: Args; -} - -export namespace NavigationTarget { - export const ELEMENT_IDS = 'elementIds'; - export const ELEMENT_IDS_SEPARATOR = '&'; - export const TEXT_LINE = 'line'; - export const TEXT_COLUMN = 'column'; - - export function hasArguments(target: NavigationTarget): boolean { - return target.args !== undefined && Object.keys(target.args).length > 0; - } - - export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean): void { - if (target.args === undefined) { - target.args = {}; - } - target.args[key] = value; - } - - export function getElementIds(target: NavigationTarget): string[] { - if (target.args === undefined || target.args[NavigationTarget.ELEMENT_IDS] === undefined) { - return []; - } - const elementIdsValue = target.args[NavigationTarget.ELEMENT_IDS].toString(); - return elementIdsValue.split(NavigationTarget.ELEMENT_IDS_SEPARATOR); - } - - export function setElementIds(target: NavigationTarget, elementIds: string[]): string { - if (target.args === undefined) { - target.args = {}; - } - return (target.args[NavigationTarget.ELEMENT_IDS] = elementIds.join(NavigationTarget.ELEMENT_IDS_SEPARATOR)); - } - - export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void { - if (position) { - if (target.args === undefined) { - target.args = {}; - } - target.args[NavigationTarget.TEXT_LINE] = position.line; - target.args[NavigationTarget.TEXT_COLUMN] = position.character; - } - } - - export function getTextPosition(target: NavigationTarget): TextPosition | undefined { - if ( - target.args === undefined || - target.args[NavigationTarget.TEXT_LINE] === undefined || - target.args[NavigationTarget.TEXT_COLUMN] === undefined - ) { - return undefined; - } - return { - line: Number(target.args[NavigationTarget.TEXT_LINE]), - character: Number(target.args[NavigationTarget.TEXT_COLUMN]) - }; - } -} - -export interface TextPosition { - line: number; - character: number; -} - -export class ResolveNavigationTargetAction implements RequestAction { - static readonly KIND = 'resolveNavigationTarget'; - kind = ResolveNavigationTargetAction.KIND; - constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) {} -} - -export class SetResolvedNavigationTargetAction implements ResponseAction { - static readonly KIND = 'setResolvedNavigationTarget'; - kind = SetResolvedNavigationTargetAction.KIND; - constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') {} -} - -export function isSetResolvedNavigationTargets(action: Action): action is SetResolvedNavigationTargetAction { - return action !== undefined && action.kind === SetResolvedNavigationTargetAction.KIND; -} - /** * Resolves `NavigationTargets` to element ids. * diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index 651f471..0b67b81 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -16,7 +16,7 @@ import { ContainerModule } from 'inversify'; import { TYPES } from 'sprotty'; -import { SaveModelKeyboardListener } from './save'; +import { SaveModelKeyboardListener } from './save-keylistener'; /** * This module is not required the diagram is deployed in Theia with the `GLSPDiagramWidget` diff --git a/packages/client/src/features/save/save.ts b/packages/client/src/features/save/save-keylistener.ts similarity index 75% rename from packages/client/src/features/save/save.ts rename to packages/client/src/features/save/save-keylistener.ts index 5a972a7..1d56bd6 100644 --- a/packages/client/src/features/save/save.ts +++ b/packages/client/src/features/save/save-keylistener.ts @@ -13,18 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, KeyListener, SModelRoot } from 'sprotty'; +import { Action, SaveModelAction } from '@eclipse-glsp/protocol'; +import { KeyListener, SModelRoot } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -export class SaveModelAction implements Action { - static readonly KIND = 'saveModel'; - constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) {} -} - -export function isSaveModelAction(action: Action): action is SaveModelAction { - return action.kind === SaveModelAction.KIND; -} - export class SaveModelKeyboardListener extends KeyListener { keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index 1e0afa4..6f33b24 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -14,10 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ +import { Action } from '@eclipse-glsp/protocol'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; -import { Action, defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from 'sprotty'; - +import { defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from 'sprotty'; import { GLSP_TYPES } from '../../base/types'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index d1a9a30..393e1d0 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -13,17 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, distinctAdd, remove, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; import { - Action, Command, CommandExecutionContext, ILogger, isSelectable, isSelected, SChildElement, - SelectAction, - SelectAllAction, SelectAllCommand as SprottySelectAllCommand, SelectCommand as SprottySelectCommand, SModelElement, @@ -31,10 +29,8 @@ import { SParentElement, TYPES } from 'sprotty'; - import { SModelRootListener } from '../../base/model/update-model-command'; import { GLSP_TYPES } from '../../base/types'; -import { distinctAdd, remove } from '../../utils/array-utils'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 6ac7779..e38db18 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { - Action, CommandExecutionContext, CommandReturn, ElementMove, @@ -26,7 +26,6 @@ import { isViewport, MouseListener, MoveAction, - Point, SChildElement, SModelElement, SModelRoot, @@ -128,9 +127,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } protected collectStartPositions(root: SModelRoot): void { - const selectedElements = root.index - .all() - .filter(element => isSelectable(element) && element.selected); + const selectedElements = root.index.all().filter(element => isSelectable(element) && element.selected); const elementsSet = new Set(selectedElements); selectedElements .filter(element => !this.isChildOfSelected(elementsSet, element)) diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 1cc5be7..0e2508e 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, add, AnchorComputerRegistry, center, @@ -28,7 +28,6 @@ import { isConnectable, MouseListener, MoveAction, - Point, PolylineEdgeRouter, SChildElement, SConnectableElement, @@ -39,7 +38,6 @@ import { SRoutableElement, TYPES } from 'sprotty'; - import { isRoutable } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackCommand } from './model'; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 5964451..e3dd420 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, isStringArray } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES } from 'sprotty'; -import { isStringArray } from '../../utils/array-utils'; +import { CommandExecutionContext, SModelElement, SModelRoot, TYPES } from 'sprotty'; import { addCssClasses, removeCssClasses } from '../../utils/smodel-util'; import { FeedbackCommand } from './model'; diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index fe8c35e..d89f239 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { - Action, add, AnchorComputerRegistry, center, @@ -33,7 +33,6 @@ import { isViewport, MouseListener, MoveAction, - Point, PolylineEdgeRouter, SConnectableElement, SModelElement, @@ -43,7 +42,6 @@ import { SwitchEditModeCommand, TYPES } from 'sprotty'; - import { isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index 2eee4f0..15621c5 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, IActionDispatcher, ILogger, TYPES } from 'sprotty'; +import { IActionDispatcher, ILogger, TYPES } from 'sprotty'; export interface IFeedbackEmitter {} diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index 81d2f0a..0515110 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -13,20 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, AnchorComputerRegistry, CommandExecutionContext, CommandReturn, MouseListener, - Point, SChildElement, SModelElement, SModelRoot, TYPES } from 'sprotty'; - import { FeedbackCommand } from './model'; export class DrawMarqueeAction implements Action { diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index 31840b0..cad13e0 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, ORIGIN_POINT, Point, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; - +import { IView, ORIGIN_POINT, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts index 9cf5802..096b440 100644 --- a/packages/client/src/features/tool-palette/palette-item.ts +++ b/packages/client/src/features/tool-palette/palette-item.ts @@ -13,9 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isLabeledAction, LabeledAction } from 'sprotty'; - -import { isTriggerElementTypeCreationAction, TriggerElementCreationAction } from '../../base/operations/operation'; +import { isLabeledAction, isTriggerElementTypeCreationAction, LabeledAction, TriggerElementCreationAction } from '@eclipse-glsp/protocol'; export interface PaletteItem extends LabeledAction { readonly id: string; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 1e6108a..b0cc990 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { isSetContextActionsAction, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, @@ -29,11 +30,9 @@ import { import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { isSetContextActionsAction, RequestContextActions, SetContextActions } from '../../base/actions/context-actions'; -import { EditModeListener, EditorContextService } from '../../base/editor-context'; +import { EditModeListener, EditorContextService } from '../../base/editor-context-service'; import { MouseDeleteTool } from '../tools/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; -import { RequestMarkersAction } from '../validation/validate'; import { PaletteItem } from './palette-item'; const CLICKED_CSS_CLASS = 'clicked'; diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 7cfaea5..4d10371 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, IActionDispatcher, KeyTool, TYPES } from 'sprotty'; - -import { EditorContextService } from '../../base/editor-context'; +import { IActionDispatcher, KeyTool, TYPES } from 'sprotty'; +import { EditorContextService } from '../../base/editor-context-service'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; import { GLSP_TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 1b1a85a..d822ccc 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -13,10 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, Bounds, + ChangeBoundsOperation, + ChangeRoutingPointsOperation, + CompoundOperation, + ElementAndRoutingPoints, + Operation, + Point +} from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { BoundsAware, Dimension, EdgeRouterRegistry, @@ -26,7 +34,6 @@ import { isSelected, isViewport, MouseListener, - Point, SChildElement, SConnectableElement, SetBoundsAction, @@ -36,13 +43,6 @@ import { TYPES } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { - ChangeBoundsOperation, - ChangeRoutingPointsOperation, - CompoundOperation, - ElementAndRoutingPoints, - Operation -} from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { isValidMove, isValidSize, WriteablePoint } from '../../utils/layout-utils'; import { @@ -214,7 +214,8 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele }); const selectionSet: Set = new Set(selectedElements); - selectedElements.filter(element => !this.isChildOfSelected(selectionSet, element)) + selectedElements + .filter(element => !this.isChildOfSelected(selectionSet, element)) .map(element => this.createElementAndBounds(element)) .forEach(bounds => newBounds.push(...bounds)); diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index 7b7600d..defefaa 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, EnableDefaultToolsAction, findParentByFeature, isCtrlOrCmd, @@ -27,8 +27,6 @@ import { SModelElement } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; - -import { DeleteElementOperation } from '../../base/operations/operation'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; import { GLSP_TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 125ae95..2231957 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { ContainerModule, interfaces } from 'inversify'; import { configureActionHandler, configureModelElement } from 'sprotty'; - -import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; import { ChangeBoundsTool } from './change-bounds-tool'; diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 6de4647..dd68ab6 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, CreateEdgeOperation, isTriggerElementTypeCreationAction, TriggerEdgeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, AnchorComputerRegistry, EnableDefaultToolsAction, EnableToolsAction, @@ -26,9 +26,7 @@ import { SEdge, SModelElement } from 'sprotty'; - import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { CreateEdgeOperation, isTriggerElementTypeCreationAction, TriggerEdgeCreationAction } from '../../base/operations/operation'; import { DrawFeedbackEdgeAction, FeedbackEdgeEndMovingMouseListener, diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 66471b0..9a593c5 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, ChangeRoutingPointsOperation, ReconnectEdgeOperation } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { - Action, AnchorComputerRegistry, canEditRouting, Connectable, @@ -29,7 +29,6 @@ import { SRoutingHandle } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from '../../base/operations/operation'; import { GLSP_TYPES } from '../../base/types'; import { isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle } from '../reconnect/model'; diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index 1635918..476a8c1 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -13,23 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Point, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { - Action, - BoundsAware, - EnableDefaultToolsAction, - isSelectable, - isSelected, - KeyListener, - Point, - SEdge, - SelectAction, - SModelElement, - SNode, - TYPES -} from 'sprotty'; +import { BoundsAware, EnableDefaultToolsAction, isSelectable, isSelected, KeyListener, SEdge, SModelElement, SNode, TYPES } from 'sprotty'; import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; - import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index 674dd21..acf0a45 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from 'sprotty'; - +import { EnableToolsAction, KeyListener, KeyTool, SModelElement } from 'sprotty'; import { BaseGLSPTool } from '../tools/base-glsp-tool'; import { MarqueeMouseTool } from './marquee-mouse-tool'; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index 20bca83..bfac386 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, CreateNodeOperation, isTriggerNodeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { - Action, EnableDefaultToolsAction, EnableToolsAction, findParentByFeature, @@ -26,9 +26,7 @@ import { SNode, TYPES } from 'sprotty'; - import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { CreateNodeOperation, isTriggerNodeCreationAction, TriggerNodeCreationAction } from '../../base/operations/operation'; import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { Containable, isContainable } from '../hints/model'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 805a478..b074ef4 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Marker, MarkerKind } from '@eclipse-glsp/protocol'; import { SDecoration, SIssue, SIssueMarker, SParentElement } from 'sprotty'; -import { Marker, MarkerKind } from '../../utils/marker'; - export class GIssueMarker extends SIssueMarker { constructor() { super(); diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index d786406..34cbeeb 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -14,11 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ -import 'mocha'; -import 'reflect-metadata'; - import { expect } from 'chai'; import { Container } from 'inversify'; +import 'mocha'; +import 'reflect-metadata'; import { BoundsAware, decorationModule, @@ -30,10 +29,9 @@ import { SNodeSchema, SParentElement, TYPES -} from 'sprotty/lib'; +} from 'sprotty'; import defaultModule from 'sprotty/lib/base/di.config'; import { SGraphFactory } from 'sprotty/lib/graph/sgraph-factory'; - import { markerNavigatorModule } from './di.config'; import { MarkerNavigator } from './marker-navigator'; diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 75b7028..0670c1b 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, CenterAction, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, BoundsAware, - CenterAction, CenterCommand, Command, CommandExecutionContext, @@ -28,7 +27,6 @@ import { isSelectable, KeyListener, MenuItem, - Point, Selectable, SelectAction, SIssueMarker, @@ -38,7 +36,6 @@ import { TYPES } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; - import { GLSP_TYPES } from '../../base/types'; import { collectIssueMarkers, MarkerPredicates } from '../../utils/marker'; import { isSelectableAndBoundsAware } from '../../utils/smodel-util'; diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index ca4b8b4..5d02130 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, DeleteMarkersAction, Marker, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { - Action, Command, CommandExecutionContext, CommandReturn, @@ -27,10 +27,8 @@ import { SParentElement, TYPES } from 'sprotty'; - -import { EditorContextService } from '../../base/editor-context'; +import { EditorContextService } from '../../base/editor-context-service'; import { GLSP_TYPES } from '../../base/types'; -import { Marker } from '../../utils/marker'; import { removeCssClasses } from '../../utils/smodel-util'; import { getSeverity } from '../hover/hover'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; @@ -72,17 +70,6 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { } } -/** - * Action to set markers for a model - */ -export class SetMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = SetMarkersCommand.KIND) {} -} - -export function isSetMarkersAction(action: Action): action is SetMarkersAction { - return SetMarkersCommand.KIND === action.kind && 'markers' in action; -} - /** * Manages current markers for the outside of the GLSP. * @@ -117,7 +104,7 @@ export class SetMarkersCommand extends Command { @inject(ExternalMarkerManager) @optional() protected externalMarkerManager?: ExternalMarkerManager; @inject(EditorContextService) protected editorContextService: EditorContextService; - static readonly KIND = 'setMarkers'; + static readonly KIND = SetMarkersAction.KIND; constructor(@inject(TYPES.Action) public action: SetMarkersAction) { super(); @@ -143,14 +130,6 @@ export class SetMarkersCommand extends Command { } } -/** - * Action to retrieve markers for a model - */ -export class RequestMarkersAction implements Action { - static readonly KIND = 'requestMarkers'; - constructor(public readonly elementsIDs: string[] = [], public readonly kind = RequestMarkersAction.KIND) {} -} - /** * Interface for actions processing markers */ @@ -216,20 +195,12 @@ function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker removeCssClasses(modelElement, [getSeverity(issueMarker)]); } -/** - * Action for clearing makers of a model - */ -@injectable() -export class DeleteMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = DeleteMarkersCommand.KIND) {} -} - /** * Command for handling `DeleteMarkersAction` */ @injectable() export class DeleteMarkersCommand extends Command { - static KIND = 'deleteMarkers'; + static KIND = DeleteMarkersAction.KIND; constructor(@inject(TYPES.Action) protected action: DeleteMarkersAction) { super(); diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index ca5f964..f865a3e 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from 'sprotty'; +import { EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from 'sprotty'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @injectable() diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3af82ea..97a0894 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -35,11 +35,8 @@ import glspViewportModule from './features/viewport/di.config'; export * from 'sprotty'; export * from './base/action-dispatcher'; -export * from './base/actions/context-actions'; -export * from './base/actions/edit-mode-action'; -export * from './base/actions/edit-validation-actions'; export * from './base/actions/focus-change-action'; -export * from './base/args'; +export * from './base/argumentable'; export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; @@ -47,12 +44,11 @@ export * from './base/command-stack'; export * from './base/container-modules'; export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; -export * from './base/editor-context'; +export * from './base/editor-context-service'; export * from './base/focus-tracker'; export * from './base/model-initialization-constraint'; export * from './base/model/model-registry'; export * from './base/model/update-model-command'; -export * from './base/operations/operation'; export * from './base/selection-clearing-mouse-listener'; export * from './base/source-uri-aware'; export * from './base/types'; @@ -67,11 +63,9 @@ export * from './features/decoration/decoration-placer'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; export * from './features/hints/model'; -export * from './features/hints/request-type-hints-action'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; export * from './features/layout/layout-commands'; -export * from './features/model-source-watcher/model-source-changed-action'; export * from './features/model-source-watcher/model-source-changed-action-handler'; export * from './features/mouse-tool/mouse-tool'; export * from './features/navigation/navigation-action-handler'; @@ -79,7 +73,7 @@ export * from './features/navigation/navigation-target-resolver'; export * from './features/rank/model'; export * from './features/reconnect/model'; export * from './features/save/model'; -export * from './features/save/save'; +export * from './features/save/save-keylistener'; export * from './features/tool-feedback/change-bounds-tool-feedback'; export * from './features/tool-feedback/creation-tool-feedback'; export * from './features/tool-feedback/css-feedback'; @@ -94,16 +88,14 @@ export * from './features/tools/delete-tool'; export * from './features/tools/edge-creation-tool'; export * from './features/tools/edge-edit-tool'; export * from './features/tools/node-creation-tool'; -export * from './features/undo-redo/model'; export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './lib/model'; export * from './model-source/glsp-diagram-server'; -export * from './model-source/glsp-server-status'; +export * from './protocol-reexport'; export * from './utils/argument-utils'; -export * from './utils/array-utils'; export * from './utils/marker'; export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 5b43c80..959aebd 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -13,23 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GLSPClient } from '@eclipse-glsp/protocol'; -import { injectable } from 'inversify'; import { Action, - ActionHandlerRegistry, - ActionMessage, ComputedBoundsAction, - DiagramServer, - ICommand, - RequestModelAction, - ServerStatusAction, - SwitchEditModeCommand -} from 'sprotty'; - -import { isSetEditModeAction, SetEditModeAction } from '../base/actions/edit-mode-action'; + GLSPClient, + isRequestModelAction, + isServerMessageAction, + isSetEditModeAction, + ServerMessageAction, + SetEditModeAction +} from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { ActionHandlerRegistry, ActionMessage, DiagramServer, ICommand, ServerStatusAction, SwitchEditModeCommand } from 'sprotty'; import { SourceUriAware } from '../base/source-uri-aware'; -import { isServerMessageAction, ServerMessageAction } from './glsp-server-status'; const receivedFromServerProperty = '__receivedFromServer'; @@ -66,7 +62,7 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { } handle(action: Action): void | ICommand | Action { - if (action instanceof RequestModelAction && action.options !== undefined) { + if (isRequestModelAction(action) && action.options) { this._sourceUri = action.options.sourceUri as string; } return super.handle(action); diff --git a/packages/client/src/model-source/glsp-server-status.ts b/packages/client/src/model-source/glsp-server-status.ts deleted file mode 100644 index 1b0b963..0000000 --- a/packages/client/src/model-source/glsp-server-status.ts +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Action, ServerStatusAction as SprottyServerStatusAction } from 'sprotty'; - -export class GLSPServerStatusAction extends SprottyServerStatusAction { - timeout = -1; -} - -export function isGLSPServerStatusAction(serverStatusAction: SprottyServerStatusAction): serverStatusAction is GLSPServerStatusAction { - return (serverStatusAction as GLSPServerStatusAction).timeout !== undefined; -} - -export class ServerMessageAction implements Action { - static KIND = 'serverMessage'; - - kind = ServerMessageAction.KIND; - severity: 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL'; - message: string; - details = ''; - timeout = -1; -} - -export function isServerMessageAction(action: Action): action is ServerMessageAction { - return ServerMessageAction.KIND === action.kind && 'severity' in action && 'message' in action; -} diff --git a/packages/client/src/protocol-reexport.ts b/packages/client/src/protocol-reexport.ts new file mode 100644 index 0000000..e0d1a31 --- /dev/null +++ b/packages/client/src/protocol-reexport.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * Workaround for now to solve ambiguous exports in glsp-client. This module provides all glsp-only components. + * i.e. typings that are not defined in sprotty + */ + +export { + CompoundOperation, + isActionKind, + isCompoundOperation, + isRejectAction, + Operation +} from '@eclipse-glsp/protocol/lib/action-protocol/base-protocol'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/client-notification'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/clipboard'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/contexts'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/edge-modification'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/element-creation'; +export { isRequestPopupModelAction, isSetPopupModelAction } from '@eclipse-glsp/protocol/lib/action-protocol/element-hover'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/element-navigation'; +export { isSelectAction, isSelectAllAction } from '@eclipse-glsp/protocol/lib/action-protocol/element-selection'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/element-text-editing'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/element-type-hints'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/element-validation'; +export { LayoutOperation } from '@eclipse-glsp/protocol/lib/action-protocol/mode-layout'; +export { + isModelSourceChangedAction, + isRequestModelAction, + isSetModelAction, + ModelSourceChangedAction +} from '@eclipse-glsp/protocol/lib/action-protocol/model-data'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/model-edit-mode'; +export { + DirtyStateChangeReason, + isExportSvgAction, + isSaveModelAction, + isSetDirtyStateAction, + SaveModelAction, + SetDirtyStateAction +} from '@eclipse-glsp/protocol/lib/action-protocol/model-saving'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/node-modification'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/tool-palette'; +export { Args, EditorContext, ElementAndRoutingPoints } from '@eclipse-glsp/protocol/lib/action-protocol/types'; +export * from '@eclipse-glsp/protocol/lib/action-protocol/undo-redo'; +export * from '@eclipse-glsp/protocol/lib/glsp-client'; +export * from '@eclipse-glsp/protocol/lib/jsonrpc/base-jsonrpc-glsp-client'; +export * from '@eclipse-glsp/protocol/lib/jsonrpc/glsp-jsonrpc-client'; +export * from '@eclipse-glsp/protocol/lib/utils/array-util'; +export * from '@eclipse-glsp/protocol/lib/utils/launch-util'; diff --git a/packages/client/src/utils/argument-utils.ts b/packages/client/src/utils/argument-utils.ts index 0186784..54428d0 100644 --- a/packages/client/src/utils/argument-utils.ts +++ b/packages/client/src/utils/argument-utils.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { SModelElement } from 'sprotty'; - -import { hasArguments } from '../base/args'; +import { hasArguments } from '../base/argumentable'; export namespace GArgument { export function asNumber(argValue: string | number | boolean): number | undefined { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 06585e4..bb8fb81 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement } from 'sprotty'; - +import { Point } from '@eclipse-glsp/protocol'; +import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, SModelElement } from 'sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; export function minWidth(element: SModelElement & BoundsAware): number { diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts index c70b3b0..5a37bc8 100644 --- a/packages/client/src/utils/marker.ts +++ b/packages/client/src/utils/marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2021 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { MarkerKind } from '@eclipse-glsp/protocol'; import { SIssueMarker, SParentElement } from 'sprotty'; -export namespace MarkerKind { - export const INFO = 'info'; - export const WARNING = 'warning'; - export const ERROR = 'error'; -} - export namespace MarkerPredicates { export const ALL = (): boolean => true; export const ERRORS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.ERROR); @@ -42,22 +37,3 @@ export function collectIssueMarkers(root: SParentElement): SIssueMarker[] { } return markers; } - -export interface Marker { - /** - * Short label describing this marker message, e.g., short validation message - */ - readonly label: string; - /** - * Full description of this marker, e.g., full validation message - */ - readonly description: string; - /** - * Id of the model element this marker refers to - */ - readonly elementId: string; - /** - * Marker kind, e.g., info, warning, error or custom kind - */ - readonly kind: string; -} diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index ef1a39c..b5d39b0 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ElementAndRoutingPoints } from '@eclipse-glsp/protocol'; import { BoundsAware, ElementAndBounds, @@ -27,7 +28,6 @@ import { SRoutableElement, SRoutingHandle } from 'sprotty'; -import { ElementAndRoutingPoints } from '../base/operations/operation'; export function getIndex(element: SModelElement): SModelIndex { return element.root.index; diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 6ba4fc4..f9ba8df 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -13,15 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Bounds, Point } from '@eclipse-glsp/protocol'; import { - Bounds, BoundsAware, Dimension, findParentByFeature, isAlignable, isViewport, ORIGIN_POINT, - Point, SModelElement, translateBounds, Viewport diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index 5ca21d9..2edd9ec 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -22,16 +22,16 @@ const JSX = { createElement: svg }; @injectable() export class StructureCompartmentView extends ShapeView { - render(model: Readonly, context: RenderingContext): VNode | undefined { if (!this.isVisible(model, context)) { return undefined; } - return - - {context.renderChildren(model)} - ; + return ( + + + {context.renderChildren(model)} + + ); } - } diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index 8490527..0b8a01d 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { Point, PolylineEdgeView, RenderingContext, SEdge, svg } from 'sprotty'; - +import { PolylineEdgeView, RenderingContext, SEdge, svg } from 'sprotty'; import { EdgePadding } from '../utils/argument-utils'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index 5966326..d62c19b 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension, Hoverable, Selectable, SShapeElement } from 'sprotty/lib'; - +import { Dimension } from '@eclipse-glsp/protocol'; +import { Hoverable, Selectable, SShapeElement } from 'sprotty'; import { CornerRadius } from '../utils/argument-utils'; export interface RoundedCorner { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 11d1b82..36deb31 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -34,7 +34,8 @@ ], "dependencies": { "uuid": "7.0.3", - "vscode-ws-jsonrpc": "0.2.0" + "vscode-ws-jsonrpc": "0.2.0", + "inversify": "^5.0.1" }, "devDependencies": { "@types/uuid": "3.4.5", diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts new file mode 100644 index 0000000..3c64ca4 --- /dev/null +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -0,0 +1,122 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as uuid from 'uuid'; +import { isArray, isString } from '../utils/typeguard-util'; +import { JsonAny } from './types'; + +/** + * A general message serves as an envelope carrying an action to be transmitted between the client and the server via a DiagramServer. + */ +export interface ActionMessage { + /** + * Used to identify a specific client session. + */ + clientId: string; + /** + * The action to execute. + */ + action: Action; +} + +export function isActionMessage(object: any): object is ActionMessage { + return object !== undefined && isString(object, 'clientId') && isAction(object['action']); +} + +/** + * An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. + * It is a plain data structure, and as such transferable between server and client. An action must never contain actual + * SModelElement instances, but either refer to them via their ids or contain serializable schema for model elements. + */ +export interface Action { + /** + * Unique identifier specifying the kind of action to process. + */ + readonly kind: string; +} + +export function isAction(action: any): action is Action { + return action !== undefined && isString(action, 'kind'); +} + +export function isActionKind(action: any, kind: string): action is Action { + return isAction(action) && action.kind === kind; +} + +/** + * A request action is tied to the expectation of receiving a corresponding response action. The requestId property is used to match the + * received response with the original request. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export interface RequestAction extends Action { + /** + * Unique id for this request. In order to match a response to this request, the response needs to have the same id. + */ + readonly requestId: string; +} + +export function isRequestAction(action?: any): action is RequestAction { + return isAction(action) && isString(action, 'requestId'); +} + +export function generateRequestId(): string { + return uuid.v4(); +} +/** + * A response action is sent to respond to a request action. The responseId must match the requestId of the preceding request. + * In case the responseId is empty or undefined, the action is handled as standalone, i.e. it was fired without a preceding request. + */ +export interface ResponseAction extends Action { + /** + * Id corresponding to the request this action responds to. + */ + readonly responseId: string; +} + +export function isResponseAction(action?: any): action is ResponseAction { + return isAction(action) && isString(action, 'responseId'); +} + +/** + * A reject action is a response fired to indicate that a request must be rejected. + */ +export class RejectAction implements ResponseAction { + static readonly KIND = 'rejectRequest'; + readonly kind = RejectAction.KIND; + + constructor(public readonly message: string, public readonly responseId: string, public readonly detail?: JsonAny) {} +} + +export function isRejectAction(action: any): action is RejectAction { + return isResponseAction(action) && action.kind === RejectAction.KIND && isString(action, 'message'); +} + +/** + * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the + * server. After a successful modification, the server sends the updated model back to the client using the `UpdateModelAction`. + */ +export interface Operation extends Action {} + +/** + * An operation that executes a list of sub-operations. + */ +export class CompoundOperation implements Operation { + static readonly KIND = 'compound'; + constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) {} +} + +export function isCompoundOperation(operation?: any): operation is CompoundOperation { + return isActionKind(operation, CompoundOperation.KIND) && isArray(operation, 'operationList'); +} diff --git a/packages/protocol/src/action-protocol/client-notification.ts b/packages/protocol/src/action-protocol/client-notification.ts new file mode 100644 index 0000000..c2f9bc7 --- /dev/null +++ b/packages/protocol/src/action-protocol/client-notification.ts @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { isNumber, isString } from '../utils/typeguard-util'; +import { Action, isActionKind } from './base-protocol'; + +/** + * This action is typically sent by the server to signal a state change. This action extends the corresponding Sprotty action to include + * a timeout. If a timeout is given the respective status should disappear after the timeout is reached. + */ +export class GLSPServerStatusAction implements Action { + static KIND = 'serverStatus'; + + /** + * The kind of the action. + */ + kind = GLSPServerStatusAction.KIND; + + /** + * The message describing the status. + */ + severity: string; + + /** + * The severity of the status. + */ + message: string; + + /** + * Timeout after which a displayed status disappears. + */ + timeout = 1; +} + +export function isGLSPServerStatusAction(action: any): action is GLSPServerStatusAction { + return ( + isActionKind(action, GLSPServerStatusAction.KIND) && + isString(action, 'severity') && + isString(action, 'message') && + isNumber(action, 'timeout') + ); +} +/** + * This action is typically sent by the server to notify the user about something of interest. + */ +export class ServerMessageAction implements Action { + static KIND = 'serverMessage'; + + /** + * The kind of the action. + */ + kind = ServerMessageAction.KIND; + + /** + * The severity of the message. + */ + severity: 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL'; + + /** + * The message text. + */ + message: string; + + /** + * Further details on the message. + */ + details = ''; + + /** + * Timeout after which a displayed message disappears. + */ + timeout = -1; +} + +export function isServerMessageAction(action?: any): action is ServerMessageAction { + return ( + isActionKind(action, ServerMessageAction.KIND) && + isString(action, 'severity') && + isString(action, 'message') && + isString(action, 'details') && + isNumber(action, 'timeout') + ); +} diff --git a/packages/client/src/features/copy-paste/copy-paste-actions.ts b/packages/protocol/src/action-protocol/clipboard.ts similarity index 55% rename from packages/client/src/features/copy-paste/copy-paste-actions.ts rename to packages/protocol/src/action-protocol/clipboard.ts index a44de87..fe7e997 100644 --- a/packages/client/src/features/copy-paste/copy-paste-actions.ts +++ b/packages/protocol/src/action-protocol/clipboard.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2021 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,27 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { generateRequestId, RequestAction, ResponseAction } from 'sprotty'; -import { EditorContext } from '../../base/editor-context'; -import { Operation } from '../../base/operations/operation'; - -export class CutOperation implements Operation { - static readonly KIND = 'cut'; - - constructor(public readonly editorContext: EditorContext, public readonly kind: string = CutOperation.KIND) {} -} - -export class PasteOperation implements Operation { - static readonly KIND = 'paste'; - - constructor( - public readonly clipboardData: ClipboardData, - public readonly editorContext: EditorContext, - public readonly kind: string = PasteOperation.KIND - ) {} -} +import { isObject, isString } from '../utils/typeguard-util'; +import { generateRequestId, isActionKind, Operation, RequestAction, ResponseAction } from './base-protocol'; +import { EditorContext } from './types'; +/** + * Requests the clipboard data for the current editor context, i.e., the selected elements, in a clipboard-compatible format. + */ export class RequestClipboardDataAction implements RequestAction { static readonly KIND = 'requestClipboardData'; @@ -48,10 +35,13 @@ export class RequestClipboardDataAction implements RequestAction { static readonly KIND = 'requestContextActions'; constructor( @@ -28,6 +31,18 @@ export class RequestContextActions implements RequestAction { ) {} } +export function isRequestContextActions(action: any): action is RequestContextActions { + return ( + isActionKind(action, RequestContextActions.KIND) && + isString(action, 'contextId') && + isObject(action, 'editorContext') && + isString(action, 'requestId') + ); +} + +/** + * The `SetContextActions` is the response to a {@link RequestContextActions} containing all actions for the queried context. + */ export class SetContextActions implements ResponseAction { static readonly KIND = 'setContextActions'; constructor( @@ -38,6 +53,6 @@ export class SetContextActions implements ResponseAction { ) {} } -export function isSetContextActionsAction(action: Action): action is SetContextActions { - return action !== undefined && action.kind === SetContextActions.KIND && (action as SetContextActions).actions !== undefined; +export function isSetContextActionsAction(action: any): action is SetContextActions { + return isActionKind(action, SetContextActions.KIND) && isArray(action, 'actions') && isString(action, 'responseId'); } diff --git a/packages/protocol/src/action-protocol/edge-modification.ts b/packages/protocol/src/action-protocol/edge-modification.ts new file mode 100644 index 0000000..6e0e703 --- /dev/null +++ b/packages/protocol/src/action-protocol/edge-modification.ts @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isArray, isString } from '../utils/typeguard-util'; +import { isActionKind, Operation } from './base-protocol'; +import { ElementAndRoutingPoints } from './types'; + +/** + * If the source and/or target element of an edge should be adapted, the client can send a `ReconnectEdgeOperation` to the server. + */ +export class ReconnectEdgeOperation implements Operation { + static readonly KIND = 'reconnectEdge'; + constructor( + public readonly edgeElementId: string, + public readonly sourceElementId: string, + public readonly targetElementId: string, + public readonly kind: string = ReconnectEdgeOperation.KIND + ) {} +} + +export function isReconnectEdgeOperation(action: any): action is ReconnectEdgeOperation { + return ( + isActionKind(action, ReconnectEdgeOperation.KIND) && + isString(action, 'edgeElementId') && + isString(action, 'sourceElementId') && + isString(action, 'targetElementId') + ); +} + +/** + * An edge may have zero or more routing points that "re-direct" the edge between the source and the target element. In order to set these + * routing points the client may send a `ChangeRoutingPointsOperation`. + */ +export class ChangeRoutingPointsOperation implements Operation { + static readonly KIND = 'changeRoutingPoints'; + constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) {} +} + +export function isChangeRoutingsPointsOperation(action: any): action is ChangeRoutingPointsOperation { + return isActionKind(action, ChangeRoutingPointsOperation.KIND) && isArray(action, 'newRoutingPoints'); +} diff --git a/packages/protocol/src/action-protocol/element-creation.ts b/packages/protocol/src/action-protocol/element-creation.ts new file mode 100644 index 0000000..e4e9293 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-creation.ts @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isArray, isString } from '../utils/typeguard-util'; +import { isAction, isActionKind, Operation } from './base-protocol'; +import { Args, Point } from './types'; + +export interface CreateOperation extends Operation { + elementTypeId: string; + args?: Args; +} + +export function isCreateOperation(action: any): action is CreateOperation { + return isAction(action) && isString(action, 'elementTypeId'); +} +/** + * In order to create a node in the model the client can send a CreateNodeOperation with the necessary information to create that node. + */ +export class CreateNodeOperation implements CreateOperation { + static readonly KIND = 'createNode'; + + constructor( + public readonly elementTypeId: string, + public location?: Point, + public containerId?: string, + public args?: Args, + public readonly kind: string = CreateNodeOperation.KIND + ) {} +} + +export function isCreateNodeOperation(action: any): action is CreateNodeOperation { + return isActionKind(action, CreateNodeOperation.KIND) && isString(action, 'elementTypeId'); +} + +/** + * In order to create an edge in the model the client can send a `CreateEdgeOperation` with the necessary information to create that edge. + */ +export class CreateEdgeOperation implements CreateOperation { + static readonly KIND = 'createEdge'; + + constructor( + public readonly elementTypeId: string, + public sourceElementId: string, + public targetElementId: string, + public args?: Args, + public readonly kind: string = CreateEdgeOperation.KIND + ) {} +} + +export function isCreateEdgeOperation(action: any): action is CreateEdgeOperation { + return ( + isActionKind(action, CreateEdgeOperation.KIND) && + isString(action, 'elementTypeId') && + isString(action, 'sourceElementId') && + isString(action, 'targetElementId') + ); +} + +/** + * The client sends a `DeleteElementOperation` to the server to request the deletion of an element from the model. + */ +export class DeleteElementOperation implements Operation { + static readonly KIND = 'deleteElement'; + constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) {} +} + +export function isDeleteElementOperation(action: any): action is DeleteElementOperation { + return isActionKind(action, DeleteElementOperation.KIND) && isArray(action, 'elementIds'); +} diff --git a/packages/protocol/src/action-protocol/element-hover.ts b/packages/protocol/src/action-protocol/element-hover.ts new file mode 100644 index 0000000..8b2430e --- /dev/null +++ b/packages/protocol/src/action-protocol/element-hover.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { isObject, isString } from '../utils/typeguard-util'; +import { generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import { SModelRootSchema } from './model-structure'; +import { Bounds } from './types'; + +/** + * Triggered when the user hovers the mouse pointer over an element to get a popup with details on that element. + * This action is sent from the client to the server. The response is a `SetPopupModelAction`. + */ +export class RequestPopupModelAction implements RequestAction { + static readonly KIND = 'requestPopupModel'; + readonly kind = RequestPopupModelAction.KIND; + + constructor(public readonly elementId: string, public readonly bounds: Bounds, public readonly requestId = '') {} + + /** Factory function to dispatch a request with the `IActionDispatcher` */ + static create(elementId: string, bounds: Bounds): RequestAction { + return new RequestPopupModelAction(elementId, bounds, generateRequestId()); + } +} + +export function isRequestPopupModelAction(action: any): action is RequestPopupModelAction { + return ( + isActionKind(action, RequestPopupModelAction.KIND) && + isString(action, 'elementId') && + isObject(action, 'bounds') && + isString(action, 'requestId') + ); +} + +/** + * Sent from the server to the client to display a popup in response to a RequestPopupModelAction. This action can also be used to remove + * any existing popup by choosing EMPTY_ROOT as root element. + */ +export class SetPopupModelAction implements ResponseAction { + static readonly KIND = 'setPopupModel'; + readonly kind = SetPopupModelAction.KIND; + + constructor(public readonly newRoot: SModelRootSchema, public readonly responseId = '') {} +} + +export function isSetPopupModelAction(action: any): action is SetPopupModelAction { + return isActionKind(action, SetPopupModelAction.KIND) && isObject(action, 'newRoot') && isString(action, 'responseId'); +} diff --git a/packages/protocol/src/action-protocol/element-navigation.ts b/packages/protocol/src/action-protocol/element-navigation.ts new file mode 100644 index 0000000..2c4c53d --- /dev/null +++ b/packages/protocol/src/action-protocol/element-navigation.ts @@ -0,0 +1,176 @@ +/******************************************************************************** + * Copyright (c) 2020-2021 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { isArray, isObject, isString } from '../utils/typeguard-util'; +import { Action, generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import { Args, EditorContext } from './types'; + +/** + * A NavigationTarget identifies the object we want to navigate to via its uri and may further provide a label to display for the client. + */ +export interface NavigationTarget { + uri: string; + label?: string; + args?: Args; +} + +export namespace NavigationTarget { + export const ELEMENT_IDS = 'elementIds'; + export const ELEMENT_IDS_SEPARATOR = '&'; + export const TEXT_LINE = 'line'; + export const TEXT_COLUMN = 'column'; + + export interface TextPosition { + line: number; + character: number; + } + + export function hasArguments(target: NavigationTarget): boolean { + return target.args !== undefined && Object.keys(target.args).length > 0; + } + + export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean): void { + if (target.args === undefined) { + target.args = {}; + } + target.args[key] = value; + } + + export function getElementIds(target: NavigationTarget): string[] { + if (target.args === undefined || target.args[ELEMENT_IDS] === undefined) { + return []; + } + const elementIdsValue = target.args[ELEMENT_IDS].toString(); + return elementIdsValue.split(ELEMENT_IDS_SEPARATOR); + } + + export function setElementIds(target: NavigationTarget, elementIds: string[]): string { + if (target.args === undefined) { + target.args = {}; + } + return (target.args[ELEMENT_IDS] = elementIds.join(ELEMENT_IDS_SEPARATOR)); + } + + export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void { + if (position) { + if (target.args === undefined) { + target.args = {}; + } + target.args[TEXT_LINE] = position.line; + target.args[TEXT_COLUMN] = position.character; + } + } + + export function getTextPosition(target: NavigationTarget): TextPosition | undefined { + if (target.args === undefined || target.args[TEXT_LINE] === undefined || target.args[TEXT_COLUMN] === undefined) { + return undefined; + } + return { + line: Number(target.args[TEXT_LINE]), + character: Number(target.args[TEXT_COLUMN]) + }; + } +} + +/** + * Action that is usually sent from the client to the server to request navigation targets for a specific navigation type such as + * documentation or implementation in the given editor context. + */ +export class RequestNavigationTargetsAction implements RequestAction { + static readonly KIND = 'requestNavigationTargets'; + kind = RequestNavigationTargetsAction.KIND; + constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, readonly requestId: string = generateRequestId()) {} +} + +export function isRequestNavigationTargetsAction(action: any): action is RequestNavigationTargetsAction { + return ( + isActionKind(action, RequestNavigationTargetsAction.KIND) && + isString(action, 'targetTypeId') && + isObject(action, 'editorContext') && + isString(action, 'requestId') + ); +} + +/** + * Response action from the server following a {@link RequestNavigationTargetsAction}. It contains all available navigation targets for the + * queried target type in the provided editor context. The server may also provide additional information using the arguments, e.g., + * warnings, that can be interpreted by the client. + */ +export class SetNavigationTargetsAction implements ResponseAction { + static readonly KIND = 'setNavigationTargets'; + kind = SetNavigationTargetsAction.KIND; + constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) {} +} + +export function isSetNavigationTargetsAction(action: any): action is SetNavigationTargetsAction { + return isActionKind(action, SetNavigationTargetsAction.KIND) && isArray(action, 'targets') && isString(action, 'responseId'); +} + +/** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ +export class NavigateToTargetAction implements Action { + static readonly KIND = 'navigateToTarget'; + readonly kind = NavigateToTargetAction.KIND; + constructor(readonly target: NavigationTarget) {} +} + +export function isNavigateToTargetAction(action: any): action is NavigateToTargetAction { + return isActionKind(action, NavigateToTargetAction.KIND) && isObject(action, 'target'); +} + +/** + * If a client cannot navigate to a target directly, a {@link ResolveNavigationTargetAction} may be sent to the server to resolve the + * navigation target to one or more model elements. This may be useful in cases where the resolution of each target is expensive or the + * client architecture requires an indirection. + */ +export class ResolveNavigationTargetAction implements RequestAction { + static readonly KIND = 'resolveNavigationTarget'; + kind = ResolveNavigationTargetAction.KIND; + constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) {} +} + +export function isResolveNavigationTargetAction(action: any): action is ResolveNavigationTargetAction { + return ( + isActionKind(action, ResolveNavigationTargetAction.KIND) && isObject(action, 'navigationTarget') && isString(action, 'requestId') + ); +} + +/** + * An action sent from the server in response to a {@link ResolveNavigationTargetAction}. The response contains the resolved element ids + * for the given target and may contain additional information in the args property. + */ +export class SetResolvedNavigationTargetAction implements ResponseAction { + static readonly KIND = 'setResolvedNavigationTarget'; + kind = SetResolvedNavigationTargetAction.KIND; + constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') {} +} + +export function isSetResolvedNavigationTargets(action: any): action is SetResolvedNavigationTargetAction { + return isActionKind(action, SetResolvedNavigationTargetAction.KIND) && isArray(action, 'elementIds') && isString(action, 'responseId'); +} + +/** + * If a {@link NavigationTarget} cannot be resolved or the resolved target is something that is not part of our model source, e.g., + * a separate documentation file, a {@link NavigateToExternalTargetAction} may be sent. Since the target it outside of the model scope such + * an action would be typically handled by an integration layer (such as the surrounding IDE). + */ +export class NavigateToExternalTargetAction implements Action { + static readonly KIND = 'navigateToExternalTarget'; + readonly kind = NavigateToExternalTargetAction.KIND; + constructor(readonly target: NavigationTarget) {} +} + +export function isNavigateToExternalTargetAction(action: any): action is NavigateToExternalTargetAction { + return isActionKind(action, NavigateToExternalTargetAction.KIND) && isObject(action, 'target'); +} diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts new file mode 100644 index 0000000..9a23ed1 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2021 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { isArray, isBoolean } from '../utils/typeguard-util'; +import { Action, isActionKind } from './base-protocol'; + +/** + * Triggered when the user changes the selection, e.g. by clicking on a selectable element. The action should trigger a change in the + * selected state accordingly, so the elements can be rendered differently. The server can send such an action to the client in order to + * change the selection remotely. + */ +export class SelectAction implements Action { + static readonly KIND = 'elementSelected'; + kind = SelectAction.KIND; + + constructor(public readonly selectedElementsIDs: string[] = [], public readonly deselectedElementsIDs: string[] = []) {} +} + +export function isSelectAction(action: any): action is SelectAction { + return isActionKind(action, SelectAction.KIND) && isArray(action, 'selectedElementsIDs') && isArray(action, 'deselectedElementsIDs'); +} + +/** + * Programmatic action for selecting or deselecting all elements. + */ +export class SelectAllAction implements Action { + static readonly KIND = 'allSelected'; + kind = SelectAllAction.KIND; + + /** + * If `select` is true, all elements are selected, otherwise they are deselected. + */ + constructor(public readonly select: boolean = true) {} +} + +export function isSelectAllAction(action: any): action is SelectAllAction { + return isActionKind(action, SelectAllAction.KIND) && isBoolean(action, 'select'); +} diff --git a/packages/client/src/base/actions/edit-validation-actions.ts b/packages/protocol/src/action-protocol/element-text-editing.ts similarity index 72% rename from packages/client/src/base/actions/edit-validation-actions.ts rename to packages/protocol/src/action-protocol/element-text-editing.ts index 3d4ef9b..dcf6447 100644 --- a/packages/client/src/base/actions/edit-validation-actions.ts +++ b/packages/protocol/src/action-protocol/element-text-editing.ts @@ -13,9 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, generateRequestId, RequestAction, ResponseAction } from 'sprotty'; -import { Args } from '../args'; +import { isObject, isString } from '../utils/typeguard-util'; +import { Action, generateRequestId, isActionKind, isRequestAction, Operation, RequestAction, ResponseAction } from './base-protocol'; +import { Args } from './types'; +/** + * Requests the validation of the given text in the context of the provided model element. Typically sent from the client to the server. + */ export class RequestEditValidationAction implements RequestAction { static readonly KIND = 'requestEditValidation'; constructor( @@ -27,6 +31,19 @@ export class RequestEditValidationAction implements RequestAction
); - } - getRadius(): number { - return 14; + const subType = getSubType(element); + if (subType) { + setAttr(vnode, 'class', subType); + } + return vnode; } } diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 3d34479..2d108d0 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -11,84 +11,50 @@ ], "children": [ { - "id": "task0_header", - "children": [ - { - "id": "task0_icon", - "children": [ - { - "id": "task0_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task0_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 48.90625, - "height": 22.0 - }, - "text": "Push", - "alignment": { - "x": 24.453125, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "task0_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 91.90625, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "task0_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 31.921875, + "height": 16.0 }, - "layout": "hbox" + "text": "Push", + "alignment": { + "x": 15.9609375, + "y": 13.0 + } } ], "type": "task:manual", "position": { - "x": 470.0, - "y": 250.0 + "x": 70.0, + "y": 140.0 }, "size": { - "width": 101.90625, - "height": 52.0 + "width": 72.921875, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "paddingRight": 10.0, + "prefWidth": 72.921875, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -97,93 +63,59 @@ } }, { - "name": "ManualTask1", - "taskType": "manual", - "id": "task1", + "name": "Push2", + "taskType": "automated", + "id": "task0_automated", "cssClasses": [ "task", - "manual" + "automated" ], "children": [ { - "id": "task1_header", - "children": [ - { - "id": "task1_icon", - "children": [ - { - "id": "task1_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task1_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 52.5625, - "height": 22.0 - }, - "text": "RflWt", - "alignment": { - "x": 26.109375, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "task0_automated_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 95.5625, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "task0_automated_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 }, - "layout": "hbox" + "size": { + "width": 42.1103515625, + "height": 16.0 + }, + "text": "ChkWt", + "alignment": { + "x": 21.0, + "y": 13.0 + } } ], - "type": "task:manual", + "type": "task:automated", "position": { - "x": 930.0, - "y": 150.0 + "x": 220.0, + "y": 90.0 }, "size": { - "width": 105.5625, - "height": 52.0 + "width": 83.1103515625, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 75.6103515625, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -192,93 +124,177 @@ } }, { - "name": "ManualTask2", - "taskType": "manual", - "id": "task2", + "nodeType": "mergeNode", + "id": "activityNode1", + "type": "activityNode:merge", + "position": { + "x": 500.0, + "y": 90.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "nodeType": "decisionNode", + "id": "activityNode2", + "type": "activityNode:decision", + "position": { + "x": 330.0, + "y": 190.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "nodeType": "mergeNode", + "id": "activityNode4", + "type": "activityNode:merge", + "position": { + "x": 500.0, + "y": 190.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "nodeType": "decisionNode", + "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "type": "activityNode:decision", + "position": { + "x": 330.0, + "y": 90.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "nodeType": "forkNode", + "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "cssClasses": [ + "forkOrJoin" + ], + "type": "activityNode:fork", + "position": { + "x": 170.0, + "y": 130.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", + "type": "edge", + "sourceId": "task0", + "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" + }, + { + "nodeType": "joinNode", + "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "cssClasses": [ + "forkOrJoin" + ], + "type": "activityNode:join", + "position": { + "x": 560.0, + "y": 130.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", + "type": "edge", + "sourceId": "activityNode1", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "id": "0471cae4-c754-4e89-8337-96ed1546dd02", + "type": "edge", + "sourceId": "activityNode4", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "name": "AutomatedTask8", + "taskType": "automated", + "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", "cssClasses": [ "task", - "manual" + "automated" ], "children": [ { - "id": "task2_header", - "children": [ - { - "id": "task2_icon", - "children": [ - { - "id": "task2_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task2_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 62.21875, - "height": 22.0 - }, - "text": "ChkTp", - "alignment": { - "x": 31.109375, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 105.21875, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 37.9931640625, + "height": 16.0 }, - "layout": "hbox" + "text": "WtOK", + "alignment": { + "x": 18.671875, + "y": 13.0 + } } ], - "type": "task:manual", + "type": "task:automated", "position": { - "x": 670.0, - "y": 320.0 + "x": 390.0, + "y": 120.0 }, "size": { - "width": 115.21875, - "height": 52.0 + "width": 78.9931640625, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 71.4931640625, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -287,93 +303,86 @@ } }, { - "name": "ManualTask3", + "id": "2317f24a-034e-4352-8857-d361dc8f5ed7", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "e47d5eba-612d-4c43-9aba-2c5502ff4f04" + }, + { + "id": "0a57ab51-c0b6-4a51-b42e-0192bf3767dc", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task0_automated", + "args": { + "edgePadding": 10.0 + } + }, + { + "id": "7a5d6bc3-c61e-4baa-8556-6015f33269c7", + "type": "edge", + "sourceId": "task0_automated", + "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "args": { + "edgePadding": 10.0 + } + }, + { + "name": "Push", "taskType": "manual", - "id": "task3", + "id": "a63cfd87-da7c-4846-912b-29040b776bfb", "cssClasses": [ "task", "manual" ], "children": [ { - "id": "task3_header", - "children": [ - { - "id": "task3_icon", - "children": [ - { - "id": "task3_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task3_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 75.953125, - "height": 22.0 - }, - "text": "PreHeat", - "alignment": { - "x": 37.8125, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "54f08126-6041-44ce-9d8b-e7a549e48f91", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 118.953125, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "6e007d62-d441-4b08-8815-ad59de3b2ca8", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 34.32421875, + "height": 16.0 }, - "layout": "hbox" + "text": "RflWt", + "alignment": { + "x": 17.109375, + "y": 13.0 + } } ], "type": "task:manual", "position": { - "x": 930.0, - "y": 360.0 + "x": 390.0, + "y": 70.0 }, "size": { - "width": 128.953125, - "height": 52.0 + "width": 75.32421875, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 67.82421875, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -382,93 +391,86 @@ } }, { - "name": "ManualTask4", - "taskType": "manual", - "id": "task4", + "id": "9bb443d7-44f3-4192-8dd8-1a2782593373", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "a63cfd87-da7c-4846-912b-29040b776bfb" + }, + { + "id": "a36985a7-3e61-499c-9bdb-5be2b00cb75c", + "type": "edge", + "sourceId": "a63cfd87-da7c-4846-912b-29040b776bfb", + "targetId": "activityNode1", + "args": { + "edgePadding": 10.0 + } + }, + { + "id": "8880cd08-8d90-4408-acd4-6eae218b1650", + "type": "edge", + "sourceId": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", + "targetId": "activityNode1", + "args": { + "edgePadding": 10.0 + } + }, + { + "name": "AutomatedTask7", + "taskType": "automated", + "id": "7afd430b-5031-4082-8190-7e755c57cde9", "cssClasses": [ "task", - "manual" + "automated" ], "children": [ { - "id": "task4_header", - "children": [ - { - "id": "task4_icon", - "children": [ - { - "id": "task4_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task4_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 49.359375, - "height": 22.0 - }, - "text": "Brew", - "alignment": { - "x": 24.453125, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "7afd430b-5031-4082-8190-7e755c57cde9_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 92.359375, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "7afd430b-5031-4082-8190-7e755c57cde9_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 31.90625, + "height": 16.0 }, - "layout": "hbox" + "text": "Brew", + "alignment": { + "x": 15.953125, + "y": 13.0 + } } ], - "type": "task:manual", + "type": "task:automated", "position": { - "x": 1260.0, - "y": 250.0 + "x": 600.0, + "y": 140.0 }, "size": { - "width": 102.359375, - "height": 52.0 + "width": 72.90625, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 72.90625, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -477,93 +479,68 @@ } }, { - "name": "ManualTask6", - "taskType": "manual", - "id": "task6", + "id": "81efd1ef-ae19-4ea6-a20b-20857b1c6552", + "type": "edge", + "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "targetId": "7afd430b-5031-4082-8190-7e755c57cde9", + "args": { + "edgePadding": 10.0 + } + }, + { + "name": "AutomatedTask8", + "taskType": "automated", + "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", "cssClasses": [ "task", - "manual" + "automated" ], "children": [ { - "id": "task6_header", - "children": [ - { - "id": "task6_icon", - "children": [ - { - "id": "task6_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task6_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 73.34375, - "height": 22.0 - }, - "text": "KeepTp", - "alignment": { - "x": 36.671875, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 116.34375, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 }, - "layout": "hbox" + "size": { + "width": 41.4482421875, + "height": 16.0 + }, + "text": "ChkTp", + "alignment": { + "x": 20.6171875, + "y": 13.0 + } } ], - "type": "task:manual", + "type": "task:automated", "position": { - "x": 930.0, - "y": 290.0 + "x": 220.0, + "y": 190.0 }, "size": { - "width": 126.34375, - "height": 52.0 + "width": 82.4482421875, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 74.9482421875, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -571,94 +548,78 @@ "radiusBottomRight": 5.0 } }, + { + "id": "189aecf6-6043-4915-9f3e-7b2da8fd0d9c", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", + "args": { + "edgePadding": 10.0 + } + }, + { + "id": "69e45ef0-9c74-41da-a3ab-0a5669f83f96", + "type": "edge", + "sourceId": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", + "targetId": "activityNode2", + "args": { + "edgePadding": 10.0 + } + }, { "name": "AutomatedTask8", "taskType": "automated", - "id": "task8", + "id": "4b06ed95-c9be-47df-9941-98099259be4a", "cssClasses": [ "task", "automated" ], "children": [ { - "id": "task8_header", - "children": [ - { - "id": "task8_icon", - "children": [ - { - "id": "task8_ticon", - "type": "label:icon", - "position": { - "x": 8.4921875, - "y": 5.0 - }, - "size": { - "width": 15.015625, - "height": 22.0 - }, - "text": "A", - "alignment": { - "x": 7.671875, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task8_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 63.671875, - "height": 22.0 - }, - "text": "ChkWt", - "alignment": { - "x": 31.671875, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "4b06ed95-c9be-47df-9941-98099259be4a_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 106.671875, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "4b06ed95-c9be-47df-9941-98099259be4a_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 49.248046875, + "height": 16.0 }, - "layout": "hbox" + "text": "KeepTp", + "alignment": { + "x": 24.5234375, + "y": 13.0 + } } ], "type": "task:automated", "position": { - "x": 670.0, - "y": 180.0 + "x": 390.0, + "y": 170.0 }, "size": { - "width": 116.671875, - "height": 52.0 + "width": 90.248046875, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 82.748046875, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -669,91 +630,57 @@ { "name": "AutomatedTask9", "taskType": "automated", - "id": "task9", + "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", "cssClasses": [ "task", "automated" ], "children": [ { - "id": "task9_header", - "children": [ - { - "id": "task9_icon", - "children": [ - { - "id": "task9_ticon", - "type": "label:icon", - "position": { - "x": 8.4921875, - "y": 5.0 - }, - "size": { - "width": 15.015625, - "height": 22.0 - }, - "text": "A", - "alignment": { - "x": 7.671875, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task9_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 56.109375, - "height": 22.0 - }, - "text": "WtOK", - "alignment": { - "x": 27.78125, - "y": 18.0 - } - } - ], - "type": "comp:header", + "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6_icon", + "type": "icon", "position": { "x": 5.0, "y": 5.0 }, "size": { - "width": 99.109375, - "height": 42.0 + "width": 25.0, + "height": 20.0 + } + }, + { + "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6_classname", + "type": "label:heading", + "position": { + "x": 31.0, + "y": 7.0 + }, + "size": { + "width": 51.46875, + "height": 16.0 }, - "layout": "hbox" + "text": "PreHeat", + "alignment": { + "x": 25.6796875, + "y": 13.0 + } } ], "type": "task:automated", "position": { - "x": 930.0, + "x": 390.0, "y": 220.0 }, "size": { - "width": 109.109375, - "height": 52.0 + "width": 92.46875, + "height": 30.0 + }, + "layout": "hbox", + "layoutOptions": { + "prefWidth": 84.96875, + "paddingRight": 10.0, + "prefHeight": 30.0 }, - "layout": "vbox", "args": { "radiusBottomLeft": 5.0, "radiusTopLeft": 5.0, @@ -762,214 +689,42 @@ } }, { - "nodeType": "mergeNode", - "id": "activityNode1", - "type": "activityNode:merge", - "position": { - "x": 1100.0, - "y": 190.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } - }, - { - "id": "edge4", - "type": "edge", - "sourceId": "task1", - "targetId": "activityNode1" - }, - { - "id": "edge5", - "type": "edge", - "sourceId": "task9", - "targetId": "activityNode1" - }, - { - "nodeType": "decisionNode", - "id": "activityNode2", - "type": "activityNode:decision", - "position": { - "x": 840.0, - "y": 330.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "id": "edge7", - "type": "edge", - "sourceId": "task2", - "targetId": "activityNode2" - }, - { - "nodeType": "mergeNode", - "id": "activityNode4", - "type": "activityNode:merge", - "position": { - "x": 1110.0, - "y": 330.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } - }, - { - "id": "edge11", - "type": "edge", - "sourceId": "task6", - "targetId": "activityNode4" - }, - { - "id": "edge12", - "type": "edge", - "sourceId": "task3", - "targetId": "activityNode4" - }, - { - "nodeType": "decisionNode", - "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "type": "activityNode:decision", - "position": { - "x": 830.0, - "y": 190.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "id": "69d7d698-c5ce-4e85-be06-d9bd9357322d", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "task1" - }, - { - "nodeType": "forkNode", - "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "cssClasses": [ - "forkOrJoin" - ], - "type": "activityNode:fork", - "position": { - "x": 590.0, - "y": 250.0 - }, - "size": { - "width": 10.0, - "height": 50.0 - } - }, - { - "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", - "type": "edge", - "sourceId": "task0", - "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" - }, - { - "id": "5eeaa218-ad56-4052-8b28-bbc08d82ed34", - "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task2" - }, - { - "nodeType": "joinNode", - "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "cssClasses": [ - "forkOrJoin" - ], - "type": "activityNode:join", - "position": { - "x": 1220.0, - "y": 250.0 - }, - "size": { - "width": 10.0, - "height": 50.0 - } - }, - { - "id": "2f57af4e-2960-496e-992d-03c8b021d81a", + "id": "d04afaf8-0894-4284-b844-22b255893107", "cssClasses": [ "medium" ], "type": "edge:weighted", "sourceId": "activityNode2", - "targetId": "task6" + "targetId": "4b06ed95-c9be-47df-9941-98099259be4a" }, { - "id": "2c7427b0-9743-4bfb-b04c-078ba98f8d7e", + "id": "b9ef468f-2aaf-41b9-b408-29e2d08e490e", "cssClasses": [ "medium" ], "type": "edge:weighted", "sourceId": "activityNode2", - "targetId": "task3" - }, - { - "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", - "type": "edge", - "sourceId": "activityNode1", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" - }, - { - "id": "0471cae4-c754-4e89-8337-96ed1546dd02", - "type": "edge", - "sourceId": "activityNode4", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" - }, - { - "id": "2c3ec34b-291c-43ef-bdf9-035afc4764e6", - "type": "edge", - "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "targetId": "task4" + "targetId": "80d1792f-9c7e-41c0-8eca-eeb0509397b6" }, { - "id": "e9aa796a-aa5b-4e6d-8860-4b9c5a5b8d89", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "task9" - }, - { - "id": "f49a588c-618c-4d9f-96fc-feee776a0ead", + "id": "58657bcf-6d32-4e7d-b73a-f84fd8d7158b", "type": "edge", - "sourceId": "task8", - "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" + "sourceId": "4b06ed95-c9be-47df-9941-98099259be4a", + "targetId": "activityNode4", + "args": { + "edgePadding": 10.0 + } }, { - "id": "568d91e3-d5bf-464d-923e-9769a8a651f3", + "id": "39a75477-facf-4960-b0bb-1989be7fb57f", "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task8" + "sourceId": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", + "targetId": "activityNode4", + "args": { + "edgePadding": 10.0 + } } ], "type": "graph", - "revision": 18 -} + "revision": 10 +} \ No newline at end of file diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 59111c9..00fbbe0 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -38,6 +38,7 @@ border: 1px solid; max-width: 400px; min-width: 100px; + z-index: 1; } .sprotty-popup > div { @@ -50,23 +51,55 @@ .sprotty-resize-handle { /* radius does not work in Firefox */ - r: 7px; - fill: #884; + r: 5px; + fill: #1d80d1; stroke: none; z-index: 1000; } .sprotty-resize-handle.selected { - fill: #66a; + fill: #1d80d1; } .sprotty-resize-handle.active { - fill: #0074e8; + fill: #1d80d1; } -.sprotty-resize-handle.mouseover { - stroke: #112; - stroke-width: 1; +.sprotty-edge { + fill: none; + stroke-width: 1.5px; +} + +.sprotty-edge.selected { + stroke-width: 1.5px; +} + +.sprotty-edge.mouseover:not(.selected) path { + stroke-width: 1.5px; +} + +.sprotty-node:not(.selected) { + stroke-width: 0px; +} + +.sprotty-node.mouseover:not(.selected), +.sprotty-edge.mouseover:not(.selected) { + opacity: 60%; +} + +.sprotty-node.selected, +.sprotty-edge.selected { + stroke: #1d80d1; + stroke-width: 1.5px; + stroke-dashoffset: 5; + stroke-dasharray: 5, 5; + stroke-linecap: round; +} + +.sprotty-edge.mouseover:not(.selected) .arrow, +.sprotty-edge.selected .arrow { + stroke-dashoffset: none; + stroke-dasharray: none; } .node-creation-mode { @@ -113,3 +146,72 @@ .marquee-mode { cursor: crosshair; } + +.sprotty-node.marquee { + fill: lightgray; + opacity: 0.2; +} + +.sprotty-edge > .sprotty-routing-handle { + r: 5px; + fill: #1d80d1; + stroke: none; + z-index: 1000; +} + +.sprotty-edge > .sprotty-routing-handle[data-kind='line'] { + opacity: 0.35; +} + +.sprotty-edge > .sprotty-routing-handle.selected { + fill: #1d80d1; +} + +.sprotty-edge > .sprotty-routing-handle.mouseover { + stroke: #1d80d1a1; + stroke-width: 1; +} + +.sprotty-missing { + stroke-width: 1; + stroke: #f00; + fill: #f00; + font-size: 14pt; + text-anchor: middle; +} + +g .movement-not-allowed > .sprotty-node { + stroke: var(--glsp-error-foreground) !important; +} + +.sprotty-resize-handle.movement-not-allowed { + stroke: var(--glsp-error-foreground) !important; + fill: var(--glsp-error-foreground) !important; +} + +.error > .sprotty-node { + filter: drop-shadow(0px 0px 2px var(--glsp-error-foreground)) !important; +} + +.warning > .sprotty-node { + filter: drop-shadow(0px 0px 2px var(--glsp-warning-foreground)) !important; +} + +.info > .sprotty-node { + filter: drop-shadow(0px 0px 2px var(--glsp-info-foreground)) !important; +} + +svg { + border-style: solid; + border-width: 1px; + border-color: #bbb; +} + +text { + stroke-width: 0; + stroke: #000; + fill: #000; + font-family: sans-serif; + font-size: 8pt; + text-anchor: middle; +} From 9fed5338ef3b746c2b0874b2bd67f0dc3f25f6f1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 17 Jan 2022 23:19:32 +0100 Subject: [PATCH 075/566] Initial Contribution Contributed on behalf of STMicroelectronics Signed-off-by :Tobias Ortmayr Co-authored-by: Simon Graband Co-authored-by: Stefan Dirix Co-authored-by: Nina Doschek Co-authored-by: Thierry Le Gal --- examples/workflow-server/LICENSE | 277 +++++ examples/workflow-server/package.json | 62 ++ examples/workflow-server/src/app.ts | 32 + .../workflow-server/src/graph-extension.ts | 186 ++++ .../handler/create-activity-node-handler.ts | 34 + .../handler/create-automated-task-handler.ts | 30 + .../src/handler/create-category-handler.ts | 36 + .../handler/create-decision-node-handler.ts | 30 + .../src/handler/create-edge-handler.ts | 25 + .../src/handler/create-fork-node-handler.ts | 24 + .../create-fork-or-join-node-handler.ts | 26 + .../src/handler/create-join-node-handler.ts | 24 + .../src/handler/create-manual-task-handler.ts | 30 + .../src/handler/create-merge-node-handler.ts | 30 + .../src/handler/create-task-handler.ts | 38 + .../handler/create-weighted-edge-handler.ts | 27 + .../create-workflow-node-operation-handler.ts | 56 + .../src/handler/grid-snapper.ts | 32 + examples/workflow-server/src/index.ts | 19 + .../workflow-label-edit-validator.ts | 39 + .../src/marker/workflow-model-validator.ts | 137 +++ .../workflow-navigation-target-resolver.ts | 40 + ...-or-previous-navigation-target-provider.ts | 53 + .../next-node-navigation-target-provider.ts | 31 + ...ocumentation-navigation-target-provider.ts | 52 + ...revious-node-navigation-target-provider.ts | 31 + ...orkflow-command-palette-action-provider.ts | 124 +++ .../workflow-context-menu-item-provider.ts | 38 + .../workflow-server/src/util/model-types.ts | 55 + .../src/workflow-diagram-configuration.ts | 122 +++ .../src/workflow-diagram-module.ts | 107 ++ .../src/workflow-glsp-server.ts | 37 + .../src/workflow-model-index.ts | 31 + .../src/workflow-popup-factory.ts | 46 + examples/workflow-server/tsconfig.json | 12 + .../workflow-server/workspace/example1.wf | 975 ++++++++++++++++++ packages/graph/LICENSE | 642 ++++++++++++ packages/graph/README.md | 8 + packages/graph/package.json | 70 ++ packages/graph/src/default-types.ts | 48 + packages/graph/src/galignable.ts | 42 + packages/graph/src/gbound-aware.ts | 53 + packages/graph/src/gbutton.ts | 34 + packages/graph/src/gcompartment.ts | 47 + packages/graph/src/gedge-layoutable.ts | 45 + packages/graph/src/gedge.ts | 100 ++ packages/graph/src/ggraph.ts | 49 + packages/graph/src/gissue-marker.ts | 49 + packages/graph/src/glabel.ts | 50 + packages/graph/src/glayout-container.ts | 36 + packages/graph/src/glayoutable.ts | 57 + packages/graph/src/gmodel-element.spec.ts | 18 + packages/graph/src/gmodel-element.ts | 216 ++++ packages/graph/src/gnode.ts | 34 + packages/graph/src/gport.ts | 26 + packages/graph/src/gpre-rendered-element.ts | 40 + packages/graph/src/gshape-element.ts | 51 + packages/graph/src/index.ts | 32 + packages/graph/tsconfig.json | 12 + packages/server-node/LICENSE | 642 ++++++++++++ packages/server-node/README.md | 8 + packages/server-node/package.json | 73 ++ .../src/actions/action-dispatcher.spec.ts | 255 +++++ .../src/actions/action-dispatcher.ts | 135 +++ .../actions/action-handler-registry.spec.ts | 52 + .../src/actions/action-handler-registry.ts | 60 ++ .../server-node/src/actions/action-handler.ts | 62 ++ .../src/actions/client-action-handler.ts | 50 + .../actions/global-action-provider.spec.ts | 79 ++ .../src/actions/global-action-provider.ts | 72 ++ .../src/actions/save-model-action-handler.ts | 57 + .../change-bounds-operation-handler.ts | 55 + .../computed-bounds-action-handler.ts | 65 ++ .../src/base-impl/delete-operation-handler.ts | 113 ++ .../src/base-impl/gmodel-diagram-module.ts | 90 ++ .../src/base-impl/gmodel-loader.ts | 75 ++ .../server-node/src/base-impl/gmodel-state.ts | 31 + packages/server-node/src/base-impl/index.ts | 19 + .../server-node/src/command/command-stack.ts | 74 ++ packages/server-node/src/command/command.ts | 39 + packages/server-node/src/di/app-module.ts | 99 ++ .../src/di/client-session-module.ts | 29 + packages/server-node/src/di/diagram-module.ts | 246 +++++ packages/server-node/src/di/glsp-module.ts | 66 ++ packages/server-node/src/di/multi-binding.ts | 91 ++ .../server-node/src/di/multi-bindings.spec.ts | 133 +++ packages/server-node/src/di/server-module.ts | 85 ++ .../server-node/src/di/service-identifiers.ts | 39 + .../src/diagram/diagram-configuration.ts | 97 ++ .../request-type-hints-action-handler.spec.ts | 72 ++ .../request-type-hints-action-handler.ts | 34 + .../request-clipboard-data-action-handler.ts | 51 + .../command-palette-action-provider.ts | 94 ++ .../context-actions-provider-registry.spec.ts | 69 ++ .../context-actions-provider-registry.ts | 47 + .../context-actions-provider.ts | 36 + .../context-menu-item-provider.ts | 84 ++ .../request-context-actions-handler.spec.ts | 73 ++ .../request-context-actions-handler.ts | 37 + .../tool-palette-item-provider.spec.ts | 46 + .../tool-palette-item-provider.ts | 111 ++ ...apply-label-edit-operation-handler.spec.ts | 36 + .../apply-label-edit-operation-handler.ts | 38 + .../context-edit-validator-registry.spec.ts | 54 + .../context-edit-validator-registry.ts | 43 + .../directediting/context-edit-validator.ts | 55 + .../directediting/label-edit-validator.ts | 35 + .../request-edit-validation-handler.spec.ts | 125 +++ .../request-edit-validation-handler.ts | 43 + .../src/features/model/gmodel-factory.ts | 60 ++ .../src/features/model/gmodel-index.ts | 169 +++ .../features/model/gmodel-serializer.spec.ts | 141 +++ .../src/features/model/gmodel-serializer.ts | 147 +++ .../src/features/model/model-source-loader.ts | 40 + .../src/features/model/model-state.ts | 84 ++ .../model/model-submission-handler.ts | 94 ++ .../model/request-model-action-handler.ts | 66 ++ .../navigation/json-opener-options.ts | 69 ++ .../navigation-target-provider-registry.ts | 36 + .../navigation/navigation-target-provider.ts | 50 + .../navigation-target-resolution.ts | 22 + .../navigation/navigation-target-resolver.ts | 61 ++ ...quest-navigation-targets-action-handler.ts | 39 + ...solve-navigation-targets-action-handler.ts | 42 + .../src/features/popup/popup-model-factory.ts | 38 + .../request-popup-model-action-handler.ts | 60 ++ .../features/validation/model-validator.ts | 34 + .../validation/request-markers-handler.ts | 51 + packages/server-node/src/index.ts | 87 ++ .../server-node/src/launch/cli-parser.spec.ts | 61 ++ packages/server-node/src/launch/cli-parser.ts | 86 ++ .../src/launch/glsp-server-launcher.ts | 69 ++ .../src/launch/socket-cli-parser.spec.ts | 73 ++ .../src/launch/socket-cli-parser.ts | 52 + .../src/launch/socket-server-launcher.ts | 74 ++ .../server-node/src/layout/layout-engine.ts | 27 + .../operations/compound-operation-handler.ts | 38 + .../operations/create-operation-handler.ts | 146 +++ .../src/operations/cut-operation-handler.ts | 38 + .../operations/operation-action-handler.ts | 64 ++ .../operation-handler-registry.spec.ts | 50 + .../operations/operation-handler-registry.ts | 51 + .../src/operations/operation-handler.ts | 57 + .../src/operations/paste-operation-handler.ts | 116 +++ .../reconnect-edge-operation-handler.ts | 55 + .../src/protocol/glsp-client-proxy.ts | 65 ++ .../src/protocol/glsp-server-listener.ts | 39 + .../src/protocol/glsp-server.spec.ts | 140 +++ .../server-node/src/protocol/glsp-server.ts | 313 ++++++ .../session/client-session-factory.spec.ts | 60 ++ .../src/session/client-session-factory.ts | 70 ++ .../src/session/client-session-initializer.ts | 27 + .../src/session/client-session-listener.ts | 41 + .../session/client-session-manager.spec.ts | 82 ++ .../src/session/client-session-manager.ts | 187 ++++ .../server-node/src/session/client-session.ts | 64 ++ .../src/test/integration-test.spec.ts | 163 +++ packages/server-node/src/test/minimal.json | 32 + packages/server-node/src/test/mock-util.ts | 253 +++++ packages/server-node/src/utils/args-util.ts | 52 + .../src/utils/client-options-util.ts | 22 + packages/server-node/src/utils/di-utilts.ts | 19 + .../server-node/src/utils/disposable.spec.ts | 42 + packages/server-node/src/utils/disposable.ts | 68 ++ .../server-node/src/utils/geometry-util.ts | 61 ++ .../src/utils/glsp-server-error.ts | 27 + packages/server-node/src/utils/logger.ts | 55 + .../server-node/src/utils/maybe-promise.ts | 28 + .../src/utils/promise-queue.spec.ts | 138 +++ .../server-node/src/utils/promise-queue.ts | 103 ++ .../server-node/src/utils/registry.spec.ts | 223 ++++ packages/server-node/src/utils/registry.ts | 153 +++ .../server-node/src/utils/winston-logger.ts | 76 ++ packages/server-node/tsconfig.json | 12 + 174 files changed, 13943 insertions(+) create mode 100644 examples/workflow-server/LICENSE create mode 100644 examples/workflow-server/package.json create mode 100644 examples/workflow-server/src/app.ts create mode 100644 examples/workflow-server/src/graph-extension.ts create mode 100644 examples/workflow-server/src/handler/create-activity-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-automated-task-handler.ts create mode 100644 examples/workflow-server/src/handler/create-category-handler.ts create mode 100644 examples/workflow-server/src/handler/create-decision-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-edge-handler.ts create mode 100644 examples/workflow-server/src/handler/create-fork-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-join-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-manual-task-handler.ts create mode 100644 examples/workflow-server/src/handler/create-merge-node-handler.ts create mode 100644 examples/workflow-server/src/handler/create-task-handler.ts create mode 100644 examples/workflow-server/src/handler/create-weighted-edge-handler.ts create mode 100644 examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts create mode 100644 examples/workflow-server/src/handler/grid-snapper.ts create mode 100644 examples/workflow-server/src/index.ts create mode 100644 examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts create mode 100644 examples/workflow-server/src/marker/workflow-model-validator.ts create mode 100644 examples/workflow-server/src/model/workflow-navigation-target-resolver.ts create mode 100644 examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts create mode 100644 examples/workflow-server/src/provider/next-node-navigation-target-provider.ts create mode 100644 examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts create mode 100644 examples/workflow-server/src/provider/previous-node-navigation-target-provider.ts create mode 100644 examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts create mode 100644 examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts create mode 100644 examples/workflow-server/src/util/model-types.ts create mode 100644 examples/workflow-server/src/workflow-diagram-configuration.ts create mode 100644 examples/workflow-server/src/workflow-diagram-module.ts create mode 100644 examples/workflow-server/src/workflow-glsp-server.ts create mode 100644 examples/workflow-server/src/workflow-model-index.ts create mode 100644 examples/workflow-server/src/workflow-popup-factory.ts create mode 100644 examples/workflow-server/tsconfig.json create mode 100644 examples/workflow-server/workspace/example1.wf create mode 100644 packages/graph/LICENSE create mode 100644 packages/graph/README.md create mode 100644 packages/graph/package.json create mode 100644 packages/graph/src/default-types.ts create mode 100644 packages/graph/src/galignable.ts create mode 100644 packages/graph/src/gbound-aware.ts create mode 100644 packages/graph/src/gbutton.ts create mode 100644 packages/graph/src/gcompartment.ts create mode 100644 packages/graph/src/gedge-layoutable.ts create mode 100644 packages/graph/src/gedge.ts create mode 100644 packages/graph/src/ggraph.ts create mode 100644 packages/graph/src/gissue-marker.ts create mode 100644 packages/graph/src/glabel.ts create mode 100644 packages/graph/src/glayout-container.ts create mode 100644 packages/graph/src/glayoutable.ts create mode 100644 packages/graph/src/gmodel-element.spec.ts create mode 100644 packages/graph/src/gmodel-element.ts create mode 100644 packages/graph/src/gnode.ts create mode 100644 packages/graph/src/gport.ts create mode 100644 packages/graph/src/gpre-rendered-element.ts create mode 100644 packages/graph/src/gshape-element.ts create mode 100644 packages/graph/src/index.ts create mode 100644 packages/graph/tsconfig.json create mode 100644 packages/server-node/LICENSE create mode 100644 packages/server-node/README.md create mode 100644 packages/server-node/package.json create mode 100644 packages/server-node/src/actions/action-dispatcher.spec.ts create mode 100644 packages/server-node/src/actions/action-dispatcher.ts create mode 100644 packages/server-node/src/actions/action-handler-registry.spec.ts create mode 100644 packages/server-node/src/actions/action-handler-registry.ts create mode 100644 packages/server-node/src/actions/action-handler.ts create mode 100644 packages/server-node/src/actions/client-action-handler.ts create mode 100644 packages/server-node/src/actions/global-action-provider.spec.ts create mode 100644 packages/server-node/src/actions/global-action-provider.ts create mode 100644 packages/server-node/src/actions/save-model-action-handler.ts create mode 100644 packages/server-node/src/base-impl/change-bounds-operation-handler.ts create mode 100644 packages/server-node/src/base-impl/computed-bounds-action-handler.ts create mode 100644 packages/server-node/src/base-impl/delete-operation-handler.ts create mode 100644 packages/server-node/src/base-impl/gmodel-diagram-module.ts create mode 100644 packages/server-node/src/base-impl/gmodel-loader.ts create mode 100644 packages/server-node/src/base-impl/gmodel-state.ts create mode 100644 packages/server-node/src/base-impl/index.ts create mode 100644 packages/server-node/src/command/command-stack.ts create mode 100644 packages/server-node/src/command/command.ts create mode 100644 packages/server-node/src/di/app-module.ts create mode 100644 packages/server-node/src/di/client-session-module.ts create mode 100644 packages/server-node/src/di/diagram-module.ts create mode 100644 packages/server-node/src/di/glsp-module.ts create mode 100644 packages/server-node/src/di/multi-binding.ts create mode 100644 packages/server-node/src/di/multi-bindings.spec.ts create mode 100644 packages/server-node/src/di/server-module.ts create mode 100644 packages/server-node/src/di/service-identifiers.ts create mode 100644 packages/server-node/src/diagram/diagram-configuration.ts create mode 100644 packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts create mode 100644 packages/server-node/src/diagram/request-type-hints-action-handler.ts create mode 100644 packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts create mode 100644 packages/server-node/src/features/contextactions/command-palette-action-provider.ts create mode 100644 packages/server-node/src/features/contextactions/context-actions-provider-registry.spec.ts create mode 100644 packages/server-node/src/features/contextactions/context-actions-provider-registry.ts create mode 100644 packages/server-node/src/features/contextactions/context-actions-provider.ts create mode 100644 packages/server-node/src/features/contextactions/context-menu-item-provider.ts create mode 100644 packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts create mode 100644 packages/server-node/src/features/contextactions/request-context-actions-handler.ts create mode 100644 packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts create mode 100644 packages/server-node/src/features/contextactions/tool-palette-item-provider.ts create mode 100644 packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts create mode 100644 packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts create mode 100644 packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts create mode 100644 packages/server-node/src/features/directediting/context-edit-validator-registry.ts create mode 100644 packages/server-node/src/features/directediting/context-edit-validator.ts create mode 100644 packages/server-node/src/features/directediting/label-edit-validator.ts create mode 100644 packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts create mode 100644 packages/server-node/src/features/directediting/request-edit-validation-handler.ts create mode 100644 packages/server-node/src/features/model/gmodel-factory.ts create mode 100644 packages/server-node/src/features/model/gmodel-index.ts create mode 100644 packages/server-node/src/features/model/gmodel-serializer.spec.ts create mode 100644 packages/server-node/src/features/model/gmodel-serializer.ts create mode 100644 packages/server-node/src/features/model/model-source-loader.ts create mode 100644 packages/server-node/src/features/model/model-state.ts create mode 100644 packages/server-node/src/features/model/model-submission-handler.ts create mode 100644 packages/server-node/src/features/model/request-model-action-handler.ts create mode 100644 packages/server-node/src/features/navigation/json-opener-options.ts create mode 100644 packages/server-node/src/features/navigation/navigation-target-provider-registry.ts create mode 100644 packages/server-node/src/features/navigation/navigation-target-provider.ts create mode 100644 packages/server-node/src/features/navigation/navigation-target-resolution.ts create mode 100644 packages/server-node/src/features/navigation/navigation-target-resolver.ts create mode 100644 packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts create mode 100644 packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts create mode 100644 packages/server-node/src/features/popup/popup-model-factory.ts create mode 100644 packages/server-node/src/features/popup/request-popup-model-action-handler.ts create mode 100644 packages/server-node/src/features/validation/model-validator.ts create mode 100644 packages/server-node/src/features/validation/request-markers-handler.ts create mode 100644 packages/server-node/src/index.ts create mode 100644 packages/server-node/src/launch/cli-parser.spec.ts create mode 100644 packages/server-node/src/launch/cli-parser.ts create mode 100644 packages/server-node/src/launch/glsp-server-launcher.ts create mode 100644 packages/server-node/src/launch/socket-cli-parser.spec.ts create mode 100644 packages/server-node/src/launch/socket-cli-parser.ts create mode 100644 packages/server-node/src/launch/socket-server-launcher.ts create mode 100644 packages/server-node/src/layout/layout-engine.ts create mode 100644 packages/server-node/src/operations/compound-operation-handler.ts create mode 100644 packages/server-node/src/operations/create-operation-handler.ts create mode 100644 packages/server-node/src/operations/cut-operation-handler.ts create mode 100644 packages/server-node/src/operations/operation-action-handler.ts create mode 100644 packages/server-node/src/operations/operation-handler-registry.spec.ts create mode 100644 packages/server-node/src/operations/operation-handler-registry.ts create mode 100644 packages/server-node/src/operations/operation-handler.ts create mode 100644 packages/server-node/src/operations/paste-operation-handler.ts create mode 100644 packages/server-node/src/operations/reconnect-edge-operation-handler.ts create mode 100644 packages/server-node/src/protocol/glsp-client-proxy.ts create mode 100644 packages/server-node/src/protocol/glsp-server-listener.ts create mode 100644 packages/server-node/src/protocol/glsp-server.spec.ts create mode 100644 packages/server-node/src/protocol/glsp-server.ts create mode 100644 packages/server-node/src/session/client-session-factory.spec.ts create mode 100644 packages/server-node/src/session/client-session-factory.ts create mode 100644 packages/server-node/src/session/client-session-initializer.ts create mode 100644 packages/server-node/src/session/client-session-listener.ts create mode 100644 packages/server-node/src/session/client-session-manager.spec.ts create mode 100644 packages/server-node/src/session/client-session-manager.ts create mode 100644 packages/server-node/src/session/client-session.ts create mode 100644 packages/server-node/src/test/integration-test.spec.ts create mode 100644 packages/server-node/src/test/minimal.json create mode 100644 packages/server-node/src/test/mock-util.ts create mode 100644 packages/server-node/src/utils/args-util.ts create mode 100644 packages/server-node/src/utils/client-options-util.ts create mode 100644 packages/server-node/src/utils/di-utilts.ts create mode 100644 packages/server-node/src/utils/disposable.spec.ts create mode 100644 packages/server-node/src/utils/disposable.ts create mode 100644 packages/server-node/src/utils/geometry-util.ts create mode 100644 packages/server-node/src/utils/glsp-server-error.ts create mode 100644 packages/server-node/src/utils/logger.ts create mode 100644 packages/server-node/src/utils/maybe-promise.ts create mode 100644 packages/server-node/src/utils/promise-queue.spec.ts create mode 100644 packages/server-node/src/utils/promise-queue.ts create mode 100644 packages/server-node/src/utils/registry.spec.ts create mode 100644 packages/server-node/src/utils/registry.ts create mode 100644 packages/server-node/src/utils/winston-logger.ts create mode 100644 packages/server-node/tsconfig.json diff --git a/examples/workflow-server/LICENSE b/examples/workflow-server/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/examples/workflow-server/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json new file mode 100644 index 0000000..2a518cb --- /dev/null +++ b/examples/workflow-server/package.json @@ -0,0 +1,62 @@ +{ + "name": "@eclipse-glsp-examples/workflow-server", + "version": "0.9.0", + "description": "GLSP node server for the workflow example", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-server-node.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "STMicroelectronics", + "url": "https://www.st.com/" + }, + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "files": [ + "lib", + "src" + ], + "dependencies": { + "@eclipse-glsp/server-node": "0.9.0" + }, + "devDependencies": { + "@types/node": "12.x", + "reflect-metadata": "^0.1.13", + "ts-node": "^10.4.0", + "typescript": "^3.9.2" + }, + "scripts": { + "clean": "rimraf lib", + "build": "yarn run clean && tsc", + "test": "", + "lint": "eslint -c ../.eslintrc.js --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "watch": "tsc -w", + "start:server": "yarn ts-node src/index.ts" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/examples/workflow-server/src/app.ts b/examples/workflow-server/src/app.ts new file mode 100644 index 0000000..6de9ebe --- /dev/null +++ b/examples/workflow-server/src/app.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { createAppModule, createSocketCliParser, LoggerFactory, resolveAndCatch, SocketServerLauncher } from '@eclipse-glsp/server-node'; +import { Container } from 'inversify'; +import { WorkflowDiagramModule, WorkflowServerModule } from './workflow-diagram-module'; + +export function launch(argv?: string[]): void { + const options = createSocketCliParser().parse(argv); + const appContainer = new Container(); + appContainer.load(createAppModule(options)); + + const logger = appContainer.get(LoggerFactory)('WorkflowServerApp'); + const launcher = appContainer.resolve(SocketServerLauncher); + const serverModule = new WorkflowServerModule().configureDiagramModule(new WorkflowDiagramModule()); + + const errorHandler = (error: any): void => logger.error('Error in workflow server launcher:', error); + launcher.configure(serverModule); + resolveAndCatch(() => launcher.start({ port: options.port, host: options.host }), errorHandler); +} diff --git a/examples/workflow-server/src/graph-extension.ts b/examples/workflow-server/src/graph-extension.ts new file mode 100644 index 0000000..bffd57a --- /dev/null +++ b/examples/workflow-server/src/graph-extension.ts @@ -0,0 +1,186 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Args, + ArgsUtil, + GCompartment, + GCompartmentBuilder, + GEdge, + GEdgeBuilder, + GLabel, + GLabelBuilder, + GNode, + GNodeBuilder +} from '@eclipse-glsp/server-node'; +import { ModelTypes } from './util/model-types'; + +export class ActivityNode extends GNode { + nodeType: string; + + static builder(): ActivityNodeBuilder { + return new ActivityNodeBuilder(ActivityNode); + } +} + +export class ActivityNodeBuilder extends GNodeBuilder { + nodeType(nodeType: string): this { + this.proxy.nodeType = nodeType; + return this; + } +} + +export class TaskNode extends GNode { + name: string; + duration: number; + taskType: string; + references: string; + + static builder(): TaskNodeBuilder { + return new TaskNodeBuilder(TaskNode).layout('vbox').addArgs(ArgsUtil.cornerRadius(5)); + } +} + +export class TaskNodeBuilder extends GNodeBuilder { + name(name: string): this { + this.proxy.name = name; + return this; + } + + duration(duration: number): this { + this.proxy.duration = duration; + return this; + } + + taskType(tasktype: string): this { + this.proxy.taskType = tasktype; + return this; + } + + references(references: string): this { + this.proxy.references = references; + return this; + } + + children(): this { + this.proxy.children.push(this.createCompartment()); + return this; + } + + protected createCompartment(): GCompartment { + const layoutOptions: Args = {}; + return new GCompartmentBuilder(GCompartment) + .type(ModelTypes.COMP_HEADER) + .id(this.proxy.id + '_header') + .layout('hbox') + .addLayoutOptions(layoutOptions) + .add(this.createCompartmentIcon()) + .add(this.createCompartmentHeader()) + .build(); + } + + protected createCompartmentHeader(): GLabel { + return new GLabelBuilder(GLabel) + .type(ModelTypes.LABEL_HEADING) + .id(this.proxy.id + '_classname') + .text(this.proxy.name) + .build(); + } + + protected createCompartmentIcon(): GCompartment { + return new GCompartmentBuilder(GCompartment) + .type(ModelTypes.ICON) + .id(this.proxy.id + '_icon') + .layout('stack') + .addLayoutOptions({ hAlign: 'center', resizeContainer: false }) + .add(this.createCompartmentIconLabel()) + .build(); + } + + protected createCompartmentIconLabel(): GLabel { + return new GLabelBuilder(GLabel) + .type(ModelTypes.LABEL_ICON) + .id(this.proxy.id + '_ticon') + .text(`${this.proxy.taskType.toUpperCase().charAt(0)}`) + .build(); + } +} + +export class WeightedEdge extends GEdge { + probability?: string; + + static builder(): WeightedEdgeBuilder { + return new WeightedEdgeBuilder(WeightedEdge).type(ModelTypes.WEIGHTED_EDGE); + } +} + +export class WeightedEdgeBuilder extends GEdgeBuilder { + probability(probability: string): this { + this.proxy.probability = probability; + return this; + } +} + +export class Category extends ActivityNode { + name: string; + + static builder(): CategoryNodeBuilder { + return new CategoryNodeBuilder(Category) + .layout('vbox') + .addLayoutOptions({ hAlign: 'center', hGrab: false, vGrab: false }) + .addCssClass('category'); + } +} + +export class CategoryNodeBuilder extends ActivityNodeBuilder { + name(name: string): this { + this.proxy.name = name; + return this; + } + + children(): this { + this.proxy.children.push(this.createLabelCompartment()); + this.proxy.children.push(this.createStructCompartment()); + return this; + } + + protected createLabelCompartment(): GCompartment { + const layoutOptions: Args = {}; + return new GCompartmentBuilder(GCompartment) + .type(ModelTypes.COMP_HEADER) + .id(this.proxy.id + '_header') + .layout('hbox') + .addLayoutOptions(layoutOptions) + .add(this.createCompartmentHeader()) + .build(); + } + + protected createCompartmentHeader(): GLabel { + return new GLabelBuilder(GLabel) + .type(ModelTypes.LABEL_HEADING) + .id(this.proxy.id + '_classname') + .text(this.proxy.name) + .build(); + } + + protected createStructCompartment(): GCompartment { + return new GCompartmentBuilder(GCompartment) + .type(ModelTypes.STRUCTURE) + .id(this.proxy.id + '_struct') + .layout('freeform') + .addLayoutOptions({ hAlign: 'left', hGrab: true, vGrab: true }) + .build(); + } +} diff --git a/examples/workflow-server/src/handler/create-activity-node-handler.ts b/examples/workflow-server/src/handler/create-activity-node-handler.ts new file mode 100644 index 0000000..28dbf5f --- /dev/null +++ b/examples/workflow-server/src/handler/create-activity-node-handler.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point, Args, GNode, GModelState, ORIGIN_POINT } from '@eclipse-glsp/server-node'; +import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; +import { injectable } from 'inversify'; +import { ActivityNode, ActivityNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; + +@injectable() +export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperationHandler { + createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + return this.builder(relativeLocation, this.modelState).build(); + } + + protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + return ActivityNode.builder() + .position(point ?? ORIGIN_POINT) + .type(this.elementTypeIds[0]) + .nodeType(ModelTypes.toNodeType(this.elementTypeIds[0])); + } +} diff --git a/examples/workflow-server/src/handler/create-automated-task-handler.ts b/examples/workflow-server/src/handler/create-automated-task-handler.ts new file mode 100644 index 0000000..2dc20a3 --- /dev/null +++ b/examples/workflow-server/src/handler/create-automated-task-handler.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { CreateTaskHandler } from './create-task-handler'; +import { injectable } from 'inversify'; +import { Point, GModelState } from '@eclipse-glsp/server-node'; +import { TaskNodeBuilder } from '../graph-extension'; + +@injectable() +export class CreateAutomatedTaskHandler extends CreateTaskHandler { + elementTypeIds = [ModelTypes.AUTOMATED_TASK]; + label = 'Automated Task'; + + protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + return super.builder(point, modelState).addCssClass('automated'); + } +} diff --git a/examples/workflow-server/src/handler/create-category-handler.ts b/examples/workflow-server/src/handler/create-category-handler.ts new file mode 100644 index 0000000..cd4fd99 --- /dev/null +++ b/examples/workflow-server/src/handler/create-category-handler.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point, Args, GNode, GModelState, ORIGIN_POINT } from '@eclipse-glsp/server-node'; +import { Category, CategoryNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; + +export class CreateCategoryHandler extends CreateWorkflowNodeOperationHandler { + elementTypeIds = [ModelTypes.CATEGORY]; + label = 'Category'; + + createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + return this.builder(relativeLocation, this.modelState).build(); + } + + protected builder(point: Point | undefined, modelState: GModelState): CategoryNodeBuilder { + return Category.builder() + .type(this.elementTypeIds[0]) + .position(point ?? ORIGIN_POINT) + .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(Category).length) + .children(); + } +} diff --git a/examples/workflow-server/src/handler/create-decision-node-handler.ts b/examples/workflow-server/src/handler/create-decision-node-handler.ts new file mode 100644 index 0000000..743db96 --- /dev/null +++ b/examples/workflow-server/src/handler/create-decision-node-handler.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { injectable } from 'inversify'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; +import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ActivityNodeBuilder } from '../graph-extension'; + +@injectable() +export class CreateDecisionNodeHandler extends CreateActivityNodeHandler { + elementTypeIds = [ModelTypes.DECISION_NODE]; + label = 'Decision Node'; + + protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + return super.builder(point, modelState).addCssClass('decision'); + } +} diff --git a/examples/workflow-server/src/handler/create-edge-handler.ts b/examples/workflow-server/src/handler/create-edge-handler.ts new file mode 100644 index 0000000..d3b07e7 --- /dev/null +++ b/examples/workflow-server/src/handler/create-edge-handler.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CreateEdgeOperationHandler, DefaultTypes, GEdge, GEdgeBuilder, GModelElement, GModelState } from '@eclipse-glsp/server-node'; + +export class CreateEdgeHandler extends CreateEdgeOperationHandler { + label = 'Edge'; + elementTypeIds = [DefaultTypes.EDGE]; + + createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + return new GEdgeBuilder(GEdge).sourceId(source.id).targetId(target.id).build(); + } +} diff --git a/examples/workflow-server/src/handler/create-fork-node-handler.ts b/examples/workflow-server/src/handler/create-fork-node-handler.ts new file mode 100644 index 0000000..9a548b4 --- /dev/null +++ b/examples/workflow-server/src/handler/create-fork-node-handler.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { CreateForkOrJoinNodeHandler } from './create-fork-or-join-node-handler'; +import { injectable } from 'inversify'; + +@injectable() +export class CreateForkNodeHandler extends CreateForkOrJoinNodeHandler { + elementTypeIds = [ModelTypes.FORK_NODE]; + label = 'Fork Node'; +} diff --git a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts new file mode 100644 index 0000000..379cedb --- /dev/null +++ b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point, GModelState } from '@eclipse-glsp/server-node'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; +import { injectable } from 'inversify'; +import { ActivityNodeBuilder } from '../graph-extension'; + +@injectable() +export abstract class CreateForkOrJoinNodeHandler extends CreateActivityNodeHandler { + protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + return super.builder(point, modelState).addCssClass('forkOrJoin').size(10, 50); + } +} diff --git a/examples/workflow-server/src/handler/create-join-node-handler.ts b/examples/workflow-server/src/handler/create-join-node-handler.ts new file mode 100644 index 0000000..4ace744 --- /dev/null +++ b/examples/workflow-server/src/handler/create-join-node-handler.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { CreateForkOrJoinNodeHandler } from './create-fork-or-join-node-handler'; +import { injectable } from 'inversify'; + +@injectable() +export class CreateJoinNodeHandler extends CreateForkOrJoinNodeHandler { + elementTypeIds = [ModelTypes.JOIN_NODE]; + label = 'Join Node'; +} diff --git a/examples/workflow-server/src/handler/create-manual-task-handler.ts b/examples/workflow-server/src/handler/create-manual-task-handler.ts new file mode 100644 index 0000000..b6a14f2 --- /dev/null +++ b/examples/workflow-server/src/handler/create-manual-task-handler.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { CreateTaskHandler } from './create-task-handler'; +import { injectable } from 'inversify'; +import { Point, GModelState } from '@eclipse-glsp/server-node'; +import { TaskNodeBuilder } from '../graph-extension'; + +@injectable() +export class CreateManualTaskHandler extends CreateTaskHandler { + elementTypeIds = [ModelTypes.MANUAL_TASK]; + label = 'Manual Task'; + + protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + return super.builder(point, modelState).addCssClass('manual'); + } +} diff --git a/examples/workflow-server/src/handler/create-merge-node-handler.ts b/examples/workflow-server/src/handler/create-merge-node-handler.ts new file mode 100644 index 0000000..0e42258 --- /dev/null +++ b/examples/workflow-server/src/handler/create-merge-node-handler.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ModelTypes } from '../util/model-types'; +import { injectable } from 'inversify'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; +import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ActivityNodeBuilder } from '../graph-extension'; + +@injectable() +export class CreateMergeNodeHandler extends CreateActivityNodeHandler { + elementTypeIds = [ModelTypes.MERGE_NODE]; + label = 'Merge Node'; + + protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + return super.builder(point, modelState).addCssClass('merge'); + } +} diff --git a/examples/workflow-server/src/handler/create-task-handler.ts b/examples/workflow-server/src/handler/create-task-handler.ts new file mode 100644 index 0000000..2c468c8 --- /dev/null +++ b/examples/workflow-server/src/handler/create-task-handler.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; +import { injectable } from 'inversify'; +import { Args, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { GModelState, GNode } from '@eclipse-glsp/server-node'; +import { TaskNode, TaskNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; + +@injectable() +export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandler { + createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + return this.builder(relativeLocation, this.modelState).build(); + } + + protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + return TaskNode.builder() + .position(point ?? ORIGIN_POINT) + .addCssClass('task') + .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(TaskNode).length) + .type(this.elementTypeIds[0]) + .taskType(ModelTypes.toNodeType(this.elementTypeIds[0])) + .children(); + } +} diff --git a/examples/workflow-server/src/handler/create-weighted-edge-handler.ts b/examples/workflow-server/src/handler/create-weighted-edge-handler.ts new file mode 100644 index 0000000..41ecebc --- /dev/null +++ b/examples/workflow-server/src/handler/create-weighted-edge-handler.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CreateEdgeOperationHandler, GEdge, GModelElement, GModelState } from '@eclipse-glsp/server-node'; +import { WeightedEdge } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; + +export class CreateWeightedEdgeHandler extends CreateEdgeOperationHandler { + elementTypeIds = [ModelTypes.WEIGHTED_EDGE]; + label = 'Weighted edge'; + + createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + return WeightedEdge.builder().sourceId(source.id).targetId(target.id).probability('medium').addCssClass('medium').build(); + } +} diff --git a/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts new file mode 100644 index 0000000..ef1133d --- /dev/null +++ b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + CreateNodeOperation, + CreateNodeOperationHandler, + GCompartment, + GModelElement, + GModelState, + Point +} from '@eclipse-glsp/server-node'; +import { injectable, inject } from 'inversify'; +import { Category } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { GridSnapper } from './grid-snapper'; + +@injectable() +export abstract class CreateWorkflowNodeOperationHandler extends CreateNodeOperationHandler { + @inject(GModelState) + protected modelState: GModelState; + + getLocation(operation: CreateNodeOperation): Point | undefined { + return GridSnapper.snap(operation.location); + } + + getContainer(operation: CreateNodeOperation): GModelElement | undefined { + const container = super.getContainer(operation); + + if (container instanceof Category) { + const structComp = this.getCategoryCompartment(container); + if (structComp) { + return structComp; + } + } + return container; + } + + getCategoryCompartment(category: Category): GCompartment | undefined { + return category.children + .filter(child => child instanceof GCompartment) + .map(child => child as GCompartment) + .find(comp => ModelTypes.STRUCTURE === comp.type); + } +} diff --git a/examples/workflow-server/src/handler/grid-snapper.ts b/examples/workflow-server/src/handler/grid-snapper.ts new file mode 100644 index 0000000..f8ecb26 --- /dev/null +++ b/examples/workflow-server/src/handler/grid-snapper.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point } from '@eclipse-glsp/server-node'; + +export class GridSnapper { + public static GRID_X = 10.0; + public static GRID_Y = 10.0; + + public static snap(originalPoint: Point | undefined): Point | undefined { + if (originalPoint) { + return { + x: Math.round(originalPoint.x / this.GRID_X) * this.GRID_X, + y: Math.round(originalPoint.y / this.GRID_Y) * this.GRID_Y + }; + } else { + return undefined; + } + } +} diff --git a/examples/workflow-server/src/index.ts b/examples/workflow-server/src/index.ts new file mode 100644 index 0000000..e6bf0e2 --- /dev/null +++ b/examples/workflow-server/src/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import 'reflect-metadata'; +import { launch } from './app'; + +launch(); diff --git a/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts b/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts new file mode 100644 index 0000000..3c9f6d1 --- /dev/null +++ b/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, GModelState, LabelEditValidator, ValidationStatus } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { TaskNode } from '../graph-extension'; + +@injectable() +export class WorkflowLabelEditValidator implements LabelEditValidator { + @inject(GModelState) + protected modelState: GModelState; + + validate(label: string, element: GModelElement): ValidationStatus { + if (label.length < 1) { + return { severity: ValidationStatus.Severity.ERROR, message: 'Name must not be empty' }; + } + const taskNodes = this.modelState.index.getAllByClass(TaskNode); + const hasDuplicate = taskNodes + .filter(e => !(e.id === element.id)) + .map(task => task.name) + .some(name => name === label); + if (hasDuplicate) { + return { severity: ValidationStatus.Severity.WARNING, message: 'Name should be unique' }; + } + return { severity: ValidationStatus.Severity.OK }; + } +} diff --git a/examples/workflow-server/src/marker/workflow-model-validator.ts b/examples/workflow-server/src/marker/workflow-model-validator.ts new file mode 100644 index 0000000..ba0703a --- /dev/null +++ b/examples/workflow-server/src/marker/workflow-model-validator.ts @@ -0,0 +1,137 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, Marker, ModelValidator, GModelState, MarkerKind, GCompartment, GLabel } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { ActivityNode, TaskNode } from '../graph-extension'; + +@injectable() +export class WorkflowModelValidator implements ModelValidator { + @inject(GModelState) + protected readonly modelState: GModelState; + + validate(elements: GModelElement[]): Marker[] { + const markers: Marker[] = []; + for (const element of elements) { + if (element instanceof TaskNode) { + markers.push(...this.validateTaskNode(element)); + } else if (element instanceof ActivityNode) { + if (element.nodeType === 'decisionNode') { + markers.push(...this.validateDecisionNode(element)); + } else if (element.nodeType === 'mergeNode') { + markers.push(...this.validateMergeNode(element)); + } + } + if (element.children) { + markers.push(...this.validate(element.children)); + } + } + return markers; + } + + protected validateTaskNode(taskNode: TaskNode): Marker[] { + const markers: Marker[] = []; + const automated = this.validateTaskNode_isAutomated(taskNode); + if (automated) { + markers.push(automated); + } + const upperCase = this.validateTaskNode_labelStartsUpperCase(taskNode); + if (upperCase) { + markers.push(upperCase); + } + return markers; + } + + protected validateTaskNode_isAutomated(taskNode: TaskNode): Marker | undefined { + if (taskNode.taskType === 'automated') { + return { kind: MarkerKind.INFO, description: 'This is an automated task', elementId: taskNode.id, label: 'Automated task' }; + } + return undefined; + } + + protected validateTaskNode_labelStartsUpperCase(taskNode: TaskNode): Marker | undefined { + const gCompartment = taskNode.children.find(child => child instanceof GCompartment); + if (gCompartment) { + const gLabels = gCompartment.children.filter(child => child instanceof GLabel).map(element => element as GLabel); + if (gLabels.length > 0 && gLabels[0].text.charAt(0) === gLabels[0].text.charAt(0).toLowerCase()) { + return { + kind: MarkerKind.WARNING, + description: 'Task node names should start with upper case letters', + elementId: taskNode.id, + label: 'Task node label in upper case' + }; + } + } + return undefined; + } + + protected validateDecisionNode(decisionNode: ActivityNode): Marker[] { + const markers: Marker[] = []; + const oneIncoming = this.validateDecisionNode_hasOneIncomingEdge(decisionNode); + if (oneIncoming) { + markers.push(oneIncoming); + } + return markers; + } + + protected validateDecisionNode_hasOneIncomingEdge(decisionNode: ActivityNode): Marker | undefined { + const edges = this.modelState.index.getIncomingEdges(decisionNode); + if (edges.length > 1) { + return { + kind: MarkerKind.ERROR, + description: 'Decision node may only have one incoming edge.', + elementId: decisionNode.id, + label: 'Too many incoming edges' + }; + } else if (edges.length === 0) { + return { + kind: MarkerKind.ERROR, + description: 'Decision node must have one incoming edge.', + elementId: decisionNode.id, + label: 'Missing incoming edge' + }; + } + return undefined; + } + + protected validateMergeNode(mergeNode: ActivityNode): Marker[] { + const markers: Marker[] = []; + const oneOutgoing = this.validateMergeNode_hasOneOutgoingEdge(mergeNode); + if (oneOutgoing) { + markers.push(oneOutgoing); + } + return markers; + } + + protected validateMergeNode_hasOneOutgoingEdge(mergeNode: ActivityNode): Marker | undefined { + const edges = this.modelState.index.getOutgoingEdges(mergeNode); + if (edges.length > 1) { + return { + kind: MarkerKind.ERROR, + description: 'Merge node may only have one outgoing edge.', + elementId: mergeNode.id, + label: 'Too many outgoing edges' + }; + } else if (edges.length === 0) { + return { + kind: MarkerKind.ERROR, + description: 'Merge node must have one outgoing edge.', + elementId: mergeNode.id, + label: 'Missing outgoing edge' + }; + } + return undefined; + } +} diff --git a/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts b/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts new file mode 100644 index 0000000..3ebb0de --- /dev/null +++ b/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { NavigationTarget } from '@eclipse-glsp/protocol'; +import { GModelState } from '@eclipse-glsp/server-node'; +import { NavigationTargetResolution } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolution'; +import { NavigationTargetResolver } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolver'; +import { inject, injectable } from 'inversify'; +import { TaskNode } from '../graph-extension'; + +@injectable() +export class WorkflowNavigationTargetResolver extends NavigationTargetResolver { + @inject(GModelState) + protected readonly modelState: GModelState; + + async resolve(navigationTarget: NavigationTarget): Promise { + if (navigationTarget.args && navigationTarget.args['name']) { + const name = navigationTarget.args['name']; + const taskNodes = this.modelState.index.getAllByClass(TaskNode); + const element = taskNodes.find(node => name === node.name); + if (element) { + return new NavigationTargetResolution([element.id]); + } + return new NavigationTargetResolution([], this.createArgsWithWarning(`Couldn't find element with name ${name}`)); + } + return NavigationTargetResolution.EMPTY; + } +} diff --git a/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts b/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts new file mode 100644 index 0000000..1958633 --- /dev/null +++ b/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; +import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; +import { inject, injectable } from 'inversify'; +import { GEdge, GModelState } from '@eclipse-glsp/server-node'; +import { TaskNode } from '../graph-extension'; + +@injectable() +export abstract class AbstractNextOrPreviousNavigationTargetProvider implements NavigationTargetProvider { + abstract targetTypeId: string; + + @inject(GModelState) + protected readonly modelState: GModelState; + + getTargets(editorContext: EditorContext): NavigationTarget[] { + const sourceUri = this.modelState.sourceUri; + const edges: GEdge[] = []; + editorContext.selectedElementIds + .map(id => this.modelState.index.get(id)) + .filter(e => e instanceof TaskNode) + .map(e => e as TaskNode) + .map(taskNode => edges.push(...this.getEdges(taskNode))); + return edges.map(edge => this.getSourceOrTarget(edge)).map(id => this.createNavigationTarget(sourceUri, id)); + } + + protected createNavigationTarget(sourceURI: string | undefined, id: string): NavigationTarget { + return { uri: sourceURI ?? '', args: this.createElementIdMap(id) }; + } + + protected createElementIdMap(id: string): Args { + const map: Args = {}; + map[NavigationTarget.ELEMENT_IDS] = id; + return map; + } + + protected abstract getEdges(taskNode: TaskNode): GEdge[]; + + protected abstract getSourceOrTarget(edge: GEdge): string; +} diff --git a/examples/workflow-server/src/provider/next-node-navigation-target-provider.ts b/examples/workflow-server/src/provider/next-node-navigation-target-provider.ts new file mode 100644 index 0000000..e13a2cf --- /dev/null +++ b/examples/workflow-server/src/provider/next-node-navigation-target-provider.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge } from '@eclipse-glsp/graph'; +import { TaskNode } from '../graph-extension'; +import { AbstractNextOrPreviousNavigationTargetProvider } from './abstract-next-or-previous-navigation-target-provider'; +import { injectable } from 'inversify'; + +@injectable() +export class NextNodeNavigationTargetProvider extends AbstractNextOrPreviousNavigationTargetProvider { + targetTypeId = 'next'; + protected getEdges(taskNode: TaskNode): GEdge[] { + return this.modelState.index.getOutgoingEdges(taskNode); + } + + protected getSourceOrTarget(edge: GEdge): string { + return edge.targetId; + } +} diff --git a/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts new file mode 100644 index 0000000..8445225 --- /dev/null +++ b/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; +import { GModelState } from '@eclipse-glsp/server-node'; +import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; +import { inject, injectable } from 'inversify'; +import { TaskNode } from '../graph-extension'; +import { JsonOpenerOptions } from '@eclipse-glsp/server-node/lib/features/navigation/json-opener-options'; + +@injectable() +export class NodeDocumentationNavigationTargetProvider implements NavigationTargetProvider { + targetTypeId = 'documentation'; + + @inject(GModelState) + protected readonly modelState: GModelState; + + getTargets(editorContext: EditorContext): NavigationTarget[] { + if (editorContext.selectedElementIds.length === 1) { + const taskNode = this.modelState.index.findByClass(editorContext.selectedElementIds[0], TaskNode); + if (!taskNode || !(taskNode.id === 'task0')) { + return []; + } + + const sourceUri = this.modelState.sourceUri; + if (!sourceUri) { + return []; + } + + const docUri = sourceUri.replace('.wf', '.md'); + const args: Args = {}; + args['jsonOpenerOptions'] = new JsonOpenerOptions({ + start: { line: 2, character: 3 }, + end: { line: 2, character: 7 } + }).toJson(); + return [{ uri: docUri, args: args }]; + } + return []; + } +} diff --git a/examples/workflow-server/src/provider/previous-node-navigation-target-provider.ts b/examples/workflow-server/src/provider/previous-node-navigation-target-provider.ts new file mode 100644 index 0000000..f95ddf2 --- /dev/null +++ b/examples/workflow-server/src/provider/previous-node-navigation-target-provider.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge } from '@eclipse-glsp/graph'; +import { TaskNode } from '../graph-extension'; +import { AbstractNextOrPreviousNavigationTargetProvider } from './abstract-next-or-previous-navigation-target-provider'; +import { injectable } from 'inversify'; + +@injectable() +export class PreviousNodeNavigationTargetProvider extends AbstractNextOrPreviousNavigationTargetProvider { + targetTypeId = 'previous'; + protected getEdges(taskNode: TaskNode): GEdge[] { + return this.modelState.index.getIncomingEdges(taskNode); + } + + protected getSourceOrTarget(edge: GEdge): string { + return edge.sourceId; + } +} diff --git a/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts new file mode 100644 index 0000000..b25c182 --- /dev/null +++ b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts @@ -0,0 +1,124 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Args, + CommandPaletteActionProvider, + CreateEdgeOperation, + CreateNodeOperation, + DefaultTypes, + DeleteElementOperation, + GModelElement, + GModelState, + GNode, + LabeledAction, + ORIGIN_POINT, + Point +} from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { TaskNode } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; + +@injectable() +export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionProvider { + @inject(GModelState) + protected modelState: GModelState; + + getPaletteActions(selectedElementIds: string[], selectedElements: GModelElement[], position: Point, args?: Args): LabeledAction[] { + const actions: LabeledAction[] = []; + if (this.modelState.isReadonly) { + return actions; + } + const index = this.modelState.index; + // Create actions + actions.push( + new LabeledAction( + 'Create Automated Task', + [new CreateNodeOperation(ModelTypes.AUTOMATED_TASK, position ?? ORIGIN_POINT)], + 'fa-plus-square' + ), + new LabeledAction( + 'Create Manual Task', + [new CreateNodeOperation(ModelTypes.MANUAL_TASK, position ?? ORIGIN_POINT)], + 'fa-plus-square' + ), + new LabeledAction( + 'Create Merge Node', + [new CreateNodeOperation(ModelTypes.MERGE_NODE, position ?? ORIGIN_POINT)], + 'fa-plus-square' + ), + new LabeledAction( + 'Create Decision Node', + [new CreateNodeOperation(ModelTypes.DECISION_NODE, position ?? ORIGIN_POINT)], + 'fa-plus-square' + ), + new LabeledAction('Create Category', [new CreateNodeOperation(ModelTypes.CATEGORY, position ?? ORIGIN_POINT)], 'fa-plus-square') + ); + // Create edge action between two nodes + if (selectedElements.length === 1) { + const element = selectedElements[0]; + if (element instanceof GNode) { + actions.push(...this.createEdgeActions(element, index.getAllByClass(TaskNode))); + } + } else if (selectedElements.length === 2) { + const source = selectedElements[0]; + const target = selectedElements[1]; + if (source instanceof TaskNode && target instanceof TaskNode) { + actions.push( + this.createEdgeAction(`Create Edge from ${this.getLabel(source)} to ${this.getLabel(target)}`, source, target) + ); + actions.push( + this.createWeightedEdgeAction( + `Create Weighted Edge from ${this.getLabel(source)} to ${this.getLabel(target)}`, + source, + target + ) + ); + } + } + // Delete action + if (selectedElements.length === 1) { + actions.push(new LabeledAction('Delete', [new DeleteElementOperation(selectedElementIds)], 'fa-minus-square')); + } else if (selectedElements.length > 1) { + actions.push(new LabeledAction('Delete All', [new DeleteElementOperation(selectedElementIds)], 'fa-minus-square')); + } + + return actions; + } + + private createEdgeActions(source: GNode, targets: GNode[]): LabeledAction[] { + const actions: LabeledAction[] = []; + targets.forEach(node => actions.push(this.createEdgeAction(`Create Edge to ${this.getLabel(node)}`, source, node))); + targets.forEach(node => + actions.push(this.createWeightedEdgeAction(`Create Weighted Edge to ${this.getLabel(node)}`, source, node)) + ); + return actions; + } + + private createWeightedEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { + return new LabeledAction(label, [new CreateEdgeOperation(ModelTypes.WEIGHTED_EDGE, source.id, node.id)], 'fa-plus-square'); + } + + private createEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { + return new LabeledAction(label, [new CreateEdgeOperation(DefaultTypes.EDGE, source.id, node.id)], 'fa-plus-square'); + } + + private getLabel(node: GNode): string { + if (node instanceof TaskNode) { + return node.name; + } + return node.id; + } +} diff --git a/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts new file mode 100644 index 0000000..831f203 --- /dev/null +++ b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, ContextMenuItemProvider, CreateNodeOperation, GModelState, MenuItem, Point } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { GridSnapper } from '../handler/grid-snapper'; +import { ModelTypes } from '../util/model-types'; + +@injectable() +export class WorkflowContextMenuItemProvider extends ContextMenuItemProvider { + @inject(GModelState) + protected modelState: GModelState; + + getItems(selectedElementIds: string[], position: Point, args?: Args): MenuItem[] { + if (this.modelState.isReadonly) { + return []; + } + const snappedPosition = GridSnapper.snap(position); + const newAutTask = new MenuItem('newAutoTask', 'Automated Task', [ + new CreateNodeOperation(ModelTypes.AUTOMATED_TASK, snappedPosition) + ]); + const newManTask = new MenuItem('newManualTask', 'Manual Task', [new CreateNodeOperation(ModelTypes.MANUAL_TASK, snappedPosition)]); + const newChildMenu = new MenuItem('new', 'New', undefined, [newAutTask, newManTask], 'add', '0_new'); + return [newChildMenu]; + } +} diff --git a/examples/workflow-server/src/util/model-types.ts b/examples/workflow-server/src/util/model-types.ts new file mode 100644 index 0000000..d05f8ff --- /dev/null +++ b/examples/workflow-server/src/util/model-types.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/padding-line-between-statements */ +export namespace ModelTypes { + export const LABEL_HEADING = 'label:heading'; + export const LABEL_TEXT = 'label:text'; + export const COMP_HEADER = 'comp:header'; + export const LABEL_ICON = 'label:icon'; + export const WEIGHTED_EDGE = 'edge:weighted'; + export const ICON = 'icon'; + export const ACTIVITY_NODE = 'activityNode'; + export const DECISION_NODE = `${ACTIVITY_NODE}:decision`; + export const MERGE_NODE = `${ACTIVITY_NODE}:merge`; + export const FORK_NODE = `${ACTIVITY_NODE}:fork`; + export const JOIN_NODE = `${ACTIVITY_NODE}:join`; + export const TASK = 'task'; + export const MANUAL_TASK = `${TASK}:manual`; + export const AUTOMATED_TASK = `${TASK}:automated`; + export const CATEGORY = 'category'; + export const STRUCTURE = 'struct'; + + export function toNodeType(type: string): string { + switch (type) { + case DECISION_NODE: + return 'decisionNode'; + case MERGE_NODE: + return 'mergeNode'; + case FORK_NODE: + return 'forkNode'; + case JOIN_NODE: + return 'joinNode'; + case MANUAL_TASK: + return 'manual'; + case AUTOMATED_TASK: + return 'automated'; + case CATEGORY: + return 'category'; + default: + return 'unknown'; + } + } +} diff --git a/examples/workflow-server/src/workflow-diagram-configuration.ts b/examples/workflow-server/src/workflow-diagram-configuration.ts new file mode 100644 index 0000000..2726717 --- /dev/null +++ b/examples/workflow-server/src/workflow-diagram-configuration.ts @@ -0,0 +1,122 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes, EdgeTypeHint, ShapeTypeHint } from '@eclipse-glsp/protocol'; +import { + DiagramConfiguration, + GCompartment, + GEdge, + getDefaultMapping, + GLabel, + GModelElementConstructor, + ServerLayoutKind +} from '@eclipse-glsp/server-node'; +import { injectable } from 'inversify'; +import { ActivityNode, Category, TaskNode } from './graph-extension'; +import { ModelTypes as types } from './util/model-types'; + +@injectable() +export class WorkflowDiagramConfiguration implements DiagramConfiguration { + get typeMapping(): Map { + const mapping = getDefaultMapping(); + mapping.set(types.LABEL_HEADING, GLabel); + mapping.set(types.LABEL_TEXT, GLabel); + mapping.set(types.COMP_HEADER, GCompartment); + mapping.set(types.LABEL_ICON, GLabel); + mapping.set(types.WEIGHTED_EDGE, GEdge); + mapping.set(types.ICON, GCompartment); + mapping.set(types.ACTIVITY_NODE, ActivityNode); + mapping.set(types.TASK, TaskNode); + mapping.set(types.CATEGORY, Category); + mapping.set(types.STRUCTURE, GCompartment); + return mapping; + } + + get shapeTypeHints(): ShapeTypeHint[] { + return [ + createDefaultShapeTypeHint(types.MANUAL_TASK), + createDefaultShapeTypeHint(types.AUTOMATED_TASK), + createDefaultShapeTypeHint(types.FORK_NODE), + createDefaultShapeTypeHint(types.JOIN_NODE), + createDefaultShapeTypeHint(types.DECISION_NODE), + createDefaultShapeTypeHint(types.MERGE_NODE), + { + elementTypeId: types.CATEGORY, + repositionable: true, + deletable: true, + resizable: true, + reparentable: true, + containableElementTypeIds: [ + types.DECISION_NODE, + types.MERGE_NODE, + types.FORK_NODE, + types.JOIN_NODE, + types.AUTOMATED_TASK, + types.MANUAL_TASK, + types.CATEGORY + ] + } + ]; + } + + get edgeTypeHints(): EdgeTypeHint[] { + return [ + createDefaultEdgeTypeHint(DefaultTypes.EDGE), + { + elementTypeId: types.WEIGHTED_EDGE, + repositionable: true, + deletable: true, + routable: true, + sourceElementTypeIds: [types.DECISION_NODE], + targetElementTypeIds: [types.MANUAL_TASK, types.AUTOMATED_TASK, types.FORK_NODE, types.JOIN_NODE] + } + ]; + } + + layoutKind = ServerLayoutKind.MANUAL; + needsClientLayout = true; + animatedUpdate = true; +} + +export function createDefaultShapeTypeHint(elementId: string): ShapeTypeHint { + return { elementTypeId: elementId, repositionable: true, deletable: true, resizable: true, reparentable: true }; +} + +export function createDefaultEdgeTypeHint(elementId: string): EdgeTypeHint { + return { + elementTypeId: elementId, + repositionable: true, + deletable: true, + routable: true, + sourceElementTypeIds: [ + types.MANUAL_TASK, + types.AUTOMATED_TASK, + types.DECISION_NODE, + types.MERGE_NODE, + types.FORK_NODE, + types.JOIN_NODE, + types.CATEGORY + ], + targetElementTypeIds: [ + types.MANUAL_TASK, + types.AUTOMATED_TASK, + types.DECISION_NODE, + types.MERGE_NODE, + types.FORK_NODE, + types.JOIN_NODE, + types.CATEGORY + ] + }; +} diff --git a/examples/workflow-server/src/workflow-diagram-module.ts b/examples/workflow-server/src/workflow-diagram-module.ts new file mode 100644 index 0000000..5f28ab0 --- /dev/null +++ b/examples/workflow-server/src/workflow-diagram-module.ts @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + ClassMultiBinding, + CommandPaletteActionProvider, + ContextMenuItemProvider, + DefaultToolPaletteItemProvider, + DiagramConfiguration, + GLSPServer, + GModelDiagramModule, + GModelIndex, + InstanceMultiBinding, + JsonRpcGLSPServer, + LabelEditValidator, + ModelValidator, + OperationHandlerConstructor, + PopupModelFactory, + ServerModule, + ToolPaletteItemProvider +} from '@eclipse-glsp/server-node'; +import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; +import { NavigationTargetResolver } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolver'; +import { injectable, interfaces } from 'inversify'; +import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler'; +import { CreateCategoryHandler } from './handler/create-category-handler'; +import { CreateDecisionNodeHandler } from './handler/create-decision-node-handler'; +import { CreateEdgeHandler } from './handler/create-edge-handler'; +import { CreateForkNodeHandler } from './handler/create-fork-node-handler'; +import { CreateJoinNodeHandler } from './handler/create-join-node-handler'; +import { CreateManualTaskHandler } from './handler/create-manual-task-handler'; +import { CreateMergeNodeHandler } from './handler/create-merge-node-handler'; +import { CreateWeightedEdgeHandler } from './handler/create-weighted-edge-handler'; +import { WorkflowLabelEditValidator } from './labeledit/workflow-label-edit-validator'; +import { WorkflowModelValidator } from './marker/workflow-model-validator'; +import { WorkflowNavigationTargetResolver } from './model/workflow-navigation-target-resolver'; +import { NextNodeNavigationTargetProvider } from './provider/next-node-navigation-target-provider'; +import { NodeDocumentationNavigationTargetProvider } from './provider/node-documentation-navigation-target-provider'; +import { PreviousNodeNavigationTargetProvider } from './provider/previous-node-navigation-target-provider'; +import { WorkflowCommandPaletteActionProvider } from './provider/workflow-command-palette-action-provider'; +import { WorkflowContextMenuItemProvider } from './provider/workflow-context-menu-item-provider'; +import { WorkflowDiagramConfiguration } from './workflow-diagram-configuration'; +import { WorkflowGLSPServer } from './workflow-glsp-server'; +import { WorkflowModelIndex } from './workflow-model-index'; +import { WorkflowPopupFactory } from './workflow-popup-factory'; + +@injectable() +export class WorkflowServerModule extends ServerModule { + configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + super.configure(bind, unbind, isBound, rebind); + bind(WorkflowGLSPServer).toSelf().inSingletonScope(); + rebind(GLSPServer).toService(WorkflowGLSPServer); + rebind(JsonRpcGLSPServer).toService(WorkflowGLSPServer); + } +} + +@injectable() +export class WorkflowDiagramModule extends GModelDiagramModule { + get diagramType(): string { + return 'workflow-diagram'; + } + + protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + super.configure(bind, unbind, isBound, rebind); + bind(DiagramConfiguration).to(WorkflowDiagramConfiguration).inSingletonScope(); + bind(NavigationTargetResolver).to(WorkflowNavigationTargetResolver).inSingletonScope(); + bind(ContextMenuItemProvider).to(WorkflowContextMenuItemProvider).inSingletonScope(); + bind(CommandPaletteActionProvider).to(WorkflowCommandPaletteActionProvider).inSingletonScope(); + bind(LabelEditValidator).to(WorkflowLabelEditValidator).inSingletonScope(); + bind(PopupModelFactory).to(WorkflowPopupFactory).inSingletonScope(); + bind(ModelValidator).to(WorkflowModelValidator).inSingletonScope(); + bind(ToolPaletteItemProvider).to(DefaultToolPaletteItemProvider).inSingletonScope(); + rebind(GModelIndex).to(WorkflowModelIndex).inSingletonScope(); + } + + protected configureOperationHandlers(binding: InstanceMultiBinding): void { + super.configureOperationHandlers(binding); + binding.add(CreateAutomatedTaskHandler); + binding.add(CreateManualTaskHandler); + binding.add(CreateJoinNodeHandler); + binding.add(CreateForkNodeHandler); + binding.add(CreateEdgeHandler); + binding.add(CreateWeightedEdgeHandler); + binding.add(CreateMergeNodeHandler); + binding.add(CreateDecisionNodeHandler); + binding.add(CreateCategoryHandler); + } + + protected configureNavigationTargetProviders(binding: ClassMultiBinding): void { + super.configureNavigationTargetProviders(binding); + binding.add(NextNodeNavigationTargetProvider); + binding.add(PreviousNodeNavigationTargetProvider); + binding.add(NodeDocumentationNavigationTargetProvider); + } +} diff --git a/examples/workflow-server/src/workflow-glsp-server.ts b/examples/workflow-server/src/workflow-glsp-server.ts new file mode 100644 index 0000000..982f49f --- /dev/null +++ b/examples/workflow-server/src/workflow-glsp-server.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, ArgsUtil, DefaultGLSPServer, InitializeResult, Logger, MaybePromise } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; + +@injectable() +export class WorkflowGLSPServer extends DefaultGLSPServer { + MESSAGE_KEY = 'message'; + TIMESTAMP_KEY = 'timestamp'; + + @inject(Logger) + protected logger: Logger; + + protected handleInitializeArgs(result: InitializeResult, args: Args | undefined): MaybePromise { + if (!args) { + return result; + } + const timestamp = ArgsUtil.get(args, this.TIMESTAMP_KEY); + const message = ArgsUtil.get(args, this.MESSAGE_KEY); + + this.logger.debug(`${timestamp}: ${message}`); + return result; + } +} diff --git a/examples/workflow-server/src/workflow-model-index.ts b/examples/workflow-server/src/workflow-model-index.ts new file mode 100644 index 0000000..51065d9 --- /dev/null +++ b/examples/workflow-server/src/workflow-model-index.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, GModelIndex } from '@eclipse-glsp/server-node'; +import { injectable } from 'inversify'; +import { ModelTypes } from './util/model-types'; + +@injectable() +export class WorkflowModelIndex extends GModelIndex { + getAllEdges(): GEdge[] { + const edges: GEdge[] = super.getAllEdges(); + this.getElements(ModelTypes.WEIGHTED_EDGE).forEach(weighted => { + if (weighted instanceof GEdge) { + edges.push(weighted); + } + }); + return edges; + } +} diff --git a/examples/workflow-server/src/workflow-popup-factory.ts b/examples/workflow-server/src/workflow-popup-factory.ts new file mode 100644 index 0000000..9a6d8a8 --- /dev/null +++ b/examples/workflow-server/src/workflow-popup-factory.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, GModelRoot, GPreRenderedElement, PopupModelFactory, RequestPopupModelAction } from '@eclipse-glsp/server-node'; +import { TaskNode } from './graph-extension'; +import { injectable } from 'inversify'; + +const NL = '
'; + +@injectable() +export class WorkflowPopupFactory implements PopupModelFactory { + createPopupModel(element: GModelElement, action: RequestPopupModelAction): GModelRoot | undefined { + if (element && element instanceof TaskNode) { + const popupTitle = GPreRenderedElement.builder() + .id('popup-title') + .code(`
${element.name}
`) + .build(); + + const popupBody = GPreRenderedElement.builder() + .id('popup-body') + .code(`
${this.generateBody(element)}
`) + .build(); + + return GModelRoot.builder().type('html').canvasBounds(action.bounds).id('sprotty-popup').add(popupTitle).add(popupBody).build(); + } + return undefined; + } + + private generateBody(task: TaskNode): string { + return `Type: ${task.taskType} ${NL} + Duration: ${task.duration} ${NL} + Reference: ${task.references} ${NL}`; + } +} diff --git a/examples/workflow-server/tsconfig.json b/examples/workflow-server/tsconfig.json new file mode 100644 index 0000000..53504ac --- /dev/null +++ b/examples/workflow-server/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": ".", + "types": ["node", "reflect-metadata"] + }, + "include": ["src"], + "exclude": ["**/*.spec.ts"] +} diff --git a/examples/workflow-server/workspace/example1.wf b/examples/workflow-server/workspace/example1.wf new file mode 100644 index 0000000..e63f57b --- /dev/null +++ b/examples/workflow-server/workspace/example1.wf @@ -0,0 +1,975 @@ +{ + "id": "sprotty", + "children": [ + { + "name": "Push", + "taskType": "manual", + "id": "task0", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task0_header", + "children": [ + { + "id": "task0_icon", + "children": [ + { + "id": "task0_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task0_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 48.90625, + "height": 22.0 + }, + "text": "Push", + "alignment": { + "x": 24.453125, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 91.90625, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 50.0, + "y": 130.0 + }, + "size": { + "width": 101.90625, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "ManualTask1", + "taskType": "manual", + "id": "task1", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task1_header", + "children": [ + { + "id": "task1_icon", + "children": [ + { + "id": "task1_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task1_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 52.5625, + "height": 22.0 + }, + "text": "RflWt", + "alignment": { + "x": 26.109375, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 95.5625, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 510.0, + "y": 30.0 + }, + "size": { + "width": 105.5625, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "ManualTask2", + "taskType": "manual", + "id": "task2", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task2_header", + "children": [ + { + "id": "task2_icon", + "children": [ + { + "id": "task2_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task2_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 62.21875, + "height": 22.0 + }, + "text": "ChkTp", + "alignment": { + "x": 31.109375, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 105.21875, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 250.0, + "y": 200.0 + }, + "size": { + "width": 115.21875, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "ManualTask3", + "taskType": "manual", + "id": "task3", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task3_header", + "children": [ + { + "id": "task3_icon", + "children": [ + { + "id": "task3_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task3_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 75.953125, + "height": 22.0 + }, + "text": "PreHeat", + "alignment": { + "x": 37.8125, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 118.953125, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 510.0, + "y": 240.0 + }, + "size": { + "width": 128.953125, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "ManualTask4", + "taskType": "manual", + "id": "task4", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task4_header", + "children": [ + { + "id": "task4_icon", + "children": [ + { + "id": "task4_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task4_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 49.359375, + "height": 22.0 + }, + "text": "Brew", + "alignment": { + "x": 24.453125, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 92.359375, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 840.0, + "y": 130.0 + }, + "size": { + "width": 102.359375, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "ManualTask6", + "taskType": "manual", + "id": "task6", + "cssClasses": [ + "task", + "manual" + ], + "children": [ + { + "id": "task6_header", + "children": [ + { + "id": "task6_icon", + "children": [ + { + "id": "task6_ticon", + "type": "label:icon", + "position": { + "x": 7.65625, + "y": 5.0 + }, + "size": { + "width": 16.6875, + "height": 22.0 + }, + "text": "M", + "alignment": { + "x": 8.34375, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task6_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 73.34375, + "height": 22.0 + }, + "text": "KeepTp", + "alignment": { + "x": 36.671875, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 116.34375, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:manual", + "position": { + "x": 510.0, + "y": 170.0 + }, + "size": { + "width": 126.34375, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "AutomatedTask8", + "taskType": "automated", + "id": "task8", + "cssClasses": [ + "task", + "automated" + ], + "children": [ + { + "id": "task8_header", + "children": [ + { + "id": "task8_icon", + "children": [ + { + "id": "task8_ticon", + "type": "label:icon", + "position": { + "x": 8.4921875, + "y": 5.0 + }, + "size": { + "width": 15.015625, + "height": 22.0 + }, + "text": "A", + "alignment": { + "x": 7.671875, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task8_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 63.671875, + "height": 22.0 + }, + "text": "ChkWt", + "alignment": { + "x": 31.671875, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 106.671875, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:automated", + "position": { + "x": 250.0, + "y": 60.0 + }, + "size": { + "width": 116.671875, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "name": "AutomatedTask9", + "taskType": "automated", + "id": "task9", + "cssClasses": [ + "task", + "automated" + ], + "children": [ + { + "id": "task9_header", + "children": [ + { + "id": "task9_icon", + "children": [ + { + "id": "task9_ticon", + "type": "label:icon", + "position": { + "x": 8.4921875, + "y": 5.0 + }, + "size": { + "width": 15.015625, + "height": 22.0 + }, + "text": "A", + "alignment": { + "x": 7.671875, + "y": 18.0 + } + } + ], + "type": "icon", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layout": "stack", + "layoutOptions": { + "resizeContainer": false, + "hAlign": "center" + } + }, + { + "id": "task9_classname", + "type": "label:heading", + "position": { + "x": 38.0, + "y": 10.0 + }, + "size": { + "width": 56.109375, + "height": 22.0 + }, + "text": "WtOK", + "alignment": { + "x": 27.78125, + "y": 18.0 + } + } + ], + "type": "comp:header", + "position": { + "x": 5.0, + "y": 5.0 + }, + "size": { + "width": 99.109375, + "height": 42.0 + }, + "layout": "hbox" + } + ], + "type": "task:automated", + "position": { + "x": 510.0, + "y": 100.0 + }, + "size": { + "width": 109.109375, + "height": 52.0 + }, + "layout": "vbox", + "args": { + "radiusBottomLeft": 5.0, + "radiusTopLeft": 5.0, + "radiusTopRight": 5.0, + "radiusBottomRight": 5.0 + } + }, + { + "nodeType": "mergeNode", + "id": "activityNode1", + "type": "activityNode:merge", + "position": { + "x": 680.0, + "y": 70.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "id": "edge4", + "type": "edge", + "sourceId": "task1", + "targetId": "activityNode1" + }, + { + "id": "edge5", + "type": "edge", + "sourceId": "task9", + "targetId": "activityNode1" + }, + { + "nodeType": "decisionNode", + "id": "activityNode2", + "type": "activityNode:decision", + "position": { + "x": 420.0, + "y": 210.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "id": "edge7", + "type": "edge", + "sourceId": "task2", + "targetId": "activityNode2" + }, + { + "nodeType": "mergeNode", + "id": "activityNode4", + "type": "activityNode:merge", + "position": { + "x": 690.0, + "y": 210.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minHeight": 32.0, + "resizeContainer": false + } + }, + { + "id": "edge11", + "type": "edge", + "sourceId": "task6", + "targetId": "activityNode4" + }, + { + "id": "edge12", + "type": "edge", + "sourceId": "task3", + "targetId": "activityNode4" + }, + { + "nodeType": "decisionNode", + "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "type": "activityNode:decision", + "position": { + "x": 410.0, + "y": 70.0 + }, + "size": { + "width": 32.0, + "height": 32.0 + }, + "layoutOptions": { + "minWidth": 32.0, + "minHeight": 32.0 + } + }, + { + "id": "69d7d698-c5ce-4e85-be06-d9bd9357322d", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "task1" + }, + { + "nodeType": "forkNode", + "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "cssClasses": [ + "forkOrJoin" + ], + "type": "activityNode:fork", + "position": { + "x": 170.0, + "y": 130.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", + "type": "edge", + "sourceId": "task0", + "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" + }, + { + "id": "5eeaa218-ad56-4052-8b28-bbc08d82ed34", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task2" + }, + { + "nodeType": "joinNode", + "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "cssClasses": [ + "forkOrJoin" + ], + "type": "activityNode:join", + "position": { + "x": 800.0, + "y": 130.0 + }, + "size": { + "width": 10.0, + "height": 50.0 + } + }, + { + "id": "2f57af4e-2960-496e-992d-03c8b021d81a", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "activityNode2", + "targetId": "task6" + }, + { + "id": "2c7427b0-9743-4bfb-b04c-078ba98f8d7e", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "activityNode2", + "targetId": "task3" + }, + { + "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", + "type": "edge", + "sourceId": "activityNode1", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "id": "0471cae4-c754-4e89-8337-96ed1546dd02", + "type": "edge", + "sourceId": "activityNode4", + "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + }, + { + "id": "2c3ec34b-291c-43ef-bdf9-035afc4764e6", + "type": "edge", + "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", + "targetId": "task4" + }, + { + "id": "e9aa796a-aa5b-4e6d-8860-4b9c5a5b8d89", + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", + "targetId": "task9" + }, + { + "id": "f49a588c-618c-4d9f-96fc-feee776a0ead", + "type": "edge", + "sourceId": "task8", + "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" + }, + { + "id": "568d91e3-d5bf-464d-923e-9769a8a651f3", + "type": "edge", + "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", + "targetId": "task8" + } + ], + "type": "graph", + "revision": 1 +} \ No newline at end of file diff --git a/packages/graph/LICENSE b/packages/graph/LICENSE new file mode 100644 index 0000000..4ee1e0a --- /dev/null +++ b/packages/graph/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. \ No newline at end of file diff --git a/packages/graph/README.md b/packages/graph/README.md new file mode 100644 index 0000000..f0dede2 --- /dev/null +++ b/packages/graph/README.md @@ -0,0 +1,8 @@ +# Eclipse GLSP - Graph model + +A typescript implementation of the [GLSP graph model](https://github.com/eclipse-glsp/glsp-server/tree/master/plugins/org.eclipse.glsp.graph). + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/packages/graph/package.json b/packages/graph/package.json new file mode 100644 index 0000000..96ed358 --- /dev/null +++ b/packages/graph/package.json @@ -0,0 +1,70 @@ +{ + "name": "@eclipse-glsp/graph", + "version": "0.9.0", + "description": "The typescript implementation of the GLSP graphical model (GModel)", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-server-node.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "STMicroelectronics", + "url": "https://www.st.com/" + }, + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "files": [ + "lib", + "src", + "css" + ], + "dependencies": { + "@eclipse-glsp/protocol": "next" + }, + "devDependencies": { + "@types/ws": "^8.2.0", + "typescript": "^3.9.2", + "@types/chai": "^4.2.22", + "@types/mocha": "^9.0.0", + "@types/sinon": "^10.0.6", + "@types/moxios": "^0.4.13", + "moxios": "^0.4.0", + "mocha": "^9.1.3", + "chai": "^4.3.4", + "sinon": "^12.0.1", + "ts-node": "^10.4.0", + "@types/uuid": "8.3.1" + }, + "scripts": { + "clean": "rimraf lib", + "build": "yarn run clean && tsc", + "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "watch": "tsc -w" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts new file mode 100644 index 0000000..6fd0482 --- /dev/null +++ b/packages/graph/src/default-types.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes as types } from '@eclipse-glsp/protocol'; +import { GButton } from './gbutton'; +import { GCompartment } from './gcompartment'; +import { GEdge } from './gedge'; +import { GGraph } from './ggraph'; +import { GIssueMarker } from './gissue-marker'; +import { GLabel } from './glabel'; +import { GModelElementConstructor, GModelRoot } from './gmodel-element'; +import { GNode } from './gnode'; +import { GPort } from './gport'; +import { GPreRenderedElement } from './gpre-rendered-element'; + +export function getDefaultMapping(): Map { + const mapping = new Map(); + mapping.set(types.GRAPH, GGraph); + mapping.set(types.NODE, GNode); + mapping.set(types.EDGE, GEdge); + mapping.set(types.PORT, GPort); + mapping.set(types.LABEL, GLabel); + mapping.set(types.COMPARTMENT, GCompartment); + mapping.set(types.COMPARTMENT_HEADER, GCompartment); + // TODO: mapping.set(types.BUTTON, GButton); + mapping.set(types.BUTTON_EXPAND, GButton); + mapping.set(types.ISSUE_MARKER, GIssueMarker); + + mapping.set(types.NODE_CIRCLE, GNode); + mapping.set(types.NODE_RECTANGLE, GNode); + mapping.set(types.NODE_DIAMOND, GNode); + + mapping.set(types.HTML, GModelRoot); + mapping.set(types.PRE_RENDERED, GPreRenderedElement); + return mapping; +} diff --git a/packages/graph/src/galignable.ts b/packages/graph/src/galignable.ts new file mode 100644 index 0000000..ab924d3 --- /dev/null +++ b/packages/graph/src/galignable.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export const GAlignable = Symbol('GAlignable'); + +export interface GAlignable { + alignment: Point; + [GAlignable]: boolean; +} + +export function isGAlignable(element: G): element is G & GAlignable { + return ((element as any)[GAlignable] = true); +} + +export type GAlignableBuilder = GModelElementBuilder; + +export namespace GAlignableBuilder { + export function alignment(builder: B, pointOrX: Point | number, y?: number): B { + const proxy = builder['proxy']; + if (typeof pointOrX === 'object') { + proxy.alignment = pointOrX; + } else if (y) { + proxy.alignment = { x: pointOrX, y }; + } + return builder; + } +} diff --git a/packages/graph/src/gbound-aware.ts b/packages/graph/src/gbound-aware.ts new file mode 100644 index 0000000..a1011ca --- /dev/null +++ b/packages/graph/src/gbound-aware.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Dimension, Point } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export const GBoundsAware = Symbol('GBoundsAware'); + +export interface GBoundsAware { + position?: Point; + size?: Dimension; + [GBoundsAware]: boolean; +} + +export function isGBoundsAware(element: G): element is G & GBoundsAware { + return ((element as any)[GBoundsAware] = true); +} + +export type GBoundsAwareBuilder = GModelElementBuilder; + +export namespace GBoundsAwareBuilder { + export function position(builder: B, pointOrX: Point | number, y?: number): B { + const proxy = builder['proxy']; + if (typeof pointOrX === 'object') { + proxy.position = pointOrX; + } else if (y) { + proxy.position = { x: pointOrX, y }; + } + return builder; + } + + export function size(builder: B, sizeOrWidth: Dimension | number, height?: number): B { + const proxy = builder['proxy']; + if (typeof sizeOrWidth === 'object') { + proxy.size = sizeOrWidth; + } else if (height) { + proxy.size = { width: sizeOrWidth, height }; + } + return builder; + } +} diff --git a/packages/graph/src/gbutton.ts b/packages/graph/src/gbutton.ts new file mode 100644 index 0000000..7ad7c5b --- /dev/null +++ b/packages/graph/src/gbutton.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { GModelElementConstructor } from './gmodel-element'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export class GButton extends GShapeElement { + static builder(constructor?: GModelElementConstructor): GButtonBuilder { + return new GButtonBuilder(constructor ?? GButton).type(DefaultTypes.BUTTON_EXPAND); + } + + type = DefaultTypes.BUTTON_EXPAND; + enabled = true; +} + +export class GButtonBuilder extends GShapeElementBuilder { + enabled(enabled: boolean): this { + this.proxy.enabled = enabled; + return this; + } +} diff --git a/packages/graph/src/gcompartment.ts b/packages/graph/src/gcompartment.ts new file mode 100644 index 0000000..12725d8 --- /dev/null +++ b/packages/graph/src/gcompartment.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, DefaultTypes, JsonPrimitive } from '@eclipse-glsp/protocol'; +import { GLayoutContainer, GLayoutContainerBuilder } from './glayout-container'; +import { GLayoutable, GLayoutableBuilder } from './glayoutable'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export class GCompartment extends GShapeElement implements GLayoutContainer, GLayoutable { + static builder(): GCompartmentBuilder { + return new GCompartmentBuilder(GCompartment).type(DefaultTypes.COMPARTMENT); + } + + type = DefaultTypes.COMPARTMENT; + layout?: string; + layoutOptions?: Args; + [GLayoutContainer] = true; + [GLayoutable] = true; +} + +export class GCompartmentBuilder extends GShapeElementBuilder { + layout(layout?: string): this { + return GLayoutContainerBuilder.layout(this, layout); + } + + addLayoutOption(key: string, value: JsonPrimitive): this { + return GLayoutableBuilder.addLayoutOption(this, key, value); + } + + addLayoutOptions(layoutOptions: Args): this; + addLayoutOptions(layoutOptions: Map): this; + addLayoutOptions(layoutOptions: Args | Map): this { + return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); + } +} diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts new file mode 100644 index 0000000..c3e46a9 --- /dev/null +++ b/packages/graph/src/gedge-layoutable.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export type EdgeSide = 'left' | 'right' | 'top' | 'bottom' | 'on'; + +export class EdgePlacement extends Object { + rotate: boolean; + side: EdgeSide; + position: number; + offset: number; +} + +export const GEdgeLayoutable = Symbol('GEdgeLayoutable'); + +export interface GEdgeLayoutable { + edgePlacement?: EdgePlacement; + [GEdgeLayoutable]: boolean; +} + +export function isGEdgeLayoutable(element: G): element is G & GEdgeLayoutable { + return ((element as any)[GEdgeLayoutable] = true); +} + +export type GEdgeLayoutableBuilder = GModelElementBuilder; + +export namespace GEdgeLayoutableBuilder { + export function edgePlacement(builder: B, placement?: EdgePlacement): B { + builder['proxy'].edgePlacement = placement; + return builder; + } +} diff --git a/packages/graph/src/gedge.ts b/packages/graph/src/gedge.ts new file mode 100644 index 0000000..2c54821 --- /dev/null +++ b/packages/graph/src/gedge.ts @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes, flatPush, MaybeArray, Point } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export class GEdge extends GModelElement { + static builder(): GEdgeBuilder { + return new GEdgeBuilder(GEdge).type(DefaultTypes.EDGE); + } + + type = DefaultTypes.EDGE; + routingPoints: Point[] = []; + sourceId: string; + targetId: string; + routerKind?: string; + private _source: GModelElement; + private _target: GModelElement; + + get source(): GModelElement { + return this._source; + } + + set source(value: GModelElement) { + this._source = value; + this.sourceId = value.id; + } + + get target(): GModelElement { + return this._target; + } + + set target(value: GModelElement) { + this._target = value; + this.targetId = value.id; + } +} + +export namespace GEdge { + export function is(object: unknown): object is GEdge { + return GEdge.is(object); + } +} + +export class GEdgeBuilder extends GModelElementBuilder { + source(source: GModelElement): this { + this.proxy.source = source; + return this; + } + + sourceId(sourceId: string): this { + this.proxy.sourceId = sourceId; + return this; + } + + target(target: GModelElement): this { + this.proxy.target = target; + return this; + } + + targetId(targetId: string): this { + this.proxy.targetId = targetId; + return this; + } + + routerKind(routerKind: string): this { + this.proxy.routerKind = routerKind; + return this; + } + + addRoutingPoint(x: number, y: number): this; + addRoutingPoint(point: Point): this; + addRoutingPoint(xOrPoint: Point | number, y?: number): this { + if (typeof xOrPoint === 'object') { + this.proxy.routingPoints.push(xOrPoint); + } else if (y) { + this.proxy.routingPoints.push({ x: xOrPoint, y }); + } + return this; + } + + addRoutingPoints(routingPoints: Point[]): this; + addRoutingPoints(...routingPoints: Point[]): this; + addRoutingPoints(...routingPoints: MaybeArray[]): this { + flatPush(this.proxy.routingPoints, routingPoints); + return this; + } +} diff --git a/packages/graph/src/ggraph.ts b/packages/graph/src/ggraph.ts new file mode 100644 index 0000000..30e7f46 --- /dev/null +++ b/packages/graph/src/ggraph.ts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2022 StMicroelectronics. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/** + * TS implementation of the GLSP graph definition + * (https://github.com/eclipse-glsp/glsp-server/tree/master/plugins/org.eclipse.glsp.graph/src-gen/org/eclipse/glsp/graph) + * Parts of the implementation is derived from Sprotty's SModel API + * (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel.ts) + */ +import { DefaultTypes as types, Dimension, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { GModelRoot, GModelRootBuilder } from './gmodel-element'; + +export class GGraph extends GModelRoot implements GBoundsAware { + static builder(): GGraphBuilder { + return new GGraphBuilder(GGraph); + } + + type = types.GRAPH; + position: Point = ORIGIN_POINT; + size?: Dimension; + [GBoundsAware] = true; +} + +export class GGraphBuilder extends GModelRootBuilder { + position(x: number, y: number): this; + position(position: Point): this; + position(positionOrX: Point | number, y?: number): this { + return GBoundsAwareBuilder.position(this, positionOrX, y); + } + + size(width: number, height: number): this; + size(size: Dimension): this; + size(sizeOrWidth: Dimension | number, height?: number): this { + return GBoundsAwareBuilder.size(this, sizeOrWidth, height); + } +} diff --git a/packages/graph/src/gissue-marker.ts b/packages/graph/src/gissue-marker.ts new file mode 100644 index 0000000..9dbe03d --- /dev/null +++ b/packages/graph/src/gissue-marker.ts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes, flatPush, MaybeArray } from '@eclipse-glsp/protocol'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export type GIssueSeverity = 'error' | 'warning' | 'info'; + +export class GIssue { + message: string; + severity: GIssueSeverity; +} + +export class GIssueMarker extends GShapeElement { + type = DefaultTypes.ISSUE_MARKER; + issues: GIssue[] = []; +} + +export class GIssueMarkerBuilder extends GShapeElementBuilder { + addIssue(issue: GIssue): this; + addIssue(message: string, severity: GIssueSeverity): this; + addIssue(issueOrMessage: GIssue | string, severity?: GIssueSeverity): this { + if (typeof issueOrMessage === 'object') { + this.proxy.issues.push(issueOrMessage); + } else if (severity) { + this.proxy.issues.push({ message: issueOrMessage, severity }); + } + return this; + } + + addIssues(issues: GIssue[]): this; + addIssues(...issues: GIssue[]): this; + addIssues(...issues: MaybeArray[]): this { + flatPush(this.proxy.issues, issues); + return this; + } +} diff --git a/packages/graph/src/glabel.ts b/packages/graph/src/glabel.ts new file mode 100644 index 0000000..6724f35 --- /dev/null +++ b/packages/graph/src/glabel.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { GAlignable, GAlignableBuilder } from './galignable'; +import { EdgePlacement, GEdgeLayoutable, GEdgeLayoutableBuilder } from './gedge-layoutable'; +import { GModelElementConstructor } from './gmodel-element'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export class GLabel extends GShapeElement implements GAlignable, GEdgeLayoutable { + static builder(constructor?: GModelElementConstructor): GLabelBuilder { + return new GLabelBuilder(constructor ?? GLabel).type(DefaultTypes.LABEL); + } + + type = DefaultTypes.LABEL; + text: string; + alignment: Point = ORIGIN_POINT; + edgePlacement?: EdgePlacement; + [GAlignable] = true; + [GEdgeLayoutable] = true; +} + +export class GLabelBuilder extends GShapeElementBuilder { + alignment(x: number, y: number): this; + alignment(alignment: Point): this; + alignment(xOrAlign: number | Point, y?: number): this { + return GAlignableBuilder.alignment(this, xOrAlign, y); + } + + text(text: string): this { + this.proxy.text = text; + return this; + } + + edgePlacement(placement: EdgePlacement): this { + return GEdgeLayoutableBuilder.edgePlacement(this, placement); + } +} diff --git a/packages/graph/src/glayout-container.ts b/packages/graph/src/glayout-container.ts new file mode 100644 index 0000000..75236b3 --- /dev/null +++ b/packages/graph/src/glayout-container.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export const GLayoutContainer = Symbol('GLayoutContainer'); + +export interface GLayoutContainer { + layout?: string; + [GLayoutContainer]: boolean; +} + +export function isGLayoutContainer(element: G): element is G & GLayoutContainer { + return ((element as any)[GLayoutContainer] = true); +} + +export type GLayoutContainerBuilder = GModelElementBuilder; + +export namespace GLayoutContainerBuilder { + export function layout(builder: B, newLayout?: string): B { + builder['proxy'].layout = newLayout; + return builder; + } +} diff --git a/packages/graph/src/glayoutable.ts b/packages/graph/src/glayoutable.ts new file mode 100644 index 0000000..04e1e44 --- /dev/null +++ b/packages/graph/src/glayoutable.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, JsonPrimitive } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export const GLayoutable = Symbol('GLayoutable'); + +export interface GLayoutable { + layoutOptions?: Args; + [GLayoutable]: boolean; +} + +export function isGLayoutable(element: G): element is G & GLayoutable { + return ((element as any)[GLayoutable] = true); +} + +export type GLayoutableBuilder = GModelElementBuilder; + +export namespace GLayoutableBuilder { + export function addLayoutOption(builder: B, key: string, value: JsonPrimitive): B { + const proxy = builder['proxy']; + if (!proxy.layoutOptions) { + proxy.layoutOptions = {}; + } + proxy.layoutOptions[key] = value; + return builder; + } + + export function addLayoutOptions(builder: B, layoutOptions: Args | Map): B { + const toAssign: Args = {}; + const proxy = builder['proxy']; + if (layoutOptions instanceof Map) { + [...layoutOptions.keys()].forEach(key => (toAssign[key] = layoutOptions.get(key)!)); + } else { + Object.keys(layoutOptions).forEach(key => (toAssign[key] = layoutOptions[key])); + } + if (proxy.layoutOptions) { + Object.assign(proxy.layoutOptions, toAssign); + } else { + proxy.layoutOptions = toAssign; + } + return builder; + } +} diff --git a/packages/graph/src/gmodel-element.spec.ts b/packages/graph/src/gmodel-element.spec.ts new file mode 100644 index 0000000..b6110a3 --- /dev/null +++ b/packages/graph/src/gmodel-element.spec.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +describe('foo', () => { + // Test stub +}); diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts new file mode 100644 index 0000000..e340324 --- /dev/null +++ b/packages/graph/src/gmodel-element.ts @@ -0,0 +1,216 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Args, + Bounds, + Dimension, + flatPush, + isSModelElementSchema, + isSModelRootSchema, + JsonPrimitive, + MaybeArray, + Point, + SModelElementSchema, + SModelRootSchema +} from '@eclipse-glsp/protocol'; +import * as uuid from 'uuid'; +export type GModelElementConstructor = new () => G; + +/** + * Represents a `GModeElement` serialized as plain JSON object. + */ +export type GModelElementSchema = SModelElementSchema; + +export function isGModelElementSchema(schema: any): schema is GModelElementSchema { + return isSModelElementSchema(schema); +} + +/** + * Base type for all elements of the graphical model. + * Each model element must have a unique ID and a type that is used to look up its view. + */ +export abstract class GModelElement implements GModelElementSchema { + /** + * The `type` of a `GModelElement` has two main purposes. It is used by the GLSP client + * during the rendering phase to lookup the View that is responsible for rendering this element. + * In addition, it serves as the discriminator for serialization & deserialization of `GModeElements` and their corresponding + * `GModelElementSchema`. e.g. If the glsp server receives a `GModelElementSchema` (plain JSON object) the type is used to + * lookup and construct the corresponding `GModelElement` class. + */ + type: string; + /** + * Each model element must have a unique ID. Duplicate ids in the graphical element will result in an error during the + * rendering phase. + */ + id: string; + /** + * A set of css classes that should be applied to the DOM element that corresponds to this element. + */ + cssClasses: string[] = []; + /** A `GModelElement can have an arbitrary amount of children. This parent-child relation ship is also reflected in + * the corresponding DOM element i.e. DOM elements that reflect children of this element are also children + * of the DOM element that reflects this element. + */ + children: GModelElement[] = []; + /** + * Each `GModelElement` (apart from the root element) must have an assigned parent. Orphan elements are not allowed. + */ + parent: GModelElement; + /** + * Additional custom arguments. Can be used to transmit additional information between client & server without + * having to extend the model. + */ + args?: Args; + + get root(): GModelRoot { + let current: GModelElement = this; + while (!(current instanceof GModelRoot)) { + current = current.parent; + } + return current; + } +} + +/** + * A fluent builder API that simplifies the construction of complex {@link GModelElement}s. + * The builder API is derived from the Java GLSP server implementation where it is used to hide the complexity + * of creating EMF objects. However, the API is also useful in a Typescript/Node context to declare the creation of a new + * {@link GModelElement} in a more concise way. + */ +export abstract class GModelElementBuilder { + protected proxy: G; + protected elementConstructor: GModelElementConstructor; + + constructor(elementConstructor: GModelElementConstructor) { + this.elementConstructor = elementConstructor; + this.proxy = new elementConstructor(); + this.proxy.cssClasses = []; + this.proxy.children = []; + this.proxy.id = uuid.v4(); + } + + reset(): this { + this.proxy = new this.elementConstructor(); + return this; + } + + build(): G { + const element = new this.elementConstructor(); + Object.assign(element, this.proxy); + element.children.forEach(child => (child.parent = element)); + if (element.id === undefined) { + throw new Error('The `id` property of a GModelElement must not be undefined!'); + } + if (element.type === undefined) { + throw new Error('The `type` property of a GModelElement must not be undefined!'); + } + return element; + } + + id(id: string): this { + this.proxy.id = id; + return this; + } + + type(type: string): this { + this.proxy.type = type; + return this; + } + + addCssClass(cssClass: string): this { + this.proxy.cssClasses.push(cssClass); + return this; + } + + addCssClasses(cssClasses: string[]): this; + addCssClasses(...cssClasses: string[]): this; + addCssClasses(...cssClasses: MaybeArray[]): this { + flatPush(this.proxy.cssClasses, cssClasses); + return this; + } + + add(child: GModelElement): this { + this.proxy.children.push(child); + return this; + } + + addChildren(children: GModelElement[]): this; + addChildren(...children: GModelElement[]): this; + addChildren(...children: MaybeArray[]): this { + flatPush(this.proxy.children, children); + return this; + } + + addArg(key: string, value: JsonPrimitive): this { + if (!this.proxy.args) { + this.proxy.args = {}; + } + this.proxy.args[key] = value; + return this; + } + + addArgs(args: Args): this; + addArgs(args: Map): this; + addArgs(args: Args | Map): this { + const toAssign: Args = {}; + if (args instanceof Map) { + [...args.keys()].forEach(key => (toAssign[key] = args.get(key)!)); + } else { + Object.keys(args).forEach(key => (toAssign[key] = args[key])); + } + if (this.proxy.args) { + Object.assign(this.proxy.args, toAssign); + } else { + this.proxy.args = toAssign; + } + return this; + } +} + +export type GModelRootSchema = SModelRootSchema; + +export function isGModelRootSchema(schema: any): schema is GModelRootSchema { + return isSModelRootSchema(schema); +} + +export class GModelRoot extends GModelElement implements SModelRootSchema { + static builder(): GModelRootBuilder { + return new GModelRootBuilder(GModelRoot); + } + + canvasBounds?: Bounds; + revision = 0; +} + +export class GModelRootBuilder extends GModelElementBuilder { + revision(revision: number): this { + this.proxy.revision = revision; + return this; + } + + canvasBounds(position: Point, size: Dimension): this; + canvasBounds(bounds: Bounds): this; + canvasBounds(positionOrBounds: Point | Bounds, size?: Dimension): this { + let bounds: Bounds; + if (size) { + bounds = { ...positionOrBounds, ...size }; + } else { + bounds = positionOrBounds as Bounds; + } + this.proxy.canvasBounds = bounds; + return this; + } +} diff --git a/packages/graph/src/gnode.ts b/packages/graph/src/gnode.ts new file mode 100644 index 0000000..1480a57 --- /dev/null +++ b/packages/graph/src/gnode.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { GLayoutContainer, GLayoutContainerBuilder } from './glayout-container'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export class GNode extends GShapeElement implements GLayoutContainer { + static builder(): GNodeBuilder { + return new GNodeBuilder(GNode).type(DefaultTypes.NODE); + } + + type = DefaultTypes.NODE; + layout?: string; + [GLayoutContainer] = true; +} + +export class GNodeBuilder extends GShapeElementBuilder { + layout(layout?: string): this { + return GLayoutContainerBuilder.layout(this, layout); + } +} diff --git a/packages/graph/src/gport.ts b/packages/graph/src/gport.ts new file mode 100644 index 0000000..ba12566 --- /dev/null +++ b/packages/graph/src/gport.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { GShapeElement, GShapeElementBuilder } from './gshape-element'; + +export class GPort extends GShapeElement { + static builder(): GShapeElementBuilder { + return new GShapeElementBuilder(GPort).type(DefaultTypes.PORT); + } + + type = DefaultTypes.PORT; +} diff --git a/packages/graph/src/gpre-rendered-element.ts b/packages/graph/src/gpre-rendered-element.ts new file mode 100644 index 0000000..32bc2a2 --- /dev/null +++ b/packages/graph/src/gpre-rendered-element.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement, GModelElementBuilder } from './gmodel-element'; +import { DefaultTypes } from '@eclipse-glsp/protocol'; + +/** + * Pre-rendered elements contain HTML or SVG code to be transferred to the DOM. This can be useful to + * render complex figures or to compute the view on the server instead of the client code. + * Pre rendered elements are often used for the popup model that is created by a `PopupModelFactory`. + * A popup model is rendered when hovering over a element and for many common use cases e.g rendering a tooltip + * this model can be computed entirely on the server side. + */ +export class GPreRenderedElement extends GModelElement { + static builder(): GPreRenderedElementBuilder { + return new GPreRenderedElementBuilder(GPreRenderedElement).type(DefaultTypes.PRE_RENDERED); + } + + code: string; +} + +export class GPreRenderedElementBuilder extends GModelElementBuilder { + code(code: string): this { + this.proxy.code = code; + return this; + } +} diff --git a/packages/graph/src/gshape-element.ts b/packages/graph/src/gshape-element.ts new file mode 100644 index 0000000..a4fa001 --- /dev/null +++ b/packages/graph/src/gshape-element.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, Dimension, JsonPrimitive, Point } from '@eclipse-glsp/protocol'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { GLayoutable, GLayoutableBuilder } from './glayoutable'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export abstract class GShapeElement extends GModelElement implements GBoundsAware, GLayoutable { + layoutOptions?: Args; + position: Point; + size: Dimension; + [GBoundsAware] = true; + [GLayoutable] = true; +} + +export class GShapeElementBuilder extends GModelElementBuilder { + position(x: number, y: number): this; + position(position: Point): this; + position(pointOrX: Point | number, y?: number): this { + return GBoundsAwareBuilder.position(this, pointOrX, y); + } + + size(width: number, height: number): this; + size(size: Dimension): this; + size(sizeOrWidth: Dimension | number, height?: number): this { + return GBoundsAwareBuilder.size(this, sizeOrWidth, height); + } + + addLayoutOption(key: string, value: JsonPrimitive): this { + return GLayoutableBuilder.addLayoutOption(this, key, value); + } + + addLayoutOptions(layoutOptions: Args): this; + addLayoutOptions(layoutOptions: Map): this; + addLayoutOptions(layoutOptions: Args | Map): this { + return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); + } +} diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts new file mode 100644 index 0000000..8addc77 --- /dev/null +++ b/packages/graph/src/index.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './default-types'; +export * from './galignable'; +export * from './gbound-aware'; +export * from './gbutton'; +export * from './gcompartment'; +export * from './gedge'; +export * from './gedge-layoutable'; +export * from './ggraph'; +export * from './gissue-marker'; +export * from './glabel'; +export * from './glayout-container'; +export * from './glayoutable'; +export * from './gpre-rendered-element'; +export * from './gmodel-element'; +export * from './gnode'; +export * from './gport'; +export * from './gshape-element'; diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json new file mode 100644 index 0000000..920ba34 --- /dev/null +++ b/packages/graph/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": "src", + "types": ["node", "reflect-metadata"] + }, + "include": ["src"], + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/server-node/LICENSE b/packages/server-node/LICENSE new file mode 100644 index 0000000..4ee1e0a --- /dev/null +++ b/packages/server-node/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. \ No newline at end of file diff --git a/packages/server-node/README.md b/packages/server-node/README.md new file mode 100644 index 0000000..f0985ce --- /dev/null +++ b/packages/server-node/README.md @@ -0,0 +1,8 @@ +# Eclipse GLSP - Node Server + +A typescript implementation of the [GLSP Server](https://github.com/eclipse-glsp/glsp-server). + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/packages/server-node/package.json b/packages/server-node/package.json new file mode 100644 index 0000000..0da3938 --- /dev/null +++ b/packages/server-node/package.json @@ -0,0 +1,73 @@ +{ + "name": "@eclipse-glsp/server-node", + "version": "0.9.0", + "description": "A node.js server framework for GLSP", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-server-node.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "STMicroelectronics", + "url": "https://www.st.com/" + }, + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "files": [ + "lib", + "src" + ], + "dependencies": { + "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "0.9.0", + "inversify": "5.1.1", + "commander": "^8.3.0", + "winston": "^3.3.3", + "@types/uuid": "8.3.1" + }, + "devDependencies": { + "@types/fs-extra": "^9.0.13", + "@types/node": "12.x", + "reflect-metadata": "^0.1.13", + "typescript": "^3.9.2", + "@types/chai": "^4.2.22", + "@types/mocha": "^9.0.0", + "@types/sinon": "^10.0.6", + "mocha": "^9.1.3", + "chai": "^4.3.4", + "sinon": "^12.0.1", + "ts-node": "^10.4.0" + }, + "scripts": { + "clean": "rimraf lib", + "build": "yarn run clean && tsc", + "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "watch": "tsc -w" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/packages/server-node/src/actions/action-dispatcher.spec.ts b/packages/server-node/src/actions/action-dispatcher.spec.ts new file mode 100644 index 0000000..8168fc5 --- /dev/null +++ b/packages/server-node/src/actions/action-dispatcher.spec.ts @@ -0,0 +1,255 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule } from 'inversify'; +import { ClientActionKinds, ClientId } from '../di/service-identifiers'; +import { ClientSessionManager } from '../session/client-session-manager'; +import * as mock from '../test/mock-util'; +import { Logger } from '../utils/logger'; +import { DefaultActionDispatcher } from './action-dispatcher'; +import { ActionHandler } from './action-handler'; +import { ActionHandlerRegistry } from './action-handler-registry'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { Action } from '@eclipse-glsp/protocol'; + +function waitSync(timeInMillis: number): void { + const start = Date.now(); + let now = start; + while (now - start < timeInMillis) { + now = Date.now(); + } +} + +describe('test DefaultActionDispatcher', () => { + const container = new Container(); + const clientId = 'myClientId'; + const actionHandlerRegistry = new ActionHandlerRegistry(); + let registry_get_stub: sinon.SinonStub<[string], ActionHandler[]>; + const sandbox = sinon.createSandbox(); + + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(ClientSessionManager).toConstantValue(new mock.StubClientSessionManager()); + bind(ClientId).toConstantValue(clientId); + bind(ActionHandlerRegistry).toConstantValue(actionHandlerRegistry); + bind(ClientActionKinds).toConstantValue(['response', 'response1', 'response2']); + }) + ); + const actionDispatcher = container.resolve(DefaultActionDispatcher); + + beforeEach(() => { + registry_get_stub = sandbox.stub(actionHandlerRegistry, 'get'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('test with one-way actions (no response actions)', () => { + it('dispatch- unhandled action', async () => { + expect(actionDispatcher.dispatch({ kind: 'unhandled' })).to.throw; + }); + + it('dispatch - one action', async () => { + // Mock setup + const action = 'action'; + const handler = new mock.StubActionHandler([action]); + const getHandler = (kind: string): ActionHandler[] => (kind === action ? [handler] : []); + registry_get_stub.callsFake(getHandler); + const spy_handler_execute = sinon.spy(handler, 'execute'); + // Test execution + await actionDispatcher.dispatch({ kind: action }); + expect(spy_handler_execute.calledOnce).true; + }); + + describe('test multi dispatch with single-handled actions', () => { + // Mock Setup + const action1 = 'a1'; + const action2 = 'a2'; + const action3 = 'a3'; + + const handler1 = new mock.StubActionHandler([action1]); + const handler2 = new mock.StubActionHandler([action2]); + const handler3 = new mock.StubActionHandler([action3]); + + const spy_handler1_execute = sinon.stub(handler1, 'execute').returns([]); + const spy_handler2_execute = sinon.stub(handler2, 'execute').returns([]); + const spy_handler3_execute = sinon.stub(handler3, 'execute').returns([]); + const handlerMockImpl = (kind: string): ActionHandler[] => { + switch (kind) { + case action1: + return [handler1]; + case action2: + return [handler2]; + case action3: + return [handler3]; + default: + return []; + } + }; + + it('dispatch - multiple actions', async () => { + // Mock setup + registry_get_stub.callsFake(handlerMockImpl); + // Test execution + actionDispatcher.dispatch({ kind: action1 }); + actionDispatcher.dispatch({ kind: action2 }); + await actionDispatcher.dispatch({ kind: action3 }); + // Check if all handlers have been called + expect(spy_handler1_execute.called).true; + expect(spy_handler2_execute.called).true; + expect(spy_handler3_execute.called).true; + // Check if all handlers have been called in the right order + sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute); + }); + + it('dispatchAll- multiple actions', async () => { + // Mock setup + registry_get_stub.callsFake(handlerMockImpl); + // Test execution + await actionDispatcher.dispatchAll([{ kind: action1 }, { kind: action2 }, { kind: action3 }]); + // Check if all handlers have been called + expect(spy_handler1_execute.calledOnce); + expect(spy_handler2_execute.calledOnce); + expect(spy_handler3_execute.calledOnce); + // Check if all handlers have been called in the right order + sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute); + }); + + it('dispatch - multiple actions (racing execution times)', async () => { + // Mock setup + registry_get_stub.callsFake(handlerMockImpl); + spy_handler1_execute.callsFake((_action: Action) => { + waitSync(500); + return []; + }); + spy_handler2_execute.callsFake((_action: Action) => { + waitSync(200); + return []; + }); + spy_handler3_execute.callsFake((_action: Action) => { + waitSync(100); + return []; + }); + // Test execution + actionDispatcher.dispatch({ kind: action1 }); + actionDispatcher.dispatch({ kind: action2 }); + await actionDispatcher.dispatch({ kind: action3 }); + // Check if all handlers have been called + expect(spy_handler1_execute.calledOnce); + expect(spy_handler2_execute.calledOnce); + expect(spy_handler3_execute.calledOnce); + // Check if all handlers have been called in the right order + sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute); + }); + }); + + it('dispatch- one action & multiple handlers', async () => { + // Mock setup + const action1 = 'a1'; + + const handler1 = new mock.StubActionHandler([action1]); + const handler2 = new mock.StubActionHandler([action1]); + + registry_get_stub.callsFake((kind: string) => (kind === action1 ? [handler1, handler2] : [])); + const spy_handler1_execute = sinon.spy(handler1, 'execute'); + const spy_handler2_execute = sinon.spy(handler2, 'execute'); + // Test execution + await actionDispatcher.dispatch({ kind: action1 }); + expect(spy_handler1_execute.calledOnce); + expect(spy_handler2_execute.calledOnce); + sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute); + }); + }); + + describe('test with handler response actions ', () => { + it('dispatch - one action & one handler response action', async () => { + // Mock setup + const request = 'request'; + const response = 'response'; + + const requestHandler = new mock.StubActionHandler([request]); + const responseHandler = new mock.StubActionHandler([response]); + + const spy_requestHandler_execute = sinon.stub(requestHandler, 'execute').returns([{ kind: response }]); + const spy_responseHandler_execute = sinon.spy(responseHandler, 'execute'); + registry_get_stub.callsFake((kind: string) => { + switch (kind) { + case request: + return [requestHandler]; + case response: + return [responseHandler]; + default: + return []; + } + }); + // Test execution + await actionDispatcher.dispatch({ kind: request }); + // Add a delay so that the action dispatcher has time to dispatch the handler response + await mock.delay(200); + // Check if all handlers have been called + expect(spy_requestHandler_execute.calledOnce); + expect(spy_responseHandler_execute.calledOnce); + }); + + it('dispatch - multiple actions & multiple response', async () => { + // Mock setup + const request1 = 'request1'; + const request2 = 'request2'; + const response1 = 'response1'; + const response2 = 'response2'; + + const responseHandler1 = new mock.StubActionHandler([response1]); + const responseHandler2 = new mock.StubActionHandler([response2]); + const requestHandler1 = new mock.StubActionHandler([request1]); + const requestHandler2 = new mock.StubActionHandler([request2]); + + const spy_requestHandler1_execute = sinon.stub(requestHandler1, 'execute').returns([{ kind: response1 }, { kind: response2 }]); + const spy_requestHandler2_execute = sinon.stub(requestHandler2, 'execute').returns([{ kind: response2 }]); + const spy_responseHandler1_execute = sinon.spy(responseHandler1, 'execute'); + const spy_responseHandler2_execute = sinon.spy(responseHandler2, 'execute'); + registry_get_stub.callsFake((kind: string) => { + switch (kind) { + case request1: + return [requestHandler1]; + case request2: + return [requestHandler2]; + case response1: + return [responseHandler1, responseHandler2]; + case response2: + return [responseHandler2]; + default: + return []; + } + }); + // Test execution + actionDispatcher.dispatch({ kind: request1 }); + await actionDispatcher.dispatch({ kind: request2 }); + + // Add a delay so that the action dispatcher has time to dispatch the handler response + await mock.delay(100); + // Check if all handlers have been called correctly + expect(spy_requestHandler1_execute.calledOnce); + expect(spy_requestHandler2_execute.calledOnce); + expect(spy_responseHandler1_execute.calledOnce); + expect(spy_responseHandler2_execute.calledThrice); + // Check if all handlers have been called in the right order + sinon.assert.callOrder(spy_requestHandler1_execute, spy_requestHandler2_execute); + sinon.assert.callOrder(spy_responseHandler1_execute, spy_responseHandler2_execute); + }); + }); +}); diff --git a/packages/server-node/src/actions/action-dispatcher.ts b/packages/server-node/src/actions/action-dispatcher.ts new file mode 100644 index 0000000..e4e44c6 --- /dev/null +++ b/packages/server-node/src/actions/action-dispatcher.ts @@ -0,0 +1,135 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, flatPush, isAction, isRequestAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../actions/action-handler'; +import { ClientActionKinds, ClientId } from '../di/service-identifiers'; +import { Disposable } from '../utils/disposable'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { Logger } from '../utils/logger'; +import { PromiseQueue } from '../utils/promise-queue'; +import { ActionHandlerRegistry } from './action-handler-registry'; + +export const ActionDispatcher = Symbol('ActionDispatcher'); + +/** + * The central component that process all {@link Action}s by dispatching them to their designated + * handlers. + */ +export interface ActionDispatcher { + /** + * Processes the given action by dispatching it to all registered handlers. + * + * @param action The action that should be dispatched. + * @returns A promise indicating when the action processing is complete. + */ + dispatch(action: Action): Promise; + + /** + * Processes all given actions by dispatching to the corresponding handlers. + * + * @param actions Actions to dispatch + * @returns A promise indicating that all actions have been processed. + */ + dispatchAll(actions: Action[]): Promise; + dispatchAll(...actions: Action[]): Promise; +} + +@injectable() +export class DefaultActionDispatcher extends Disposable implements ActionDispatcher { + @inject(ActionHandlerRegistry) + protected actionHandlerRegistry: ActionHandlerRegistry; + + @inject(ClientActionKinds) + protected clientActionKinds: string[]; + + @inject(Logger) + private logger: Logger; + + @inject(ClientId) + protected clientId: string; + + protected actionQueue = new PromiseQueue(); + + dispatch(action: Action): Promise { + // Dont queue actions that are just delegated to the client + if (this.clientActionKinds.includes(action.kind)) { + return this.doDispatch(action); + } + return this.actionQueue.enqueue(() => this.doDispatch(action)); + } + + protected async doDispatch(action: Action): Promise { + this.logger.debug('Dispatch action:', action.kind); + const actionHandlers = this.actionHandlerRegistry.get(action.kind); + if (actionHandlers.length === 0) { + throw new GLSPServerError(`No handler registered for action kind: ${action.kind}`); + } + + const responses: Action[] = []; + for (const handler of actionHandlers) { + const response = await this.executeHandler(handler, action); + responses.push(...response); + } + + return this.dispatchResponses(responses); + } + + protected async executeHandler(handler: ActionHandler, request: Action): Promise { + const responseActions = await handler.execute(request); + return responseActions.map(action => respond(request, action)); + } + + protected dispatchResponses(actions: Action[]): Promise { + if (actions.length === 0) { + return Promise.resolve(); + } + const responseQueue = new PromiseQueue(); + const responses = actions.map(action => responseQueue.enqueue(() => this.doDispatch(action))); + return Promise.all(responses).then(() => Promise.resolve()); + } + + dispatchAll(...actions: MaybeArray[]): Promise { + if (actions.length === 0) { + return Promise.resolve(); + } + const flat: Action[] = []; + flatPush(flat, actions); + return Promise.all(flat.map(action => this.dispatch(action))).then(() => Promise.resolve()); + } + + doDispose(): void { + this.actionQueue.clear(); + } +} + +/** + * Transfers the {@link RequestAction.requestId id} from request to response if applicable. + * + * @param request potential {@link RequestAction} + * @param response potential {@link ResponseAction} + * @returns given response action with id set if applicable + */ +export function respond(request: Action, response: Action): Action { + if (isRequestAction(request) && isResponseAction(response)) { + (response as any).responseId = request.requestId; + } + return response; +} + +function isResponseAction(action?: any): action is ResponseAction { + return isAction(action) && 'responseId' in action && typeof action['responseId'] === 'string'; +} diff --git a/packages/server-node/src/actions/action-handler-registry.spec.ts b/packages/server-node/src/actions/action-handler-registry.spec.ts new file mode 100644 index 0000000..1930e24 --- /dev/null +++ b/packages/server-node/src/actions/action-handler-registry.spec.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as mock from '../test/mock-util'; +import { ActionHandlerRegistry } from './action-handler-registry'; +import { expect } from 'chai'; + +describe('Test ActionHandlerRegistry (only functionality that is not covered by "registry.spec.ts"', () => { + const singleAction = 'singleHandledAction'; + const multiAction = 'multiHandledAction'; + + const h1 = new mock.StubActionHandler([singleAction]); + const h2 = new mock.StubActionHandler([multiAction]); + const h3 = new mock.StubActionHandler([multiAction]); + const h4 = new mock.StubActionHandler([multiAction]); + const registry: ActionHandlerRegistry = new ActionHandlerRegistry(); + + it('registerHandler - should register given handlers', () => { + // setup + registry.registerHandler(h1); + registry.registerHandler(h2); + registry.registerHandler(h3); + registry.registerHandler(h4); + + const result = registry.getAll(); + expect(result).to.have.length(4); + expect(result.includes(h1)).true; + expect(result.includes(h2)).true; + expect(result.includes(h3)).true; + expect(result.includes(h4)).true; + }); + + it('get - should return three handlers sorted by priority', () => { + const result = registry.get(multiAction); + expect(result.length).to.be.equal(3); + expect(result[0]).to.be.deep.equal(h4); + expect(result[1]).to.be.deep.equal(h2); + expect(result[2]).to.be.deep.equal(h3); + }); +}); diff --git a/packages/server-node/src/actions/action-handler-registry.ts b/packages/server-node/src/actions/action-handler-registry.ts new file mode 100644 index 0000000..060e6e9 --- /dev/null +++ b/packages/server-node/src/actions/action-handler-registry.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { MultiRegistry } from '../utils/registry'; +import { ActionHandler, ActionHandlerConstructor, ActionHandlerFactory } from './action-handler'; + +/** + * An action handler registry keeps track of registered action handlers for a certain action. + */ +@injectable() +export class ActionHandlerRegistry extends MultiRegistry { + registerHandler(handler: ActionHandler): void { + return handler.actionKinds.forEach(kind => this.register(kind, handler)); + } + + /** + * Retrieve all registered {@link ActionHandler}s that can handle the given action. + * The resulting list is ordered descending using {@link ActionHandler.getPriority}. + * + * @param action The action + * @returns A list of all registered handlers that can handle the given action. + */ + get(key: string): ActionHandler[] { + const result = super.get(key); + return result.sort((a, b) => a.priority ?? 0 - (b.priority ?? 0)); + } +} + +@injectable() +export class ActionHandlerRegistryInitializer implements ClientSessionInitializer { + @inject(ActionHandlerFactory) + protected factory: ActionHandlerFactory; + + @inject(ActionHandlerConstructor) + @optional() + protected handlerConstructors: ActionHandlerConstructor[] = []; + + @inject(ActionHandlerRegistry) + protected registry: ActionHandlerRegistry; + + initialize(_args?: Args): void { + const handlers = this.handlerConstructors.map(constructor => this.factory(constructor)); + handlers.forEach(handler => this.registry.registerHandler(handler)); + } +} diff --git a/packages/server-node/src/actions/action-handler.ts b/packages/server-node/src/actions/action-handler.ts new file mode 100644 index 0000000..b73329a --- /dev/null +++ b/packages/server-node/src/actions/action-handler.ts @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MaybePromise } from '@eclipse-glsp/protocol'; +import { interfaces } from 'inversify'; + +export const ActionHandler = Symbol('ActionHandler'); + +/** + * An action handler can execute certain {@link Action} types that are dispatched by the + * {@link ActionDispatcher}. The action handler processes the action in the {@link ActionHandler.execute()} + * method and returns a list of response actions to be dispatched as a result of processing the original action. + * One action handler can handle multiple different action types, see {@link ActionHandler.actionKinds}. + */ +export interface ActionHandler { + /** + * Returns the list of action kinds that can be handled by this action handler. + * + * @returns A list of action kind strings. + */ + actionKinds: string[]; + + /** + * Executes the action handler for the given {@link Action} and returns a list of response actions that should be + * dispatched as a result of processing the original action. This list can be empty, if no more actions need to be + * executed. + * + * @param action The action that should be processed. + * @returns A list of response actions that should be dispatched. + */ + execute(action: Action, ...args: unknown[]): MaybePromise; + + /** + * Returns the priority of this action handler. The priority is used to derive the execution order if multiple + * action handlers should execute the same {@link Action}. The default priority is `0` and the priority is sorted + * descending. This means handlers with a priority > 0 are executed before handlers with a default priority and + * handlers with a priority < 0 are executed afterwards. + * + * @returns the priority as integer. + */ + priority?: number; +} + +export const ActionHandlerConstructor = Symbol('ActionHandlerConstructor'); + +export type ActionHandlerConstructor = interfaces.Newable; + +export const ActionHandlerFactory = Symbol('ActionHandlerFactory'); + +export type ActionHandlerFactory = (constructor: ActionHandlerConstructor) => ActionHandler; diff --git a/packages/server-node/src/actions/client-action-handler.ts b/packages/server-node/src/actions/client-action-handler.ts new file mode 100644 index 0000000..17d49ec --- /dev/null +++ b/packages/server-node/src/actions/client-action-handler.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, ActionMessage } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ClientActionKinds, ClientId } from '../di/service-identifiers'; +import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; + +/** + * The client action handler is responsible of handling action kinds that are intended for the + * GLSP client, by sending them to the client over json-rpc. + */ + +@injectable() +export class ClientActionHandler implements ClientActionHandler { + @inject(GLSPClientProxy) + @optional() + protected glspClient?: GLSPClientProxy; + + @inject(ClientId) + protected readonly clientId: string; + + constructor(@inject(ClientActionKinds) @optional() public actionKinds: string[] = []) {} + + execute(action: Action): [] { + this.send(action); + return []; + } + + protected send(action: Action): void { + const message: ActionMessage = { action, clientId: this.clientId }; + if (this.glspClient) { + this.glspClient.process(message); + return; + } + throw new Error('Could not send message to client. No GLSPClientProxy is defined'); + } +} diff --git a/packages/server-node/src/actions/global-action-provider.spec.ts b/packages/server-node/src/actions/global-action-provider.spec.ts new file mode 100644 index 0000000..186a47a --- /dev/null +++ b/packages/server-node/src/actions/global-action-provider.spec.ts @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule } from 'inversify'; +import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import * as mock from '../test/mock-util'; +import { Logger } from '../utils/logger'; +import { ActionHandler } from './action-handler'; +import { ActionHandlerRegistry } from './action-handler-registry'; +import { DefaultGlobalActionProvider } from './global-action-provider'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; + +describe('test DefaultGlobalActionProvider', () => { + const container = new Container(); + const serverActions = ['A1', 'A2', 'A3']; + const clientActions = ['C1', 'C2']; + const diagramType = 'myDiagramType'; + + const handler1Actions = ['A1', 'A2']; + const handler2Actions = ['A2', 'A3']; + + const h1 = new mock.StubActionHandler(handler1Actions); + const h2 = new mock.StubActionHandler(handler2Actions); + + const handlerRegistry = new ActionHandlerRegistry(); + sinon.stub(handlerRegistry, 'getAll').returns([h1, h2]); + + const diagramModule1 = new ContainerModule(bind => { + bind(ClientSessionInitializer).toConstantValue(new mock.StubClientSessionInitializer()); + bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler1Actions)); + bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler2Actions)); + bind(ActionHandlerRegistry).toConstantValue(handlerRegistry); + bind(ClientActionKinds).toConstantValue(clientActions[0]); + bind(ClientActionKinds).toConstantValue(clientActions[1]); + }); + + const diagramModules = new Map(); + diagramModules.set(diagramType, [diagramModule1]); + + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(InjectionContainer).toConstantValue(container); + bind(DiagramModules).toConstantValue(diagramModules); + }) + ); + + const actionProvider = container.resolve(DefaultGlobalActionProvider); + + it('serverActionsKinds', () => { + const result = actionProvider.serverActionKinds; + expect(result.size).to.be.equal(1); + const resultServerActions = result.get(diagramType); + expect(resultServerActions).to.not.be.undefined; + expect(serverActions.every(action => resultServerActions!.includes(action))).true; + }); + + it('clientActionKinds', () => { + const result = actionProvider.clientActionKinds; + expect(result.size).to.be.equal(1); + const resultClientActions = result.get(diagramType); + expect(resultClientActions).to.not.be.undefined; + expect(clientActions.every(action => resultClientActions!.includes(action))).true; + }); +}); diff --git a/packages/server-node/src/actions/global-action-provider.ts b/packages/server-node/src/actions/global-action-provider.ts new file mode 100644 index 0000000..d70270b --- /dev/null +++ b/packages/server-node/src/actions/global-action-provider.ts @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule, inject, injectable } from 'inversify'; +import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { ActionHandlerRegistry } from './action-handler-registry'; +import { ClientActionHandler } from './client-action-handler'; + +export const GlobalActionProvider = Symbol('GlobalActionProvider'); + +export interface GlobalActionProvider { + readonly serverActionKinds: Map; + readonly clientActionKinds: Map; +} + +@injectable() +export class DefaultGlobalActionProvider implements GlobalActionProvider { + public readonly serverActionKinds: Map; + public readonly clientActionKinds: Map; + + constructor( + @inject(InjectionContainer) serverContainer: Container, + @inject(DiagramModules) diagramModules: Map + ) { + this.serverActionKinds = new Map(); + this.clientActionKinds = new Map(); + diagramModules.forEach((modules, diagramType) => { + const container = this.createDiagramContainer(serverContainer, modules); + const initializers = container.getAll(ClientSessionInitializer); + initializers.forEach(service => service.initialize()); + this.loadServerActionKinds(diagramType, container); + this.loadClientActionKinds(diagramType, container); + container.unbindAll(); + }); + } + + createDiagramContainer(serverContainer: Container, modules: ContainerModule[]): Container { + const container = serverContainer.createChild(); + container.load(...modules); + return container; + } + + loadServerActionKinds(diagramType: string, diagramContainer: Container): void { + const handlerRegistry = diagramContainer.get(ActionHandlerRegistry); + const diagramServerActions = this.serverActionKinds.get(diagramType) ?? []; + handlerRegistry + .getAll() + .filter(handler => !(handler instanceof ClientActionHandler)) + .forEach(handler => diagramServerActions.push(...handler.actionKinds)); + this.serverActionKinds.set(diagramType, [...new Set(diagramServerActions)]); + } + + loadClientActionKinds(diagramType: string, diagramContainer: Container): void { + const clientActionKinds = diagramContainer.getAll(ClientActionKinds); + const diagramClientActions = this.clientActionKinds.get(diagramType) ?? []; + diagramClientActions.push(...clientActionKinds); + this.clientActionKinds.set(diagramType, diagramClientActions); + } +} diff --git a/packages/server-node/src/actions/save-model-action-handler.ts b/packages/server-node/src/actions/save-model-action-handler.ts new file mode 100644 index 0000000..e22daa5 --- /dev/null +++ b/packages/server-node/src/actions/save-model-action-handler.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GModelState } from '../base-impl/gmodel-state'; +import { ClientOptionsUtil } from '../utils/client-options-util'; +import { ActionHandler } from './action-handler'; +import { writeFileSync } from 'fs'; +import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { GLSPServerError } from '../utils/glsp-server-error'; + +@injectable() +export class SaveModelActionHandler implements ActionHandler { + actionKinds = [SaveModelAction.KIND]; + + @inject(GModelState) + protected modelState: GModelState; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + execute(action: Action): MaybePromise { + this.saveModelState(action); + return [new SetDirtyStateAction(this.modelState.isDirty)]; // TODO: set reason to SAVE + } + + protected saveModelState(action: SaveModelAction): void { + try { + const data = this.modelSerializer.createSchema(this.modelState.root); + // eslint-disable-next-line no-null/no-null + writeFileSync(this.modelState.sourceUri!, JSON.stringify(data, null, 2)); + if (this.saveIsDone(action)) { + // TODO: call save is done when available + } + } catch (err) { + throw new GLSPServerError(`An error occured during save process: ${err}`); + } + } + + protected saveIsDone(action: SaveModelAction): boolean { + const sourceUri = this.modelState.sourceUri; + return action.fileUri ? ClientOptionsUtil.adaptUri(action.fileUri) === sourceUri : true; + } +} diff --git a/packages/server-node/src/base-impl/change-bounds-operation-handler.ts b/packages/server-node/src/base-impl/change-bounds-operation-handler.ts new file mode 100644 index 0000000..914e9e3 --- /dev/null +++ b/packages/server-node/src/base-impl/change-bounds-operation-handler.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelRoot, GNode } from '@eclipse-glsp/graph'; +import { ChangeBoundsOperation, Dimension, Point } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { OperationHandler } from '../operations/operation-handler'; +import { GModelState } from './gmodel-state'; + +@injectable() +export class ChangeBoundsOperationHandler implements OperationHandler { + operationType = ChangeBoundsOperation.KIND; + + @inject(GModelState) + protected modelState: GModelState; + + execute(operation: ChangeBoundsOperation): void { + for (const element of operation.newBounds) { + this.changeElementBounds(element.elementId, element.newSize, element.newPosition); + } + } + + changeElementBounds(elementId: string, newSize: Dimension, newPosition: Point | undefined): void { + const index = this.modelState.index; + const nodeToUpdate = index.findByClass(elementId, GNode); + if (!nodeToUpdate) { + return; + } + + const parent = nodeToUpdate.parent; + let positionToSet = { x: 0, y: 0 }; + if (newPosition) { + positionToSet = parent instanceof GModelRoot ? newPosition : { x: Math.max(0, newPosition.x), y: Math.max(0, newPosition.y) }; + } + if (!nodeToUpdate.layoutOptions) { + nodeToUpdate.layoutOptions = {}; + } + nodeToUpdate.layoutOptions['prefWidth'] = newSize.width; + nodeToUpdate.layoutOptions['prefHeight'] = newSize.height; + nodeToUpdate.size = newSize; + nodeToUpdate.position = positionToSet; + } +} diff --git a/packages/server-node/src/base-impl/computed-bounds-action-handler.ts b/packages/server-node/src/base-impl/computed-bounds-action-handler.ts new file mode 100644 index 0000000..6c1df4f --- /dev/null +++ b/packages/server-node/src/base-impl/computed-bounds-action-handler.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph'; +import { Action, ComputedBoundsAction, isComputedBoundsAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../actions/action-handler'; +import { GModelIndex } from '../features/model/gmodel-index'; +import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; +import { GModelState } from './gmodel-state'; + +@injectable() +export class ComputedBoundsActionHandler implements ActionHandler { + @inject(ModelSubmissionHandler) + protected submissionHandler: ModelSubmissionHandler; + + @inject(GModelState) + protected modelState: GModelState; + + @inject(GModelIndex) + protected index: GModelIndex; + + actionKinds = [ComputedBoundsAction.KIND]; + execute(action: Action): Action[] { + if (isComputedBoundsAction(action)) { + const model = this.modelState.root; + if (action.revision === model.revision) { + this.applyBounds(model, action); + return this.submissionHandler.submitModelDirectly(); + } + } + return []; + } + + protected applyBounds(root: GModelRoot, action: ComputedBoundsAction): void { + action.bounds.forEach(b => { + const element = this.index.get(b.elementId); + if (isGBoundsAware(element)) { + element.position = b.newPosition ?? element.position; + element.size = b.newSize; + } + }); + + action.alignments?.forEach(a => { + const element = this.index.get(a.elementId); + if (isGAlignable(element)) { + element.alignment = a.newAlignment; + } + }); + } + + priority?: number | undefined; +} diff --git a/packages/server-node/src/base-impl/delete-operation-handler.ts b/packages/server-node/src/base-impl/delete-operation-handler.ts new file mode 100644 index 0000000..75c28e4 --- /dev/null +++ b/packages/server-node/src/base-impl/delete-operation-handler.ts @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DeleteElementOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GModelIndex } from '../features/model/gmodel-index'; +import { GModelState } from './gmodel-state'; +import { OperationHandler } from '../operations/operation-handler'; +import { GEdge, GModelElement, GNode } from '@eclipse-glsp/graph'; + +@injectable() +export class DeleteOperationHandler implements OperationHandler { + protected allDependantsIds: Set; + + @inject(GModelState) protected readonly modelState: GModelState; + + get operationType(): string { + return DeleteElementOperation.KIND; + } + + execute(operation: DeleteElementOperation): void { + const elementIds = operation.elementIds; + if (!elementIds || elementIds.length === 0) { + console.log('Elements to delete are not specified'); + return; + } + const index = this.modelState.index; + this.allDependantsIds = new Set(); + const success = elementIds.every(eId => this.delete(eId, index)); + if (!success) { + console.log('Could not delete all elements as requested (see messages above to find out why)'); + } + } + + protected delete(elementId: string, index: GModelIndex): boolean { + if (this.allDependantsIds.has(elementId)) { + return true; + } + + let element; + try { + element = index.get(elementId); + } catch { + console.log('Element not found: ' + elementId); + return false; + } + + const nodeToDelete = this.findTopLevelElement(element); + if (!nodeToDelete.parent) { + console.log("The requested node doesn't have a parent; it can't be deleted"); + return false; + } + + const dependents = new Set(); + this.collectDependents(dependents, nodeToDelete, this.modelState); + + dependents.forEach(dependant => { + const index = this.modelState.root.children.findIndex(element => element === dependant); + if (index > -1) { + this.modelState.root.children.splice(index, 1); + } + this.allDependantsIds.add(dependant.id); + }); + + return true; + } + + protected collectDependents(dependents: Set, nodeToDelete: GModelElement, modelState: GModelState): void { + if (dependents.has(nodeToDelete)) { + return; + } + + if (nodeToDelete.children.length > 0) { + nodeToDelete.children.forEach(child => this.collectDependents(dependents, child, modelState)); + } + + if (nodeToDelete instanceof GNode) { + const index = this.modelState.index; + index.getIncomingEdges(nodeToDelete).forEach(incoming => { + this.collectDependents(dependents, incoming, modelState); + }); + index.getOutgoingEdges(nodeToDelete).forEach(outgoing => { + this.collectDependents(dependents, outgoing, modelState); + }); + } + + dependents.add(nodeToDelete); + } + + protected findTopLevelElement(element: GModelElement): GModelElement { + if (element instanceof GNode || element instanceof GEdge) { + return element; + } + + const parent = element.parent; + if (!parent) { + return element; + } + return this.findTopLevelElement(parent); + } +} diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/base-impl/gmodel-diagram-module.ts new file mode 100644 index 0000000..681d49a --- /dev/null +++ b/packages/server-node/src/base-impl/gmodel-diagram-module.ts @@ -0,0 +1,90 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable, interfaces } from 'inversify'; +import { ApplyLabelEditOperationHandler } from '../features/directediting/apply-label-edit-operation-handler'; +import { ActionHandlerConstructor } from '../actions/action-handler'; +import { CommandStack, DefaultCommandStack } from '../command/command-stack'; +import { DiagramModule } from '../di/diagram-module'; +import { InstanceMultiBinding } from '../di/multi-binding'; +import { RequestClipboardDataActionHandler } from '../features/clipboard/request-clipboard-data-action-handler'; +import { GModelFactory, GModelFactoryNullImpl } from '../features/model/gmodel-factory'; +import { GModelIndex } from '../features/model/gmodel-index'; +import { ModelSourceLoader } from '../features/model/model-source-loader'; +import { ModelState } from '../features/model/model-state'; +import { CutOperationHandler } from '../operations/cut-operation-handler'; +import { OperationHandlerConstructor } from '../operations/operation-handler'; +import { PasteOperationHandler } from '../operations/paste-operation-handler'; +import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; +import { GModelLoader } from './gmodel-loader'; +import { GModelState } from './gmodel-state'; +import { DeleteOperationHandler } from './delete-operation-handler'; +// TODO: investigate why direct import does not work +// eslint-disable-next-line no-restricted-imports +import { ChangeBoundsOperationHandler } from '../base-impl/change-bounds-operation-handler'; +import { SaveModelActionHandler } from '../actions/save-model-action-handler'; +import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; + +/** + * Extension of the {@link DiagramModule} to provide GModel integration. + * + * Contains all bindings of {@link DiagramModule}. + * + * Additionally binds: + * - {@link ModelSourceLoader} to {@link GModelLoader} + * - {@link CommandStack} to {@link DefaultCommandStack} + * - {@link ModelState} to {@link GModelState} + * - {@link GModelFactory} to {@link GModelFactoryNullImpl} + * - {@link GModelIndex} to self + * - {@link ComputedBoundsActionHandler} to {@link ActionHandler} + * - {@link RequestClipboardDataActionHandler} to {@link ActionHandler} + * - {@link ApplyLabelEditOperationHandler} to {@link OperationHandler} + * - {@link ChangeBoundsOperationHandler} to {@link OperationHandler} + * - {@link CutOperationHandler} to {@link OperationHandler} + * - {@link PasteOperationHandler} to {@link OperationHandler} + */ +@injectable() +export abstract class GModelDiagramModule extends DiagramModule { + protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + super.configure(bind, unbind, isBound, rebind); + + bind(ModelSourceLoader).to(GModelLoader); + bind(CommandStack).to(DefaultCommandStack).inSingletonScope(); + + // bind GModelState + bind(GModelState).toSelf().inSingletonScope(); + bind(ModelState).toService(GModelState); + + bind(GModelFactory).to(GModelFactoryNullImpl).inSingletonScope(); + bind(GModelIndex).toSelf().inSingletonScope(); + } + + protected configureActionHandlers(binding: InstanceMultiBinding): void { + super.configureActionHandlers(binding); + binding.add(ComputedBoundsActionHandler); + binding.add(SaveModelActionHandler); + binding.add(RequestClipboardDataActionHandler); + } + + protected configureOperationHandlers(binding: InstanceMultiBinding): void { + super.configureOperationHandlers(binding); + binding.add(ApplyLabelEditOperationHandler); + binding.add(ChangeBoundsOperationHandler); + binding.add(CutOperationHandler); + binding.add(DeleteOperationHandler); + binding.add(PasteOperationHandler); + binding.add(ReconnectEdgeOperationHandler); + } +} diff --git a/packages/server-node/src/base-impl/gmodel-loader.ts b/packages/server-node/src/base-impl/gmodel-loader.ts new file mode 100644 index 0000000..ac055d6 --- /dev/null +++ b/packages/server-node/src/base-impl/gmodel-loader.ts @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GGraph, GModelRoot } from '@eclipse-glsp/graph'; +import { isSModelRootSchema, RequestModelAction } from '@eclipse-glsp/protocol'; +import * as fs from 'fs-extra'; +import { inject, injectable } from 'inversify'; +import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { ModelSourceLoader } from '../features/model/model-source-loader'; +import { getOrThrow, GLSPServerError } from '../utils/glsp-server-error'; +import { Logger } from '../utils/logger'; +import { GModelState } from './gmodel-state'; + +export const EMTPY_ROOT = GGraph.builder().id('empty').build(); + +/** + * A {@link ModelSourceLoader} that reads the graph model directly from a JSON file and uses it as source model. + */ +@injectable() +export class GModelLoader implements ModelSourceLoader { + @inject(Logger) + protected logger: Logger; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + @inject(GModelState) + protected modelState: GModelState; + + loadSourceModel(action: RequestModelAction): void { + const sourceUri = getOrThrow( + this.modelState.sourceUri, + `Invalid RequestModelAction! Missing argument with key '${GModelState.SOURCE_URI}'` + ); + const root = this.loadFromFile(sourceUri); + root.revision = -1; + this.modelState.root = root; + } + + protected loadFromFile(url: string): GModelRoot { + try { + const fileContent = this.readFile(url); + if (!fileContent) { + return EMTPY_ROOT; + } + if (!isSModelRootSchema(fileContent)) { + throw new Error('The loaded root object is not of type SModelRootSchema'); + } + return this.modelSerializer.createRoot(fileContent); + } catch (error) { + throw new GLSPServerError(`Could not load model from file: ${url}`, error); + } + } + + protected readFile(url: string): unknown { + try { + const data = fs.readFileSync(url, { encoding: 'utf8' }); + return JSON.parse(data); + } catch (error) { + throw new GLSPServerError(`Could not read & parse file contents of '${url}' as json`, error); + } + } +} diff --git a/packages/server-node/src/base-impl/gmodel-state.ts b/packages/server-node/src/base-impl/gmodel-state.ts new file mode 100644 index 0000000..a019a4f --- /dev/null +++ b/packages/server-node/src/base-impl/gmodel-state.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { ClientId } from '../di/service-identifiers'; +import { DefaultModelState } from '../features/model/model-state'; + +@injectable() +export class GModelState extends DefaultModelState { + @inject(ClientId) + readonly clientId: string; + + static readonly SOURCE_URI = 'sourceUri'; + + get sourceUri(): string | undefined { + return this.get(GModelState.SOURCE_URI); + } +} diff --git a/packages/server-node/src/base-impl/index.ts b/packages/server-node/src/base-impl/index.ts new file mode 100644 index 0000000..f601e7d --- /dev/null +++ b/packages/server-node/src/base-impl/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from './gmodel-diagram-module'; +export * from './gmodel-loader'; +export * from './gmodel-state'; diff --git a/packages/server-node/src/command/command-stack.ts b/packages/server-node/src/command/command-stack.ts new file mode 100644 index 0000000..09889fc --- /dev/null +++ b/packages/server-node/src/command/command-stack.ts @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { Command } from './command'; + +export const CommandStack = Symbol('CommandStack'); + +/** + * A basic implementation of an undoable stack of commands. + * See {@link Command} for more details about the command methods that this implementation uses. + */ +export interface CommandStack { + /** + * Clears any redoable commands not yet redone, adds the command, and then executes the command. + * @param command the command to execute. + */ + execute(command: Command): void; + + /** + * Moves the top of the stack down, undoing what was formerly the top command. + */ + undo(): void; + + /** + * Moves the top of the stack up, redoing the new top command. + */ + redo(): void; + + /** + * The command stack has currently unsaved changes. + */ + isDirty(): boolean; + + /** + * Called after a save has been successfully performed. + */ + saveIsDone(): void; +} + +@injectable() +export class DefaultCommandStack implements CommandStack { + execute(command: Command): void { + // no-op + } + + undo(): void { + // no-op + } + + redo(): void { + // no-op + } + + isDirty(): boolean { + return false; + } + + saveIsDone(): void { + // no-op + } +} diff --git a/packages/server-node/src/command/command.ts b/packages/server-node/src/command/command.ts new file mode 100644 index 0000000..50e767c --- /dev/null +++ b/packages/server-node/src/command/command.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * An interface that every command is expected to support. + * A command can be executed, it can be undone, and can then be redone. + */ +export interface Command { + /** + * Performs the command activity required for the effect. + */ + execute(): void; + + /** + * Performs the command activity required to `undo` the effects of a preceding `execute` (or `redo`). + * The effect, if any, of calling `undo` before `execute` or `redo` have been called, is undefined. + */ + undo(): void; + + /** + * Performs the command activity required to `redo` the effect after undoing the effect. + * The effect, if any, of calling `redo` before `undo` is called is undefined. + * Note that if you implement `redo` to call `execute` then any derived class will be restricted by that decision also. + */ + redo(): void; +} diff --git a/packages/server-node/src/di/app-module.ts b/packages/server-node/src/di/app-module.ts new file mode 100644 index 0000000..80690ed --- /dev/null +++ b/packages/server-node/src/di/app-module.ts @@ -0,0 +1,99 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule, interfaces } from 'inversify'; +import * as winston from 'winston'; +import { LaunchOptions } from '../launch/cli-parser'; +import { Logger, LoggerFactory, LogLevel } from '../utils/logger'; +import { WinstonLogger } from '../utils/winston-logger'; +import { ModuleContext } from './glsp-module'; +import { InjectionContainer } from './service-identifiers'; + +export function createAppModule(options: LaunchOptions): ContainerModule { + return new ContainerModule((bind, unbind, isBound, rebind) => { + bind(InjectionContainer).toDynamicValue(dynamicContext => dynamicContext.container); + const context = { bind, unbind, isBound, rebind }; + configureWinstonLogger(context, options); + }); +} + +/** + * Creates the global {@link winston.Logger} instance is used internally by all instances of {@link WinstonLogger}. Can be configured with + * the given {@link LaunchOptions}. + * @param options The {@link LaunchOptions} + * @returns the configured winston instance. + */ +export function createWinstonInstance(options: T): winston.Logger { + const level = LogLevel[options.logLevel]; + const transports: winston.transport[] = []; + + const printf = winston.format.printf(({ level, message, timestamp }) => `${timestamp} ${level} - ${message}`); + const timestamp = winston.format.timestamp({ format: () => new Date().toLocaleTimeString() }); + + const consoleLogFormat = winston.format.combine(winston.format.colorize(), timestamp, printf); + const fileLogFormat = winston.format.combine(timestamp, printf); + + if (options.consoleLog) { + transports.push(new winston.transports.Console({ format: consoleLogFormat })); + } + + if (options.fileLog) { + const filename = `${new Date().toLocaleTimeString()}.log`; + transports.push(new winston.transports.File({ dirname: options.logDir, filename, format: fileLogFormat })); + } + + return winston.createLogger({ level, transports }); +} + +/** + * Configures the binding for {@link WinstonLogger} in the given {@link ModuleContext} with the given {@link LaunchOptions}. + * @param context The module context. + * @param options The launch options. + * @param rebind Flag to determine wether an existing {@link Logger} binding in the context should be rebound. Default is `true`. + * @param baseLoggerCreator The underling global {@link winston.Logger} instance. + */ +export function configureWinstonLogger( + context: ModuleContext, + options: T, + rebind = true, + baseLoggerCreator: (launchOptions: T) => winston.Logger = createWinstonInstance +): void { + const baseLogger = baseLoggerCreator(options); + if (rebind) { + if (context.isBound(Logger)) { + context.unbind(Logger); + } + if (context.isBound(LoggerFactory)) { + context.unbind(LoggerFactory); + } + } + + context.bind(Logger).toDynamicValue(dynamicContext => new WinstonLogger(baseLogger, getRequestParentName(dynamicContext))); + context.bind(LoggerFactory).toFactory(dynamicContext => (caller: string) => { + const logger = dynamicContext.container.get(Logger); + logger.caller = caller; + return logger; + }); +} + +function getRequestParentName(context: interfaces.Context): string | undefined { + if (context.currentRequest.parentRequest) { + const bindings = context.currentRequest.parentRequest.bindings; + if (bindings.length > 0) { + return bindings[0].implementationType?.name; + } + } + return undefined; +} diff --git a/packages/server-node/src/di/client-session-module.ts b/packages/server-node/src/di/client-session-module.ts new file mode 100644 index 0000000..6edd946 --- /dev/null +++ b/packages/server-node/src/di/client-session-module.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; +import { ClientId } from './service-identifiers'; + +export function createClientSessionModule(clientId: string, glspClient: GLSPClientProxy): ContainerModule { + return new ContainerModule((bind, _unbind, isBound, rebind) => { + if (isBound(ClientId)) { + rebind(ClientId).toConstantValue(clientId); + } else { + bind(ClientId).toConstantValue(clientId); + } + bind(GLSPClientProxy).toConstantValue(glspClient); + }); +} diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts new file mode 100644 index 0000000..6841074 --- /dev/null +++ b/packages/server-node/src/di/diagram-module.ts @@ -0,0 +1,246 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable no-restricted-imports */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + CenterAction, + DeleteMarkersAction, + ExportSvgAction, + FitToScreenAction, + GLSPServerStatusAction, + ModelSourceChangedAction, + NavigateToExternalTargetAction, + NavigateToTargetAction, + RequestBoundsAction, + SelectAction, + SelectAllAction, + ServerMessageAction, + SetClipboardDataAction, + SetContextActions, + SetDirtyStateAction, + SetEditModeAction, + SetEditValidationResultAction, + SetMarkersAction, + SetModelAction, + SetNavigationTargetsAction, + SetPopupModelAction, + SetResolvedNavigationTargetAction, + SetTypeHintsAction, + TriggerEdgeCreationAction, + TriggerNodeCreationAction, + UpdateModelAction +} from '@eclipse-glsp/protocol'; +import { RequestContextActionsHandler } from '../features/contextactions/request-context-actions-handler'; +import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dispatcher'; +import { ActionHandlerConstructor, ActionHandlerFactory } from '../actions/action-handler'; +import { ActionHandlerRegistry, ActionHandlerRegistryInitializer } from '../actions/action-handler-registry'; +import { ClientActionHandler } from '../actions/client-action-handler'; +import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler'; +import { ContextActionsProvider } from '../features/contextactions/context-actions-provider'; +import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry'; +import { DefaultGModelSerializer, GModelSerializer } from '../features/model/gmodel-serializer'; +import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; +import { RequestModelActionHandler } from '../features/model/request-model-action-handler'; +import { CompoundOperationHandler } from '../operations/compound-operation-handler'; +import { OperationActionHandler } from '../operations/operation-action-handler'; +import { OperationHandlerConstructor, OperationHandlerFactory } from '../operations/operation-handler'; +import { OperationHandlerRegistry, OperationHandlerRegistryInitializer } from '../operations/operation-handler-registry'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; +import { + ClientActionKinds, + ClientId, + ContextActionsProviders, + ContextEditValidators, + DiagramType, + NavigationTargetProviders, + Operations +} from './service-identifiers'; +import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; +import { + ContextEditValidatorRegistry, + DefaultContextEditValidatorRegistry +} from '../features/directediting/context-edit-validator-registry'; +import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler'; +import { NavigationTargetProvider } from '../features/navigation/navigation-target-provider'; +import { + DefaultNavigationTargetProviderRegistry, + NavigationTargetProviderRegistry +} from '../features/navigation/navigation-target-provider-registry'; +import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler'; +import { RequestNavigationTargetsActionHandler } from '../features/navigation/request-navigation-targets-action-handler'; +import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; +import { interfaces } from 'inversify'; +import { GLSPModule } from './glsp-module'; + +/** + * The diagram module is the central configuration artifact for configuring a client session specific injector. For each + * session that is initialized by a {@link GLSPServer} a new client session injector is created. The diagram module + * provides the base bindings necessary to provide diagram implementation (i.e. diagram language). In addition to the + * diagram specific bindings, session specific bindings like the {@link GModelState} are configured + * + * Client session injectors are child injectors of a server injector and therefore inherit the bindings from + * {@link ServerModule}. + * + * The following bindings are provided by this base configuration: + * - {@link ClientId} to default + * - {@link GModelSerializer} + * - {@link ActionDispatcher} + * - {@link ActionHandlerRegistry} + * - {@link OperationHandlerRegistry} + * - {@link ModelSubmissionHandler} + * - {@link NavigationTargetProviderRegistry} + * - {@link ContextActionsProviderRegistry} + * - {@link ContextEditValidatorRegistry} + * - {@link OperationHandler} as {@link InstanceMultiBinding} + * - {@link ActionHandler} as {@link InstanceMultiBinding} + * - {@link ClientSessionInitializer} as {@link ClassMultiBinding} + * - {@link ClientActionKinds} as {@link InstanceMultiBinding} + * - {@link NavigationTargetProviders} as {@link ClassMultiBinding} (empty) + * - {@link ContextActionsProviders} as {@link ClassMultiBinding} (empty) + * - {@link ContextEditValidators} as {@link ClassMultiBinding} (empty) + * + * The following bindings are required in either a subclass or an additional module: + * - {@link DiagramType} via the subclasses diagramType property + * - {@link DiagramConfiguration} + * - {@link ModelSourceLoader} + * - {@link GModelState} + * - {@link GModelFactory} + * - {@link CommandStack} + * + * The following bindings can be optionally added via a module: + * - {@link ModelValidator} + * - {@link LabelEditValidator} + * - {@link CommandPaletteActionProvider} + * - {@link ContextMenuItemProvider} + * - {@link PopupModelFactory} + * - {@link ToolPaletteItemProvider} + * - {@link NavigationTargetResolver} + */ +export abstract class DiagramModule extends GLSPModule { + static readonly FALLBACK_CLIENT_ID = 'FallbackClientId'; + abstract readonly diagramType: string; + + protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + bind(DiagramType).toConstantValue(this.diagramType); + bind(ClientId).toConstantValue(DiagramModule.FALLBACK_CLIENT_ID); + bind(GModelSerializer).to(DefaultGModelSerializer).inSingletonScope(); + bind(ActionDispatcher).to(DefaultActionDispatcher).inSingletonScope(); + bind(ActionHandlerRegistry).toSelf().inSingletonScope(); + bind(OperationHandlerRegistry).toSelf().inSingletonScope(); + bind(ModelSubmissionHandler).toSelf().inSingletonScope(); + bind(NavigationTargetProviderRegistry).to(DefaultNavigationTargetProviderRegistry).inSingletonScope(); + bind(ContextActionsProviderRegistry).toSelf().inSingletonScope(); + bind(ContextEditValidatorRegistry).to(DefaultContextEditValidatorRegistry).inSingletonScope(); + + // factory bindings + bind(ActionHandlerFactory).toDynamicValue(ctx => constructor => ctx.container.resolve(constructor)); + bind(OperationHandlerFactory).toDynamicValue(ctx => constructor => ctx.container.resolve(constructor)); + + // bind Kinds + bind(Operations).toDynamicValue(context => + context.container + .get(OperationHandlerConstructor) + .map(constructor => new constructor().operationType) + ); + + // multi-bindings + this.configureMultiBinding(new ClassMultiBinding(ClientSessionInitializer), binding => + this.configureClientSessionInitializers(binding) + ); + this.configureMultiBinding(new InstanceMultiBinding(ActionHandlerConstructor), binding => + this.configureActionHandlers(binding) + ); + this.configureMultiBinding(new InstanceMultiBinding(OperationHandlerConstructor), binding => + this.configureOperationHandlers(binding) + ); + this.configureMultiBinding(new InstanceMultiBinding(ClientActionKinds), binding => this.configureClientActions(binding)); + this.configureMultiBinding(new ClassMultiBinding(NavigationTargetProviders), binding => + this.configureNavigationTargetProviders(binding) + ); + this.configureMultiBinding(new ClassMultiBinding(ContextActionsProviders), binding => + this.configureContextActionProviders(binding) + ); + this.configureMultiBinding(new ClassMultiBinding(ContextEditValidators), binding => + this.configureContextEditValidators(binding) + ); + } + + configureClientSessionInitializers(binding: ClassMultiBinding): void { + binding.add(ActionHandlerRegistryInitializer); + binding.add(OperationHandlerRegistryInitializer); + } + + protected configureActionHandlers(binding: InstanceMultiBinding): void { + binding.add(ClientActionHandler); + binding.add(RequestModelActionHandler); + binding.add(RequestContextActionsHandler); + binding.add(RequestTypeHintsActionHandler); + binding.add(OperationActionHandler); + binding.add(RequestMarkersHandler); + binding.add(RequestPopupModelActionHandler); + binding.add(RequestEditValidationHandler); + binding.add(RequestNavigationTargetsActionHandler); + binding.add(ResolveNavigationTargetsActionHandler); + } + + protected configureOperationHandlers(binding: InstanceMultiBinding): void { + binding.add(CompoundOperationHandler); + } + + protected configureContextActionProviders(binding: ClassMultiBinding): void { + // empty as default + } + + protected configureContextEditValidators(binding: ClassMultiBinding): void { + // empty as default + } + + protected configureNavigationTargetProviders(binding: ClassMultiBinding): void { + // empty as default + } + + protected configureClientActions(binding: InstanceMultiBinding): void { + binding.add(CenterAction.KIND); + binding.add(ExportSvgAction.KIND); + binding.add(DeleteMarkersAction.KIND); + binding.add(FitToScreenAction.KIND); + binding.add(ModelSourceChangedAction.KIND); + binding.add(NavigateToTargetAction.KIND); + binding.add(NavigateToExternalTargetAction.KIND); + binding.add(RequestBoundsAction.KIND); + binding.add(SelectAction.KIND); + binding.add(SelectAllAction.KIND); + binding.add(ServerMessageAction.KIND); + // binding.add(SetBoundsAction.KIND); TODO: Add missing action to protocol + binding.add(SetClipboardDataAction.KIND); + binding.add(SetContextActions.KIND); + binding.add(SetDirtyStateAction.KIND); + binding.add(SetEditModeAction.KIND); + binding.add(SetEditValidationResultAction.KIND); + binding.add(SetMarkersAction.KIND); + binding.add(SetModelAction.KIND); + binding.add(SetNavigationTargetsAction.KIND); + binding.add(SetPopupModelAction.KIND); + binding.add(SetResolvedNavigationTargetAction.KIND); + binding.add(SetTypeHintsAction.KIND); + // binding.add(SetViewportAction.KIND); TODO: Add missing action to protocol + binding.add(GLSPServerStatusAction.KIND); + binding.add(TriggerNodeCreationAction.KIND); + binding.add(TriggerEdgeCreationAction.KIND); + binding.add(UpdateModelAction.KIND); + } +} diff --git a/packages/server-node/src/di/glsp-module.ts b/packages/server-node/src/di/glsp-module.ts new file mode 100644 index 0000000..f11a849 --- /dev/null +++ b/packages/server-node/src/di/glsp-module.ts @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule, injectable, interfaces } from 'inversify'; +import { MultiBinding } from './multi-binding'; + +/** + * A wrapper interface to get access to the binding related functions + * for a inversify container. + */ +export interface ModuleContext { + bind: interfaces.Bind; + unbind: interfaces.Unbind; + isBound: interfaces.IsBound; + rebind: interfaces.Rebind; +} + +/** + * Common super class for GLSP {@link ContainerModule}s. + */ +@injectable() +export abstract class GLSPModule extends ContainerModule { + public static CLIENT_ACTIONS = 'ClientActions'; + + protected context: ModuleContext; + + constructor() { + super((bind, unbind, isBound, rebind) => { + this.context = { bind, unbind, isBound, rebind }; + this.configure(bind, unbind, isBound, rebind); + }); + } + + protected abstract configure( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ): void; + + /** + * Configuration method for multibound values. The passed configurator is typically a submethod of this module. This + * means + * that subclasses can customize the {@link MultiBinding} object before the actual {@link MultiBinding} is created. + * + * @param Type of the {@link MultiBinding} + * @param binding The multi binding configuration object + * @param configurator The consumer that should be used to configure the given {@link MultiBinding} + */ + protected configureMultiBinding(binding: MultiBinding, configurator: (binding: MultiBinding) => void): void { + configurator(binding); + binding.applyBindings(this.context); + } +} diff --git a/packages/server-node/src/di/multi-binding.ts b/packages/server-node/src/di/multi-binding.ts new file mode 100644 index 0000000..440fe70 --- /dev/null +++ b/packages/server-node/src/di/multi-binding.ts @@ -0,0 +1,91 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; +import { interfaces } from 'inversify'; +import { ModuleContext } from './glsp-module'; + +/** + * A helper class used in `GLSPModules` to ease the configuration of multi-injected service identifiers. + * Instead of directly binding to the service identifier a new multi binding object can be created. This object + * should then be passed to a overridable configuration-function (i.e. configure(binding:V)=>void). This gives subclasses of the + * `GLSPModule` the chance to manipulate the binding configuration using the provided manipulation methods (e.g. add,remove, rebind). + * Once the configuration is finished the binding can be finalized using the {@link MultiBinding.applyBindings} method. + */ +export abstract class MultiBinding { + protected bindings: T[] = []; + + constructor(readonly identifier: string | symbol) {} + + abstract applyBindings(context: ModuleContext): void; + + add(newBinding: T): boolean { + return distinctAdd(this.bindings, newBinding); + } + + addAll(newBindings: T[]): boolean; + addAll(...newBindings: T[]): boolean; + addAll(...newBindings: MaybeArray[]): boolean { + const result: T[] = []; + flatPush(result, newBindings); + return result.every(newBinding => this.add(newBinding)); + } + + remove(toRemove: T): boolean { + return remove(this.bindings, toRemove); + } + + removeAll(toRemove: T[]): boolean { + return toRemove.every(binding => this.remove(binding)); + } + + rebind(oldBinding: T, newBinding: T): void { + if (this.remove(oldBinding)) { + this.add(newBinding); + } + } + + contains(binding: T): boolean { + return this.bindings.includes(binding); + } + + getAll(): T[] { + return this.bindings; + } +} + +/** + * Implementation of {@link MultiBinding} for multi-injected values that should be bound to classes using the + * `bind(serviceIdentifier).to(MyCustomBinding) syntax. + * + * @typeparam T the base type of the classes. + */ +export class ClassMultiBinding extends MultiBinding> { + applyBindings(context: ModuleContext): void { + this.bindings.forEach(binding => context.bind(this.identifier).to(binding)); + } +} + +/** + * Implementation of {@link MultiBinding} for multi-injected values that should be bound to concrete instance using the + * `bind(serviceIdentifier).toConstantValue(MyCustomBinding) syntax. + * + * * @typeparam T the type of the instances + */ +export class InstanceMultiBinding extends MultiBinding { + applyBindings(context: ModuleContext): void { + context.bind(this.identifier).toConstantValue(this.bindings); + } +} diff --git a/packages/server-node/src/di/multi-bindings.spec.ts b/packages/server-node/src/di/multi-bindings.spec.ts new file mode 100644 index 0000000..aee7aee --- /dev/null +++ b/packages/server-node/src/di/multi-bindings.spec.ts @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule, injectable } from 'inversify'; +import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; +import { expect } from 'chai'; + +@injectable() +class TestClass {} + +@injectable() +class TestClass1 extends TestClass {} + +@injectable() +class TestClass2 extends TestClass {} + +@injectable() +class TestClass3 extends TestClass {} + +describe('test implementations of MultiBinding', () => { + describe('test basic functionaly (ClassMultiBinding) ', () => { + const binding = new ClassMultiBinding('TestClass'); + it('add - new binding', () => { + expect(binding.add(TestClass1)).true; + expect(binding.contains(TestClass1)).true; + + const result = binding.getAll(); + expect(result.length).to.be.equal(1); + expect(result[0]).to.be.equal(TestClass1); + }); + + it('add - existing binding', () => { + expect(binding.add(TestClass1)).false; + expect(binding.getAll().length).to.be.equal(1); + expect(binding.getAll()[0]).to.be.equal(TestClass1); + }); + + it('remove - existing binding', () => { + expect(binding.contains(TestClass1)).true; + expect(binding.remove(TestClass1)).true; + expect(binding.contains(TestClass1)).false; + expect(binding.getAll()).to.have.length(0); + }); + + it('remove - non-existing binding', () => { + const previousSize = binding.getAll().length; + expect(binding.remove(TestClass2)).false; + expect(binding.getAll()).to.have.length(previousSize); + }); + + it('addAll - TestClass1 & TestClass2', () => { + expect(binding.addAll([TestClass1, TestClass2])).true; + + const result = binding.getAll(); + expect(result.length).to.be.equal(2); + expect(result.includes(TestClass1)).true; + expect(result.includes(TestClass2)).true; + }); + + it('rebind- TestClass2 to TestClass3', () => { + binding.rebind(TestClass2, TestClass3); + const result = binding.getAll(); + expect(result.length).to.be.equal(2); + expect(result.includes(TestClass1)).true; + expect(result.includes(TestClass3)).true; + }); + + it('rebind- TestClass2 to TestClass3- should fail', () => { + binding.rebind(TestClass2, TestClass3); + const result = binding.getAll(); + expect(result.length).to.be.equal(2); + expect(result.includes(TestClass1)).true; + expect(result.includes(TestClass3)).true; + }); + + it('removeAll- TestClass1 & TestClass3', () => { + expect(binding.removeAll([TestClass1, TestClass3])).true; + const result = binding.getAll(); + expect(result).to.have.length(0); + }); + }); + + describe('test applyBinding', () => { + it('apply binding of ClassMultiBinding', () => { + const testContainer = new Container(); + const testModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + const binding = new ClassMultiBinding('TestClass'); + binding.addAll([TestClass1, TestClass2, TestClass3]); + binding.applyBindings(context); + }); + testContainer.load(testModule); + const result = testContainer.getAll('TestClass'); + expect(result).to.have.length(3); + expect(result.find(instance => instance instanceof TestClass1)).to.not.be.undefined; + expect(result.find(instance => instance instanceof TestClass2)).to.not.be.undefined; + expect(result.find(instance => instance instanceof TestClass3)).to.not.be.undefined; + }); + + it('apply binding of InstanceMultiBinding', () => { + const t1 = 't1'; + const t2 = 't2'; + const t3 = 't3'; + const identifier = 'myStrings'; + + const testContainer = new Container(); + const testModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + const binding = new InstanceMultiBinding(identifier); + binding.addAll([t1, t2, t3]); + binding.applyBindings(context); + }); + testContainer.load(testModule); + const result = testContainer.get(identifier); + expect(result).to.have.length(3); + expect(result.includes(t1)).true; + expect(result.includes(t2)).true; + expect(result.includes(t3)).true; + }); + }); +}); diff --git a/packages/server-node/src/di/server-module.ts b/packages/server-node/src/di/server-module.ts new file mode 100644 index 0000000..fd8e0fb --- /dev/null +++ b/packages/server-node/src/di/server-module.ts @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule, interfaces } from 'inversify'; +import { DefaultGlobalActionProvider, GlobalActionProvider } from '../actions/global-action-provider'; +import { DefaultGLSPClientProxy, GLSPClientProxy, JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; +import { DefaultGLSPServer, GLSPServer, JsonRpcGLSPServer } from '../protocol/glsp-server'; +import { GLSPServerListener } from '../protocol/glsp-server-listener'; +import { ClientSessionFactory, DefaultClientSessionFactory } from '../session/client-session-factory'; +import { ClientSessionManager, DefaultClientSessionManager } from '../session/client-session-manager'; +import { DiagramModule } from './diagram-module'; +import { GLSPModule } from './glsp-module'; +import { DiagramModules, InjectionContainer } from './service-identifiers'; + +/** + * The server module is the central configuration artifact for configuring the server container (i.e. main container). For + * each application connecting to the server process a new server container is created. The server module provides the + * base bindings necessary for setting up the base {@link GLSPServer} infrastructure. In addition, it is used to + * configure the set of {@link DiagramModule}s. Diagram modules are used to create the diagram-session-specific child + * container when the + * {@link GLSPServer.initializeClientSession()} + * method is called. + * + * The following bindings are provided: + * + * * {@link Map} annotated with `@named("Diagram_Modules")` + * * {@link GLSPServer} + * * {@link ClientSessionFactory} + * * {@link ClientSessionManager} + * * {@link GlobalActionProvider} + * * {@link GLSPClientProxy} + * + */ +export class ServerModule extends GLSPModule { + public static DIAGRAM_MODULES = 'Diagram_Modules'; + protected readonly diagramModules: Map = new Map(); + + /** + * Configure a new {@link DiagramModule} for this server. A diagram module represents the base configuration artifact + * for configuring a diagram-language-specific client session container. The diagram type provided + * {@link DiagramModule.diagramType} is used to retrieve the correct diagram module when the {@link GLSPServer} + * initializes a new client session. + * + * @param diagramModule The base diagram module + * @param additionalModules Additional modules + * @returns The server module itself. This enables a builder-pattern like chaining of multiple diagram configuration + * calls. + */ + configureDiagramModule(diagramModule: DiagramModule, ...additionalModules: ContainerModule[]): ServerModule { + const diagramType = diagramModule.diagramType; + if (this.diagramModules.has(diagramType)) { + throw new Error(`A module configuration is already present for diagram type: '${diagramType}'`); + } + this.diagramModules.set(diagramType, [diagramModule, ...additionalModules]); + return this; + } + + configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + bind>(DiagramModules).toConstantValue(this.diagramModules); + bind(DefaultGLSPServer).toSelf().inSingletonScope(); + bind(GLSPServer).toService(DefaultGLSPServer); + bind(JsonRpcGLSPServer).toService(DefaultGLSPServer); + bind(ClientSessionFactory).to(DefaultClientSessionFactory).inSingletonScope(); + bind(DefaultClientSessionManager).toSelf().inSingletonScope(); + bind(ClientSessionManager).toService(DefaultClientSessionManager); + bind(GLSPServerListener).toService(DefaultClientSessionManager); + bind(GlobalActionProvider).to(DefaultGlobalActionProvider).inSingletonScope(); + bind(InjectionContainer).toDynamicValue(context => context.container); + bind(DefaultGLSPClientProxy).toSelf().inSingletonScope(); + bind(GLSPClientProxy).toService(DefaultGLSPClientProxy); + bind(JsonRpcGLSPClientProxy).toService(DefaultGLSPClientProxy); + } +} diff --git a/packages/server-node/src/di/service-identifiers.ts b/packages/server-node/src/di/service-identifiers.ts new file mode 100644 index 0000000..f2a470a --- /dev/null +++ b/packages/server-node/src/di/service-identifiers.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ValidateLabelEditAdapter } from '../features/directediting/context-edit-validator'; +import { LabelEditValidator } from '../features/directediting/label-edit-validator'; + +export const DiagramType = Symbol('DiagramType'); + +export const ClientId = Symbol('ClientId'); + +export const DiagramModules = Symbol('DiagramModules'); + +export const InjectionContainer = Symbol('InjectionContainer'); + +export const ClientActionKinds = Symbol('ClientActionKinds'); + +export const ContextActionsProviders = Symbol('ContextActionsProviders'); + +export const ContextEditValidators = Symbol('ContextEditValidators'); + +export const ValidateLabelEditAdapterFactory = Symbol('ValidateLabelEditAdapterFactory'); + +export const NavigationTargetProviders = Symbol('NavigationTargetProviders'); + +export type ValidateLabelEditAdapterFactory = (validator: LabelEditValidator) => ValidateLabelEditAdapter; + +export const Operations = Symbol('Operations'); diff --git a/packages/server-node/src/diagram/diagram-configuration.ts b/packages/server-node/src/diagram/diagram-configuration.ts new file mode 100644 index 0000000..117a216 --- /dev/null +++ b/packages/server-node/src/diagram/diagram-configuration.ts @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElementConstructor } from '@eclipse-glsp/graph'; +import { EdgeTypeHint, ShapeTypeHint } from '@eclipse-glsp/protocol'; + +/** + * Used to configure whether and when a diagram server should auto-layout the graphical model. + * + * The layout is computed with the layout engine configured, so a value other + * than NONE here makes sense only if such an engine is available. + */ +export enum ServerLayoutKind { + /** + * The server re-layouts the diagram on all changes automatically. Layout + * information stored in the model will be overwritten. + */ + AUTOMATIC, + + /** + * The server re-layouts the diagram only if manually triggered by a + * LayoutAction. The layout information must be stored in the model + * and will be overwritten on layout. + */ + MANUAL, + + /** + * The server never layouts the diagram. This requires that the layout + * information is stored in the model. + */ + NONE +} + +export const DiagramConfiguration = Symbol('DiagramConfiguration'); +/** + * Provides configuration constants for a specific diagram implementation (i.e. diagram language), + * The corresponding configuration for a diagram implementation is identified via its diagram type. + */ +export interface DiagramConfiguration { + /** + * Returns the shape type hints for the diagram implementation. Shape type hints are sent to the client and used to + * validate whether certain operations for shapes/nodes are allowed without having to query the server again. + * + * @returns List of all shape type hints for the diagram implementation. + */ + readonly shapeTypeHints: ShapeTypeHint[]; + + /** + * Returns the edge type hints for the diagram implementation. Edge type hints are sent to the client and used to + * validate whether certain operations for edges are allowed without having to query the server again. + * + * @returns List of all edge type hints for the diagram implementation. + */ + readonly edgeTypeHints: EdgeTypeHint[]; + + /** + * Returns the supported layout kind for this diagram implementation. + * + * @returns The layout kind. + */ + readonly layoutKind: ServerLayoutKind; + + /** + * Boolean flag to specific whether the diagram implementation expects the client to provide layout information for + * certain diagram element. Default is 'true'. + * + * @returns Boolean flag to indicate whether the client needs to be involved in the layout process. + */ + readonly needsClientLayout: boolean; + + /** + * Boolean flag to tell the client whether changed caused by an update should be animated or not. + * + * @returns Boolean flag to indicate whether the client should animate update changes. + */ + readonly animatedUpdate: boolean; + + /** + * Returns the type mappings for the diagram implementation. Type mappings are used by GSON to construct the correct + * {@link EClass} based on the "type" property of the JSON object. + * + * @returns A complete map of all type mappings for the diagram implementation. + */ + readonly typeMapping: Map; +} diff --git a/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts b/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts new file mode 100644 index 0000000..9da5099 --- /dev/null +++ b/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + EdgeTypeHint, + isSetTypeHintsAction, + RequestModelAction, + RequestTypeHintsAction, + SetTypeHintsAction, + ShapeTypeHint +} from '@eclipse-glsp/protocol'; +import { Container, ContainerModule } from 'inversify'; +import * as mock from '../test/mock-util'; +import { DiagramConfiguration } from './diagram-configuration'; +import { RequestTypeHintsActionHandler } from './request-type-hints-action-handler'; +import { expect } from 'chai'; + +describe('test RequestTypeHintsActionHandler', () => { + const container = new Container(); + const diagramConfiguration = new mock.StubDiagramConfiguration(); + const shapeTypeHint: ShapeTypeHint = { + elementTypeId: 'test', + deletable: true, + resizable: true, + repositionable: true, + reparentable: true + }; + const edgeTypeHint: EdgeTypeHint = { + elementTypeId: 'test', + deletable: true, + repositionable: true, + routable: true, + sourceElementTypeIds: [], + targetElementTypeIds: [] + }; + diagramConfiguration.shapeTypeHints = [shapeTypeHint]; + diagramConfiguration.edgeTypeHints = [edgeTypeHint]; + container.load( + new ContainerModule(bind => { + bind(DiagramConfiguration).toConstantValue(diagramConfiguration); + }) + ); + const handler = container.resolve(RequestTypeHintsActionHandler); + + it('execute with correct action', () => { + const result = handler.execute(new RequestTypeHintsAction()); + + expect(result).to.have.length(1); + expect(isSetTypeHintsAction(result[0])).true; + expect(result).to.be.deep.equal([ + { edgeHints: [edgeTypeHint], kind: 'setTypeHints', shapeHints: [shapeTypeHint], responseId: '' } as SetTypeHintsAction + ]); + }); + + it('execute with wrong action', () => { + const result = handler.execute(new RequestModelAction()); + + expect(result).to.be.deep.equal([]); + }); +}); diff --git a/packages/server-node/src/diagram/request-type-hints-action-handler.ts b/packages/server-node/src/diagram/request-type-hints-action-handler.ts new file mode 100644 index 0000000..062babf --- /dev/null +++ b/packages/server-node/src/diagram/request-type-hints-action-handler.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, isRequestTypeHintsAction, RequestTypeHintsAction, SetTypeHintsAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../actions/action-handler'; +import { DiagramConfiguration } from './diagram-configuration'; + +@injectable() +export class RequestTypeHintsActionHandler implements ActionHandler { + @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; + static KINDS = [RequestTypeHintsAction.KIND]; + + execute(action: Action): Action[] { + if (isRequestTypeHintsAction(action)) { + return [new SetTypeHintsAction(this.diagramConfiguration.shapeTypeHints, this.diagramConfiguration.edgeTypeHints)]; + } + return []; + } + + readonly actionKinds = [RequestTypeHintsAction.KIND]; +} diff --git a/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts new file mode 100644 index 0000000..a6c1e81 --- /dev/null +++ b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + ClipboardData, + MaybePromise, + RequestClipboardDataAction, + SetClipboardDataAction, + SModelElementSchema +} from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../../actions/action-handler'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { GModelSerializer } from '../model/gmodel-serializer'; + +@injectable() +export class RequestClipboardDataActionHandler implements ActionHandler { + actionKinds = [RequestClipboardDataAction.KIND]; + + @inject(GModelState) + protected modelState: GModelState; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + execute(action: RequestClipboardDataAction): MaybePromise { + const jsonArray: SModelElementSchema[] = []; + const index = this.modelState.index; + const selectedElements = index.getAll(action.editorContext.selectedElementIds); + const clipboardData: ClipboardData = {}; + selectedElements.forEach(element => { + jsonArray.push(this.modelSerializer.createSchema(element)); + }); + // eslint-disable-next-line no-null/no-null + clipboardData['application/json'] = JSON.stringify(jsonArray, null, 2); + return [new SetClipboardDataAction(clipboardData)]; + } +} diff --git a/packages/server-node/src/features/contextactions/command-palette-action-provider.ts b/packages/server-node/src/features/contextactions/command-palette-action-provider.ts new file mode 100644 index 0000000..2848d03 --- /dev/null +++ b/packages/server-node/src/features/contextactions/command-palette-action-provider.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph'; +import { Args, EditorContext, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { ContextActionsProvider } from './context-actions-provider'; + +/** + * A {@link ContextActionsProvider} for CommandPaletteActions. + */ +@injectable() +export abstract class CommandPaletteActionProvider implements ContextActionsProvider { + @inject(GModelState) + protected modelState: GModelState; + + readonly TEXT = 'text'; + readonly INDEX = 'index'; + + /** + * Returns the context id of the provider. + */ + get contextId(): string { + return 'command-palette'; + } + + /** + * Retrieves the value for the "text" key from the given arguments {@link Args}. + * + * @param args The given arguments. + * @returns The value associated with the "text" key. + */ + getText(args: Args): string { + return args[this.TEXT] ? args[this.TEXT].toString() : ''; + } + + /** + * Returns the value of the "index" key from a given {@link Args}. + * + * @param args The given arguments. + * @returns The value associated with the "index" key. + */ + getIndex(args: Args): number { + return args[this.INDEX] ? (args[this.INDEX] as number) : 0.0; + } + + /** + * Returns a list of {@link LabeledAction}s for a given {@link EditorContext}. + * + * @param editorContext The editorContext for which the actions are returned. + * @returns A list of {@link LabeledAction}s for a given {@link EditorContext}. + */ + getActions(editorContext: EditorContext): LabeledAction[] { + const actions: LabeledAction[] = []; + if (this.modelState.isReadonly) { + return actions; + } + + const selectedIds = editorContext.selectedElementIds; + const position = editorContext.lastMousePosition ? editorContext.lastMousePosition : { x: 0, y: 0 }; + const selectedElements = this.modelState.index.getAll(selectedIds); + + return this.getPaletteActions(selectedIds, selectedElements, position, editorContext.args); + } + + /** + * Returns a list of {@link LabeledAction}s for given selectedElements at a given mouse position. + * + * @param selectedElementIds The list of currently selected elementIds. + * @param selectedElements The list of currently selected elements. + * @param position The current mouse position. + * @param args Additional arguments. + * @returns A list of {@link LabeledAction}s. + */ + abstract getPaletteActions( + selectedElementIds: string[], + selectedElements: GModelElement[], + position: Point, + args?: Args + ): LabeledAction[]; +} diff --git a/packages/server-node/src/features/contextactions/context-actions-provider-registry.spec.ts b/packages/server-node/src/features/contextactions/context-actions-provider-registry.spec.ts new file mode 100644 index 0000000..01b6ac1 --- /dev/null +++ b/packages/server-node/src/features/contextactions/context-actions-provider-registry.spec.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph'; +import { Args, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { CommandPaletteActionProvider } from './command-palette-action-provider'; +import { ContextActionsProvider } from './context-actions-provider'; +import { ContextActionsProviderRegistry } from './context-actions-provider-registry'; +import { DefaultToolPaletteItemProvider } from './tool-palette-item-provider'; +import { expect } from 'chai'; + +describe('Test DefaultContextActionsProviderRegistry', () => { + it('check if default registry is empty', () => { + const contextActionsProvider: ContextActionsProvider[] = []; + const contextActionsProviderRegistry = new ContextActionsProviderRegistry(contextActionsProvider); + expect(contextActionsProviderRegistry.keys()).to.have.length(0); + }); + + it('register DefaultToolPaletteItemProvider via ContextActionsProviders list', () => { + const contextActionsProvider: ContextActionsProvider[] = [new DefaultToolPaletteItemProvider()]; + const contextActionsProviderRegistry = new ContextActionsProviderRegistry(contextActionsProvider); + expect(contextActionsProviderRegistry.keys()).to.have.length(1); + }); + + it('register DefaultToolPaletteItemProvider via ToolPaletteItemProvider', () => { + const contextActionsProvider: ContextActionsProvider[] = []; + const contextActionsProviderRegistry = new ContextActionsProviderRegistry( + contextActionsProvider, + undefined, + undefined, + new DefaultToolPaletteItemProvider() + ); + expect(contextActionsProviderRegistry.keys()).to.have.length(1); + }); + + it('register CustomCommandPaletteActionProvider via CommandPaletteActionProvider', () => { + class CustomCommandPaletteActionProvider extends CommandPaletteActionProvider { + getPaletteActions( + _selectedElementIds: string[], + _selectedElements: GModelElement[], + _position: Point, + _args?: Args + ): LabeledAction[] { + return []; + } + } + + const contextActionsProvider: ContextActionsProvider[] = []; + const contextActionsProviderRegistry = new ContextActionsProviderRegistry( + contextActionsProvider, + undefined, + new CustomCommandPaletteActionProvider(), + undefined + ); + expect(contextActionsProviderRegistry.keys()).to.have.length(1); + }); +}); diff --git a/packages/server-node/src/features/contextactions/context-actions-provider-registry.ts b/packages/server-node/src/features/contextactions/context-actions-provider-registry.ts new file mode 100644 index 0000000..8baa2c9 --- /dev/null +++ b/packages/server-node/src/features/contextactions/context-actions-provider-registry.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from 'inversify'; +import { ContextActionsProviders } from '../../di/service-identifiers'; +import { Registry } from '../../utils/registry'; +import { CommandPaletteActionProvider } from './command-palette-action-provider'; +import { ContextActionsProvider } from './context-actions-provider'; +import { ContextMenuItemProvider } from './context-menu-item-provider'; +import { ToolPaletteItemProvider } from './tool-palette-item-provider'; + +/** + * A registry that keeps track of all registered {@link ContextActionsProvider}s. + */ +@injectable() +export class ContextActionsProviderRegistry extends Registry { + constructor( + @multiInject(ContextActionsProviders) @optional() contextActionsProvider: ContextActionsProvider[] = [], + @inject(ContextMenuItemProvider) @optional() contextMenuItemProvider?: ContextMenuItemProvider, + @inject(CommandPaletteActionProvider) @optional() commandPaletteActionProvider?: CommandPaletteActionProvider, + @inject(ToolPaletteItemProvider) @optional() toolPaletteItemProvider?: ToolPaletteItemProvider + ) { + super(); + contextActionsProvider.forEach(provider => this.register(provider.contextId, provider)); + if (contextMenuItemProvider) { + this.register(contextMenuItemProvider.contextId, contextMenuItemProvider); + } + if (commandPaletteActionProvider) { + this.register(commandPaletteActionProvider.contextId, commandPaletteActionProvider); + } + if (toolPaletteItemProvider) { + this.register(toolPaletteItemProvider.contextId, toolPaletteItemProvider); + } + } +} diff --git a/packages/server-node/src/features/contextactions/context-actions-provider.ts b/packages/server-node/src/features/contextactions/context-actions-provider.ts new file mode 100644 index 0000000..0df868b --- /dev/null +++ b/packages/server-node/src/features/contextactions/context-actions-provider.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { EditorContext, LabeledAction, MaybePromise } from '@eclipse-glsp/protocol'; + +export const ContextActionsProvider = Symbol('ContextActionsProvider'); + +/** + * A provider for a certain contextId that provides {@link LabeledAction}s. + */ +export interface ContextActionsProvider { + /** + * The context id of the {@link ContextActionsProvider}. + */ + contextId: string; + + /** + * Returns a list of {@link LabeledAction}s for a given {@link EditorContext}. + * + * @param editorContext The editorContext for which the actions are returned. + * @returns A list of {@link LabeledAction}s for a given {@link EditorContext}. + */ + getActions(editorContext: EditorContext): MaybePromise; +} diff --git a/packages/server-node/src/features/contextactions/context-menu-item-provider.ts b/packages/server-node/src/features/contextactions/context-menu-item-provider.ts new file mode 100644 index 0000000..6cf1199 --- /dev/null +++ b/packages/server-node/src/features/contextactions/context-menu-item-provider.ts @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, Args, EditorContext, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { ContextActionsProvider } from './context-actions-provider'; + +/** + * A {@link ContextActionsProvider} for {@link MenuItem}s. + */ +@injectable() +export abstract class ContextMenuItemProvider implements ContextActionsProvider { + /** + * Returns the context id of the {@link ContextMenuItemProvider}. + */ + get contextId(): string { + return 'context-menu'; + } + + /** + * Returns a list of {@link MenuItem}s for a given list of selected elements at a certain mouse position. + * + * @param selectedElementIds The list of currently selected elementIds. + * @param position The current mouse position. + * @param args Additional arguments. + * @returns A list of {@link MenuItem}s for a given list of selected elements at a certain mouse position. + */ + abstract getItems(selectedElementIds: string[], position: Point, args?: Args): MenuItem[]; + + /** + * Returns a list of {@link LabeledAction}s for a given {@link EditorContext}. + * + * @param editorContext The editorContext for which the actions are returned. + * @returns A list of {@link LabeledAction}s for a given {@link EditorContext}. + */ + getActions(editorContext: EditorContext): LabeledAction[] { + const position = editorContext.lastMousePosition ? editorContext.lastMousePosition : { x: 0, y: 0 }; + return this.getItems(editorContext.selectedElementIds, position, editorContext.args); + } +} + +export class MenuItem extends LabeledAction { + readonly id: string; + readonly sortString?: string; + readonly group?: string; + readonly parentId?: string; + readonly children?: MenuItem[]; + readonly isEnabled?: boolean; + readonly isToggled?: boolean; + + constructor( + id: string, + label: string, + actions?: Action[], + children?: MenuItem[], + icon?: string, + sortString?: string, + group?: string, + parentId?: string, + isEnabled = true, + isToggled = false + ) { + super(label, actions ? actions : [], icon); + this.id = id; + this.sortString = sortString; + this.group = group; + this.parentId = parentId; + this.children = children; + this.isEnabled = isEnabled; + this.isToggled = isToggled; + } +} diff --git a/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts b/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts new file mode 100644 index 0000000..b522e5f --- /dev/null +++ b/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; +import { OperationHandlerRegistry } from '../../operations/operation-handler-registry'; +import * as mock from '../../test/mock-util'; +import { ContextActionsProvider } from './context-actions-provider'; +import { ContextActionsProviderRegistry } from './context-actions-provider-registry'; +import { RequestContextActionsHandler } from './request-context-actions-handler'; +import { DefaultToolPaletteItemProvider, PaletteItem } from './tool-palette-item-provider'; +import { expect } from 'chai'; + +describe('Test RequestContextActionsHandler', () => { + const operationHandlerRegistry = new OperationHandlerRegistry(); + const stubANode = new mock.StubCreateNodeOperationHandler('ANode'); + const stubBNode = new mock.StubCreateNodeOperationHandler('BNode'); + const stubAEdge = new mock.StubCreateEdgeOperationHandler('AEdge'); + const stubBEdge = new mock.StubCreateEdgeOperationHandler('BEdge'); + operationHandlerRegistry.registerHandler(stubBNode); + operationHandlerRegistry.registerHandler(stubANode); + operationHandlerRegistry.registerHandler(stubBEdge); + operationHandlerRegistry.registerHandler(stubAEdge); + + const contextActionsProvider: ContextActionsProvider[] = []; + const toolPaletteItemProvider = new DefaultToolPaletteItemProvider(); + toolPaletteItemProvider.operationHandlerRegistry = operationHandlerRegistry; + + const contextActionsProviderRegistry = new ContextActionsProviderRegistry( + contextActionsProvider, + undefined, + undefined, + toolPaletteItemProvider + ); + + const requestContextActionsHandler = new RequestContextActionsHandler(); + requestContextActionsHandler['contextActionsProviderRegistry'] = contextActionsProviderRegistry; + + it('request ToolPaletteContextActions', async () => { + const actions = await requestContextActionsHandler.execute(new RequestContextActions('tool-palette', { selectedElementIds: [] })); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetContextActions); + const setContextActions = action as SetContextActions; + expect(setContextActions.actions).to.have.length(2); + + expect(setContextActions.actions[0]).instanceOf(PaletteItem); + const firstPaletteItem = setContextActions.actions[0] as PaletteItem; + expect(firstPaletteItem.label).to.be.equal('Nodes'); + expect(firstPaletteItem.children).to.have.length(2); + expect(firstPaletteItem.children[0].label).to.be.equal('ANode'); + expect(firstPaletteItem.children[1].label).to.be.equal('BNode'); + + expect(setContextActions.actions[1]).instanceOf(PaletteItem); + const secondPaletteItem = setContextActions.actions[1] as PaletteItem; + expect(secondPaletteItem.label).to.be.equal('Edges'); + expect(secondPaletteItem.children).to.have.length(2); + expect(secondPaletteItem.children[0].label).to.be.equal('AEdge'); + expect(secondPaletteItem.children[1].label).to.be.equal('BEdge'); + }); +}); diff --git a/packages/server-node/src/features/contextactions/request-context-actions-handler.ts b/packages/server-node/src/features/contextactions/request-context-actions-handler.ts new file mode 100644 index 0000000..120e8ec --- /dev/null +++ b/packages/server-node/src/features/contextactions/request-context-actions-handler.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, LabeledAction, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../../actions/action-handler'; +import { ContextActionsProviderRegistry } from './context-actions-provider-registry'; + +@injectable() +export class RequestContextActionsHandler implements ActionHandler { + actionKinds = [RequestContextActions.KIND]; + + @inject(ContextActionsProviderRegistry) + protected contextActionsProviderRegistry: ContextActionsProviderRegistry; + + async execute(action: RequestContextActions): Promise { + const editorContext = action.editorContext; + const actions: LabeledAction[] = []; + if (this.contextActionsProviderRegistry.get(action.contextId)) { + const provider = this.contextActionsProviderRegistry.get(action.contextId)!; + (await provider.getActions(editorContext)).forEach(returnAction => actions.push(returnAction)); + } + return [new SetContextActions(actions, undefined, action.editorContext.args)]; + } +} diff --git a/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts b/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts new file mode 100644 index 0000000..b42defb --- /dev/null +++ b/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { OperationHandlerRegistry } from '../../operations/operation-handler-registry'; +import * as mock from '../../test/mock-util'; +import { DefaultToolPaletteItemProvider } from './tool-palette-item-provider'; +import { expect } from 'chai'; + +describe('Test DefaultToolPaletteItemProvider', () => { + const operationHandlerRegistry = new OperationHandlerRegistry(); + const stubANode = new mock.StubCreateNodeOperationHandler('ANode'); + const stubBNode = new mock.StubCreateNodeOperationHandler('BNode'); + const stubAEdge = new mock.StubCreateEdgeOperationHandler('AEdge'); + const stubBEdge = new mock.StubCreateEdgeOperationHandler('BEdge'); + operationHandlerRegistry.registerHandler(stubBNode); + operationHandlerRegistry.registerHandler(stubANode); + operationHandlerRegistry.registerHandler(stubBEdge); + operationHandlerRegistry.registerHandler(stubAEdge); + + it('test getItems', () => { + const toolPaletteItemProvider = new DefaultToolPaletteItemProvider(); + toolPaletteItemProvider.operationHandlerRegistry = operationHandlerRegistry; + const items = toolPaletteItemProvider.getItems(); + expect(items).to.have.length(2); + expect(items[0].label).to.be.equal('Nodes'); + expect(items[0].children).to.have.length(2); + expect(items[0].children[0].label).to.be.equal('ANode'); + expect(items[0].children[1].label).to.be.equal('BNode'); + expect(items[1].label).to.be.equal('Edges'); + expect(items[1].children).to.have.length(2); + expect(items[1].children[0].label).to.be.equal('AEdge'); + expect(items[1].children[1].label).to.be.equal('BEdge'); + }); +}); diff --git a/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts b/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts new file mode 100644 index 0000000..2573034 --- /dev/null +++ b/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Args, + CreateEdgeOperation, + CreateNodeOperation, + EditorContext, + LabeledAction, + MaybePromise, + TriggerElementCreationAction +} from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { CreateOperationHandler } from '../../operations/create-operation-handler'; +import { OperationHandlerRegistry } from '../../operations/operation-handler-registry'; +import { ContextActionsProvider } from './context-actions-provider'; + +/** + * A {@link ContextActionsProvider} for {@link PaletteItem}s. + */ +@injectable() +export abstract class ToolPaletteItemProvider implements ContextActionsProvider { + /** + * Returns the context id of the provider. + */ + get contextId(): string { + return 'tool-palette'; + } + + /** + * Returns a list of {@link LabeledAction}s for a given {@link EditorContext}. + * + * @param editorContext The editorContext for which the actions are returned. + * @returns A list of {@link LabeledAction}s for a given {@link EditorContext}. + */ + async getActions(editorContext: EditorContext): Promise { + return this.getItems(editorContext.args); + } + + /** + * Constructs a list of {@link PaletteItem}s for a given map of string arguments. + * + * @param args A map of string arguments. + * @returns A list of {@link PaletteItem}s for a given map of string arguments. + */ + abstract getItems(args?: Args): MaybePromise; +} + +@injectable() +export class DefaultToolPaletteItemProvider extends ToolPaletteItemProvider { + @inject(OperationHandlerRegistry) operationHandlerRegistry: OperationHandlerRegistry; + protected counter: number; + + getItems(args?: Args): PaletteItem[] { + const handlers = this.operationHandlerRegistry + .getAll() + .filter(handler => handler instanceof CreateOperationHandler) as CreateOperationHandler[]; + this.counter = 0; + const nodes = this.createPaletteItem(handlers, CreateNodeOperation.KIND); + const edges = this.createPaletteItem(handlers, CreateEdgeOperation.KIND); + return [ + PaletteItem.createPaletteGroup('node-group', 'Nodes', nodes, 'symbol-property', 'A'), + PaletteItem.createPaletteGroup('edge-group', 'Edges', edges, 'symbol-property', 'B') + ]; + } + + createPaletteItem(handlers: CreateOperationHandler[], kind: string): PaletteItem[] { + return handlers + .filter(handler => handler.operationType === kind) + .map(handler => handler.getTriggerActions().map(action => this.create(action, handler.label))) + .reduce((accumulator, value) => accumulator.concat(value), []) + .sort((a, b) => a.sortString.localeCompare(b.sortString)); + } + + create(action: TriggerElementCreationAction, label: string): PaletteItem { + return new PaletteItem(`palette-item${this.counter}`, label, action); + } +} + +export class PaletteItem extends LabeledAction { + id: string; + sortString: string; + children: PaletteItem[]; + + constructor(id: string, label: string, initializeAction?: TriggerElementCreationAction, icon?: string) { + super(label, initializeAction ? [initializeAction] : [], icon); + this.sortString = label.charAt(0); + this.id = id; + } + + static createPaletteGroup(id: string, label: string, children: PaletteItem[], icon?: string, sortString?: string): PaletteItem { + const item = new PaletteItem(id, label, undefined, icon); + item.children = children; + if (sortString) { + item.sortString = sortString; + } + return item; + } +} diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts new file mode 100644 index 0000000..48e5138 --- /dev/null +++ b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GLabel } from '@eclipse-glsp/graph'; +import { ApplyLabelEditOperation } from '@eclipse-glsp/protocol'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { GModelIndex } from '../model/gmodel-index'; +import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; + +describe('Test ApplyLabelEditOperationHandler', () => { + const label = new GLabel(); + const modelState = new GModelState(); + Object.defineProperty(modelState, 'index', { value: new GModelIndex() }); + sinon.stub(modelState.index, 'findByClass').returns(label); + const applyLabelEditOperationHandler = new ApplyLabelEditOperationHandler(); + Object.defineProperty(applyLabelEditOperationHandler, 'modelState', { value: modelState }); + + it('text is changed after ApplyLabelEditOperation', async () => { + expect(applyLabelEditOperationHandler.execute(new ApplyLabelEditOperation('undefined', 'test'))).to.not.throw; + expect(label.text).to.be.equal('test'); + }); +}); diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts new file mode 100644 index 0000000..88c4e39 --- /dev/null +++ b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GLabel } from '@eclipse-glsp/graph'; +import { ApplyLabelEditOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { OperationHandler } from '../../operations/operation-handler'; + +@injectable() +export class ApplyLabelEditOperationHandler implements OperationHandler { + readonly operationType = ApplyLabelEditOperation.KIND; + + @inject(GModelState) + protected readonly modelState: GModelState; + + execute(operation: ApplyLabelEditOperation): void { + const element = this.modelState.index.findByClass(operation.labelId, GLabel); + if (element) { + element.text = operation.text; + } else { + throw new GLSPServerError('Element with provided ID cannot be found or is not a GLabel'); + } + } +} diff --git a/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts b/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts new file mode 100644 index 0000000..b075ce9 --- /dev/null +++ b/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelState } from '../../base-impl/gmodel-state'; +import { TestContextEditValidator, TestLabelEditValidator } from '../../test/mock-util'; +import { ContextEditValidator } from './context-edit-validator'; +import { DefaultContextEditValidatorRegistry } from './context-edit-validator-registry'; +import { expect } from 'chai'; + +describe('Test DefaultContextEditValidatorRegistry', () => { + it('check if default registry is empty', () => { + const contextEditValidators: ContextEditValidator[] = []; + const contextEditValidatorRegistry = new DefaultContextEditValidatorRegistry(new GModelState(), contextEditValidators); + expect(contextEditValidatorRegistry.keys()).to.have.length(0); + }); + + it('register TestContextEditValidator via ContextEditValidators list', () => { + const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; + const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry(new GModelState(), contextEditValidators); + expect(contextEditValidatorsRegistry.keys()).to.have.length(1); + }); + + it('register TestLabelEditValidator via LabelEditValidator', () => { + const contextEditValidators: ContextEditValidator[] = []; + const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( + new GModelState(), + contextEditValidators, + new TestLabelEditValidator() + ); + expect(contextEditValidatorsRegistry.keys()).to.have.length(1); + }); + + it('register via ContextEditValidators list and LabelEditValidator', () => { + const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; + const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( + new GModelState(), + contextEditValidators, + new TestLabelEditValidator() + ); + expect(contextEditValidatorsRegistry.keys()).to.have.length(2); + }); +}); diff --git a/packages/server-node/src/features/directediting/context-edit-validator-registry.ts b/packages/server-node/src/features/directediting/context-edit-validator-registry.ts new file mode 100644 index 0000000..f517107 --- /dev/null +++ b/packages/server-node/src/features/directediting/context-edit-validator-registry.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable, multiInject, optional } from 'inversify'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { ContextEditValidators } from '../../di/service-identifiers'; +import { Registry } from '../../utils/registry'; +import { ContextEditValidator, ValidateLabelEditAdapter } from './context-edit-validator'; +import { LabelEditValidator } from './label-edit-validator'; + +export const ContextEditValidatorRegistry = Symbol.for('ContextEditValidatorRegistry'); + +/** + * A registry that keeps track of all registered {@link ContextEditValidator}s. + */ +export interface ContextEditValidatorRegistry extends Registry {} + +@injectable() +export class DefaultContextEditValidatorRegistry extends Registry implements ContextEditValidatorRegistry { + constructor( + @inject(GModelState) modelState: GModelState, + @multiInject(ContextEditValidators) @optional() contextEditValidators: ContextEditValidator[] = [], + @inject(LabelEditValidator) @optional() labelEditValidator?: LabelEditValidator + ) { + super(); + contextEditValidators.forEach(provider => this.register(provider.contextId, provider)); + if (labelEditValidator) { + this.register(LabelEditValidator.CONTEXT_ID, new ValidateLabelEditAdapter(modelState, labelEditValidator)); + } + } +} diff --git a/packages/server-node/src/features/directediting/context-edit-validator.ts b/packages/server-node/src/features/directediting/context-edit-validator.ts new file mode 100644 index 0000000..38323be --- /dev/null +++ b/packages/server-node/src/features/directediting/context-edit-validator.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph/lib/gmodel-element'; +import { RequestEditValidationAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { LabelEditValidator } from './label-edit-validator'; + +export const ContextEditValidator = Symbol('ContextEditValidator'); + +export interface ContextEditValidator { + /** + * Returns the context id of the {@link ContextActionsProvider}. + */ + contextId: string; + + /** + * Returns the {@link ValidationStatus} for a given {@link RequestEditValidationAction}. + * + * @param action The RequestEditValidationAction to validate. + * @returns The {@link ValidationStatus} for a given {@link RequestEditValidationAction}. + */ + validate(action: RequestEditValidationAction): ValidationStatus; +} + +export class ValidateLabelEditAdapter implements ContextEditValidator { + labelEditValidator: LabelEditValidator; + constructor(readonly modelState: GModelState, labelEditValidator: LabelEditValidator) { + this.labelEditValidator = labelEditValidator; + } + + get contextId(): string { + return LabelEditValidator.CONTEXT_ID; + } + + validate(action: RequestEditValidationAction): ValidationStatus { + const element = this.modelState.index.get(action.modelElementId); + if (element instanceof GModelElement) { + return this.labelEditValidator.validate(action.text, element); + } + return { severity: ValidationStatus.Severity.OK }; + } +} diff --git a/packages/server-node/src/features/directediting/label-edit-validator.ts b/packages/server-node/src/features/directediting/label-edit-validator.ts new file mode 100644 index 0000000..3476619 --- /dev/null +++ b/packages/server-node/src/features/directediting/label-edit-validator.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph'; +import { ValidationStatus } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; + +/** + * A validator that validates a new label for a given {@link GModelElement}. + */ +@injectable() +export abstract class LabelEditValidator { + static readonly CONTEXT_ID = 'label-edit'; + + /** + * Returns the {@link ValidationStatus} for a given edited label of a {@link GModelElement}. + * + * @param label The edited label to validate. + * @param element The element for which the label is going to be edited. + * @returns The {@link ValidationStatus} of the label. + */ + abstract validate(label: string, element: GModelElement): ValidationStatus; +} diff --git a/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts new file mode 100644 index 0000000..d59e112 --- /dev/null +++ b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts @@ -0,0 +1,125 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GNode } from '@eclipse-glsp/graph'; +import { RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { TestContextEditValidator, TestLabelEditValidator } from '../../test/mock-util'; +import { GModelIndex } from '../model/gmodel-index'; +import { ContextEditValidator } from './context-edit-validator'; +import { DefaultContextEditValidatorRegistry } from './context-edit-validator-registry'; +import { LabelEditValidator } from './label-edit-validator'; +import { RequestEditValidationHandler } from './request-edit-validation-handler'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; + +describe('Test RequestEditValidationHandler', () => { + const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; + const modelState = new GModelState(); + Object.defineProperty(modelState, 'index', { value: new GModelIndex() }); + sinon.stub(modelState.index, 'get').callsFake(() => new GNode()); + const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( + modelState, + contextEditValidators, + new TestLabelEditValidator() + ); + + const requestEditValidationHandler = new RequestEditValidationHandler(); + requestEditValidationHandler['contextEditValidatorRegistry'] = contextEditValidatorsRegistry; + + it('requestContextEditValidation with ok result', async () => { + const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'ok')); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.OK); + expect(status.message).to.be.equal('ok'); + }); + + it('requestContextEditValidation with warning result', async () => { + const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'warning')); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.WARNING); + expect(status.message).to.be.equal('warning'); + }); + + it('requestContextEditValidation with error result', async () => { + const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'error')); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.ERROR); + expect(status.message).to.be.equal('error'); + }); + + it('requestLabelEditValidation with ok result', async () => { + const actions = await requestEditValidationHandler.execute( + new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'ok') + ); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.OK); + expect(status.message).to.be.equal('ok'); + }); + + it('requestLabelEditValidation with warning result', async () => { + const actions = await requestEditValidationHandler.execute( + new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'warning') + ); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.WARNING); + expect(status.message).to.be.equal('warning'); + }); + + it('requestLabelEditValidation with error result', async () => { + const actions = await requestEditValidationHandler.execute( + new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'error') + ); + expect(actions).to.have.length(1); + const action = actions[0]; + + expect(action).instanceOf(SetEditValidationResultAction); + const setEditValidationResultAction = action as SetEditValidationResultAction; + const status = setEditValidationResultAction.status; + + expect(status.severity).to.be.equal(ValidationStatus.Severity.ERROR); + expect(status.message).to.be.equal('error'); + }); +}); diff --git a/packages/server-node/src/features/directediting/request-edit-validation-handler.ts b/packages/server-node/src/features/directediting/request-edit-validation-handler.ts new file mode 100644 index 0000000..dcc7f28 --- /dev/null +++ b/packages/server-node/src/features/directediting/request-edit-validation-handler.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MaybePromise, RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ContextEditValidatorRegistry } from '../../features/directediting/context-edit-validator-registry'; +import { ActionHandler } from '../../actions/action-handler'; +import { Logger } from '../../utils/logger'; + +@injectable() +export class RequestEditValidationHandler implements ActionHandler { + actionKinds = [RequestEditValidationAction.KIND]; + + @inject(Logger) + protected logger: Logger; + + @inject(ContextEditValidatorRegistry) + contextEditValidatorRegistry: ContextEditValidatorRegistry; + + execute(action: RequestEditValidationAction): MaybePromise { + const validator = this.contextEditValidatorRegistry.get(action.contextId); + if (validator) { + const validationStatus = validator.validate(action); + return [new SetEditValidationResultAction(validationStatus)]; + } else { + const message = `No validator registered for the context '${action.contextId}'`; + this.logger.warn(message); + return [new SetEditValidationResultAction({ severity: ValidationStatus.Severity.WARNING, message: message })]; + } + } +} diff --git a/packages/server-node/src/features/model/gmodel-factory.ts b/packages/server-node/src/features/model/gmodel-factory.ts new file mode 100644 index 0000000..e97f55c --- /dev/null +++ b/packages/server-node/src/features/model/gmodel-factory.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; + +export const GModelFactory = Symbol('GModelFactory'); + +/** + * A graph model factory produces a graph model from the model state; typically its contained source model. + * + * The responsibility of a {@link GModelFactory} implementation is to define how a {@link GModelState} is to be + * translated into a {@link GModelRoot} that is sent to the client for rendering. Before a {@link GModelFactory} + * is invoked, the {@link ModelSourceLoader} has already been executed for loading the source model into the + * {@link GModelState}. The {@link GModelFactory} then produces the {@link GModelRoot} from the source model in the + * {@link GModelState}. Implementations of {@link GModelFactory} are usually specific to the type of source model, as + * they need to understand the source model in order to translate it into a graph model. + * + * The graph model factory is invoked after initial load of the source model and after each operation that is applied + * to the source model by an {@link OperationHandler} in order to update the graph model before sending it to the client + * for rendering. + * + * If an index is needed for mapping between the graph model and the source model, as is typically the case for + * {@link ActionHandler action handlers} and {@link OperationHandler operation handlers}, it is the responsibility of + * the graph model factory to create such an index while producing the graph model from the source model. The index + * shall be put into the model state too. Typically the {@link GModelIndex} is extended for a particular model source + * type as well. + * + * @see ModelSourceLoader + * @see GModelIndex + */ +export interface GModelFactory { + /** + * Create a {@link GModelRoot} from the specified modelState and puts it into the + * modelState. Optionally, this step also produces and sets a {@link GModelIndex} in the model state + * that allows mapping from graph model elements to source model elements and vice versa. + */ + createModel(): void; +} + +/** + * GModelFactory to be used if the graph model is already available from the model source. + */ +@injectable() +export class GModelFactoryNullImpl implements GModelFactory { + createModel(): void { + // no-op + } +} diff --git a/packages/server-node/src/features/model/gmodel-index.ts b/packages/server-node/src/features/model/gmodel-index.ts new file mode 100644 index 0000000..9b59895 --- /dev/null +++ b/packages/server-node/src/features/model/gmodel-index.ts @@ -0,0 +1,169 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, GModelElement, GModelElementConstructor, GModelRoot, GNode } from '@eclipse-glsp/graph'; +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { getOrThrow, GLSPServerError } from '../../utils/glsp-server-error'; + +/** + * Is used to index all child elements of a {@link GModelRoot} by their id. Offers a set + * of query methods to retrieve indexed elements. + */ +@injectable() +export class GModelIndex { + protected idToElement: Map; + protected typeToElements: Map; + + constructor() { + this.idToElement = new Map(); + this.typeToElements = new Map(); + } + + clear(): void { + this.idToElement.clear(); + this.typeToElements.clear(); + } + + indexRoot(root: GModelRoot): void { + this.clear(); + this.doIndex(root); + } + + protected doIndex(element: GModelElement): void { + if (this.idToElement.has(element.id)) { + throw new GLSPServerError('Duplicate ID in model: ' + element.id); + } + this.idToElement.set(element.id, element); + const typeSet = this.typeToElements.get(element.type) ?? []; + typeSet.push(element); + this.typeToElements.set(element.type, typeSet); + (element.children ?? []).forEach(child => this.doIndex(child)); + } + + find(elementId: string, predicate?: (test: GModelElement) => boolean): GModelElement | undefined { + const element = this.idToElement.get(elementId); + if (element && predicate ? predicate(element) : true) { + return element; + } + return undefined; + } + + findByClass(elementTypeId: string, constructor: GModelElementConstructor): G | undefined { + const element = this.find(elementTypeId); + if (element && element instanceof constructor) { + return element; + } + return undefined; + } + + /** + * Returns an optional {@link GModelElement} by its elementId. + * + * @param elementId The id of the requested {@link GModelElement}. + * @returns An optional instance of the {@link GModelElement}. + */ + get(elementId: string): GModelElement { + return getOrThrow(this.find(elementId), `Could not retrieve element with id: '${elementId}'`); + } + + /** + * Returns a set of {@link GModelElement} instances by a Collection of elementIds. + * + * @param elementIds The ids to request the {@link GModelElement} from an array of elementIds. + * @returns A set of {@link GModelElement}s. + */ + getAll(elementIds: string[]): GModelElement[] { + return elementIds.map(id => this.idToElement.get(id)).filter(element => element) as GModelElement[]; + } + + /** + * Returns all elements of the type constructor contained in the {@link GModelRoot}. + * + * @type Type of the elements to be returned. + * @param constructor The class of which the returned elements should be instances. + * @returns A set containing the elements of type constructor. + */ + getAllByClass(constructor: GModelElementConstructor): G[] { + return Array.from(this.idToElement.values()).filter(element => element instanceof constructor) as G[]; + } + + /** + * Returns a list of all {@link GModelElement} ids contained in this instance of the GModelIndex. + * + * @returns A list of elementIds. + */ + allIds(): string[] { + return Array.from(this.idToElement.keys()); + } + + /** + * Returns all elements of a type {@link GModelRoot}. + * + * @param type Type of the elements to be returned. + * @returns A set containing the elements of the given type. + */ + getElements(type: string): GModelElement[] { + return this.typeToElements.get(type) ?? []; + } + + /** + * Returns the current amount of type occurrences in this instance of the GModelIndex. + * + * @param eClass The EClass to be counted in this instance of the GModelIndex. + * + * @returns The amount of type occurrences. + */ + typeCount(type: string): number { + return this.getElements(type).length; + } + + /** + * Returns all incoming edges for a node. + * + * @param node The node where the edges are connected. + * + * @returns All incoming edges. + */ + getIncomingEdges(node: GNode): GEdge[] { + return this.getAllEdges().filter(edge => edge.targetId === node.id); + } + + /** + * Returns all outgoing edges for a node. + * + * @param node The node where the edges are connected. + * + * @returns All outgoing edges. + */ + getOutgoingEdges(node: GModelElement): GEdge[] { + return this.getAllEdges().filter(edge => edge.sourceId === node.id); + } + + /** + * Returns all edges in the index. + * + * @returns All edges in the index. + */ + getAllEdges(): GEdge[] { + const edges: GEdge[] = []; + this.getElements(DefaultTypes.EDGE).forEach(edge => { + if (edge instanceof GEdge) { + edges.push(edge); + } + }); + return edges; + } +} diff --git a/packages/server-node/src/features/model/gmodel-serializer.spec.ts b/packages/server-node/src/features/model/gmodel-serializer.spec.ts new file mode 100644 index 0000000..7f88e64 --- /dev/null +++ b/packages/server-node/src/features/model/gmodel-serializer.spec.ts @@ -0,0 +1,141 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, getDefaultMapping, GGraph, GNode } from '@eclipse-glsp/graph'; +import { Container, ContainerModule } from 'inversify'; +import { DiagramConfiguration } from '../../diagram/diagram-configuration'; +import * as mock from '../../test/mock-util'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { Logger } from '../../utils/logger'; +import { DefaultGModelSerializer } from './gmodel-serializer'; +import { expect } from 'chai'; + +class TestNode extends GNode { + foo(): void { + // should not be serialized to schema + } +} + +let testRootSchema: any; + +let testNodeSchema: any; + +let testNodeSchemaWithParent: any; + +describe('test DefaultGModelSerializer', () => { + const container = new Container(); + const diagramConfiguration = new mock.StubDiagramConfiguration(); + diagramConfiguration['typeMapping'] = getDefaultMapping(); + diagramConfiguration.typeMapping.set('node', TestNode); + + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(DiagramConfiguration).toConstantValue(diagramConfiguration); + }) + ); + + const serializer = container.resolve(DefaultGModelSerializer); + + beforeEach(() => { + testRootSchema = { + id: 'graph', + type: 'graph', + children: [ + { id: 'node1', type: 'node', position: { x: 5, y: 10 } }, + { id: 'node2', type: 'node', position: { x: 15, y: 5 } }, + { id: 'edge1', type: 'edge', sourceId: 'node1', targetId: 'node2' } + ] + }; + testNodeSchema = { id: 'node1', type: 'node', position: { x: 5, y: 10 }, children: [], cssClasses: [] }; + testNodeSchemaWithParent = { + id: 'node1', + type: 'node', + position: { x: 5, y: 10 }, + children: [], + cssClasses: [], + parent: new GNode() + }; + }); + + it('createElement - unregistered type', () => { + testNodeSchema['type'] = 'notRegistered'; + expect(() => serializer.createElement(testNodeSchema)).to.throw(GLSPServerError); + }); + + it('createElement - with root schema', () => { + expect(() => serializer.createElement(testRootSchema)).to.throw(GLSPServerError); + }); + + it('createElement - with node schema', () => { + const node = serializer.createElement(testNodeSchema); + expect(node instanceof TestNode).true; + expect(node).to.be.deep.equal(testNodeSchema); + expect((node as TestNode).foo).to.not.be.undefined; + }); + + it('createElement- with sub type of registered schema', () => { + testNodeSchema.type = 'node:rectangular'; + const node = serializer.createElement(testNodeSchema); + expect(node instanceof TestNode).true; + expect(node).to.be.deep.equal(testNodeSchema); + expect((node as TestNode).foo).to.not.be.undefined; + }); + + it('createElement - with parent', () => { + const parent = new GNode(); + const child = serializer.createElement(testNodeSchema, parent); + expect(child instanceof TestNode).true; + expect(child).to.be.deep.equal(testNodeSchemaWithParent); + expect(child.parent).to.be.equal(parent); + }); + + it('createRoot - unregistered type', () => { + testRootSchema['type'] = 'notRegistered'; + expect(() => serializer.createRoot(testNodeSchema)).to.throw(GLSPServerError); + }); + + it('createRoot - with child schema ', () => { + expect(() => serializer.createRoot(testNodeSchema)).to.throw(GLSPServerError); + }); + + it('createRoot - with registered root schema', () => { + const root = serializer.createRoot(testRootSchema); + expect(root instanceof GGraph).true; + expect(root.children.length).to.be.equal(3); + const node1 = root.children[0]; + expect(node1 instanceof TestNode).true; + const node2 = root.children[1]; + expect(node2 instanceof TestNode).true; + expect(root.children[2] instanceof GEdge).true; + const edge = root.children[2] as GEdge; + expect(edge.sourceId).to.be.equal(node1.id); + expect(edge.targetId).to.be.equal(node2.id); + }); + + it('createSchema- unregistered type', () => { + expect(() => serializer.createRoot({ id: 'id', type: 'unregistered' })).to.throw(GLSPServerError); + }); + + it('createSchema- with node', () => { + const testNode = new TestNode(); + testNode.position = { x: 5, y: 10 }; + testNode.size = { width: 10, height: 100 }; + testNode.layoutOptions = { ['my']: 'Options' }; + const schema = serializer.createSchema(testNode); + delete (testNode as Partial).foo; + expect(schema).to.be.deep.equal(testNode); + }); +}); diff --git a/packages/server-node/src/features/model/gmodel-serializer.ts b/packages/server-node/src/features/model/gmodel-serializer.ts new file mode 100644 index 0000000..24947b0 --- /dev/null +++ b/packages/server-node/src/features/model/gmodel-serializer.ts @@ -0,0 +1,147 @@ +/******************************************************************************** + * Copyright (c) 2017-2022 TypeFox and other + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* Derived from sprotty (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel-factory.ts) */ +import { GModelElement, GModelElementConstructor, GModelRoot } from '@eclipse-glsp/graph'; +import { SModelElementSchema, SModelRootSchema } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { DiagramConfiguration } from '../../diagram/diagram-configuration'; +import { GLSPServerError } from '../../utils/glsp-server-error'; + +export const GModelSerializer = Symbol('GModelSerializer'); + +export interface GModelSerializer { + createRoot(schema: SModelRootSchema): GModelRoot; + + createElement(schema: SModelElementSchema, parent?: GModelElement): GModelElement; + + createSchema(element: GModelElement): SModelElementSchema; + + reservedKeys: string[]; +} + +@injectable() +export class DefaultGModelSerializer implements GModelSerializer { + @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; + + reservedKeys = ['children', 'parent', 'index']; + createRoot(schema: SModelElementSchema): GModelRoot { + const constructor = this.getConfiguredConstructor(schema); + if (constructor) { + const element = new constructor(); + if (!(element instanceof GModelRoot)) { + throw new GLSPServerError(`Element with type '${schema.type}' is expected to be a GModelRoot!`); + } + return this.initializeRoot(element, schema); + } + throw new GLSPServerError(`No constructor is configured in DiagramConfiguration for type ${schema.type}`); + } + + getConfiguredConstructor(schema: SModelElementSchema): GModelElementConstructor | undefined { + let key = schema.type; + while (!this.diagramConfiguration.typeMapping.has(key)) { + const i = key.lastIndexOf(':'); + if (i > 0) { + key = key.substring(0, i); + } else { + return undefined; + } + } + + const constructor = this.diagramConfiguration.typeMapping.get(key)!; + if (key !== schema.type) { + this.diagramConfiguration.typeMapping.set(key, constructor); + } + return constructor; + } + + createElement(schema: SModelElementSchema, parent?: GModelElement): GModelElement { + const constructor = this.getConfiguredConstructor(schema); + if (constructor) { + const element = new constructor(); + if (element instanceof GModelRoot) { + throw new GLSPServerError( + `Element with type '${schema.type}' is a GModelRoot! 'createElement()' is expected to only create child elements!` + ); + } + return this.initializeChild(element, schema, parent); + } + throw new GLSPServerError(`No constructor is configured in DiagramConfiguration for type ${schema.type}`); + } + + createSchema(element: GModelElement): SModelElementSchema { + const schema = {}; + for (const key in element) { + if (!this.isReserved(element, key)) { + const value: any = (element as any)[key]; + if (typeof value !== 'function') { + (schema as any)[key] = value; + } + } + } + (schema as any)['children'] = (element.children ?? []).map(child => this.createSchema(child)); + + return schema as SModelElementSchema; + } + + protected initializeRoot(root: GModelRoot, schema: SModelRootSchema): GModelRoot { + this.initializeParent(root, schema); + return root; + } + + protected initializeElement(element: GModelElement, schema: SModelElementSchema): GModelElement { + for (const key in schema) { + if (!this.isReserved(element, key)) { + const value = (schema as any)[key]; + if (typeof value !== 'function') { + (element as any)[key] = value; + } + } + } + return element; + } + + protected initializeChild(child: GModelElement, schema: SModelElementSchema, parent?: GModelElement): GModelElement { + this.initializeParent(child, schema); + if (parent) { + child.parent = parent; + } + return child; + } + + protected initializeParent(parent: GModelElement, schema: SModelElementSchema): GModelElement { + this.initializeElement(parent, schema); + if (schema.children) { + parent.children = schema.children.map(childSchema => this.createElement(childSchema, parent)); + } + + return parent; + } + + protected isReserved(element: GModelElement, propertyName: string): boolean { + if (this.reservedKeys.indexOf(propertyName) >= 0) { + return true; + } + let obj = element; + do { + const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + if (descriptor !== undefined) { + return descriptor.get !== undefined; + } + obj = Object.getPrototypeOf(obj); + } while (obj); + return false; + } +} diff --git a/packages/server-node/src/features/model/model-source-loader.ts b/packages/server-node/src/features/model/model-source-loader.ts new file mode 100644 index 0000000..2ee1950 --- /dev/null +++ b/packages/server-node/src/features/model/model-source-loader.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MaybePromise, RequestModelAction } from '@eclipse-glsp/protocol'; + +export const ModelSourceLoader = Symbol('ModelSourceLoader'); + +/** + * A source model loader loads models into the model state. + * + * A `source model` is an arbitrary model from which the graph model of the diagram is to be created. + * Implementations of source model loaders are specific to the type of source model or persistence format that is used + * for a type of source model. A source model loader obtains the information on which source model shall loaded from a + * {@link RequestModelAction}; typically its client options. Once the source model is loaded, a model loader is expected + * to put the loaded source model into the model state for further processing, such as transforming the loaded model + * into a graph model (see GModelFactory). + * + * @see ClientOptionsUtil + * @see GModelFactory + */ +export interface ModelSourceLoader { + /** + * Loads a source model into the `modelState`. + * + * @param action Action sent by the client to specifying the information needed to load the source model. + */ + loadSourceModel(action: RequestModelAction): MaybePromise; +} diff --git a/packages/server-node/src/features/model/model-state.ts b/packages/server-node/src/features/model/model-state.ts new file mode 100644 index 0000000..ee9f810 --- /dev/null +++ b/packages/server-node/src/features/model/model-state.ts @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelRoot } from '@eclipse-glsp/graph'; +import { EditMode } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GModelIndex } from './gmodel-index'; +import { GModelSerializer } from './gmodel-serializer'; + +export const ModelState = Symbol('ModelState'); + +export interface ModelState { + set

(key: string, property: P): void; + setAll(properties: Record): void; + get

(key: string, guard?: (object: unknown) => object is P): P | undefined; + clear(key: string): void; + root: GModelRoot; + editMode: string; + isDirty: boolean; + readonly isReadonly: boolean; + readonly index: GModelIndex; +} + +@injectable() +export class DefaultModelState implements ModelState { + @inject(GModelIndex) + readonly index: GModelIndex; + + @inject(GModelSerializer) + protected serializer: GModelSerializer; + + protected properties = new Map(); + + protected _root: GModelRoot; + + editMode = EditMode.EDITABLE; + + isDirty = false; + + set

(key: string, property: P): void { + this.properties.set(key, property); + } + + setAll(properties: Record): void { + Object.keys(properties).forEach(key => this.properties.set(key, properties[key])); + } + + get

(key: string, guard?: (object: unknown) => object is P): P | undefined; clear(key: string): void; - root: GModelRoot; + readonly root: GModelRoot; + updateRoot(newRoot: GModelRoot): void; editMode: string; isDirty: boolean; sourceUri?: string; @@ -89,8 +90,12 @@ export class DefaultModelState implements ModelState { return this._root; } - public set root(root: GModelRoot) { - this.index.indexRoot(root); + protected set root(root: GModelRoot) { this._root = root; } + + updateRoot(newRoot: GModelRoot): void { + this.root = newRoot; + this.index.indexRoot(newRoot); + } } diff --git a/packages/server-node/src/gmodel-lib/gmodel-storage.ts b/packages/server-node/src/gmodel-lib/gmodel-storage.ts index 3ff8402..45a90bb 100644 --- a/packages/server-node/src/gmodel-lib/gmodel-storage.ts +++ b/packages/server-node/src/gmodel-lib/gmodel-storage.ts @@ -42,7 +42,7 @@ export class GModelStorage extends AbstractJsonModelStorage { const sourceUri = this.getSourceUri(action); const rootSchema = this.loadFromFile(sourceUri, GModelElementSchema.is); const root = this.modelSerializer.createRoot(rootSchema); - this.modelState.root = root; + this.modelState.updateRoot(root); } saveSourceModel(action: SaveModelAction): MaybePromise { From 7f580d811b274b9cf5c22c1658b160f1b174b0ce Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 25 May 2022 06:41:52 -0700 Subject: [PATCH 108/566] Fix mousetool action dispatching & routing point flickering (#182) - Ensure that aciion promises are correctly dispatched (I.e. non-blocking) in the `MouseTool` - Fix animation flickering for routing point changes by Removing the `EdgeMorphAnimation` from the `UpdateModelCommand (Also: Fix circular import of `AlignElementsActionHandler) Fixes https://github.com/eclipse-glsp/glsp/issues/653 Fixes https://github.com/eclipse-glsp/glsp/issues/654 --- .../src/base/model/update-model-command.ts | 82 +++++++++++-------- .../client/src/features/layout/di.config.ts | 8 +- .../src/features/mouse-tool/mouse-tool.ts | 10 ++- 3 files changed, 62 insertions(+), 38 deletions(-) diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index 7e34db6..4a7e481 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -13,25 +13,31 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; -import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; +import { Action, SetModelAction, toTypeGuard, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, multiInject, optional } from 'inversify'; import { ActionHandlerRegistry, + Animation, Command, CommandActionHandler, CommandExecutionContext, CommandReturn, IActionHandler, ILogger, + MorphEdgesAnimation, SModelRoot, + UpdateAnimationData, UpdateModelCommand } from 'sprotty'; import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../features/tool-feedback/model'; import { TYPES } from '../types'; -/* ActionHandler that transforms a SetModelAction into an (feedback-aware) UpdateModelAction. This can be done because in sprotty - * UpdateModel behaves the same as SetModel if no model is present yet.*/ +/** + * ActionHandler that transforms a {@link SetModelAction} into an {@link UpdateModelAction} that can be handled + * by the {@link FeedbackAwareUpdateModelCommand}. This can be done because in sprotty an {@link UpdateModelCommand} and + * a {@link SetModelCommand} have the same behavior of no model is present yet. + */ @injectable() export class SetModelActionHandler implements IActionHandler { handle(action: Action): Action | void { @@ -46,38 +52,41 @@ export interface SModelRootListener { } /** - * A special`UpdateModelCommand` that retrieves all registered `actions` from the `IFeedbackActionDispatcher` (if present) and applies their - * feedback to the `newRoot` before performing the update + * A special {@link UpdateModelCommand} that retrieves all registered {@link Action}s from the {@link IFeedbackActionDispatcher} + * (if present) and applies their feedback to the `newRoot` before performing the update. This enables persistent client-side feedback + * across model updates initiated by the GLSP server. */ @injectable() export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { - @inject(TYPES.ILogger) protected logger: ILogger; - @inject(TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher; - @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; - @multiInject(TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []; + @inject(TYPES.ILogger) + protected logger: ILogger; + + @inject(TYPES.IFeedbackActionDispatcher) + @optional() + protected feedbackActionDispatcher: IFeedbackActionDispatcher; + + @multiInject(TYPES.SModelRootListener) + @optional() + protected modelRootListeners: SModelRootListener[] = []; protected actionHandlerRegistry?: ActionHandlerRegistry; - constructor(@inject(TYPES.Action) action: UpdateModelAction) { + constructor( + @inject(TYPES.Action) action: UpdateModelAction, + @inject(TYPES.ActionHandlerRegistryProvider) + actionHandlerRegistryProvider: () => Promise + ) { super({ animate: true, ...action }); - } - - @postConstruct() - protected initialize(): void { - this.actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); + actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } protected override performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { - // Create a temporary context wich defines the `newRoot` as `root` + // Create a temporary context which defines the `newRoot` as `root` // This way we do not corrupt the redo/undo behavior of the super class const tempContext: CommandExecutionContext = { - root: newRoot, - duration: context.duration, - logger: context.logger, - modelChanged: context.modelChanged, - modelFactory: context.modelFactory, - syncer: context.syncer + ...context, + root: newRoot }; const feedbackCommands = this.getFeedbackCommands(this.actionHandlerRegistry); @@ -91,19 +100,24 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { protected getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { const result: Command[] = []; this.feedbackActionDispatcher.getRegisteredFeedback().forEach(action => { - const handler = registry.get(action.kind).find(h => h instanceof CommandActionHandler); - if (handler instanceof CommandActionHandler) { - result.push(handler.handle(action)); - } + const commands = registry + .get(action.kind) + .filter(toTypeGuard(CommandActionHandler)) + .map(handler => handler.handle(action)); + result.push(...commands); }); - // sort commands descanding by priority - return result.sort((a, b) => getPriority(b) - getPriority(a)); + // sort commands descanting by priority + return result.sort((a, b) => this.getPriority(b) - this.getPriority(a)); + } + + protected getPriority(command: Partial): number { + return command.priority ?? 0; } -} -function getPriority(command: Command): number { - if (command instanceof FeedbackCommand) { - return command.priority; + // Override the `createAnimations` implementation and remove the animation for edge morphing. Otherwise routing & reconnect + // handles flicker after each server update. + protected override createAnimations(data: UpdateAnimationData, root: SModelRoot, context: CommandExecutionContext): Animation[] { + const animations = super.createAnimations(data, root, context); + return animations.filter(animation => !(animation instanceof MorphEdgesAnimation)); } - return 0; } diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 5704ca4..88e4da4 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -15,10 +15,14 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { configureActionHandler, configureLayout, HBoxLayouter, VBoxLayouter } from 'sprotty'; -import { AlignElementsActionHandler } from '../..'; import { FreeFormLayouter } from './freeform-layout'; import { HBoxLayouterExt } from './hbox-layout'; -import { AlignElementsAction, ResizeElementsAction, ResizeElementsActionHandler } from './layout-elements-action'; +import { + AlignElementsAction, + AlignElementsActionHandler, + ResizeElementsAction, + ResizeElementsActionHandler +} from './layout-elements-action'; import { VBoxLayouterExt } from './vbox-layout'; const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index f72e96a..dd54e26 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { injectable, multiInject, optional } from 'inversify'; import { MouseListener, MouseTool, SModelElement, SModelRoot } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -82,8 +83,13 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { if (actions.length > 0) { event.preventDefault(); for (const actionOrPromise of actions) { - const action = await actionOrPromise; - await this.actionDispatcher.dispatch(action); + if (Action.is(actionOrPromise)) { + await this.actionDispatcher.dispatch(actionOrPromise); + } else { + actionOrPromise.then((action: Action) => { + this.actionDispatcher.dispatch(action); + }); + } } } } From 94088355d4bdf27524b1af1a7c2814525ea1e60d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 29 May 2022 04:18:46 -0700 Subject: [PATCH 109/566] GH-628 Use generic context menu mouse event instead of right click event (#183) * GH-628 Use generic context menu mouse event instead of right click event - Update to latest sprotty version - Update `SelectionServiceAwareContextMenuMouseListener` to use the `contextmenu` mouse event instead of the `right-click` event. Fixes https://github.com/eclipse-glsp/glsp/issues/628 * Update selection on any mouse buttons Sprotty only updates the selection on mouse button 0 (left-click). However, this is actually inconsistent with other software's behavior as they usually update the selection on any mouse click. If we do the same GLSP editors behavior much more consistently and we also can get rid of the special case of context menu invocation. Co-authored-by: Philip Langer --- ...rvice-aware-context-menu-mouse-listener.ts | 44 +++++----- .../src/features/mouse-tool/mouse-tool.ts | 4 - .../features/select/select-mouse-listener.ts | 82 ++++++++++++++++++- 3 files changed, 100 insertions(+), 30 deletions(-) diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 33d78f4..168d09a 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -15,53 +15,47 @@ ********************************************************************************/ import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { - ContextMenuProviderRegistry, - findParentByFeature, - IContextMenuServiceProvider, - isSelectable, - MouseListener, - SModelElement -} from 'sprotty'; +import { ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { TYPES } from '../../base/types'; import { SelectionService } from '../select/selection-service'; @injectable() export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { - @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService: IContextMenuServiceProvider; - @inject(TYPES.IContextMenuProviderRegistry) @optional() protected readonly menuProvider: ContextMenuProviderRegistry; - @inject(TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.IContextMenuServiceProvider) + @optional() + protected readonly contextMenuService?: IContextMenuServiceProvider; + + @inject(TYPES.IContextMenuProviderRegistry) + @optional() + protected readonly menuProvider?: ContextMenuProviderRegistry; + + @inject(TYPES.SelectionService) + protected selectionService: SelectionService; /** - * Opens the context menu on right-click. + * Opens the context menu. */ - override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { - if (event.button === 2 && this.contextMenuService && this.menuProvider) { - return this.openContextMenu(event, target); - } - return []; + override contextMenu(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + return this.openContextMenu(target, event); } /** * Opens the context menu. * - * - query the element on the click-target - * - select the element * - query the context menu service and the context menu elements * - show the context menu * - send a focus state change to indicate that the diagram becomes inactive, once the context menu is shown * * When the context menu is closed, we focus the diagram element again. */ - protected openContextMenu(event: MouseEvent, target: SModelElement): Promise[] { - const mousePosition = { x: event.x, y: event.y }; - const selectableTarget = findParentByFeature(target, isSelectable); - if (selectableTarget) { - selectableTarget.selected = true; - this.selectionService.updateSelection(target.root, [selectableTarget.id], []); + protected openContextMenu(target: SModelElement, event: MouseEvent): Promise[] { + if (!this.contextMenuService || !this.menuProvider) { + return []; } + const mousePosition = { x: event.x, y: event.y }; + const result = Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]) .then(([menuService, menuItems]) => menuService.show(menuItems, mousePosition, () => this.focusEventTarget(event))) .then((): Action => FocusStateChangedAction.create(false)); diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index dd54e26..0b78733 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -49,10 +49,6 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); } - contextMenu(model: SModelRoot, event: MouseEvent): void { - event.preventDefault(); - } - protected override handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { this.focusOnMouseEvent(methodName, model); const element = this.getTargetElement(model, event); diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 3d96dc2..4e0695b 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,7 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SelectMouseListener } from 'sprotty'; +import { Action, SelectAction } from '@eclipse-glsp/protocol'; +import { + BringToFrontAction, + findParentByFeature, + isCtrlOrCmd, + isSelectable, + SButton, + SelectMouseListener, + SModelElement, + SModelRoot, + SRoutableElement, + SRoutingHandle, + SwitchEditModeAction +} from 'sprotty'; +import { toArray } from 'sprotty/lib/utils/iterable'; import { DEFAULT_RANK, Ranked } from '../rank/model'; /** @@ -22,4 +36,70 @@ import { DEFAULT_RANK, Ranked } from '../rank/model'; */ export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { rank: number = DEFAULT_RANK - 1; /* we want to be executed before all default mouse listeners */ + + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + const result: Action[] = []; + if (this.buttonHandlerRegistry !== undefined && target instanceof SButton && target.enabled) { + const buttonHandler = this.buttonHandlerRegistry.get(target.type); + if (buttonHandler !== undefined) { + return buttonHandler.buttonPressed(target); + } + } + const selectableTarget = findParentByFeature(target, isSelectable); + if (selectableTarget !== undefined || target instanceof SModelRoot) { + this.hasDragged = false; + let deselect: SModelElement[] = []; + // multi-selection? + if (!isCtrlOrCmd(event)) { + deselect = toArray( + target.root.index + .all() + .filter( + element => + isSelectable(element) && + element.selected && + !(selectableTarget instanceof SRoutingHandle && element === (selectableTarget.parent as SModelElement)) + ) + ); + } + if (selectableTarget !== undefined) { + if (!selectableTarget.selected) { + this.wasSelected = false; + result.push( + SelectAction.create({ + selectedElementsIDs: [selectableTarget.id], + deselectedElementsIDs: deselect.map(e => e.id) + }) + ); + result.push(BringToFrontAction.create([selectableTarget.id])); + const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); + if (selectableTarget instanceof SRoutableElement) { + result.push( + SwitchEditModeAction.create({ + elementsToActivate: [selectableTarget.id], + elementsToDeactivate: routableDeselect + }) + ); + } else if (routableDeselect.length > 0) { + result.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); + } + } else if (isCtrlOrCmd(event)) { + this.wasSelected = false; + result.push(SelectAction.create({ deselectedElementsIDs: [selectableTarget.id] })); + if (selectableTarget instanceof SRoutableElement) { + result.push(SwitchEditModeAction.create({ elementsToDeactivate: [selectableTarget.id] })); + } + } else { + this.wasSelected = true; + } + } else { + result.push(SelectAction.create({ deselectedElementsIDs: deselect.map(e => e.id) })); + const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); + if (routableDeselect.length > 0) { + result.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); + } + } + } + return result; + } } From b9590dfdbe088ad89914da78e42b3c5048812e1b Mon Sep 17 00:00:00 2001 From: Victor Gabriel Dulca Date: Wed, 1 Jun 2022 15:39:23 +0200 Subject: [PATCH 110/566] Add sprotty projections to GLSP (#180) Convert parent bounds to local before setting marker projection bounds --- examples/workflow-glsp/src/di.config.ts | 3 + packages/client/css/decoration.css | 6 +- packages/client/css/glsp-sprotty.css | 39 ++++ packages/client/css/tool-palette.css | 4 +- .../src/features/validation/issue-marker.ts | 34 ++-- .../src/features/validation/validate.ts | 9 +- .../client/src/views/glsp-projection-view.tsx | 166 ++++++++++++++++++ packages/client/src/views/index.ts | 2 + 8 files changed, 245 insertions(+), 18 deletions(-) create mode 100644 packages/client/src/views/glsp-projection-view.tsx diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 601d67a..59cbb26 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -21,6 +21,8 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, editLabelFeature, + GLSPGraph, + GLSPProjectionView, GridSnapper, LogLevel, overrideViewerOptions, @@ -53,6 +55,7 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); + configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, GLSPProjectionView); configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); diff --git a/packages/client/css/decoration.css b/packages/client/css/decoration.css index 297087c..9927b94 100644 --- a/packages/client/css/decoration.css +++ b/packages/client/css/decoration.css @@ -23,16 +23,20 @@ .sprotty-issue-background { fill: var(--glsp-issue-background); + background-color: var(--glsp-issue-background); } .sprotty-issue.sprotty-error { fill: var(--glsp-error-foreground); + background-color: var(--glsp-error-foreground); } .sprotty-issue.sprotty-warning { fill: var(--glsp-warning-foreground); + background-color: var(--glsp-warning-foreground); } .sprotty-issue.sprotty-info { fill: var(--glsp-info-foreground); -} + background-color: var(--glsp-info-foreground); +} \ No newline at end of file diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 00fbbe0..5d751e7 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -215,3 +215,42 @@ text { font-size: 8pt; text-anchor: middle; } + +.glsp-projection { + width: 40%; + opacity: 1; +} + +.sprotty-projection-bar.vertical.bordered-projection-bar { + width: var(--theia-scrollbar-rail-width); +} + +.sprotty-projection-bar.horizontal.bordered-projection-bar { + height: var(--theia-scrollbar-rail-width); +} + +.projection-scroll-bar { + transition: opacity 0.8s linear; +} + +.mouse-enter .projection-scroll-bar { + opacity: 0.3; + background: transparent; + background-color: #aaa; + z-index: 1; +} + +.mouse-leave .projection-scroll-bar { + opacity: 0; + pointer-events: none; + background: transparent; + background-color: #aaa; + z-index: 1; +} + +.bordered-projection-bar { + border-left: 1px solid rgba(212, 212, 212, 0.2); + border-top: 1px solid rgba(212, 212, 212, 0.2); + fill: transparent; + width: var(--theia-scrollbar-rail-width); +} diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css index 3314923..1a35e2c 100644 --- a/packages/client/css/tool-palette.css +++ b/packages/client/css/tool-palette.css @@ -17,7 +17,7 @@ /* Css for main container */ .tool-palette { position: absolute; - right: 30px; + right: 40px; top: 25px; text-align: center; width: 225px; @@ -140,7 +140,7 @@ .minimize-palette-button { position: absolute; - right: 10px; + right: 20px; top: 35px; z-index: 1000; } diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index b074ef4..1b15d45 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -13,14 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Marker, MarkerKind } from '@eclipse-glsp/protocol'; -import { SDecoration, SIssue, SIssueMarker, SParentElement } from 'sprotty'; +import { Bounds, Marker, MarkerKind } from '@eclipse-glsp/protocol'; +import { isBoundsAware, Projectable, SDecoration, SIssue, SIssueMarker, SParentElement } from 'sprotty'; +import { getSeverity } from '../hover/hover'; -export class GIssueMarker extends SIssueMarker { +export class GIssueMarker extends SIssueMarker implements Projectable { constructor() { super(); this.features = new Set(SDecoration.DEFAULT_FEATURES); } + projectionCssClasses: string[]; + projectedBounds?: Bounds; + override issues: SIssue[] = []; + override type = 'marker'; + + computeProjectionCssClasses(): void { + const severityCss = getSeverity(this); + this.projectionCssClasses = ['sprotty-issue', 'sprotty-' + severityCss]; + } } /** @@ -31,14 +41,15 @@ export class GIssueMarker extends SIssueMarker { * @returns the child `SIssueMarker` or a new `SIssueMarker` if no such child exists. */ export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMarker { - let issueMarker: SIssueMarker | undefined; + let issueMarker: GIssueMarker | undefined; issueMarker = getSIssueMarker(modelElement); if (issueMarker === undefined) { issueMarker = new GIssueMarker(); - issueMarker.type = 'marker'; - issueMarker.issues = new Array(); + if (isBoundsAware(modelElement)) { + issueMarker.projectedBounds = modelElement.parentToLocal(modelElement.bounds); + } modelElement.add(issueMarker); } @@ -51,11 +62,11 @@ export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMar * @param modelElement for which the `SIssueMarker` should be retrieved. * @returns the child `SIssueMarker` or `undefined` if no such child exists. */ -export function getSIssueMarker(modelElement: SParentElement): SIssueMarker | undefined { - let issueMarker: SIssueMarker | undefined; +export function getSIssueMarker(modelElement: SParentElement): GIssueMarker | undefined { + let issueMarker: GIssueMarker | undefined; for (const child of modelElement.children) { - if (child instanceof SIssueMarker) { + if (child instanceof GIssueMarker) { issueMarker = child; } } @@ -69,8 +80,8 @@ export function getSIssueMarker(modelElement: SParentElement): SIssueMarker | un * @param marker `Marker` for that an `SIssue` should be created. * @returns the created `SIssue`. */ -export function createSIssue(marker: Marker): SIssue { - const issue: SIssue = new SIssue(); +export function createSIssue(marker: Marker, parent?: SParentElement): SIssue { + const issue = new SIssue(); issue.message = marker.description; switch (marker.kind) { @@ -87,6 +98,5 @@ export function createSIssue(marker: Marker): SIssue { break; } } - return issue; } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 7df695f..5320ae9 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -22,7 +22,7 @@ import { removeCssClasses } from '../../utils/smodel-util'; import { getSeverity } from '../hover/hover'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../tool-feedback/model'; -import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from './issue-marker'; +import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker, GIssueMarker } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and @@ -151,6 +151,9 @@ export class ApplyMarkersCommand extends FeedbackCommand { const issueMarker = getOrCreateSIssueMarker(modelElement); const issue = createSIssue(marker); issueMarker.issues.push(issue); + if (issueMarker instanceof GIssueMarker) { + issueMarker.computeProjectionCssClasses(); + } addMaxSeverityCSSClassToIssueParent(modelElement, issueMarker); } }); @@ -160,7 +163,6 @@ export class ApplyMarkersCommand extends FeedbackCommand { function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { const maxSeverityCSSClass = getSeverity(issueMarker); - if (!modelElement.cssClasses) { modelElement.cssClasses = [maxSeverityCSSClass]; } else { @@ -170,7 +172,8 @@ function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issue } function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { - removeCssClasses(modelElement, [getSeverity(issueMarker)]); + const severity = getSeverity(issueMarker); + removeCssClasses(modelElement, [severity]); } /** diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx new file mode 100644 index 0000000..9bdac43 --- /dev/null +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -0,0 +1,166 @@ +/******************************************************************************** + * Copyright (c) 2017-2018 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** @jsx html */ +import { Bounds } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { h, VNode, VNodeStyle } from 'snabbdom'; +import { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + html, + IViewArgs, + ProjectedViewportView, + ProjectionParams, + RenderingContext, + setClass, + ViewportRootElement, + ViewProjection +} from 'sprotty'; + +/** + * Special viewport root view that renders horizontal and vertical projection bars for quick navigation. + */ +@injectable() +export class GLSPProjectionView extends ProjectedViewportView { + override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { + return ( +

+ ); + } + + protected override renderSvg(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { + const transform = `scale(${model.zoom}) translate(${-model.scroll.x},${-model.scroll.y})`; + const ns = 'http://www.w3.org/2000/svg'; + const svg = h('svg', { ns, style: { height: '100%' } }, h('g', { ns, attrs: { transform } }, context.renderChildren(model))); + return svg; + } + + protected override renderProjectionBar( + projections: ViewProjection[], + model: Readonly, + modelBounds: Bounds, + orientation: 'horizontal' | 'vertical' + ): VNode { + const params: ProjectionParams = { modelBounds, orientation } as ProjectionParams; + // NOTE: Here we assume that the projection bars have the same size as the diagram canvas, + // i.e. they are drawn as overlay above the canvas. + params.factor = + orientation === 'horizontal' ? model.canvasBounds.width / modelBounds.width : model.canvasBounds.height / modelBounds.height; + params.zoomedFactor = params.factor / model.zoom; + return ( +
+ {this.renderViewport(model, params)} + {projections.map(p => this.renderProjection(p, model, params))} +
+ ); + } + + protected override renderViewport(model: Readonly, params: ProjectionParams): VNode { + let canvasSize; + let viewportPos: number; + if (params.orientation === 'horizontal') { + canvasSize = model.canvasBounds.width; + viewportPos = (model.scroll.x - params.modelBounds.x) * params.factor; + } else { + canvasSize = model.canvasBounds.height; + viewportPos = (model.scroll.y - params.modelBounds.y) * params.factor; + } + let viewportSize = canvasSize * params.zoomedFactor; + if (viewportPos < 0) { + viewportSize += viewportPos; + viewportPos = 0; + } else if (viewportPos > canvasSize) { + viewportPos = canvasSize; + } + if (viewportSize < 0) { + viewportSize = 0; + } else if (viewportPos + viewportSize > canvasSize) { + viewportSize = canvasSize - viewportPos; + } + if (Math.abs(viewportSize - canvasSize) < 1) { + viewportSize = 0; + } + const style: VNodeStyle = + params.orientation === 'horizontal' + ? { + left: `${viewportPos}px`, + width: `${viewportSize}px`, + border: 'none' + } + : { + top: `${viewportPos}px`, + height: `${viewportSize}px`, + border: 'none' + }; + return
; + } + + protected override renderProjection(projection: ViewProjection, model: Readonly, params: ProjectionParams): VNode { + let canvasSize; + let projPos; + let projSize: number; + if (params.orientation === 'horizontal') { + canvasSize = model.canvasBounds.width; + projPos = (projection.projectedBounds.x - params.modelBounds.x) * params.factor; + projSize = projection.projectedBounds.width * params.factor; + } else { + canvasSize = model.canvasBounds.height; + projPos = (projection.projectedBounds.y - params.modelBounds.y) * params.factor; + projSize = projection.projectedBounds.height * params.factor; + } + if (projPos < 0) { + projSize += projPos; + projPos = 0; + } else if (projPos > canvasSize) { + projPos = canvasSize; + } + if (projSize < 0) { + projSize = 0; + } else if (projPos + projSize > canvasSize) { + projSize = canvasSize - projPos; + } + const style: VNodeStyle = + params.orientation === 'horizontal' + ? { + left: `${projPos}px`, + width: `${projSize}px`, + height: '60%' + } + : { + top: `${projPos}px`, + height: `${projSize}px`, + width: '60%' + }; + const result = ( +
+ ); + projection.cssClasses.forEach(cl => setClass(result, cl, true)); + return result; + } +} diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index b9a4238..fdf04e0 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -16,6 +16,8 @@ export * from './base-view-module'; export * from './compartments'; export * from './glsp-edge-view'; +export * from './glsp-projection-view'; export * from './issue-marker-view'; export * from './rounded-corner'; export * from './rounded-corner-view'; + From 00ecdf437789dac3ccee34b3dad082f0ce28ab46 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 1 Jun 2022 16:47:24 +0200 Subject: [PATCH 111/566] Rename `ModelSourceChangedAction` etc. (#184) ... to `SourceModelChangedAction`. This is to be consistent with the term source model that we now use to refer to the underlying model from which the GModel is created. https://github.com/eclipse-glsp/glsp/issues/655 --- packages/client/src/base/container-modules.ts | 4 +-- .../di.config.ts | 10 +++---- .../source-model-changed-action-handler.ts} | 24 ++++++++-------- packages/client/src/index.ts | 6 ++-- .../src/action-protocol/element-navigation.ts | 2 +- .../src/action-protocol/model-data.spec.ts | 26 ++++++++--------- .../src/action-protocol/model-data.ts | 28 +++++++++---------- .../src/action-protocol/model-saving.ts | 6 ++-- 8 files changed, 53 insertions(+), 53 deletions(-) rename packages/client/src/features/{model-source-watcher => source-model-watcher}/di.config.ts (74%) rename packages/client/src/features/{model-source-watcher/model-source-changed-action-handler.ts => source-model-watcher/source-model-changed-action-handler.ts} (73%) diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 5453e2d..4328d7e 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -38,10 +38,10 @@ import glspEditLabelModule from '../features/edit-label/di.config'; import modelHintsModule from '../features/hints/di.config'; import glspHoverModule from '../features/hover/di.config'; import layoutModule from '../features/layout/di.config'; -import modelSourceWatcherModule from '../features/model-source-watcher/di.config'; import glspMouseToolModule from '../features/mouse-tool/di.config'; import { navigationModule } from '../features/navigation/di.config'; import glspSelectModule from '../features/select/di.config'; +import sourceModelWatcherModule from '../features/source-model-watcher/di.config'; import toolFeedbackModule from '../features/tool-feedback/di.config'; import toolPaletteModule from '../features/tool-palette/di.config'; import { enableDefaultToolsOnFocusLossModule, toolsModule } from '../features/tools/di.config'; @@ -73,7 +73,7 @@ export const DEFAULT_MODULES = [ markerNavigatorModule, modelHintsModule, modelSourceModule, - modelSourceWatcherModule, + sourceModelWatcherModule, navigationModule, openModule, toolPaletteModule, diff --git a/packages/client/src/features/model-source-watcher/di.config.ts b/packages/client/src/features/source-model-watcher/di.config.ts similarity index 74% rename from packages/client/src/features/model-source-watcher/di.config.ts rename to packages/client/src/features/source-model-watcher/di.config.ts index 026a92a..fa6bcec 100644 --- a/packages/client/src/features/model-source-watcher/di.config.ts +++ b/packages/client/src/features/source-model-watcher/di.config.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelSourceChangedAction } from '@eclipse-glsp/protocol'; +import { SourceModelChangedAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureActionHandler } from 'sprotty'; -import { ModelSourceChangedActionHandler } from './model-source-changed-action-handler'; +import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; -const modelSourceWatcherModule = new ContainerModule((bind, _unbind, isBound) => { - configureActionHandler({ bind, isBound }, ModelSourceChangedAction.KIND, ModelSourceChangedActionHandler); +const sourceModelWatcherModule = new ContainerModule((bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, SourceModelChangedAction.KIND, SourceModelChangedActionHandler); }); -export default modelSourceWatcherModule; +export default sourceModelWatcherModule; diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts similarity index 73% rename from packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts rename to packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts index 07c8781..f9159a5 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts +++ b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts @@ -13,43 +13,43 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ModelSourceChangedAction, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; +import { Action, ServerMessageAction, ServerStatusAction, SourceModelChangedAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { IActionDispatcher, IActionHandler, ViewerOptions } from 'sprotty'; import { TYPES } from '../../base/types'; /** - * An external handler of the model source change event. + * An external handler of the source model change event. * * This allows external applications to react specifically to this event, e.g. by bringing up the diagram, * check their dirty state, show a dialog, etc. */ @injectable() -export abstract class ExternalModelSourceChangedHandler { +export abstract class ExternalSourceModelChangedHandler { /** - * Notifies about a change of the model source. + * Notifies about a change of the source model. * @returns actions to be dispatched after the notification. */ - abstract notifyModelSourceChange(modelSourceName: string, options: ViewerOptions): Promise; + abstract notifySourceModelChange(sourceModelName: string, options: ViewerOptions): Promise; } @injectable() -export class ModelSourceChangedActionHandler implements IActionHandler { +export class SourceModelChangedActionHandler implements IActionHandler { @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; @inject(TYPES.ViewerOptions) protected options: ViewerOptions; - @inject(ExternalModelSourceChangedHandler) + @inject(ExternalSourceModelChangedHandler) @optional() - protected externalModelSourceChangedHandler?: ExternalModelSourceChangedHandler; + protected externalModelSourceChangedHandler?: ExternalSourceModelChangedHandler; handle(action: Action): void { - if (ModelSourceChangedAction.is(action)) { + if (SourceModelChangedAction.is(action)) { if (this.externalModelSourceChangedHandler) { this.externalModelSourceChangedHandler - .notifyModelSourceChange(action.modelSourceName, this.options) + .notifySourceModelChange(action.sourceModelName, this.options) .then(actions => this.dispatcher.dispatchAll(actions)); return; } @@ -57,8 +57,8 @@ export class ModelSourceChangedActionHandler implements IActionHandler { } } - protected showSimpleNotification(action: ModelSourceChangedAction): void { - const message = `The model source ${action.modelSourceName} has changed. You might want to consider reloading.`; + protected showSimpleNotification(action: SourceModelChangedAction): void { + const message = `The source model ${action.sourceModelName} has changed. You might want to consider reloading.`; const timeout = 0; const severity = 'WARNING'; this.dispatcher.dispatchAll([ diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6ddd3fc..add34db 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -22,11 +22,11 @@ import glspEditLabelModule from './features/edit-label/di.config'; import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; import layoutModule from './features/layout/di.config'; -import modelSourceWatcherModule from './features/model-source-watcher/di.config'; import glspMouseToolModule from './features/mouse-tool/di.config'; import { navigationModule } from './features/navigation/di.config'; import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; +import sourceModelWatcherModule from './features/source-model-watcher/di.config'; import toolFeedbackModule from './features/tool-feedback/di.config'; import paletteModule from './features/tool-palette/di.config'; import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; @@ -66,7 +66,6 @@ export * from './features/hints/model'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; export * from './features/layout/layout-elements-action'; -export * from './features/model-source-watcher/model-source-changed-action-handler'; export * from './features/mouse-tool/mouse-tool'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; @@ -74,6 +73,7 @@ export * from './features/rank/model'; export * from './features/reconnect/model'; export * from './features/save/model'; export * from './features/save/save-keylistener'; +export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/tool-feedback/change-bounds-tool-feedback'; export * from './features/tool-feedback/creation-tool-feedback'; export * from './features/tool-feedback/css-feedback'; @@ -121,6 +121,6 @@ export { navigationModule, markerNavigatorModule, glspDecorationModule, - modelSourceWatcherModule, + sourceModelWatcherModule as modelSourceWatcherModule, glspViewportModule }; diff --git a/packages/protocol/src/action-protocol/element-navigation.ts b/packages/protocol/src/action-protocol/element-navigation.ts index 6c5037c..92f4c78 100644 --- a/packages/protocol/src/action-protocol/element-navigation.ts +++ b/packages/protocol/src/action-protocol/element-navigation.ts @@ -324,7 +324,7 @@ export namespace SetResolvedNavigationTargetAction { } /** - * If a {@link NavigationTarget} cannot be resolved or the resolved target is something that is not part of our model source, e.g., + * If a {@link NavigationTarget} cannot be resolved or the resolved target is something that is not part of our source model, e.g., * a separate documentation file, a {@link NavigateToExternalTargetAction} may be sent. Since the target it outside of the model scope such * an action would be typically handled by an integration layer (such as the surrounding IDE). * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks diff --git a/packages/protocol/src/action-protocol/model-data.spec.ts b/packages/protocol/src/action-protocol/model-data.spec.ts index 65d6186..684bcff 100644 --- a/packages/protocol/src/action-protocol/model-data.spec.ts +++ b/packages/protocol/src/action-protocol/model-data.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { ModelSourceChangedAction, RequestModelAction, SetModelAction, UpdateModelAction } from './model-data'; +import { RequestModelAction, SetModelAction, SourceModelChangedAction, UpdateModelAction } from './model-data'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. @@ -140,31 +140,31 @@ describe('Model data actions', () => { }); }); - describe('ModelSourceChangedAction', () => { + describe('SourceModelChangedAction', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { - const action: ModelSourceChangedAction = { - kind: 'modelSourceChanged', - modelSourceName: '' + const action: SourceModelChangedAction = { + kind: 'sourceModelChanged', + sourceModelName: '' }; - expect(ModelSourceChangedAction.is(action)).to.be.true; + expect(SourceModelChangedAction.is(action)).to.be.true; }); it('should return false for `undefined`', () => { - expect(ModelSourceChangedAction.is(undefined)).to.be.false; + expect(SourceModelChangedAction.is(undefined)).to.be.false; }); it('should return false for an object that does not have all required interface properties', () => { - expect(ModelSourceChangedAction.is({ kind: 'notTheRightOne' })).to.be.false; + expect(SourceModelChangedAction.is({ kind: 'notTheRightOne' })).to.be.false; }); }); describe('create', () => { it('should return an object conforming to the interface with matching properties for the given required arguments', () => { - const expected: ModelSourceChangedAction = { - kind: 'modelSourceChanged', - modelSourceName: 'myModelSource' + const expected: SourceModelChangedAction = { + kind: 'sourceModelChanged', + sourceModelName: 'myModelSource' }; - const { modelSourceName } = expected; - expect(ModelSourceChangedAction.create(modelSourceName)).to.deep.equals(expected); + const { sourceModelName: sourceModelName } = expected; + expect(SourceModelChangedAction.create(sourceModelName)).to.deep.equals(expected); }); }); }); diff --git a/packages/protocol/src/action-protocol/model-data.ts b/packages/protocol/src/action-protocol/model-data.ts index 33800a9..02b0211 100644 --- a/packages/protocol/src/action-protocol/model-data.ts +++ b/packages/protocol/src/action-protocol/model-data.ts @@ -50,7 +50,7 @@ export namespace RequestModelAction { } /** - * Sent from the model source to the client in order to set the model. If a model is already present, it is replaced. + * Sent from the server to the client in order to set the model. If a model is already present, it is replaced. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks * and creating new `SetModelActions`. */ @@ -113,31 +113,31 @@ export namespace UpdateModelAction { } /** - * Sent from the server to the client in order to indicate that the model source has changed. - * The model source denotes the data source from which the diagram has been originally derived (such as a file, a database, etc.). + * Sent from the server to the client in order to indicate that the source model has changed. + * The source model denotes the data source from which the diagram has been originally derived (such as a file, a database, etc.). * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `ModelSourceChangedActions`. + * and creating new `SourceModelChangedActions`. */ -export interface ModelSourceChangedAction extends Action { - kind: typeof ModelSourceChangedAction.KIND; +export interface SourceModelChangedAction extends Action { + kind: typeof SourceModelChangedAction.KIND; /** - * A human readable name of the model source (e.g. the file name). + * A human readable name of the source model (e.g. the file name). */ - modelSourceName: string; + sourceModelName: string; } -export namespace ModelSourceChangedAction { - export const KIND = 'modelSourceChanged'; +export namespace SourceModelChangedAction { + export const KIND = 'sourceModelChanged'; - export function is(object: any): object is ModelSourceChangedAction { - return Action.hasKind(object, KIND) && hasStringProp(object, 'modelSourceName'); + export function is(object: any): object is SourceModelChangedAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'sourceModelName'); } - export function create(modelSourceName: string): ModelSourceChangedAction { + export function create(sourceModelName: string): SourceModelChangedAction { return { kind: KIND, - modelSourceName + sourceModelName: sourceModelName }; } } diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index ac90508..912ab49 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -17,8 +17,8 @@ import { hasBooleanProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; /** - * Sent from the client to the server in order to persist the current model state back to the model source. - * A new fileUri can be defined to save the model to a new destination different from its original model source. + * Sent from the client to the server in order to persist the current model state back to the source model. + * A new fileUri can be defined to save the model to a new destination different from its original source model. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks * and creating new `SaveModelActions`. */ @@ -47,7 +47,7 @@ export namespace SaveModelAction { /** * The server sends this action to indicate to the client that the current model state on the server does not correspond - * to the persisted model state of the model source. A client may ignore such an action or use it to indicate to the user the dirty state. + * to the persisted model state of the source model. A client may ignore such an action or use it to indicate to the user the dirty state. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks * and creating new `SetDirtyStateActions`. */ From 24c5966fc878194bf427631e0a863c49525c41aa Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 1 Jun 2022 08:37:33 -0700 Subject: [PATCH 112/566] Update to stable sprotty version in preparation for the 1.0.0 release (#185) --- packages/client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/package.json b/packages/client/package.json index f952427..fc63556 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -36,7 +36,7 @@ "dependencies": { "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", - "sprotty": "next" + "sprotty": "0.12.0" }, "devDependencies": { "@vscode/codicons": "^0.0.25", From 732683f94971f4b1fc647cdb26a54049f7e122a3 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 1 Jun 2022 14:08:09 +0200 Subject: [PATCH 113/566] Rename `ModelSourceChangedAction` ... to `SourceModelChangedAction`. This is to be consistent with the term source model that we now use to refer to the underlying model from which the GModel is created. https://github.com/eclipse-glsp/glsp/issues/655 --- packages/server-node/src/di/diagram-module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index fa43104..64e349e 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -18,7 +18,6 @@ import { DeleteMarkersAction, ExportSvgAction, FitToScreenAction, - ModelSourceChangedAction, NavigateToExternalTargetAction, NavigateToTargetAction, RequestBoundsAction, @@ -39,6 +38,7 @@ import { SetResolvedNavigationTargetAction, SetTypeHintsAction, SetViewportAction, + SourceModelChangedAction, TriggerEdgeCreationAction, TriggerNodeCreationAction, UpdateModelAction @@ -311,7 +311,7 @@ export abstract class DiagramModule extends GLSPModule { binding.add(ExportSvgAction.KIND); binding.add(DeleteMarkersAction.KIND); binding.add(FitToScreenAction.KIND); - binding.add(ModelSourceChangedAction.KIND); + binding.add(SourceModelChangedAction.KIND); binding.add(NavigateToTargetAction.KIND); binding.add(NavigateToExternalTargetAction.KIND); binding.add(RequestBoundsAction.KIND); From cee234dd8834793960d63a0f7290c73dfa233df9 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 29 Jun 2022 05:02:41 -0700 Subject: [PATCH 114/566] Update workflow example to not use `GLSPProjectionView` (#186) Temporary remove usage of `GLSPProjectionView` in the workflow example. Can be readded once https://github.com/eclipse-glsp/glsp/issues/689 is resolved. --- examples/workflow-glsp/src/di.config.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 59cbb26..601d67a 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -21,8 +21,6 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, editLabelFeature, - GLSPGraph, - GLSPProjectionView, GridSnapper, LogLevel, overrideViewerOptions, @@ -55,7 +53,6 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); - configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, GLSPProjectionView); configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); From 6bb96fbbace55657fa467f5fd8d50f66516f82c5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 1 Jul 2022 18:33:54 +0200 Subject: [PATCH 115/566] v1.0.0 - Use `yarn upgrade:next` to update our fluent dependencies - Update changelog - Use 1.0.0 version of @eclipse-glsp/config package - Fix publish scripts --- examples/workflow-glsp/package.json | 4 +-- examples/workflow-glsp/scripts/download.ts | 10 ++++--- examples/workflow-standalone/package.json | 6 ++--- packages/client/package.json | 4 +-- packages/client/src/features/hover/hover.ts | 26 ++----------------- .../src/features/validation/issue-marker.ts | 16 ++++++++++-- .../src/features/validation/validate.ts | 3 +-- packages/protocol/package.json | 6 ++--- 8 files changed, 33 insertions(+), 42 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index babf64f..628f9af 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "0.9.0", + "version": "1.0.0", "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -26,7 +26,7 @@ } ], "dependencies": { - "@eclipse-glsp/client": "0.9.0", + "@eclipse-glsp/client": "^1.0.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index 10ba3c9..ad3e584 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -17,18 +17,20 @@ import download from 'mvn-artifact-download'; import { join, resolve } from 'path'; const downloadDir = resolve(join(__dirname)) + '/../../..'; -const mavenRepository = 'https://oss.sonatype.org/content/repositories/snapshots/'; +const mavenRepository = 'https://repo1.maven.org/maven2/'; const groupId = 'org.eclipse.glsp.example'; const artifactId = 'org.eclipse.glsp.example.workflow'; -const version = '0.9.0'; +const version = '1.0.0'; const classifier = 'glsp'; console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); -download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository).then(() => +const isSnapShot = false; +download({ groupId, artifactId, version, classifier, isSnapShot }, downloadDir, mavenRepository).then(() => console.log( 'Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-' + version + - '-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n' + + `${isSnapShot ? '-SNAPSHOT' : ''}` + + '-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n' + 'After starting the server, access the following file locally in your browser to see the running example:\n' + './examples/workflow-standalone/app/diagram.html' ) diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 160949e..0777838 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "workflow-standalone", - "version": "0.9.0", + "version": "1.0.0", "description": "Standalone browser-app for the Workflow example", "author": { "name": "Eclipse GLSP" @@ -20,8 +20,8 @@ } ], "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "0.9.0", - "@eclipse-glsp/client": "0.9.0" + "@eclipse-glsp-examples/workflow-glsp": "^1.0.0", + "@eclipse-glsp/client": "^1.0.0" }, "devDependencies": { "circular-dependency-plugin": "^5.2.2", diff --git a/packages/client/package.json b/packages/client/package.json index fc63556..4807bf2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "0.9.0", + "version": "1.0.0", "description": "A sprotty-based client for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -34,7 +34,7 @@ "css" ], "dependencies": { - "@eclipse-glsp/protocol": "0.9.0", + "@eclipse-glsp/protocol": "^1.0.0", "autocompleter": "5.1.0", "sprotty": "0.12.0" }, diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index f967b16..b8294f5 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -23,20 +23,11 @@ import { SModelRootSchema } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { - EMPTY_ROOT, - HoverFeedbackAction, - HoverMouseListener, - IActionHandler, - ICommand, - SIssueMarker, - SIssueSeverity, - SModelElement -} from 'sprotty'; +import { EMPTY_ROOT, HoverFeedbackAction, HoverMouseListener, IActionHandler, ICommand, SModelElement } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool-actions'; import { EdgeCreationTool } from '../tools/edge-creation-tool'; -import { GIssueMarker } from '../validation/issue-marker'; +import { getSeverity, GIssueMarker } from '../validation/issue-marker'; @injectable() export class GlspHoverMouseListener extends HoverMouseListener implements IActionHandler { protected enableHover = true; @@ -117,16 +108,3 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio return bounds; } } - -export function getSeverity(marker: SIssueMarker): SIssueSeverity { - let currentSeverity: SIssueSeverity = 'info'; - for (const severity of marker.issues.map(s => s.severity)) { - if (severity === 'error') { - return severity; - } - if (severity === 'warning' && currentSeverity === 'info') { - currentSeverity = severity; - } - } - return currentSeverity; -} diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 1b15d45..f293723 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Bounds, Marker, MarkerKind } from '@eclipse-glsp/protocol'; -import { isBoundsAware, Projectable, SDecoration, SIssue, SIssueMarker, SParentElement } from 'sprotty'; -import { getSeverity } from '../hover/hover'; +import { isBoundsAware, Projectable, SDecoration, SIssue, SIssueMarker, SIssueSeverity, SParentElement } from 'sprotty'; export class GIssueMarker extends SIssueMarker implements Projectable { constructor() { @@ -100,3 +99,16 @@ export function createSIssue(marker: Marker, parent?: SParentElement): SIssue { } return issue; } + +export function getSeverity(marker: SIssueMarker): SIssueSeverity { + let currentSeverity: SIssueSeverity = 'info'; + for (const severity of marker.issues.map(s => s.severity)) { + if (severity === 'error') { + return severity; + } + if (severity === 'warning' && currentSeverity === 'info') { + currentSeverity = severity; + } + } + return currentSeverity; +} diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 5320ae9..efaef7c 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -19,10 +19,9 @@ import { CommandExecutionContext, CommandReturn, IActionDispatcher, IActionHandl import { EditorContextService } from '../../base/editor-context-service'; import { TYPES } from '../../base/types'; import { removeCssClasses } from '../../utils/smodel-util'; -import { getSeverity } from '../hover/hover'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../tool-feedback/model'; -import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker, GIssueMarker } from './issue-marker'; +import { createSIssue, getOrCreateSIssueMarker, getSeverity, getSIssueMarker, GIssueMarker } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 5b07e91..b0033b4 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "0.9.0", + "version": "1.0.0", "description": "The protocol definition for client-server communication in GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -33,9 +33,9 @@ "src" ], "dependencies": { + "sprotty-protocol": "0.12.0", "uuid": "7.0.3", - "vscode-ws-jsonrpc": "0.2.0", - "sprotty-protocol": "next" + "vscode-ws-jsonrpc": "0.2.0" }, "devDependencies": { "@types/uuid": "3.4.5" From 61448915a65bd9b988dfac9645c134cc86c0b8be Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 3 Jul 2022 22:15:14 +0200 Subject: [PATCH 116/566] v1.0.0 - Upgrade yarn.lock - Adapt changelog - Upgrade versions to 1.0.0 - Update security md --- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 8 ++++---- packages/server-node/package.json | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 8e66312..9bfe874 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "0.9.0", + "version": "1.0.0", "description": "GLSP node server for the workflow example", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -37,8 +37,8 @@ "src" ], "dependencies": { - "@eclipse-glsp/server-node": "0.9.0", - "@eclipse-glsp/layout-elk": "0.9.0" + "@eclipse-glsp/layout-elk": "^1.0.0", + "@eclipse-glsp/server-node": "^1.0.0" }, "scripts": { "clean": "rimraf tsconfig.tsbuildinfo lib", diff --git a/packages/graph/package.json b/packages/graph/package.json index 2c343f7..fa98f30 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "0.9.0", + "version": "1.0.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -38,7 +38,7 @@ "css" ], "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "1.0.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index f7d01ce..4fc0096 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "0.9.0", + "version": "1.0.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -38,10 +38,10 @@ "css" ], "dependencies": { - "sprotty-elk": "next", + "@eclipse-glsp/server-node": "^1.0.0", + "elkjs": "^0.7.1", "inversify": "^5.1.1", - "@eclipse-glsp/server-node": "0.9.0", - "elkjs": "^0.7.1" + "sprotty-elk": "0.12.0" }, "scripts": { "clean": "rimraf tsconfig.tsbuildinfo lib", diff --git a/packages/server-node/package.json b/packages/server-node/package.json index 1ef2088..288a0e4 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server-node", - "version": "0.9.0", + "version": "1.0.0", "description": "A node.js server framework for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -37,12 +37,12 @@ "src" ], "dependencies": { - "@eclipse-glsp/protocol": "next", - "@eclipse-glsp/graph": "0.9.0", - "inversify": "5.1.1", + "@eclipse-glsp/graph": "^1.0.0", + "@eclipse-glsp/protocol": "1.0.0", + "@types/uuid": "8.3.1", "commander": "^8.3.0", - "winston": "^3.3.3", - "@types/uuid": "8.3.1" + "inversify": "5.1.1", + "winston": "^3.3.3" }, "devDependencies": { "@types/fs-extra": "^9.0.13" From 195628045e34b7f3077b3a6e52dbb9e326f91446 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 6 Jul 2022 16:08:10 +0200 Subject: [PATCH 117/566] Switch to next versions --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 4 ++-- packages/protocol/package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 628f9af..f794b01 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "1.0.0", + "version": "1.1.0-next", "description": "GLSP diagrams for the Workflow DSL", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -26,7 +26,7 @@ } ], "dependencies": { - "@eclipse-glsp/client": "^1.0.0", + "@eclipse-glsp/client": "1.1.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 0777838..a9b48bf 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "workflow-standalone", - "version": "1.0.0", + "version": "1.1.0-next", "description": "Standalone browser-app for the Workflow example", "author": { "name": "Eclipse GLSP" @@ -20,8 +20,8 @@ } ], "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "^1.0.0", - "@eclipse-glsp/client": "^1.0.0" + "@eclipse-glsp-examples/workflow-glsp": "1.1.0-next", + "@eclipse-glsp/client": "1.1.0-next" }, "devDependencies": { "circular-dependency-plugin": "^5.2.2", diff --git a/packages/client/package.json b/packages/client/package.json index 4807bf2..41ade36 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "1.0.0", + "version": "1.1.0-next", "description": "A sprotty-based client for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -34,7 +34,7 @@ "css" ], "dependencies": { - "@eclipse-glsp/protocol": "^1.0.0", + "@eclipse-glsp/protocol": "1.1.0-next", "autocompleter": "5.1.0", "sprotty": "0.12.0" }, diff --git a/packages/protocol/package.json b/packages/protocol/package.json index b0033b4..d93025c 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "1.0.0", + "version": "1.1.0-next", "description": "The protocol definition for client-server communication in GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ From 5cae85ce52213e4bc59fc515904081ad3e16a2b1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 6 Jul 2022 10:34:19 +0200 Subject: [PATCH 118/566] Switch to next versions --- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 8 +++++--- packages/server-node/package.json | 6 +++--- packages/server-node/src/di/diagram-module.ts | 13 +++++++++++++ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 9bfe874..902a54b 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "1.0.0", + "version": "1.1.0-next", "description": "GLSP node server for the workflow example", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -37,8 +37,8 @@ "src" ], "dependencies": { - "@eclipse-glsp/layout-elk": "^1.0.0", - "@eclipse-glsp/server-node": "^1.0.0" + "@eclipse-glsp/layout-elk": "1.1.0-next", + "@eclipse-glsp/server-node": "1.1.0-next" }, "scripts": { "clean": "rimraf tsconfig.tsbuildinfo lib", diff --git a/packages/graph/package.json b/packages/graph/package.json index fa98f30..f79808a 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "1.0.0", + "version": "1.1.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -38,7 +38,7 @@ "css" ], "dependencies": { - "@eclipse-glsp/protocol": "1.0.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 4fc0096..212a459 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "1.0.0", + "version": "1.1.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -38,11 +38,13 @@ "css" ], "dependencies": { - "@eclipse-glsp/server-node": "^1.0.0", + "@eclipse-glsp/server-node": "1.1.0-next", "elkjs": "^0.7.1", - "inversify": "^5.1.1", "sprotty-elk": "0.12.0" }, + "peerDependencies": { + "inversify": "^5.1.1" + }, "scripts": { "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "yarn run clean && tsc", diff --git a/packages/server-node/package.json b/packages/server-node/package.json index 288a0e4..f6479af 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server-node", - "version": "1.0.0", + "version": "1.1.0-next", "description": "A node.js server framework for GLSP", "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", "keywords": [ @@ -37,8 +37,8 @@ "src" ], "dependencies": { - "@eclipse-glsp/graph": "^1.0.0", - "@eclipse-glsp/protocol": "1.0.0", + "@eclipse-glsp/graph": "1.1.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "inversify": "5.1.1", diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index 64e349e..5ed5fa5 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -345,6 +345,19 @@ export abstract class DiagramModule extends GLSPModule { protected abstract bindSourceModelStorage(): BindingTarget; + /** + * Returns the {@link BindingTarget} for the {@link ModelState} interface. + * Typically a {@link ServiceTarget} is returned as this ensures that both + * `@inject(ModelState)` and `@inject(MyCustomModelState`) can be used and resolve + * to the same instance. + * + * Example: + * ```ts + * protected override bindModelState():BindingTarget { + * return { service: MyCustomModelState}; + * } + *``` + */ protected abstract bindModelState(): BindingTarget; protected abstract bindDiagramConfiguration(): BindingTarget; From 0d64c52e269105119949abb630e0ca49a5679c44 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Thu, 14 Jul 2022 08:37:59 +0200 Subject: [PATCH 119/566] 694: Improve Layouter to support more dynamic layouts and complex (#187) parent/children node structures - Introduce a new StatefulLayouter, which applies layout in 2 passes: children -> parent for the "minimal size" (resizeContainer), then parent -> children to take the correct parent size into account for alignment (alignment, vGrab/hGrab) - Update HBox and VBox layouts to take the new layouter into account - Do not inherit child-specific layout properties (PrefSize, Grab) - Properly take padding into account - Update the Style for nested categories, to make them more readable (Add a border, use a different color for nested categories) fixes https://github.com/eclipse-glsp/glsp/issues/694 Contributed on behalf of STMicroelectronics. Signed-off-by: Camille Letavernier --- examples/workflow-glsp/css/diagram.css | 7 + .../client/src/features/layout/di.config.ts | 4 +- .../client/src/features/layout/hbox-layout.ts | 31 +++- .../client/src/features/layout/layouter.ts | 137 ++++++++++++++++++ .../client/src/features/layout/vbox-layout.ts | 27 +++- 5 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 packages/client/src/features/layout/layouter.ts diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index 24b2acb..e09b088 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -65,6 +65,13 @@ fill: #5c87bd; } +.category .category > .sprotty-node { + /** Add a border to nested categories, so we can distinguish them */ + stroke: #38679a; + fill: #6f9ad0; + stroke-width: 1; +} + .sprotty-edge.weighted.low:not(.selected), .sprotty-edge.weighted.low:not(.selected) .arrow { stroke: rgb(128, 90, 233); diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 88e4da4..6311950 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureLayout, HBoxLayouter, VBoxLayouter } from 'sprotty'; +import { configureActionHandler, configureLayout, HBoxLayouter, TYPES, VBoxLayouter } from 'sprotty'; import { FreeFormLayouter } from './freeform-layout'; import { HBoxLayouterExt } from './hbox-layout'; import { @@ -23,6 +23,7 @@ import { ResizeElementsAction, ResizeElementsActionHandler } from './layout-elements-action'; +import { LayouterExt } from './layouter'; import { VBoxLayouterExt } from './vbox-layout'; const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { @@ -32,6 +33,7 @@ const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { rebind(VBoxLayouter).to(VBoxLayouterExt); rebind(HBoxLayouter).to(HBoxLayouterExt); + rebind(TYPES.Layouter).to(LayouterExt); configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); }); diff --git a/packages/client/src/features/layout/hbox-layout.ts b/packages/client/src/features/layout/hbox-layout.ts index 9775333..3618172 100644 --- a/packages/client/src/features/layout/hbox-layout.ts +++ b/packages/client/src/features/layout/hbox-layout.ts @@ -49,6 +49,9 @@ export class HBoxLayouterExt extends HBoxLayouter { const fixedSize = this.getFixedContainerBounds(container, options, layouter); + const currentWidth = boundsData.bounds ? boundsData.bounds?.width - options.paddingLeft - options.paddingRight : 0; + const currentHeight = boundsData.bounds ? boundsData.bounds?.height - options.paddingTop - options.paddingBottom : 0; + const maxWidth = options.paddingFactor * (options.resizeContainer @@ -60,8 +63,11 @@ export class HBoxLayouterExt extends HBoxLayouter { ? Math.max(fixedSize.height - options.paddingTop - options.paddingBottom, childrenSize.height) : Math.max(0, fixedSize.height - options.paddingTop - options.paddingBottom)); + const width = Math.max(currentWidth, maxWidth); + const height = Math.max(currentHeight, maxHeight); + // Remaining size that can be grabbed by children with the hGrab option - const grabWidth: number = maxWidth - childrenSize.width; + const grabWidth = width - childrenSize.width; // Number of children that request hGrab // FIXME: This approach works fine when only 1 child uses HGrab, but may cause rounding issues // when the grabHeight can't be equally shared by all children. @@ -69,8 +75,8 @@ export class HBoxLayouterExt extends HBoxLayouter { .map(child => this.getChildLayoutOptions(child, options)) .filter(opt => opt.hGrab).length; - if (maxWidth > 0 && maxHeight > 0) { - const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight, grabWidth, grabbingChildren); + if (width > 0 && height > 0) { + const offset = this.layoutChildren(container, layouter, options, width, height, grabWidth, grabbingChildren); boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); boundsData.boundsChanged = true; } @@ -155,8 +161,15 @@ export class HBoxLayouterExt extends HBoxLayouter { grabWidth?: number, grabbingChildren?: number ): Point { + const vAlign = childOptions.vGrab ? 'top' : childOptions.vAlign; + const dy = this.getDy(vAlign, bounds, maxHeight); let offset = super.layoutChild(child, boundsData, bounds, childOptions, containerOptions, currentOffset, maxWidth, maxHeight); - if (childOptions.hGrab) { + boundsData.bounds = { + ...boundsData.bounds!, + x: currentOffset.x, + y: currentOffset.y + dy + }; + if (childOptions.vGrab) { boundsData.bounds = { x: boundsData.bounds!.x, y: boundsData.bounds!.y, @@ -197,7 +210,7 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override getChildLayoutOptions(child: SChildElement, containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { - return super.getChildLayoutOptions(child, containerOptions) as HBoxLayoutOptionsExt; + return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as HBoxLayoutOptionsExt; } protected override getLayoutOptions(element: SModelElement): HBoxLayoutOptionsExt { @@ -250,7 +263,11 @@ export class HBoxLayouterExt extends HBoxLayouter { }; } - protected override spread(a: HBoxLayoutOptionsExt, b: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { - return { ...a, ...b }; + protected filterContainerOptions(containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { + // Reset object-specific layout options to default before merging, + // to make sure they won't be inherited (grab, prefSize) + // eslint-disable-next-line no-null/no-null + const localOptions = { vGrab: false, hGrab: false, prefHeight: null, prefWidth: null }; + return { ...containerOptions, ...localOptions }; } } diff --git a/packages/client/src/features/layout/layouter.ts b/packages/client/src/features/layout/layouter.ts new file mode 100644 index 0000000..f063a8e --- /dev/null +++ b/packages/client/src/features/layout/layouter.ts @@ -0,0 +1,137 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + BoundsData, + ILogger, + LayoutRegistry, + StatefulLayouter, + SModelElement, + Layouter, + SParentElement, + LayoutContainer, + isLayoutContainer +} from 'sprotty'; +import { injectable } from 'inversify'; +import { Bounds } from 'sprotty-protocol/lib/utils/geometry'; + +@injectable() +export class LayouterExt extends Layouter { + override layout(element2boundsData: Map): void { + new StatefulLayouterExt(element2boundsData, this.layoutRegistry, this.logger).layout(); + } +} + +// 2-pass layout: +// Step 1: Find "rendered size" of each element (may take resizeContainer into account) +// Child-to-parent layout +// Step 2: Extend parents as necessary, then use the adjusted parent size to properly +// align children (center/end alignments, hGrab/vGrab) +// Parent-to-children layout + +export class StatefulLayouterExt extends StatefulLayouter { + protected toBeLayouted2: (SParentElement & LayoutContainer)[]; + + /** + * + * @param elementToBoundsData The map of element to bounds data. Bounds Data are computed from the hidden + * SVG rendering pass. + * @param layoutRegistry2 The registry of available layouts. + * @param log The log. + */ + constructor( + protected readonly elementToBoundsData: Map, + protected readonly layoutRegistry2: LayoutRegistry, + log: ILogger + ) { + super(elementToBoundsData, layoutRegistry2, log); + this.toBeLayouted2 = []; + elementToBoundsData.forEach((data, element) => { + if (isLayoutContainer(element)) { + this.toBeLayouted2.push(element); + } + }); + for (const element of this.toBeLayouted2) { + // Clear previous layout information for dynamic-layout objects + elementToBoundsData.delete(element); + } + } + + override getBoundsData(element: SModelElement): BoundsData { + let boundsData = this.elementToBoundsData.get(element); + let bounds = (element as any).bounds; + if (isLayoutContainer(element) && this.toBeLayouted2.indexOf(element) >= 0) { + bounds = this.doLayout(element); + } else if (isLayoutContainer(element)) { + bounds = { + x: 0, + y: 0, + width: -1, + height: -1 + }; + } + if (!boundsData) { + boundsData = { + bounds: bounds, + boundsChanged: false, + alignmentChanged: false + }; + this.elementToBoundsData.set(element, boundsData); + } + return boundsData; + } + + override layout(): void { + // First pass: apply layout with cleared container data. Will get + // preferred size for all elements (Children first, then parents) + while (this.toBeLayouted2.length > 0) { + const element = this.toBeLayouted2[0]; + this.doLayout(element); + } + + this.toBeLayouted2 = []; + this.elementToBoundsData.forEach((data, element) => { + if (isLayoutContainer(element)) { + this.toBeLayouted2.push(element); + } + }); + + // Second pass: apply layout with initial size data for all + // nodes. Update the position/size of all elements, taking + // vGrab/hGrab into account (parent -> children). + while (this.toBeLayouted2.length > 0) { + const element = this.toBeLayouted2[0]; + this.doLayout(element); + } + } + + protected override doLayout(element: SParentElement & LayoutContainer): Bounds { + const index = this.toBeLayouted2.indexOf(element); + if (index >= 0) { + this.toBeLayouted2.splice(index, 1); + } + const layout = this.layoutRegistry2.get(element.layout); + if (layout) { + layout.layout(element, this); + } + const boundsData = this.elementToBoundsData.get(element); + if (boundsData !== undefined && boundsData.bounds !== undefined) { + return boundsData.bounds; + } else { + this.log.error(element, 'Layout failed'); + return Bounds.EMPTY; + } + } +} diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/layout/vbox-layout.ts index a45afe5..eb0f393 100644 --- a/packages/client/src/features/layout/vbox-layout.ts +++ b/packages/client/src/features/layout/vbox-layout.ts @@ -49,6 +49,9 @@ export class VBoxLayouterExt extends VBoxLayouter { const fixedSize = this.getFixedContainerBounds(container, options, layouter); + const currentWidth = (boundsData.bounds?.width || 0) - options.paddingLeft - options.paddingRight; + const currentHeight = (boundsData.bounds?.height || 0) - options.paddingTop - options.paddingBottom; + const maxWidth = options.paddingFactor * (options.resizeContainer @@ -60,8 +63,11 @@ export class VBoxLayouterExt extends VBoxLayouter { ? Math.max(fixedSize.height - options.paddingTop - options.paddingBottom, childrenSize.height) : Math.max(0, fixedSize.height - options.paddingTop - options.paddingBottom)); + const width = Math.max(currentWidth, maxWidth); + const height = Math.max(currentHeight, maxHeight); + // Remaining size that can be grabbed by children with the vGrab option - const grabHeight: number = maxHeight - childrenSize.height; + const grabHeight: number = height - childrenSize.height; // Number of children that request vGrab // FIXME: This approach works fine when only 1 child uses VGrab, but may cause rounding issues // when the grabHeight can't be equally shared by all children. @@ -70,7 +76,7 @@ export class VBoxLayouterExt extends VBoxLayouter { .filter(opt => opt.vGrab).length; if (maxWidth > 0 && maxHeight > 0) { - const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight, grabHeight, grabbingChildren); + const offset = this.layoutChildren(container, layouter, options, width, height, grabHeight, grabbingChildren); boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); boundsData.boundsChanged = true; } @@ -155,7 +161,14 @@ export class VBoxLayouterExt extends VBoxLayouter { grabHeight?: number, grabbingChildren?: number ): Point { + const hAlign = childOptions.hGrab ? 'left' : childOptions.hAlign; + const dx = this.getDx(hAlign, bounds, maxWidth); let offset = super.layoutChild(child, boundsData, bounds, childOptions, containerOptions, currentOffset, maxWidth, maxHeight); + boundsData.bounds = { + ...boundsData.bounds!, + x: currentOffset.x + dx, + y: currentOffset.y + }; if (childOptions.hGrab) { boundsData.bounds = { x: boundsData.bounds!.x, @@ -197,7 +210,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { - return super.getChildLayoutOptions(child, containerOptions) as VBoxLayoutOptionsExt; + return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as VBoxLayoutOptionsExt; } protected override getLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt { @@ -250,7 +263,11 @@ export class VBoxLayouterExt extends VBoxLayouter { }; } - protected override spread(a: VBoxLayoutOptionsExt, b: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { - return { ...a, ...b }; + protected filterContainerOptions(containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + // Reset object-specific layout options to default before merging, + // to make sure they won't be inherited (grab, prefSize) + // eslint-disable-next-line no-null/no-null + const localOptions = { vGrab: false, hGrab: false, prefHeight: null, prefWidth: null }; + return { ...containerOptions, ...localOptions }; } } From 35f8842dea719fe6672a7d0e4a0a6101d3d75bbb Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Fri, 15 Jul 2022 15:05:35 +0200 Subject: [PATCH 120/566] #712 Align GGraph model with newest changes from glsp-server (#22) * #712 Align GGraph model with newest changes from glsp-server - Align model in packages/graph with GGraph/SGraph - Add missing interface GHtmlRoot - Add missing static builder function for GIssueMarker - Rename files to match class/interface name - Update build scripts and VSCode build task - Update CHANGELOG Resolves https://github.com/eclipse-glsp/glsp/issues/712 Contributed on behalf of STMicroelectronics. --- examples/workflow-server/package.json | 4 +- packages/graph/package.json | 3 +- packages/graph/src/default-types.ts | 12 ++++-- .../src/{gbound-aware.ts => gbounds-aware.ts} | 0 packages/graph/src/gcompartment.ts | 23 +++-------- packages/graph/src/gedge-layoutable.ts | 6 +-- packages/graph/src/ggraph.ts | 19 ++++++++-- packages/graph/src/ghtmlroot.ts | 38 +++++++++++++++++++ packages/graph/src/gissue-marker.ts | 4 ++ packages/graph/src/glabel.ts | 6 +-- .../{glayout-container.ts => glayouting.ts} | 16 ++++---- packages/graph/src/gmodel-element.ts | 5 ++- packages/graph/src/gnode.ts | 15 ++++++-- packages/graph/src/gshape-element.ts | 2 +- ...ment.ts => gshaped-prerendered-element.ts} | 12 +++--- packages/graph/src/index.ts | 7 ++-- packages/layout-elk/package.json | 3 +- packages/server-node/package.json | 3 +- 18 files changed, 118 insertions(+), 60 deletions(-) rename packages/graph/src/{gbound-aware.ts => gbounds-aware.ts} (100%) create mode 100644 packages/graph/src/ghtmlroot.ts rename packages/graph/src/{glayout-container.ts => glayouting.ts} (65%) rename packages/graph/src/{gpre-shape-prerendered-element.ts => gshaped-prerendered-element.ts} (87%) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 902a54b..6d5c20a 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -41,9 +41,9 @@ "@eclipse-glsp/server-node": "1.1.0-next" }, "scripts": { + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "yarn run clean && tsc", - "test": "", + "build": "tsc", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", diff --git a/packages/graph/package.json b/packages/graph/package.json index f79808a..ea5c87c 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -44,8 +44,9 @@ "@types/uuid": "8.3.1" }, "scripts": { + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "yarn run clean && tsc", + "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts index eb2b2ca..cb04e64 100644 --- a/packages/graph/src/default-types.ts +++ b/packages/graph/src/default-types.ts @@ -18,15 +18,19 @@ import { GButton } from './gbutton'; import { GCompartment } from './gcompartment'; import { GEdge } from './gedge'; import { GGraph } from './ggraph'; +import { GHtmlRoot } from './ghtmlroot'; import { GIssueMarker } from './gissue-marker'; import { GLabel } from './glabel'; -import { GModelElementConstructor, GModelRoot } from './gmodel-element'; +import { GModelElementConstructor } from './gmodel-element'; import { GNode } from './gnode'; import { GPort } from './gport'; import { GPreRenderedElement } from './gpre-rendered-element'; -import { GShapePreRenderedElement } from './gpre-shape-prerendered-element'; +import { GShapedPreRenderedElement } from './gshaped-prerendered-element'; export function getDefaultMapping(): Map { + // The GModelSerializer (packages/server-node/src/features/model/gmodel-serializer.ts) + // has a built-in subtype handling. + // If the mapping for a subtype, e.g. for comp:header, is not found it will use the mapping for comp instead. const mapping = new Map(); mapping.set(types.GRAPH, GGraph); mapping.set(types.NODE, GNode); @@ -37,8 +41,8 @@ export function getDefaultMapping(): Map { mapping.set(types.BUTTON, GButton); mapping.set(types.ISSUE_MARKER, GIssueMarker); - mapping.set(types.HTML, GModelRoot); + mapping.set(types.HTML, GHtmlRoot); mapping.set(types.PRE_RENDERED, GPreRenderedElement); - mapping.set(types.FOREIGN_OBJECT, GShapePreRenderedElement); + mapping.set(types.FOREIGN_OBJECT, GShapedPreRenderedElement); return mapping; } diff --git a/packages/graph/src/gbound-aware.ts b/packages/graph/src/gbounds-aware.ts similarity index 100% rename from packages/graph/src/gbound-aware.ts rename to packages/graph/src/gbounds-aware.ts diff --git a/packages/graph/src/gcompartment.ts b/packages/graph/src/gcompartment.ts index 37200b3..087911f 100644 --- a/packages/graph/src/gcompartment.ts +++ b/packages/graph/src/gcompartment.ts @@ -13,35 +13,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, DefaultTypes, JsonPrimitive } from '@eclipse-glsp/protocol'; -import { GLayoutContainer, GLayoutContainerBuilder } from './glayout-container'; -import { GLayoutable, GLayoutableBuilder } from './glayoutable'; +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { GLayouting, GLayoutingBuilder } from './glayouting'; import { GShapeElement, GShapeElementBuilder } from './gshape-element'; -export class GCompartment extends GShapeElement implements GLayoutContainer, GLayoutable { +export class GCompartment extends GShapeElement implements GLayouting { static builder(): GCompartmentBuilder { return new GCompartmentBuilder(GCompartment).type(DefaultTypes.COMPARTMENT); } override type = DefaultTypes.COMPARTMENT; layout?: string; - override layoutOptions?: Args; - [GLayoutContainer] = true; - [GLayoutable] = true; + [GLayouting] = true; } export class GCompartmentBuilder extends GShapeElementBuilder { layout(layout?: string): this { - return GLayoutContainerBuilder.layout(this, layout); - } - - override addLayoutOption(key: string, value: JsonPrimitive): this { - return GLayoutableBuilder.addLayoutOption(this, key, value); - } - - override addLayoutOptions(layoutOptions: Args): this; - override addLayoutOptions(layoutOptions: Map): this; - override addLayoutOptions(layoutOptions: Args | Map): this { - return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); + return GLayoutingBuilder.layout(this, layout); } } diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts index c3e46a9..f0db47d 100644 --- a/packages/graph/src/gedge-layoutable.ts +++ b/packages/graph/src/gedge-layoutable.ts @@ -17,7 +17,7 @@ import { GModelElement, GModelElementBuilder } from './gmodel-element'; export type EdgeSide = 'left' | 'right' | 'top' | 'bottom' | 'on'; -export class EdgePlacement extends Object { +export interface GEdgePlacement { rotate: boolean; side: EdgeSide; position: number; @@ -27,7 +27,7 @@ export class EdgePlacement extends Object { export const GEdgeLayoutable = Symbol('GEdgeLayoutable'); export interface GEdgeLayoutable { - edgePlacement?: EdgePlacement; + edgePlacement?: GEdgePlacement; [GEdgeLayoutable]: boolean; } @@ -38,7 +38,7 @@ export function isGEdgeLayoutable(element: G): element export type GEdgeLayoutableBuilder = GModelElementBuilder; export namespace GEdgeLayoutableBuilder { - export function edgePlacement(builder: B, placement?: EdgePlacement): B { + export function edgePlacement(builder: B, placement?: GEdgePlacement): B { builder['proxy'].edgePlacement = placement; return builder; } diff --git a/packages/graph/src/ggraph.ts b/packages/graph/src/ggraph.ts index 571b846..9a031f1 100644 --- a/packages/graph/src/ggraph.ts +++ b/packages/graph/src/ggraph.ts @@ -19,11 +19,12 @@ * Parts of the implementation is derived from Sprotty's SModel API * (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel.ts) */ -import { DefaultTypes as types, Dimension, Point } from '@eclipse-glsp/protocol'; -import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { Args, DefaultTypes as types, Dimension, JsonPrimitive, Point } from '@eclipse-glsp/protocol'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbounds-aware'; +import { GLayoutable, GLayoutableBuilder } from './glayoutable'; import { GModelRoot, GModelRootBuilder } from './gmodel-element'; -export class GGraph extends GModelRoot implements GBoundsAware { +export class GGraph extends GModelRoot implements GBoundsAware, GLayoutable { static override builder(): GGraphBuilder { return new GGraphBuilder(GGraph); } @@ -31,7 +32,9 @@ export class GGraph extends GModelRoot implements GBoundsAware { override type = types.GRAPH; position: Point = Point.ORIGIN; size?: Dimension; + layoutOptions?: Args; [GBoundsAware] = true; + [GLayoutable] = true; } export class GGraphBuilder extends GModelRootBuilder { @@ -46,4 +49,14 @@ export class GGraphBuilder extends GModelRootBuilder< size(sizeOrWidth: Dimension | number, height?: number): this { return GBoundsAwareBuilder.size(this, sizeOrWidth, height); } + + addLayoutOption(key: string, value: JsonPrimitive): this { + return GLayoutableBuilder.addLayoutOption(this, key, value); + } + + addLayoutOptions(layoutOptions: Args): this; + addLayoutOptions(layoutOptions: Map): this; + addLayoutOptions(layoutOptions: Args | Map): this { + return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); + } } diff --git a/packages/graph/src/ghtmlroot.ts b/packages/graph/src/ghtmlroot.ts new file mode 100644 index 0000000..36a5923 --- /dev/null +++ b/packages/graph/src/ghtmlroot.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 StMicroelectronics. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes as types, flatPush, MaybeArray } from '@eclipse-glsp/protocol'; +import { GModelRoot, GModelRootBuilder } from './gmodel-element'; + +/** + * Root model element class for HTML content. Usually this is rendered with a `div` DOM element. + */ +export class GHtmlRoot extends GModelRoot { + static override builder(): GHtmlRootBuilder { + return new GHtmlRootBuilder(GHtmlRoot); + } + + override type = types.HTML; + classes: string[] = []; +} + +export class GHtmlRootBuilder extends GModelRootBuilder { + addClasses(classes: string[]): this; + addClasses(...classes: string[]): this; + addClasses(...classes: MaybeArray[]): this { + flatPush(this.proxy.classes, classes); + return this; + } +} diff --git a/packages/graph/src/gissue-marker.ts b/packages/graph/src/gissue-marker.ts index 0a10422..e470b4c 100644 --- a/packages/graph/src/gissue-marker.ts +++ b/packages/graph/src/gissue-marker.ts @@ -24,6 +24,10 @@ export class GIssue { } export class GIssueMarker extends GShapeElement { + static builder(): GIssueMarkerBuilder { + return new GIssueMarkerBuilder(GIssueMarker).type(DefaultTypes.ISSUE_MARKER); + } + override type = DefaultTypes.ISSUE_MARKER; issues: GIssue[] = []; } diff --git a/packages/graph/src/glabel.ts b/packages/graph/src/glabel.ts index 6f0c6d1..388b609 100644 --- a/packages/graph/src/glabel.ts +++ b/packages/graph/src/glabel.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { DefaultTypes, Point } from '@eclipse-glsp/protocol'; import { GAlignable, GAlignableBuilder } from './galignable'; -import { EdgePlacement, GEdgeLayoutable, GEdgeLayoutableBuilder } from './gedge-layoutable'; +import { GEdgeLayoutable, GEdgeLayoutableBuilder, GEdgePlacement } from './gedge-layoutable'; import { GModelElementConstructor } from './gmodel-element'; import { GShapeElement, GShapeElementBuilder } from './gshape-element'; @@ -27,7 +27,7 @@ export class GLabel extends GShapeElement implements GAlignable, GEdgeLayoutable override type = DefaultTypes.LABEL; text: string; alignment: Point = Point.ORIGIN; - edgePlacement?: EdgePlacement; + edgePlacement?: GEdgePlacement; [GAlignable] = true; [GEdgeLayoutable] = true; } @@ -44,7 +44,7 @@ export class GLabelBuilder extends GShapeElementBuild return this; } - edgePlacement(placement: EdgePlacement): this { + edgePlacement(placement: GEdgePlacement): this { return GEdgeLayoutableBuilder.edgePlacement(this, placement); } } diff --git a/packages/graph/src/glayout-container.ts b/packages/graph/src/glayouting.ts similarity index 65% rename from packages/graph/src/glayout-container.ts rename to packages/graph/src/glayouting.ts index 75236b3..f41564f 100644 --- a/packages/graph/src/glayout-container.ts +++ b/packages/graph/src/glayouting.ts @@ -15,21 +15,21 @@ ********************************************************************************/ import { GModelElement, GModelElementBuilder } from './gmodel-element'; -export const GLayoutContainer = Symbol('GLayoutContainer'); +export const GLayouting = Symbol('GLayouting'); -export interface GLayoutContainer { +export interface GLayouting { layout?: string; - [GLayoutContainer]: boolean; + [GLayouting]: boolean; } -export function isGLayoutContainer(element: G): element is G & GLayoutContainer { - return ((element as any)[GLayoutContainer] = true); +export function isGLayouting(element: G): element is G & GLayouting { + return ((element as any)[GLayouting] = true); } -export type GLayoutContainerBuilder = GModelElementBuilder; +export type GLayoutingBuilder = GModelElementBuilder; -export namespace GLayoutContainerBuilder { - export function layout(builder: B, newLayout?: string): B { +export namespace GLayoutingBuilder { + export function layout(builder: B, newLayout?: string): B { builder['proxy'].layout = newLayout; return builder; } diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts index cf586e2..c88080e 100644 --- a/packages/graph/src/gmodel-element.ts +++ b/packages/graph/src/gmodel-element.ts @@ -63,7 +63,8 @@ export abstract class GModelElement implements GModelElementSchema { * A set of css classes that should be applied to the DOM element that corresponds to this element. */ cssClasses: string[] = []; - /** A `GModelElement can have an arbitrary amount of children. This parent-child relation ship is also reflected in + /** + * A `GModelElement can have an arbitrary amount of children. This parent-child relation ship is also reflected in * the corresponding DOM element i.e. DOM elements that reflect children of this element are also children * of the DOM element that reflects this element. */ @@ -194,7 +195,7 @@ export class GModelRoot extends GModelElement implements SModelRootSchema { } canvasBounds?: Bounds; - revision = 0; + revision?: number; } export class GModelRootBuilder extends GModelElementBuilder { diff --git a/packages/graph/src/gnode.ts b/packages/graph/src/gnode.ts index b59e1a6..9907c4f 100644 --- a/packages/graph/src/gnode.ts +++ b/packages/graph/src/gnode.ts @@ -14,21 +14,28 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { DefaultTypes } from '@eclipse-glsp/protocol'; -import { GLayoutContainer, GLayoutContainerBuilder } from './glayout-container'; +import { GEdgeLayoutable, GEdgeLayoutableBuilder, GEdgePlacement } from './gedge-layoutable'; +import { GLayouting, GLayoutingBuilder } from './glayouting'; import { GShapeElement, GShapeElementBuilder } from './gshape-element'; -export class GNode extends GShapeElement implements GLayoutContainer { +export class GNode extends GShapeElement implements GEdgeLayoutable, GLayouting { static builder(): GNodeBuilder { return new GNodeBuilder(GNode).type(DefaultTypes.NODE); } override type = DefaultTypes.NODE; + edgePlacement?: GEdgePlacement; layout?: string; - [GLayoutContainer] = true; + [GEdgeLayoutable] = true; + [GLayouting] = true; } export class GNodeBuilder extends GShapeElementBuilder { + edgePlacement(placement: GEdgePlacement): this { + return GEdgeLayoutableBuilder.edgePlacement(this, placement); + } + layout(layout?: string): this { - return GLayoutContainerBuilder.layout(this, layout); + return GLayoutingBuilder.layout(this, layout); } } diff --git a/packages/graph/src/gshape-element.ts b/packages/graph/src/gshape-element.ts index a4fa001..ceec7ca 100644 --- a/packages/graph/src/gshape-element.ts +++ b/packages/graph/src/gshape-element.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Args, Dimension, JsonPrimitive, Point } from '@eclipse-glsp/protocol'; -import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbounds-aware'; import { GLayoutable, GLayoutableBuilder } from './glayoutable'; import { GModelElement, GModelElementBuilder } from './gmodel-element'; diff --git a/packages/graph/src/gpre-shape-prerendered-element.ts b/packages/graph/src/gshaped-prerendered-element.ts similarity index 87% rename from packages/graph/src/gpre-shape-prerendered-element.ts rename to packages/graph/src/gshaped-prerendered-element.ts index f53e70e..b5fd70b 100644 --- a/packages/graph/src/gpre-shape-prerendered-element.ts +++ b/packages/graph/src/gshaped-prerendered-element.ts @@ -16,7 +16,7 @@ import { Dimension, Point } from '@eclipse-glsp/protocol'; import { GAlignable } from './galignable'; -import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbounds-aware'; import { GModelElementBuilder } from './gmodel-element'; import { GPreRenderedElement } from './gpre-rendered-element'; @@ -27,20 +27,20 @@ import { GPreRenderedElement } from './gpre-rendered-element'; * A popup model is rendered when hovering over a element and for many common use cases e.g rendering a tooltip * this model can be computed entirely on the server side. */ -export class GShapePreRenderedElement extends GPreRenderedElement implements GBoundsAware, GAlignable { +export class GShapedPreRenderedElement extends GPreRenderedElement implements GBoundsAware, GAlignable { static override builder(): GShapePreRenderedElementBuilder { - return new GShapePreRenderedElementBuilder(GShapePreRenderedElement); + return new GShapePreRenderedElementBuilder(GShapedPreRenderedElement); } - [GBoundsAware] = true; - [GAlignable] = true; position: Point; size: Dimension; alignment: Point; + [GBoundsAware] = true; + [GAlignable] = true; } export class GShapePreRenderedElementBuilder< - G extends GShapePreRenderedElement = GShapePreRenderedElement + G extends GShapedPreRenderedElement = GShapedPreRenderedElement > extends GModelElementBuilder { code(code: string): this { this.proxy.code = code; diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index 65f9331..2360334 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -15,20 +15,21 @@ ********************************************************************************/ export * from './default-types'; export * from './galignable'; -export * from './gbound-aware'; +export * from './gbounds-aware'; export * from './gbutton'; export * from './gcompartment'; export * from './gedge'; export * from './gedge-layoutable'; export * from './ggraph'; +export * from './ghtmlroot'; export * from './gissue-marker'; export * from './glabel'; -export * from './glayout-container'; export * from './glayoutable'; +export * from './glayouting'; export * from './gmodel-element'; export * from './gmodel-util'; export * from './gnode'; export * from './gport'; export * from './gpre-rendered-element'; -export * from './gpre-shape-prerendered-element'; export * from './gshape-element'; +export * from './gshaped-prerendered-element'; diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 212a459..da4b9d4 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -46,8 +46,9 @@ "inversify": "^5.1.1" }, "scripts": { + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "yarn run clean && tsc", + "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", diff --git a/packages/server-node/package.json b/packages/server-node/package.json index f6479af..ccc7431 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -48,8 +48,9 @@ "@types/fs-extra": "^9.0.13" }, "scripts": { + "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "yarn run clean && tsc", + "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", From bc2496c3907870e7cecc66cd09761c13d2fc9c2e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 17 Jul 2022 23:58:30 -0700 Subject: [PATCH 121/566] Update download server script (#189) Ensure that the snapshot version of the example server is downloaded --- examples/workflow-glsp/scripts/download.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index ad3e584..cc82bed 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -17,14 +17,14 @@ import download from 'mvn-artifact-download'; import { join, resolve } from 'path'; const downloadDir = resolve(join(__dirname)) + '/../../..'; -const mavenRepository = 'https://repo1.maven.org/maven2/'; +const mavenRepository = 'https://oss.sonatype.org/content/repositories/snapshots/'; const groupId = 'org.eclipse.glsp.example'; const artifactId = 'org.eclipse.glsp.example.workflow'; -const version = '1.0.0'; +const version = '1.1.0'; const classifier = 'glsp'; console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); -const isSnapShot = false; +const isSnapShot = true; download({ groupId, artifactId, version, classifier, isSnapShot }, downloadDir, mavenRepository).then(() => console.log( 'Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-' + From 8dddd5e99b58da963a2a574ad8858b8741454d9f Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Mon, 18 Jul 2022 10:46:08 +0200 Subject: [PATCH 122/566] Layout edges that are children of ports (#23) Edges that are children of ports should also be layouted by the GlspElkLayoutEngine. Resolves eclipse-glsp/glsp#661 Contributed on behalf of STMicroelectronics --- packages/layout-elk/src/glsp-elk-layout-engine.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index cb76b8f..a769853 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -240,6 +240,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { }; if (port.children) { elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; + this.elkEdges.push(...(this.findChildren(port, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); } this.transformShape(elkPort, port); this.idToElkElement.set(port.id, elkPort); From f8b334819b2feac77f8e9a1f1d8ca20eaaba8ce8 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Fri, 22 Jul 2022 13:40:32 +0200 Subject: [PATCH 123/566] #711 Add test cases for improved Layouters (#193) - Add hbox and vbox layouter tests (adapted and extended test cases from sprotty) - Add freeform layouter tests according to #610 and #694 layouting problems - Extract test util methods Part of https://github.com/eclipse-glsp/glsp/issues/711 --- .../layout/tests/freeform-layout.spec.ts | 216 ++++++++++++++++++ .../features/layout/tests/hbox-layout.spec.ts | 171 ++++++++++++++ .../layout/tests/layouter-test-util.ts | 122 ++++++++++ .../features/layout/tests/vbox-layout.spec.ts | 171 ++++++++++++++ 4 files changed, 680 insertions(+) create mode 100644 packages/client/src/features/layout/tests/freeform-layout.spec.ts create mode 100644 packages/client/src/features/layout/tests/hbox-layout.spec.ts create mode 100644 packages/client/src/features/layout/tests/layouter-test-util.ts create mode 100644 packages/client/src/features/layout/tests/vbox-layout.spec.ts diff --git a/packages/client/src/features/layout/tests/freeform-layout.spec.ts b/packages/client/src/features/layout/tests/freeform-layout.spec.ts new file mode 100644 index 0000000..490af55 --- /dev/null +++ b/packages/client/src/features/layout/tests/freeform-layout.spec.ts @@ -0,0 +1,216 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import 'mocha'; +import 'reflect-metadata'; +import { BoundsData, ConsoleLogger, SModelElement } from 'sprotty'; +import { gModel, layout, sComp, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; + +describe('FreeFormLayouter', () => { + const layoutRegistry = setupLayoutRegistry(); + const log = new ConsoleLogger(); + const map = new Map(); + + describe('issue-610', () => { + it('recursive hGrab/vGrab', () => { + /** + * _________________________ + * | Category 1 | + * | _______________________ | + * || || + * || || + * || || + * || || + * ||_______________________|| + * | _______________________ | + * ||_left____________right_|| + * |_________________________| + * + * This test case checks recursive hGrab/vGrab functionality of nested compartments + * See also issue 610 for details and an example model https://github.com/eclipse-glsp/glsp/issues/610. + */ + const model = gModel(); + const category = sNode('category', 'vbox', { width: 410.0, height: 215.0 }, undefined, { + vGrab: false, + hGrab: false, + hAlign: 'center', + prefWidth: 410.0, + prefHeight: 215.0 + }); + const headerComp = sComp('comp:header', 'hbox'); + headerComp.children = [sLabel('Category 1')]; + const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); + const labelContainer = sComp('struct', 'hbox', { hGrab: true }); + labelContainer.children = [sLabel('left text', { hGrab: true }), sLabel('right text')]; + category.children = [headerComp, childContainer, labelContainer]; + + model.add(category); + layout(layoutRegistry, log, map, model); + // check category + expect(map.get(category)!.bounds).to.deep.equal({ x: 0, y: 0, width: 410.0, height: 215.0 }); + // check header compartment + expect(map.get(category.children[0])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + // check child freeform compartment + expect(map.get(category.children[1])!.bounds).to.deep.equal({ x: 5, y: 5, width: 400.0, height: 205.0 }); + // check label compartment + expect(map.get(category.children[2])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + }); + }); + + describe('issue-694', () => { + it('Structure compartment (hGrab=true, vGrab=true), Left-aligned label (hGrab=true), right-aligned label (hGrab=false)', () => { + /** + * _________________________ + * | Category 2 | + * | _______________________ | + * || || + * || || + * || Task node || + * || || + * ||_______________________|| + * | _______________________ | + * ||_left____________right_|| + * |_________________________| + */ + const model = gModel(); + const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + vGrab: false, + hGrab: false, + hAlign: 'center', + prefWidth: 500.0, + prefHeight: 375.0 + }); + const headerComp = sComp('comp:header', 'hbox'); + headerComp.children = [sLabel('Category 2')]; + const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = sNode('task', 'hbox', undefined, { x: 170, y: 190 }); + childNode.children = [sLabel('Task node', undefined, { x: 5.0, y: 5.0 }, { width: 25.0, height: 20.0 })]; + childContainer.children = [childNode]; + const labelContainer = sComp('struct', 'hbox', { hGrab: true }); + labelContainer.children = [sLabel('left text', { hGrab: true }), sLabel('right text')]; + category.children = [headerComp, childContainer, labelContainer]; + + model.add(category); + + // layout graph + layout(layoutRegistry, log, map, model); + // check category + expect(map.get(category)!.bounds).to.deep.equal({ x: 0, y: 0, width: 500.0, height: 375.0 }); + // check header compartment + expect(map.get(category.children[0])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + // check child freeform compartment + expect(map.get(category.children[1])!.bounds).to.deep.equal({ x: 5.0, y: 5.0, width: 490.0, height: 365.0 }); + // check child task node + expect(map.get(category.children[1].children[0])!.bounds).to.deep.equal({ x: 170.0, y: 190.0, width: 35.0, height: 30.0 }); + // check label compartment + expect(map.get(category.children[2])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + }); + + it('Structure compartment (hGrab=true, vGrab=true)', () => { + /** + * _________________________ + * | Category 2 | + * | _______________________ | + * || || + * || Task node || + * || || + * || || + * ||_______________________|| + * |_________________________| + */ + const model = gModel(); + const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + vGrab: false, + hGrab: false, + hAlign: 'center', + prefWidth: 500.0, + prefHeight: 375.0 + }); + const headerComp = sComp('comp:header', 'hbox'); + headerComp.children = [sLabel('Category 2')]; + const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = sNode('task', 'hbox', undefined, { x: 55, y: 15 }); + childNode.children = [sLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; + childContainer.children = [childNode]; + category.children = [headerComp, childContainer]; + + model.add(category); + + // layout graph + layout(layoutRegistry, log, map, model); + // check category + expect(map.get(category)!.bounds).to.deep.equal({ x: 0, y: 0, width: 500.0, height: 375.0 }); + // check header compartment + expect(map.get(category.children[0])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + // check child freeform compartment + expect(map.get(category.children[1])!.bounds).to.deep.equal({ x: 5.0, y: 5.0, width: 490.0, height: 365.0 }); + // check child task node + expect(map.get(category.children[1].children[0])!.bounds).to.deep.equal({ x: 55.0, y: 15.0, width: 60.0, height: 45.0 }); + }); + + it('Structure compartment (hGrab=true, vGrab=true, padding*=10)', () => { + /** + * _________________________ + * | Category 2 | + * | _______________________ | + * || || + * || Task node || + * || || + * || || + * ||_______________________|| + * |_________________________| + */ + const model = gModel(); + const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + vGrab: false, + hGrab: false, + hAlign: 'center', + prefWidth: 500.0, + prefHeight: 375.0, + paddingLeft: 10, + paddingRight: 10, + paddingTop: 10, + paddingBottom: 10 + }); + const headerComp = sComp('comp:header', 'hbox'); + headerComp.children = [sLabel('Category 2')]; + const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = sNode( + 'task', + 'hbox', + undefined, + { x: 55, y: 15 }, + { paddingLeft: 10, paddingRight: 10, paddingTop: 10, paddingBottom: 10 } + ); + childNode.children = [sLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; + childContainer.children = [childNode]; + category.children = [headerComp, childContainer]; + + model.add(category); + + // layout graph + layout(layoutRegistry, log, map, model); + // check category + expect(map.get(category)!.bounds).to.deep.equal({ x: 0, y: 0, width: 500.0, height: 375.0 }); + // check header compartment + expect(map.get(category.children[0])!.bounds).to.deep.equal({ x: 0, y: 0, width: -1, height: -1 }); + // check child freeform compartment + expect(map.get(category.children[1])!.bounds).to.deep.equal({ x: 10.0, y: 10.0, width: 480.0, height: 355.0 }); + // check child task node + expect(map.get(category.children[1].children[0])!.bounds).to.deep.equal({ x: 55.0, y: 15.0, width: 70.0, height: 55.0 }); + }); + }); +}); diff --git a/packages/client/src/features/layout/tests/hbox-layout.spec.ts b/packages/client/src/features/layout/tests/hbox-layout.spec.ts new file mode 100644 index 0000000..d10e1af --- /dev/null +++ b/packages/client/src/features/layout/tests/hbox-layout.spec.ts @@ -0,0 +1,171 @@ +/******************************************************************************** + * Copyright (c) 2022 TypeFox, STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable deprecation/deprecation */ +import { Dimension } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import 'mocha'; +import 'reflect-metadata'; +import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; +import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; + +describe('HBoxLayouter', () => { + const layoutRegistry = setupLayoutRegistry(); + const log = new ConsoleLogger(); + const map = new Map(); + + function createModel(): SNode { + const model = sNode('node', undefined, Dimension.EMPTY); + model.children = [ + sLabel('label1', undefined, undefined, { width: 1, height: 2 }), + sLabel('label2', undefined, undefined, { width: 2, height: 1 }), + sLabel('label3', undefined, undefined, { width: 3, height: 3 }) + ]; + model.layout = 'hbox'; + return model; + } + + it('defaultParams', () => { + const model = createModel(); + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 6, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('alignTop', () => { + const model = createModel(); + model.layoutOptions = { + vAlign: 'top' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 5, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('alignCenter', () => { + const model = createModel(); + model.layoutOptions = { + vAlign: 'center' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 6, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('alignBottom', () => { + const model = createModel(); + model.layoutOptions = { + vAlign: 'bottom' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 6, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 7, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('padding', () => { + const model = createModel(); + model.layoutOptions = { + paddingTop: 7, + paddingBottom: 8, + paddingLeft: 9, + paddingRight: 10 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 27, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 9, y: 7.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 11, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 14, y: 7, width: 3, height: 3 }); + }); + + it('hGap', () => { + const model = createModel(); + model.layoutOptions = { + hGap: 4 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 24, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 10, y: 6, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 16, y: 5, width: 3, height: 3 }); + }); + + it('paddingFactor', () => { + const model = createModel(); + model.layoutOptions = { + paddingFactor: 2 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 9, y: 8.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 11, y: 9, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 14, y: 8, width: 3, height: 3 }); + }); + + it('minWidth', () => { + const model = createModel(); + model.layoutOptions = { + minWidth: 25 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 25, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 6, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('minHeight', () => { + const model = createModel(); + model.layoutOptions = { + minHeight: 25 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 25 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 11.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 12, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 11, width: 3, height: 3 }); + }); + + it('prefWidth', () => { + const model = createModel(); + model.layoutOptions = { + prefWidth: 20 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 20, height: 13 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5.5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 6, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 5, width: 3, height: 3 }); + }); + + it('prefHeight', () => { + const model = createModel(); + model.layoutOptions = { + prefHeight: 20 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 18, height: 20 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 9, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 7, y: 9.5, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 10, y: 8.5, width: 3, height: 3 }); + }); +}); diff --git a/packages/client/src/features/layout/tests/layouter-test-util.ts b/packages/client/src/features/layout/tests/layouter-test-util.ts new file mode 100644 index 0000000..362821a --- /dev/null +++ b/packages/client/src/features/layout/tests/layouter-test-util.ts @@ -0,0 +1,122 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Dimension, Point } from '@eclipse-glsp/protocol'; +import 'mocha'; +import 'reflect-metadata'; +import { + BoundsData, + ConsoleLogger, + createFeatureSet, + layoutableChildFeature, + LayoutRegistry, + SCompartment, + SLabel, + SModelElement, + SNode, + SParentElement, + TYPES +} from 'sprotty'; +import { createClientContainer } from '../../../base/container-modules'; +import { GLSPGraph } from '../../../lib/model'; +import { StatefulLayouterExt } from '../layouter'; + +export function gModel(): GLSPGraph { + return new GLSPGraph(); +} + +export function sNode( + type: string, + nodeLayout?: string, + size?: Dimension, + position?: Point, + layoutOptions?: { [key: string]: string | number | boolean } +): SNode { + const node = new SNode(); + node.features = createFeatureSet(SNode.DEFAULT_FEATURES, { enable: [layoutableChildFeature] }); + node.position = position || { + x: 0, + y: 0 + }; + if (size) { + node.size = size; + } + node.type = type; + if (nodeLayout) { + node.layout = nodeLayout; + } + node.layoutOptions = layoutOptions; + return node; +} + +export function sComp(type: string, compLayout: string, layoutOptions?: { [key: string]: string | number | boolean }): SCompartment { + const comp = new SCompartment(); + comp.features = createFeatureSet(SCompartment.DEFAULT_FEATURES); + comp.type = type; + comp.layout = compLayout; + comp.layoutOptions = layoutOptions; + return comp; +} + +export function sLabel( + labelText: string, + layoutOptions?: { [key: string]: string | number | boolean }, + position?: Point, + size?: Dimension +): SLabel { + const label = new SLabel(); + label.features = createFeatureSet(SLabel.DEFAULT_FEATURES); + if (position) { + label.position = position; + } + if (size) { + label.size = size; + } + label.layoutOptions = layoutOptions; + label.text = labelText; + label.type = 'label'; + return label; +} + +export function addToMap(map: Map, element: SModelElement): void { + map.set(element, { + bounds: (element as any).bounds, + boundsChanged: true, + alignmentChanged: true + }); + if (element instanceof SParentElement) { + element.children.forEach(c => addToMap(map, c)); + } +} + +export function layout( + layoutRegistry: LayoutRegistry, + log: ConsoleLogger, + map: Map, + model: SNode | GLSPGraph +): void { + map.clear(); + addToMap(map, model); + const layouter = new StatefulLayouterExt(map, layoutRegistry, log); + layouter.layout(); +} + +export function setupLayoutRegistry(): LayoutRegistry { + // Generic Test setup + // create client container that registers all default modules including the layoutModule + const layoutContainer = createClientContainer(); + return layoutContainer.get(TYPES.LayoutRegistry); +} diff --git a/packages/client/src/features/layout/tests/vbox-layout.spec.ts b/packages/client/src/features/layout/tests/vbox-layout.spec.ts new file mode 100644 index 0000000..23bbf70 --- /dev/null +++ b/packages/client/src/features/layout/tests/vbox-layout.spec.ts @@ -0,0 +1,171 @@ +/******************************************************************************** + * Copyright (c) 2022 TypeFox, STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable deprecation/deprecation */ +import { Dimension } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import 'mocha'; +import 'reflect-metadata'; +import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; +import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; + +describe('VBoxLayouter', () => { + const layoutRegistry = setupLayoutRegistry(); + const log = new ConsoleLogger(); + const map = new Map(); + + function createModel(): SNode { + const model = sNode('node', undefined, Dimension.EMPTY); + model.children = [ + sLabel('label1', undefined, undefined, { width: 1, height: 2 }), + sLabel('label2', undefined, undefined, { width: 2, height: 1 }), + sLabel('label3', undefined, undefined, { width: 3, height: 3 }) + ]; + model.layout = 'vbox'; + return model; + } + + it('defaultParams', () => { + const model = createModel(); + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 6, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5.5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); + + it('alignLeft', () => { + const model = createModel(); + model.layoutOptions = { + hAlign: 'left' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 5, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); + + it('alignCenter', () => { + const model = createModel(); + model.layoutOptions = { + hAlign: 'center' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 6, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5.5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); + + it('alignRight', () => { + const model = createModel(); + model.layoutOptions = { + hAlign: 'right' + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 7, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 6, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); + + it('padding', () => { + const model = createModel(); + model.layoutOptions = { + paddingTop: 7, + paddingBottom: 8, + paddingLeft: 9, + paddingRight: 10 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 22, height: 23 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 10, y: 7, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 9.5, y: 10, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 9, y: 12, width: 3, height: 3 }); + }); + + it('vGap', () => { + const model = createModel(); + model.layoutOptions = { + vGap: 4 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 24 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 6, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5.5, y: 11, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 16, width: 3, height: 3 }); + }); + + it('paddingFactor', () => { + const model = createModel(); + model.layoutOptions = { + paddingFactor: 2 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 9, y: 9, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 8.5, y: 12, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 8, y: 14, width: 3, height: 3 }); + }); + + it('minWidth', () => { + const model = createModel(); + model.layoutOptions = { + minWidth: 25 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 25, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 12, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 11.5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 11, y: 10, width: 3, height: 3 }); + }); + + it('minHeight', () => { + const model = createModel(); + model.layoutOptions = { + minHeight: 25 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 25 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 6, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5.5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); + + it('prefWidth', () => { + const model = createModel(); + model.layoutOptions = { + prefWidth: 20 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 20, height: 18 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 9.5, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 9, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 8.5, y: 10, width: 3, height: 3 }); + }); + + it('prefHeight', () => { + const model = createModel(); + model.layoutOptions = { + prefHeight: 20 + }; + layout(layoutRegistry, log, map, model); + expect(map.get(model)!.bounds).to.deep.equal({ x: 0, y: 0, width: 13, height: 20 }); + expect(map.get(model.children[0])!.bounds).to.deep.equal({ x: 6, y: 5, width: 1, height: 2 }); + expect(map.get(model.children[1])!.bounds).to.deep.equal({ x: 5.5, y: 8, width: 2, height: 1 }); + expect(map.get(model.children[2])!.bounds).to.deep.equal({ x: 5, y: 10, width: 3, height: 3 }); + }); +}); From 2eddaca41d25c902dbd75749dfe7896c2f1fe0ce Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 27 Jul 2022 05:37:56 -0700 Subject: [PATCH 124/566] GH-708 Improve eslint config (#190) - Refactor eslint config according to the recommendations of https://typescript-eslint.io/docs/linting/monorepo/ - Add CLI script for fixing eslint issues Part of https://github.com/eclipse-glsp/glsp/issues/708 --- examples/workflow-glsp/.eslintrc.js | 22 ---------------------- examples/workflow-glsp/package.json | 3 ++- examples/workflow-glsp/scripts/download.ts | 2 +- examples/workflow-standalone/.eslintrc.js | 22 ---------------------- examples/workflow-standalone/package.json | 3 ++- packages/client/.eslintrc.js | 6 +----- packages/client/package.json | 3 ++- packages/protocol/.eslintrc.js | 6 +----- packages/protocol/package.json | 3 ++- 9 files changed, 11 insertions(+), 59 deletions(-) delete mode 100644 examples/workflow-glsp/.eslintrc.js delete mode 100644 examples/workflow-standalone/.eslintrc.js diff --git a/examples/workflow-glsp/.eslintrc.js b/examples/workflow-glsp/.eslintrc.js deleted file mode 100644 index 2af1914..0000000 --- a/examples/workflow-glsp/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - extends: '@eclipse-glsp', - parserOptions: { - tsconfigRootDir: __dirname, - project: 'tsconfig.json' - }, - rules: { - 'no-restricted-imports': [ - 'warn', - { - name: 'sprotty', - message: "The sprotty default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" - }, - { - name: 'sprotty-protocol', - message: - "The sprotty-protocol default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" - } - ] - } -}; diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index f794b01..bf2fecc 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -36,7 +36,8 @@ "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "tsc", - "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "files": [ diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index cc82bed..d8d3541 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/.eslintrc.js b/examples/workflow-standalone/.eslintrc.js deleted file mode 100644 index 2af1914..0000000 --- a/examples/workflow-standalone/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - extends: '@eclipse-glsp', - parserOptions: { - tsconfigRootDir: __dirname, - project: 'tsconfig.json' - }, - rules: { - 'no-restricted-imports': [ - 'warn', - { - name: 'sprotty', - message: "The sprotty default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" - }, - { - name: 'sprotty-protocol', - message: - "The sprotty-protocol default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" - } - ] - } -}; diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index a9b48bf..204f55d 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -38,7 +38,8 @@ "scripts": { "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib tsconfig.tsbuildinfo app/bundle.js app/bundle.js.map app/css", - "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "build": "tsc && webpack", "watch": "tsc -w -p ./tsconfig.json" }, diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 4303f12..c35647a 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -1,10 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: '@eclipse-glsp', - parserOptions: { - tsconfigRootDir: __dirname, - project: 'tsconfig.json' - }, + extends: '../../.eslintrc.js', rules: { 'no-restricted-imports': [ 'warn', diff --git a/packages/client/package.json b/packages/client/package.json index 41ade36..b102e4c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -47,7 +47,8 @@ "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib tsconfig.tsbuildinfo ", "build": "tsc", - "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter" diff --git a/packages/protocol/.eslintrc.js b/packages/protocol/.eslintrc.js index c649a32..3447069 100644 --- a/packages/protocol/.eslintrc.js +++ b/packages/protocol/.eslintrc.js @@ -1,10 +1,6 @@ /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: '@eclipse-glsp', - parserOptions: { - tsconfigRootDir: __dirname, - project: 'tsconfig.json' - }, + extends: '../../.eslintrc.js', rules: { 'no-restricted-imports': [ 'warn', diff --git a/packages/protocol/package.json b/packages/protocol/package.json index d93025c..aead7b6 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -44,7 +44,8 @@ "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib tsconfig.tsbuildinfo ", "build": "tsc", - "lint": "eslint -c ./.eslintrc.js --ext .ts ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "watch": "tsc -w" From f50a8da868c80c500e43e751a23ce27514e4cd71 Mon Sep 17 00:00:00 2001 From: Victor Gabriel Dulca Date: Wed, 27 Jul 2022 14:51:15 +0200 Subject: [PATCH 125/566] Fix keylisteners when using 'GLSPProjectionView' (#191) * Fix keylisteners when using 'GLSPProjectionView' * Fix styling for standalone example --- examples/workflow-glsp/src/di.config.ts | 4 +++- packages/client/src/views/glsp-projection-view.tsx | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 601d67a..0f0458a 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -21,6 +21,8 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, editLabelFeature, + GLSPGraph, + GLSPProjectionView, GridSnapper, LogLevel, overrideViewerOptions, @@ -66,7 +68,7 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); configureModelElement(context, 'activityNode:fork', ActivityNode, RectangularNodeView); configureModelElement(context, 'activityNode:join', ActivityNode, RectangularNodeView); - + configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, GLSPProjectionView); configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); configureModelElement(context, 'struct', SCompartment, StructureCompartmentView); }); diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 9bdac43..0b73a6f 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -25,6 +25,7 @@ import { ProjectedViewportView, ProjectionParams, RenderingContext, + setAttr, setClass, ViewportRootElement, ViewProjection @@ -36,12 +37,18 @@ import { @injectable() export class GLSPProjectionView extends ProjectedViewportView { override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { - return ( -
- {this.renderSvg(model, context, args)} + const svgElem = this.renderSvg(model, context, args); + if (svgElem.data) { + svgElem.data!.class = { 'sprotty-graph': true }; + } + const rootNode: VNode = ( +
+ {svgElem} {this.renderProjections(model, context, args)}
); + setAttr(rootNode, 'tabindex', 0); + return rootNode; } protected override renderSvg(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { From 2e883acf248d0b40d8fde7fb94f5f88d6f0715c6 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 27 Jul 2022 05:52:24 -0700 Subject: [PATCH 126/566] GH-708 Improve eslint config (#24) - Refactor eslint config according to the recommendations of https://typescript-eslint.io/docs/linting/monorepo/ - Add CLI script for fixing eslint issues Part of https://github.com/eclipse-glsp/glsp/issues/708 --- examples/workflow-server/package.json | 4 ++-- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server-node/package.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 6d5c20a..d22ddb4 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -44,8 +44,8 @@ "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "tsc", - "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w", "start:server": "yarn ts-node src/index.ts" }, diff --git a/packages/graph/package.json b/packages/graph/package.json index ea5c87c..ed269de 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -49,8 +49,8 @@ "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index da4b9d4..eb3d5bf 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -51,8 +51,8 @@ "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/server-node/package.json b/packages/server-node/package.json index ccc7431..b170010 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -53,8 +53,8 @@ "build": "tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { From c9a1a8eef4fc9811651ccfa0ec64f56d294624de Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 2 Aug 2022 02:13:24 -0700 Subject: [PATCH 127/566] Refactor download-server script (#194) Refactor configuration for the download server-script into separate JSON resource. This makes it easier to maintain and offers a simple approach to update the configuration in automated (CI) jobs --- examples/workflow-glsp/scripts/config.json | 9 +++++++++ examples/workflow-glsp/scripts/download.ts | 9 ++++----- examples/workflow-glsp/tsconfig.json | 3 ++- 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 examples/workflow-glsp/scripts/config.json diff --git a/examples/workflow-glsp/scripts/config.json b/examples/workflow-glsp/scripts/config.json new file mode 100644 index 0000000..093bc7d --- /dev/null +++ b/examples/workflow-glsp/scripts/config.json @@ -0,0 +1,9 @@ +{ + "releaseRepository": "https://repo1.maven.org/maven2/", + "snapshotRepository": "https://oss.sonatype.org/content/repositories/snapshots/", + "groupId": "org.eclipse.glsp.example", + "artifactId": "org.eclipse.glsp.example.workflow", + "version": "1.1.0", + "isSnapshot": true, + "classifier": "glsp" +} diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index d8d3541..b8e9ed8 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -15,13 +15,12 @@ ********************************************************************************/ import download from 'mvn-artifact-download'; import { join, resolve } from 'path'; +import * as config from './config.json'; const downloadDir = resolve(join(__dirname)) + '/../../..'; -const mavenRepository = 'https://oss.sonatype.org/content/repositories/snapshots/'; -const groupId = 'org.eclipse.glsp.example'; -const artifactId = 'org.eclipse.glsp.example.workflow'; -const version = '1.1.0'; -const classifier = 'glsp'; + +const { groupId, artifactId, classifier, version, isSnapshot } = config; +const mavenRepository = isSnapshot ? config.snapshotRepository : config.releaseRepository; console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); const isSnapShot = true; diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index 9ac8a4f..c139e00 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "lib", "baseUrl": ".", "reactNamespace": "JSX", - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true }, "include": ["src"] } From 4b436004bdf83a7619baf52098177404b48dc350 Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Thu, 4 Aug 2022 10:11:56 +0200 Subject: [PATCH 128/566] Update sprotty + use new button registration (#195) Injecting a `ButtonHandler` constructor is deprecated by sprotty. Stop reexporting the deprecated registration. Reexport new registration method `configureButtonHandler`. Contributed on behalf of STMicroelectronics --- packages/client/package.json | 2 +- packages/client/src/sprotty-reexport.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index b102e4c..e8aa2f7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -36,7 +36,7 @@ "dependencies": { "@eclipse-glsp/protocol": "1.1.0-next", "autocompleter": "5.1.0", - "sprotty": "0.12.0" + "sprotty": "0.13.0-next.1c4343c.328" }, "devDependencies": { "@vscode/codicons": "^0.0.25", diff --git a/packages/client/src/sprotty-reexport.ts b/packages/client/src/sprotty-reexport.ts index e5f740b..c891df3 100644 --- a/packages/client/src/sprotty-reexport.ts +++ b/packages/client/src/sprotty-reexport.ts @@ -104,7 +104,12 @@ export { export * from 'sprotty/lib/features/bounds/stack-layout'; export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; -export * from 'sprotty/lib/features/button/button-handler'; +export { + ButtonHandlerRegistry, + configureButtonHandler, + IButtonHandler, + IButtonHandlerRegistration +} from 'sprotty/lib/features/button/button-handler'; export * from 'sprotty/lib/features/button/model'; export * from 'sprotty/lib/features/command-palette/action-providers'; export * from 'sprotty/lib/features/command-palette/command-palette'; From c16cf45700e5e2ad4ee30d8f94baf83c1d494c14 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 18 Aug 2022 17:23:41 +0200 Subject: [PATCH 129/566] #642: Fix export SVG handling (#196) * #642: Fix export SVG handling - SVG element is not properly detected as it is nested - SVG element is not properly copied when it does not have an ID - Ensure we provide proper sizing information for SVG https://github.com/eclipse-glsp/glsp/issues/642 --- .../src/features/export/glsp-svg-exporter.ts | 50 +++++++++++++++++++ .../client/src/features/tools/di.config.ts | 6 ++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/features/export/glsp-svg-exporter.ts diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts new file mode 100644 index 0000000..a427b5d --- /dev/null +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { RequestAction } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { ExportSvgAction, SModelRoot, SvgExporter } from 'sprotty'; +import { v4 as uuid } from 'uuid'; + + @injectable() + export class GLSPSvgExporter extends SvgExporter { + override export(root: SModelRoot, _request?: RequestAction): void { + if (typeof document !== 'undefined') { + const svgElement = this.findSvgElement(); + if (svgElement) { + // createSvg requires the svg to have a non-empty id, so we generate one if necessary + const originalId = svgElement.id; + try { + svgElement.id = originalId || uuid(); + // provide generated svg code with respective sizing for proper viewing in browser and remove undesired border + const bounds = this.getBounds(root); + const svg = this.createSvg(svgElement, root) + .replace('style="', + `style="width: ${bounds.width}px !important;height: ${bounds.height}px !important;border: none !important;`); + // do not give request/response id here as otherwise the action is treated as an unrequested response + this.actionDispatcher.dispatch(ExportSvgAction.create(svg, '')); + } finally { + svgElement.id = originalId; + } + } + } + } + + protected findSvgElement(): SVGSVGElement | null { + const div = document.getElementById(this.options.hiddenDiv); + // search for first svg element as hierarchy within Sprotty might change + return div && div.querySelector('svg'); + } + } diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index d782f94..aa71e4a 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -18,6 +18,7 @@ import { ContainerModule, interfaces } from 'inversify'; import { configureActionHandler, configureModelElement } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { TYPES } from '../../base/types'; +import { GLSPSvgExporter } from '../export/glsp-svg-exporter'; import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; import { ChangeBoundsTool } from './change-bounds-tool'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; @@ -34,7 +35,7 @@ import { MarqueeView } from './view'; * Registers the default tools of GLSP (node and edge creation, changing bounds, edge editing, deletion) * and adds the marquee selection tool. */ -export const toolsModule = new ContainerModule((bind, _unbind, isBound) => { +export const toolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { // Register default tools bind(TYPES.IDefaultTool).to(ChangeBoundsTool); bind(TYPES.IDefaultTool).to(EdgeEditTool); @@ -50,6 +51,9 @@ export const toolsModule = new ContainerModule((bind, _unbind, isBound) => { configureMarqueeTool({ bind, isBound }); configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); + + bind(GLSPSvgExporter).toSelf().inSingletonScope(); + rebind(TYPES.SvgExporter).toService(GLSPSvgExporter); }); export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { From 45cff5e206cdd2d9a1517822baa693cbb4cc903f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 7 Sep 2022 11:13:06 +0200 Subject: [PATCH 130/566] Fix download server scripts (#197) Remove hardcoded `isSnapShot` property and use the property defined in `config.json` instead. --- examples/workflow-glsp/scripts/config.json | 2 +- examples/workflow-glsp/scripts/download.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/workflow-glsp/scripts/config.json b/examples/workflow-glsp/scripts/config.json index 093bc7d..7a599ab 100644 --- a/examples/workflow-glsp/scripts/config.json +++ b/examples/workflow-glsp/scripts/config.json @@ -4,6 +4,6 @@ "groupId": "org.eclipse.glsp.example", "artifactId": "org.eclipse.glsp.example.workflow", "version": "1.1.0", - "isSnapshot": true, + "isSnapShot": true, "classifier": "glsp" } diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts index b8e9ed8..cdf07f3 100644 --- a/examples/workflow-glsp/scripts/download.ts +++ b/examples/workflow-glsp/scripts/download.ts @@ -19,11 +19,10 @@ import * as config from './config.json'; const downloadDir = resolve(join(__dirname)) + '/../../..'; -const { groupId, artifactId, classifier, version, isSnapshot } = config; -const mavenRepository = isSnapshot ? config.snapshotRepository : config.releaseRepository; +const { groupId, artifactId, classifier, version, isSnapShot } = config; +const mavenRepository = isSnapShot ? config.snapshotRepository : config.releaseRepository; console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); -const isSnapShot = true; download({ groupId, artifactId, version, classifier, isSnapShot }, downloadDir, mavenRepository).then(() => console.log( 'Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-' + From 999e6060b16436e25d8d00ce6a4fe494325b5d0b Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 8 Sep 2022 10:33:43 +0200 Subject: [PATCH 131/566] #612: Recalculate all routing points on routing point update (#198) * #612: Recalculate all routing points on routing point update Fixes https://github.com/eclipse-glsp/glsp/issues/612 --- .../src/features/tools/change-bounds-tool.ts | 29 ++++++++++--------- .../src/features/tools/edge-edit-tool.ts | 11 ++++--- packages/client/src/utils/smodel-util.ts | 21 +++++++++++++- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 79666de..3ab11cc 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { + import { Action, Bounds, ChangeBoundsOperation, @@ -44,10 +44,10 @@ import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { TYPES } from '../../base/types'; import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { + calcElementAndRoutingPoints, forEachElement, isNonRoutableSelectedMovableBoundsAware, - toElementAndBounds, - toElementAndRoutingPoints + toElementAndBounds } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable, Resizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; import { @@ -237,19 +237,22 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } protected handleMoveRoutingPointsOnServer(target: SModelElement): Operation[] { - const result: Operation[] = []; const newRoutingPoints: ElementAndRoutingPoints[] = []; - forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { + const routerRegistry = this.tool.edgeRouterRegistry; + if (routerRegistry) { // If client routing is enabled -> delegate routingpoints of connected edges to server - if (this.tool.edgeRouterRegistry && element instanceof SConnectableElement) { - element.incomingEdges.map(toElementAndRoutingPoints).forEach(ear => newRoutingPoints.push(ear)); - element.outgoingEdges.map(toElementAndRoutingPoints).forEach(ear => newRoutingPoints.push(ear)); - } - }); - if (newRoutingPoints.length > 0) { - result.push(ChangeRoutingPointsOperation.create(newRoutingPoints)); + forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { + if (element instanceof SConnectableElement) { + element.incomingEdges.map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) + .forEach(ear => newRoutingPoints.push(ear)); + element.outgoingEdges.map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) + .forEach(ear => newRoutingPoints.push(ear)); + } + }); } - return result; + return newRoutingPoints.length > 0 + ? [ChangeRoutingPointsOperation.create(newRoutingPoints)] + : []; } protected handleResize(activeResizeHandle: SResizeHandle): Action[] { diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 273408f..209d2cc 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -31,7 +31,7 @@ import { } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { TYPES } from '../../base/types'; -import { isRoutable, isRoutingHandle } from '../../utils/smodel-util'; +import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle } from '../reconnect/model'; import { SelectionListener, SelectionService } from '../select/selection-service'; import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from '../tool-feedback/creation-tool-feedback'; @@ -53,7 +53,7 @@ export class EdgeEditTool extends BaseGLSPTool { @inject(TYPES.SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; - @inject(EdgeRouterRegistry) @optional() protected edgeRouterRegistry?: EdgeRouterRegistry; + @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; @@ -237,16 +237,15 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen // reroute actions const latestEdge = target.index.getById(this.edge.id); if (latestEdge && isRoutable(latestEdge)) { - result.push( - ChangeRoutingPointsOperation.create([{ elementId: latestEdge.id, newRoutingPoints: latestEdge.routingPoints }]) - ); + const newRoutingPoints = calcElementAndRoutingPoints(latestEdge, this.tool.edgeRouterRegistry); + result.push(ChangeRoutingPointsOperation.create([newRoutingPoints])); this.routingHandle = undefined; } } return result; } - override mouseOver(target: SModelElement, event: MouseEvent): Action[] { + override mouseOver(target: SModelElement, _event: MouseEvent): Action[] { if (this.edge && this.isReconnecting()) { const currentTarget = findParentByFeature(target, isConnectable); if (!this.newConnectable || currentTarget !== this.newConnectable) { diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index 853d6f8..df0bd91 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -16,6 +16,7 @@ import { distinctAdd, ElementAndBounds, ElementAndRoutingPoints, remove, SModelElementSchema, TypeGuard } from '@eclipse-glsp/protocol'; import { BoundsAware, + EdgeRouterRegistry, isBoundsAware, isMoveable, isSelectable, @@ -214,7 +215,7 @@ export function toElementAndBounds(element: BoundsAwareModelElement): ElementAnd /** * Helper function to translate a given {@link SRoutableElement} into its corresponding - * {@ElementAndRoutingPoints ElementAndBounds} representation. + * {@link ElementAndRoutingPoints} representation. * @param element The element to translate. * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. */ @@ -225,6 +226,23 @@ export function toElementAndRoutingPoints(element: SRoutableElement): ElementAnd }; } +/** + * Helper function to calculate the {@link ElementAndRoutingPoints} for a given {@link SRoutableElement}. + * If client layout is activated, i.e., the edge routing registry is given and has a router for the element, then the routing + * points from the calculated route are used, otherwise we use the already specified routing points of the {@link SRoutableElement}. + * @param element The element to translate. + * @param edgeRouterRegistry the edge router registry + * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. + */ +export function calcElementAndRoutingPoints(element: SRoutableElement, edgeRouterRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { + const router = edgeRouterRegistry?.get(element.routerKind); + // filter duplicate points (same x,y coordinates) and only keep actual routing points (no start or target, no volatile points) + const clientRoutingPoints = router?.route(element) + .filter((point, idx, route) => idx === route.findIndex(otherPoint => otherPoint.x === point.x && otherPoint.y === point.y)) + .filter(point => point.kind === 'linear' || point.kind === 'bezier-junction'); + return { elementId: element.id, newRoutingPoints: clientRoutingPoints || element.routingPoints }; +} + /** * Checks if the model is compatible with the passed type string. * (either has the same type or a subtype of this type) @@ -247,3 +265,4 @@ export function getElementTypeId(input: SModelElement | SModelElementSchema | st return (input as any)['type'] as string; } } + From 358072994850bf99281027e4a93562770fd79916 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Fri, 23 Sep 2022 19:02:21 +0200 Subject: [PATCH 132/566] GH-706 Update protocol version (#199) Increment protocol version to 1.0.0. Part of https://github.com/eclipse-glsp/glsp/issues/706 Signed-off-by: Olaf Lessenich --- packages/protocol/src/glsp-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/glsp-client.ts index c2f9e0b..32244b5 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/glsp-client.ts @@ -214,5 +214,5 @@ export namespace GLSPClient { return typeof object === 'object' && 'id' in object && typeof object['id'] === 'string'; } - export const protocolVersion = '0.9.0'; + export const protocolVersion = '1.0.0'; } From 065fdca2752f0dc37514ea77fa5f25b2703214b9 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Thu, 6 Oct 2022 12:25:32 +0200 Subject: [PATCH 133/566] GH-706 Update protocol version (#25) Increment protocol version to 1.0.0. Part of https://github.com/eclipse-glsp/glsp/issues/706 Signed-off-by: Olaf Lessenich --- packages/server-node/src/launch/cli-parser.ts | 2 +- packages/server-node/src/protocol/glsp-server.spec.ts | 2 +- packages/server-node/src/protocol/glsp-server.ts | 2 +- packages/server-node/src/test/integration-test.spec.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server-node/src/launch/cli-parser.ts b/packages/server-node/src/launch/cli-parser.ts index b8249e9..5551051 100644 --- a/packages/server-node/src/launch/cli-parser.ts +++ b/packages/server-node/src/launch/cli-parser.ts @@ -39,7 +39,7 @@ export const defaultLaunchOptions: Required = { export function createCliParser(options: LaunchOptions = defaultLaunchOptions): CliParser { const command = new cmd.Command() - .version('0.9.0') + .version('1.0.0') .description('GLSP server') .showHelpAfterError(true) .name('Launch a GLSP server') diff --git a/packages/server-node/src/protocol/glsp-server.spec.ts b/packages/server-node/src/protocol/glsp-server.spec.ts index e206f50..7a77533 100644 --- a/packages/server-node/src/protocol/glsp-server.spec.ts +++ b/packages/server-node/src/protocol/glsp-server.spec.ts @@ -31,7 +31,7 @@ describe('test DefaultGLSPServer', () => { const clientSessionId = 'myClientSession'; const diagramType = 'myDiagram'; const applicationId = 'Test'; - const protocolVersion = '0.9.0'; + const protocolVersion = '1.0.0'; const serverActionKinds = new Map(); serverActionKinds.set(diagramType, ['A1', 'A2']); const sessionManager = new mock.StubClientSessionManager(); diff --git a/packages/server-node/src/protocol/glsp-server.ts b/packages/server-node/src/protocol/glsp-server.ts index 81c4c1c..7dc564a 100644 --- a/packages/server-node/src/protocol/glsp-server.ts +++ b/packages/server-node/src/protocol/glsp-server.ts @@ -145,7 +145,7 @@ export interface JsonRpcGLSPServer extends GLSPServer { @injectable() export class DefaultGLSPServer implements JsonRpcGLSPServer { - public static readonly PROTOCOL_VERSION = '0.9.0'; + public static readonly PROTOCOL_VERSION = '1.0.0'; @inject(Logger) protected logger: Logger; diff --git a/packages/server-node/src/test/integration-test.spec.ts b/packages/server-node/src/test/integration-test.spec.ts index e81af99..e59e89a 100644 --- a/packages/server-node/src/test/integration-test.spec.ts +++ b/packages/server-node/src/test/integration-test.spec.ts @@ -42,7 +42,7 @@ import { DefaultGLSPServer, GLSPServer } from '../protocol/glsp-server'; import { Logger } from '../utils/logger'; import * as mock from './mock-util'; const applicationId = 'testApp'; -const protocolVersion = '0.9.0'; +const protocolVersion = '1.0.0'; const diagramType = 'testDiagram'; const clientId = 'session1'; const sourceUri = path.resolve(__dirname, 'minimal.json'); From 12a37244f8f7b531c5e19cebe09cb6673c52d504 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 14 Nov 2022 17:20:58 +0100 Subject: [PATCH 134/566] #738: Add route to ComputedBoundsAction (#201) Part of https://github.com/eclipse-glsp/glsp/issues/738 --- packages/client/src/base/container-modules.ts | 4 +- .../client/src/features/bounds/di.config.ts | 39 ++++++++ .../bounds/glsp-hidden-bounds-updater.ts | 88 +++++++++++++++++++ packages/client/src/utils/smodel-util.ts | 59 ++++++++++--- .../src/action-protocol/model-layout.spec.ts | 7 +- .../src/action-protocol/model-layout.ts | 8 +- 6 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 packages/client/src/features/bounds/di.config.ts create mode 100644 packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 4328d7e..286d0be 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -16,7 +16,6 @@ import { Container, ContainerModule } from 'inversify'; import { - boundsModule, buttonModule, defaultModule, edgeIntersectionModule, @@ -30,6 +29,7 @@ import { routingModule, zorderModule } from 'sprotty'; +import glspBoundsModule from '../features/bounds/di.config'; import glspCommandPaletteModule from '../features/command-palette/di.config'; import glspContextMenuModule from '../features/context-menu/di.config'; import { glspServerCopyPasteModule } from '../features/copy-paste/di.config'; @@ -52,13 +52,13 @@ import defaultGLSPModule from './di.config'; export const DEFAULT_MODULES = [ defaultModule, defaultGLSPModule, - boundsModule, buttonModule, edgeIntersectionModule, edgeLayoutModule, expandModule, exportModule, fadeModule, + glspBoundsModule, glspCommandPaletteModule, glspContextMenuModule, glspDecorationModule, diff --git a/packages/client/src/features/bounds/di.config.ts b/packages/client/src/features/bounds/di.config.ts new file mode 100644 index 0000000..e94eb9f --- /dev/null +++ b/packages/client/src/features/bounds/di.config.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { + configureCommand, configureLayout, HBoxLayouter, HiddenBoundsUpdater, Layouter, + LayoutRegistry, RequestBoundsCommand, SetBoundsCommand, StackLayouter, VBoxLayouter +} from 'sprotty'; +import '../../../css/command-palette.css'; +import { TYPES } from '../../base/types'; +import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; + +const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { + configureCommand({ bind, isBound }, SetBoundsCommand); + configureCommand({ bind, isBound }, RequestBoundsCommand); + bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); + bind(GLSPHiddenBoundsUpdater).toSelf().inSingletonScope(); + bind(TYPES.HiddenVNodePostprocessor).toService(GLSPHiddenBoundsUpdater); + bind(TYPES.Layouter).to(Layouter).inSingletonScope(); + bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); + + configureLayout({ bind, isBound }, VBoxLayouter.KIND, VBoxLayouter); + configureLayout({ bind, isBound }, HBoxLayouter.KIND, HBoxLayouter); + configureLayout({ bind, isBound }, StackLayouter.KIND, StackLayouter); +}); + +export default glspBoundsModule; diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts new file mode 100644 index 0000000..69cfc1b --- /dev/null +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, ComputedBoundsAction, Deferred, ElementAndRoutingPoints, RequestAction, + ResponseAction +} from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { VNode } from 'snabbdom'; +import { EdgeRouterRegistry, HiddenBoundsUpdater, IActionDispatcher, SModelElement, SRoutableElement } from 'sprotty'; +import { calcElementAndRoute, isRoutable } from '../../utils/smodel-util'; + +/** + * Grabs the bounds from hidden SVG DOM elements, applies layouts, collects routes and fires {@link ComputedBoundsAction}s. + * + * The actions will contain the bound, alignment, and routing points of elements. + */ +@injectable() +export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { + + @inject(EdgeRouterRegistry) @optional() protected readonly edgeRouterRegistry?: EdgeRouterRegistry; + + protected element2route: ElementAndRoutingPoints[] = []; + protected edges: SRoutableElement[] = []; + protected nodes: VNode[] = []; + + override decorate(vnode: VNode, element: SModelElement): VNode { + super.decorate(vnode, element); + if (isRoutable(element)) { + this.element2route.push(calcElementAndRoute(element, this.edgeRouterRegistry)); + } + return vnode; + } + + override postUpdate(cause?: Action): void { + const actions = this.captureActions(() => super.postUpdate(cause)); + actions.filter(action => ComputedBoundsAction.is(action)) + .forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction))); + this.element2route = []; + } + + protected captureActions(call: () => void): Action[] { + const capturingActionDispatcher = new CapturingActionDispatcher(); + const actualActionDispatcher = this.actionDispatcher; + this.actionDispatcher = capturingActionDispatcher; + try { + call(); + return capturingActionDispatcher.actions; + } finally { + this.actionDispatcher = actualActionDispatcher; + } + } + + protected enhanceAction(action: ComputedBoundsAction): ComputedBoundsAction { + action.routes = this.element2route.length === 0 ? undefined : this.element2route; + return action; + } +} + +class CapturingActionDispatcher implements IActionDispatcher { + readonly actions: Action[] = []; + + async dispatch(action: Action): Promise { + this.actions.push(action); + } + + async dispatchAll(actions: Action[]): Promise { + this.actions.push(...actions); + } + + async request(action: RequestAction): Promise { + // ignore, not needed for our purposes + return new Deferred().promise; + } +} diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index df0bd91..e45552b 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -13,15 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { distinctAdd, ElementAndBounds, ElementAndRoutingPoints, remove, SModelElementSchema, TypeGuard } from '@eclipse-glsp/protocol'; +import { + distinctAdd, ElementAndBounds, ElementAndRoutingPoints, Point, remove, SModelElementSchema, + TypeGuard +} from '@eclipse-glsp/protocol'; import { BoundsAware, EdgeRouterRegistry, isBoundsAware, isMoveable, isSelectable, - isSelected, - ModelIndexImpl, + isSelected, ModelIndexImpl, + RoutedPoint, Selectable, SModelElement, SRoutableElement, @@ -226,21 +229,55 @@ export function toElementAndRoutingPoints(element: SRoutableElement): ElementAnd }; } +/** Pure routing point data kinds. */ +export const ROUTING_POINT_KINDS = ['linear', 'bezier-junction']; + +/** Pure route data kinds. */ +export const ROUTE_KINDS = [...ROUTING_POINT_KINDS, 'source', 'target']; + /** * Helper function to calculate the {@link ElementAndRoutingPoints} for a given {@link SRoutableElement}. * If client layout is activated, i.e., the edge routing registry is given and has a router for the element, then the routing * points from the calculated route are used, otherwise we use the already specified routing points of the {@link SRoutableElement}. * @param element The element to translate. - * @param edgeRouterRegistry the edge router registry + * @param routerRegistry the edge router registry. * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. */ -export function calcElementAndRoutingPoints(element: SRoutableElement, edgeRouterRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { - const router = edgeRouterRegistry?.get(element.routerKind); - // filter duplicate points (same x,y coordinates) and only keep actual routing points (no start or target, no volatile points) - const clientRoutingPoints = router?.route(element) - .filter((point, idx, route) => idx === route.findIndex(otherPoint => otherPoint.x === point.x && otherPoint.y === point.y)) - .filter(point => point.kind === 'linear' || point.kind === 'bezier-junction'); - return { elementId: element.id, newRoutingPoints: clientRoutingPoints || element.routingPoints }; +export function calcElementAndRoutingPoints(element: SRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { + const newRoutingPoints = routerRegistry ? calcRoute(element, routerRegistry, ROUTING_POINT_KINDS) : element.routingPoints; + return { elementId: element.id, newRoutingPoints }; +} + +/** + * Helper function to calculate the route for a given {@link SRoutableElement}. + * If client layout is activated, i.e., the edge routing registry is given and has a router for the element, then the points + * from the calculated route are used, otherwise we use the already specified routing points of the {@link SRoutableElement}. + * @param element The element to translate. + * @param routerRegistry the edge router registry. + * @returns The corresponding route for the given element. + */ +export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { + let route: Point[] | undefined = routerRegistry ? calcRoute(element, routerRegistry, ROUTE_KINDS) : undefined; + if (!route) { + // add source and target to the routing points + route = [...element.routingPoints]; + route.splice(0, 0, element.source?.position || Point.ORIGIN); + route.push(element.target?.position || Point.ORIGIN); + } + return { elementId: element.id, newRoutingPoints: route }; +} + +/** + * Helper function to calculate the route for a given {@link SRoutableElement}. + * @param element The element to translate. + * @param routerRegistry the edge router registry. + * @param pointKinds the routing point kinds that should be considered. + * @returns The corresponding route for the given element. + */ +export function calcRoute(element: SRoutableElement, routerRegistry: EdgeRouterRegistry, pointKinds?: string[]): RoutedPoint[] | undefined { + return routerRegistry?.get(element.routerKind).route(element) + .filter((point, idx, fullRoute) => idx === fullRoute.findIndex(otherPoint => otherPoint.x === point.x && otherPoint.y === point.y)) + .filter(point => !pointKinds || pointKinds.includes(point.kind)); } /** diff --git a/packages/protocol/src/action-protocol/model-layout.spec.ts b/packages/protocol/src/action-protocol/model-layout.spec.ts index 5e5f9c1..620068d 100644 --- a/packages/protocol/src/action-protocol/model-layout.spec.ts +++ b/packages/protocol/src/action-protocol/model-layout.spec.ts @@ -97,10 +97,11 @@ describe('Model layout actions', () => { responseId: 'myResponse', alignments: [{ elementId: 'myElement', newAlignment: Point.ORIGIN }], revision: 5, - bounds: [{ elementId: '', newSize: Dimension.EMPTY, newPosition: Point.ORIGIN }] + bounds: [{ elementId: '', newSize: Dimension.EMPTY, newPosition: Point.ORIGIN }], + routes: [{ elementId: 'myEdge', newRoutingPoints: [{ x: 42, y: 1337 }] }] }; - const { bounds, responseId, alignments, revision } = expected; - expect(ComputedBoundsAction.create(bounds, { responseId, alignments, revision })).to.deep.equals(expected); + const { bounds, responseId, alignments, revision, routes } = expected; + expect(ComputedBoundsAction.create(bounds, { responseId, alignments, revision, routes })).to.deep.equals(expected); }); }); }); diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index 5df0d05..5560df0 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -17,7 +17,7 @@ import * as sprotty from 'sprotty-protocol/lib/actions'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; import { SModelRootSchema } from './model-structure'; -import { ElementAndAlignment, ElementAndBounds } from './types'; +import { ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints } from './types'; /** * Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can @@ -74,6 +74,11 @@ export interface ComputedBoundsAction extends ResponseAction, sprotty.ComputedBo * The new alignment of the model elements. */ alignments?: ElementAndAlignment[]; + + /** + * The route of the model elements. + */ + routes?: ElementAndRoutingPoints[]; } export namespace ComputedBoundsAction { @@ -89,6 +94,7 @@ export namespace ComputedBoundsAction { revision?: number; responseId?: string; alignments?: ElementAndAlignment[]; + routes?: ElementAndRoutingPoints[]; } = {} ): ComputedBoundsAction { return { From 27b02fbe12b2b2555e37ec0aa89f774656bde0c1 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 14 Nov 2022 17:42:13 +0100 Subject: [PATCH 135/566] #657: Allow the first Manhattan routing point to be moved (#200) Relates to https://github.com/eclipse-glsp/glsp/issues/657 --- .../client/src/features/tools/di.config.ts | 6 +- .../tools/glsp-manhattan-edge-router.ts | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/features/tools/glsp-manhattan-edge-router.ts diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index aa71e4a..a0ba9c7 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { ContainerModule, interfaces } from 'inversify'; -import { configureActionHandler, configureModelElement } from 'sprotty'; +import { configureActionHandler, configureModelElement, ManhattanEdgeRouter } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { TYPES } from '../../base/types'; import { GLSPSvgExporter } from '../export/glsp-svg-exporter'; @@ -25,6 +25,7 @@ import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; import { EdgeCreationTool } from './edge-creation-tool'; import { EdgeEditTool } from './edge-edit-tool'; import { EnableDefaultToolsOnFocusLossHandler } from './enable-default-tools-on-focus-loss'; +import { GSLPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; import { MarqueeMouseTool } from './marquee-mouse-tool'; import { MarqueeTool } from './marquee-tool'; import { MarqueeNode } from './model'; @@ -54,6 +55,9 @@ export const toolsModule = new ContainerModule((bind, _unbind, isBound, rebind) bind(GLSPSvgExporter).toSelf().inSingletonScope(); rebind(TYPES.SvgExporter).toService(GLSPSvgExporter); + + bind(GSLPManhattanEdgeRouter).toSelf().inSingletonScope(); + rebind(ManhattanEdgeRouter).toService(GSLPManhattanEdgeRouter); }); export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { diff --git a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts new file mode 100644 index 0000000..451459f --- /dev/null +++ b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { almostEquals } from '@eclipse-glsp/protocol'; +import { ManhattanEdgeRouter, ResolvedHandleMove, SRoutableElement } from 'sprotty'; + +export class GSLPManhattanEdgeRouter extends ManhattanEdgeRouter { + protected override applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { + const route = this.route(edge); + const routingPoints = edge.routingPoints; + const minimalPointDistance = this.getOptions(edge).minimalPointDistance; + moves.forEach(move => { + const handle = move.handle; + const index = handle.pointIndex; + const correctedX = this.correctX(routingPoints, index, move.toPosition.x, minimalPointDistance); + const correctedY = this.correctY(routingPoints, index, move.toPosition.y, minimalPointDistance); + switch (handle.kind) { + case 'manhattan-50%': + if (index < 0) { + if (routingPoints.length === 0) { + routingPoints.push({ x: correctedX, y: correctedY }); + move.handle.pointIndex = 0; + } + else if (almostEquals(route[0].x, route[1].x)) { this.alignX(routingPoints, 0, correctedX); } + else { this.alignY(routingPoints, 0, correctedY); } + } else if (index < routingPoints.length - 1) { + if (almostEquals(routingPoints[index].x, routingPoints[index + 1].x)) { + this.alignX(routingPoints, index, correctedX); + this.alignX(routingPoints, index + 1, correctedX); + } else { + this.alignY(routingPoints, index, correctedY); + this.alignY(routingPoints, index + 1, correctedY); + } + } else { + if (almostEquals(route[route.length - 2].x, route[route.length - 1].x)) { + this.alignX(routingPoints, routingPoints.length - 1, correctedX); + } + else { this.alignY(routingPoints, routingPoints.length - 1, correctedY); } + } + break; + } + }); + } +} From ef411b2bc29835eeaf86557c1e00943059b95663 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 6 Dec 2022 14:52:05 +0100 Subject: [PATCH 136/566] Introduce GLSP CLI dev package (#755) - Move existing config package into "dev-packages" - Implement @eclipse-glsp/cli dev package. This packages offers a CLI app that - Provide a commandsfor preparing GLSP component Github releases & npm dry-runs - Provide Docker file for locale releng Fixes https://github.com/eclipse-glsp/glsp/issues/754 Contributed on behalf of STMicroelectronics --- dev-packages/README.md | 19 + dev-packages/cli/.eslintrc.js | 11 + dev-packages/cli/README.md | 45 ++ dev-packages/cli/bin/glsp | 2 + dev-packages/cli/package.json | 61 ++ dev-packages/cli/src/app.ts | 49 ++ dev-packages/cli/src/release/common.ts | 312 +++++++++ .../cli/src/release/release-client.ts | 64 ++ .../release/release-eclipse-integration.ts | 113 +++ .../cli/src/release/release-java-server.ts | 72 ++ .../cli/src/release/release-server-node.ts | 61 ++ .../src/release/release-theia-integration.ts | 77 +++ .../src/release/release-vscode-integration.ts | 65 ++ dev-packages/cli/src/release/release.ts | 174 +++++ dev-packages/cli/src/util/command-util.ts | 63 ++ dev-packages/cli/src/util/git-util.ts | 87 +++ dev-packages/cli/src/util/logger.ts | 38 ++ dev-packages/cli/src/util/validation-util.ts | 50 ++ dev-packages/cli/tsconfig.json | 10 + dev-packages/config/LICENSE | 642 ++++++++++++++++++ dev-packages/config/README.md | 75 ++ dev-packages/config/package.json | 41 ++ dev-packages/eslint-config/LICENSE | 642 ++++++++++++++++++ dev-packages/eslint-config/README.md | 29 + .../eslint-config/configs/base.eslintrc.js | 24 + .../eslint-config/configs/errors.eslintrc.js | 84 +++ .../configs/warnings.eslintrc.js | 83 +++ dev-packages/eslint-config/index.js | 9 + dev-packages/eslint-config/package.json | 41 ++ dev-packages/mocha-config/.mocharc.json | 8 + dev-packages/mocha-config/LICENSE | 642 ++++++++++++++++++ dev-packages/mocha-config/README.md | 25 + dev-packages/mocha-config/package.json | 53 ++ dev-packages/prettier-config/LICENSE | 642 ++++++++++++++++++ dev-packages/prettier-config/README.md | 31 + dev-packages/prettier-config/index.json | 19 + dev-packages/prettier-config/package.json | 30 + dev-packages/ts-config/LICENSE | 642 ++++++++++++++++++ dev-packages/ts-config/README.md | 28 + dev-packages/ts-config/package.json | 36 + dev-packages/ts-config/tsconfig.json | 29 + 41 files changed, 5228 insertions(+) create mode 100644 dev-packages/README.md create mode 100644 dev-packages/cli/.eslintrc.js create mode 100644 dev-packages/cli/README.md create mode 100755 dev-packages/cli/bin/glsp create mode 100644 dev-packages/cli/package.json create mode 100644 dev-packages/cli/src/app.ts create mode 100644 dev-packages/cli/src/release/common.ts create mode 100644 dev-packages/cli/src/release/release-client.ts create mode 100644 dev-packages/cli/src/release/release-eclipse-integration.ts create mode 100644 dev-packages/cli/src/release/release-java-server.ts create mode 100644 dev-packages/cli/src/release/release-server-node.ts create mode 100644 dev-packages/cli/src/release/release-theia-integration.ts create mode 100644 dev-packages/cli/src/release/release-vscode-integration.ts create mode 100644 dev-packages/cli/src/release/release.ts create mode 100644 dev-packages/cli/src/util/command-util.ts create mode 100644 dev-packages/cli/src/util/git-util.ts create mode 100644 dev-packages/cli/src/util/logger.ts create mode 100644 dev-packages/cli/src/util/validation-util.ts create mode 100644 dev-packages/cli/tsconfig.json create mode 100644 dev-packages/config/LICENSE create mode 100644 dev-packages/config/README.md create mode 100644 dev-packages/config/package.json create mode 100644 dev-packages/eslint-config/LICENSE create mode 100644 dev-packages/eslint-config/README.md create mode 100644 dev-packages/eslint-config/configs/base.eslintrc.js create mode 100644 dev-packages/eslint-config/configs/errors.eslintrc.js create mode 100644 dev-packages/eslint-config/configs/warnings.eslintrc.js create mode 100644 dev-packages/eslint-config/index.js create mode 100644 dev-packages/eslint-config/package.json create mode 100644 dev-packages/mocha-config/.mocharc.json create mode 100644 dev-packages/mocha-config/LICENSE create mode 100644 dev-packages/mocha-config/README.md create mode 100644 dev-packages/mocha-config/package.json create mode 100644 dev-packages/prettier-config/LICENSE create mode 100644 dev-packages/prettier-config/README.md create mode 100644 dev-packages/prettier-config/index.json create mode 100644 dev-packages/prettier-config/package.json create mode 100644 dev-packages/ts-config/LICENSE create mode 100644 dev-packages/ts-config/README.md create mode 100644 dev-packages/ts-config/package.json create mode 100644 dev-packages/ts-config/tsconfig.json diff --git a/dev-packages/README.md b/dev-packages/README.md new file mode 100644 index 0000000..1e122a3 --- /dev/null +++ b/dev-packages/README.md @@ -0,0 +1,19 @@ +# Eclipse GLSP - Dev Packages [![build-status](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp/job/master)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) [![build-status-server](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/deploy-npm-glsp-config/&label=publish)](https://ci.eclipse.org/glsp/job/deploy-npm-glsp-config/) + +Common shared development packages for Eclipse GLSP components that are implemented with Typescript. + +## Components + +- [`@eclipse-glsp/cli`](./cli/README.md): Provides helpful scrips and commands for developing glsp components as well as release engineering. +- [`@eclipse-glsp/config`](./config/README.md): Provides a meta package that export common configuration objects for: + - [Typescript](https://www.typescriptlang.org/) (`tsconfig.json`) + - [ESLint](https://eslint.org/) (`.eslintrc`) + - [Prettier](https://prettier.io/) (`.prettierrc`). + - [Mocha](https://mochajs.org/) (`.mocharc.json`). + +The packages are available via npm and are used by all GLSP components implemented with Typescript. + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/cli/.eslintrc.js b/dev-packages/cli/.eslintrc.js new file mode 100644 index 0000000..71fb220 --- /dev/null +++ b/dev-packages/cli/.eslintrc.js @@ -0,0 +1,11 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + extends: '@eclipse-glsp', + ignorePatterns: ['**/{node_modules,lib}', '**/.eslintrc.js'], + + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/dev-packages/cli/README.md b/dev-packages/cli/README.md new file mode 100644 index 0000000..ff146db --- /dev/null +++ b/dev-packages/cli/README.md @@ -0,0 +1,45 @@ +# Eclipse GLSP - CLI + +The `@eclipse-glsp/cli` package provides helpful scripts and commands for extension and application development. +The contributed `glsp`, is a command line tool that offers all contributed commands. + +## Getting Started + +Install `@eclipse-glsp/cli` as a dev dependency in your application. + +```bash +yarn add @eclipse-glsp/cli --dev +``` + +## Commands + +Eclipse GLSP committers can use the `release` following command to prepare & publish a new Github release +for a specific GLSP component. + +```bash +$ glsp release -h +Usage: glsp release [options] [customVersion] + +Prepare & publish a new release for a glsp component + +Arguments: + component The glsp component to be released (choices: "client", "theia-integration", "vscode-integration", "eclipse-integration", "server-node", "server-java") + releaseType The release type (choices: "major", "minor", "patch", "rc", "custom") + customVersion Custom version number. Will be ignored if the release type is not "custom" + +Options: + -V, --version output the version number + -f, --force Enable force mode (default: false) + -d, --checkoutDir The git checkout directory (default: "/home/tobias/Git/OpenSource/glsp/glsp/dev-packages/cli") + -b, --branch The git branch to checkout (default: "master") + -v, --verbose Enable verbose (debug) log output (default: false) + --no-publish Only prepare release but do not publish to github + --draft Publish github releases as drafts (default: false) + --npm-dryRun Execute a npm dry-run for inspection. Publishes to the local npm registry and does not publish to github (default: false) + -h, --help display help for command +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/cli/bin/glsp b/dev-packages/cli/bin/glsp new file mode 100755 index 0000000..8933e61 --- /dev/null +++ b/dev-packages/cli/bin/glsp @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../lib/app.js'); diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json new file mode 100644 index 0000000..56aa4c1 --- /dev/null +++ b/dev-packages/cli/package.json @@ -0,0 +1,61 @@ +{ + "name": "@eclipse-glsp/cli", + "version": "1.0.0-next", + "description": "CLI Tooling & scripts for GLSP components", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "tsconfig" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "scripts": { + "prepare": "yarn clean && yarn build && yarn lint", + "clean": "rimraf lib tsconfig.tsbuildinfo ", + "build": "tsc", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix --ext .ts,.tsx ./src", + "test": "node --enable-source-maps lib/app.js" + }, + "dependencies": { + "commander": "^9.4.0", + "shelljs": "0.8.5", + "semver": "^7.3.7", + "node-fetch": "2.6.7", + "readline-sync": "^1.4.10" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "rimraf": "3.0.2", + "@types/shelljs": "0.8.11", + "@types/semver": "7.3.12", + "typescript": "^4.5.0", + "@types/node": "14.x", + "@types/readline-sync": "1.4.4", + "@eclipse-glsp/config": "1.0.0-next", + "reflect-metadata": "0.1.13", + "@types/node-fetch": "2.6.2" + }, + "bin": { + "glsp": "bin/glsp" + }, + "files": [ + "src", + "bin", + "lib" + ] +} diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts new file mode 100644 index 0000000..916b612 --- /dev/null +++ b/dev-packages/cli/src/app.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env node +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Argument, Command } from 'commander'; +import * as sh from 'shelljs'; +import { Component, ReleaseType } from './release/common'; +import { release } from './release/release'; +import { baseConfiguration } from './util/command-util'; +import { validateDirectory, validateVersion } from './util/validation-util'; + +export const ReleaseCommand = baseConfiguration(new Command()) + .name('release') + .description('Prepare & publish a new release for a glsp component') + .addArgument(new Argument('', 'The glsp component to be released').choices(Component.CLI_CHOICES).argParser(Component.parse)) + .addArgument(new Argument('', 'The release type').choices(ReleaseType.CLI_CHOICES)) + .argument('[customVersion]', 'Custom version number. Will be ignored if the release type is not "custom"', validateVersion) + .option('-f, --force', 'Enable force mode', false) + .option('-d, --checkoutDir ', 'The git checkout directory', validateDirectory, sh.pwd().stdout) + .option('-b, --branch ', 'The git branch to checkout', 'master') + .option('-v, --verbose', 'Enable verbose (debug) log output', false) + .option('--no-publish', 'Only prepare release but do not publish to github', true) + .option('--draft', 'Publish github releases as drafts', false) + .option( + '--npm-dryRun', + 'Execute a npm dry-run for inspection. Publishes to the local npm registry and does not publish to github', + false + ) + .action(release); + +const app = baseConfiguration(new Command()) + .showSuggestionAfterError(true) + .showHelpAfterError(true) + .name('glsp') + .addCommand(ReleaseCommand); + +app.parse(process.argv); diff --git a/dev-packages/cli/src/release/common.ts b/dev-packages/cli/src/release/common.ts new file mode 100644 index 0000000..5e153fb --- /dev/null +++ b/dev-packages/cli/src/release/common.ts @@ -0,0 +1,312 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as fs from 'fs'; +import fetch from 'node-fetch'; +import { resolve } from 'path'; +import * as readline from 'readline-sync'; +import * as semver from 'semver'; +import * as sh from 'shelljs'; +import { fatalExec, getShellConfig } from '../util/command-util'; +import { getLatestRelease, getLatestTag, hasGitChanges, isGitRepository } from '../util/git-util'; +import { LOGGER } from '../util/logger'; +import { validateVersion } from '../util/validation-util'; + +export const VERDACCIO_REGISTRY = 'http://localhost:4873/'; + +export interface ReleaseOptions { + component: Component; + releaseType: ReleaseType; + version: string; + checkoutDir: string; + branch: string; + verbose: boolean; + force: boolean; + publish: boolean; + npmDryRun: boolean; + draft: boolean; +} + +export type ReleaseRepository = 'npm' | 'm2' | 'm2&p2' | 'p2'; + +export interface Component { + type: typeof Component.CLI_CHOICES[number]; + releaseRepo: ReleaseRepository; + githubRepo: string; + directory: string; +} +export namespace Component { + export const CLIENT: Component = { + type: 'client', + githubRepo: 'eclipse-glsp/glsp-client', + releaseRepo: 'npm', + directory: 'glsp-client' + }; + export const THEIA_INTEGRATION: Component = { + type: 'theia-integration', + githubRepo: 'eclipse-glsp/glsp-theia-integration', + releaseRepo: 'npm', + directory: 'glsp-theia-integration' + }; + export const VSCODE_INTEGRATION: Component = { + type: 'vscode-integration', + githubRepo: 'eclipse-glsp/glsp-vscode-integration', + releaseRepo: 'npm', + directory: 'glsp-vscode-integration' + }; + export const SERVER_NODE: Component = { + type: 'server-node', + githubRepo: 'eclipse-glsp/glsp-server-node', + releaseRepo: 'npm', + directory: 'serer-node' + }; + export const SERVER_JAVA: Component = { + type: 'server-java', + githubRepo: 'eclipse-glsp/glsp-server', + releaseRepo: 'm2&p2', + directory: 'glsp-server' + }; + export const ECLIPSE_INTEGRATION: Component = { + type: 'eclipse-integration', + githubRepo: 'eclipse-glsp/glsp-eclipse-integration', + releaseRepo: 'm2', + directory: 'glsp-eclipse-integration' + }; + + export const ALL: Component[] = [CLIENT, THEIA_INTEGRATION, VSCODE_INTEGRATION, ECLIPSE_INTEGRATION, SERVER_JAVA, SERVER_NODE]; + export const CLI_CHOICES = [ + 'client', + 'theia-integration', + 'vscode-integration', + 'eclipse-integration', + 'server-node', + 'server-java' + ] as const; + + export function parse(cliChoice: string): Component { + const key = cliChoice.toUpperCase().replace('-', '_'); + const component: Component = (Component as any)[key]; + if (!component) { + throw new Error(`Could not parse component for type: ${cliChoice}`); + } + return component; + } + + export function is(object: any): object is Component { + return typeof object === 'object' && typeof object.type === 'string'; + } +} + +export namespace ReleaseType { + export const CLI_CHOICES = ['major', 'minor', 'patch', 'rc', 'custom'] as const; +} + +export type ReleaseType = typeof ReleaseType.CLI_CHOICES[number]; + +export function checkoutAndCd(options: ReleaseOptions): string { + const ghUrl = options.component.githubRepo; + LOGGER.info(`Clone repository '${ghUrl}' to directory: ${options.checkoutDir}`); + sh.cd(options.checkoutDir); + const directory = ghUrl.substring(ghUrl.lastIndexOf('/') + 1); + const repoPath = resolve(options.checkoutDir, directory); + if (fs.existsSync(resolve(options.checkoutDir, directory))) { + if (options.force) { + LOGGER.debug('A directory with the checkout name already exists.'); + LOGGER.debug('Force mode is enabled. The directory will be removed'); + fatalExec(`rm -rf ${repoPath}`, `Could not remove directory: ${repoPath}`); + } else { + throw new Error('Directory with the checkout name already exists.'); + } + } + sh.exec(`gh repo clone ${ghUrl}`, getShellConfig()); + LOGGER.debug(`Successfully cloned to ${directory}`); + sh.cd(directory); + if (options.checkoutDir !== 'master') { + sh.exec(`git switch ${options.branch} `); + } + return sh.pwd(); +} + +export function commitAndTag(version: string, repositoryPath: string): string { + LOGGER.info('Commit changes and create new tag'); + sh.cd(repositoryPath); + + LOGGER.debug('Check wether the given url is a git repository'); + if (!isGitRepository(sh.pwd())) { + throw new Error(`The given path is not a git repository: ${repositoryPath}`); + } + const tag = `v${version}`; + LOGGER.debug(`Create tag with label: ${tag}}`); + sh.exec(`git checkout -b ${tag}`, getShellConfig()); + sh.exec('git add .', getShellConfig()); + sh.exec(`git commit -m "${tag}"`, getShellConfig()); + sh.exec(`git tag ${tag}`, getShellConfig()); + return tag; +} + +export function publish(repositoryPath: string, options: ReleaseOptions): void { + if (!options.publish || options.npmDryRun) { + LOGGER.info('Skip publishing to Github'); + if (options.npmDryRun && options.component.releaseRepo === 'npm') { + fatalExec( + 'lerna publish from-git --no-git-reset --no-git-tag-version --no-verify-access --no-push --dist-tag rc --yes', + 'Dry-run publish failed', + { silent: false } + ); + } + return; + } + LOGGER.info(`Publish new GH release ${options.draft ? '[DRAFT]' : ''}`); + sh.cd(repositoryPath); + if (!options.force && hasGitChanges()) { + throw new Error('Publish failed. The repository has pending changes'); + } + + const latestReleaseTag = getLatestRelease(); + const localTag = getLatestTag(); + validateTag(latestReleaseTag, localTag); + const preRelease = options.releaseType === 'rc' || localTag.includes('-'); + doPublish(localTag, preRelease, latestReleaseTag, options.draft); +} + +function doPublish(tag: string, preRelease: boolean, latestRelease: string, draft: boolean): void { + fatalExec(`git push origin HEAD:${tag}`, 'Could not push release branch to Github', getShellConfig({ silent: false })); + fatalExec(`git push origin tag ${tag}`, 'Could not push tag to Github', getShellConfig({ silent: false })); + const version = tagToVersion(tag); + const titleSuffix = preRelease ? ` Candiate ${version.substring(version.length - 1)}` : ''; + const title = `${version.replace(/-.*/, '')} Release${titleSuffix} `; + sh.exec( + `gh release create ${tag} -t "${title}" --notes-start-tag ${latestRelease} --generate-notes ${draft ? '-d' : ''} ${ + preRelease ? '-p' : '' + }`, + getShellConfig() + ); +} + +function validateTag(currentReleaseTag: string, newTag: string): void { + const releaseVersion = tagToVersion(currentReleaseTag); + const newVersion = tagToVersion(newTag); + if (!semver.satisfies(newVersion, `>${releaseVersion}`, { includePrerelease: true })) { + throw new Error(`Tag version is lower than the current release: ${newTag} `); + } +} + +function tagToVersion(tag: string): string { + if (!tag.startsWith('v')) { + throw new Error(`Invalid format. The release tag should start with 'v': ${tag}`); + } + const version = tag.substring(1).replace('.RC', '-RC'); + return validateVersion(version); +} + +export function lernaSetVersion(repositoryPath: string, version: string): void { + LOGGER.info(`Bump version in ${repositoryPath} to: ${version}`); + sh.cd(repositoryPath); + fatalExec(`lerna version --exact ${version} --ignore-scripts --yes --no-push --no-git-tag-version`, 'Lerna version bump failed!', { + silent: false + }); + LOGGER.debug('Update root package.json version'); + sh.exec(`jq '.version="${version}"' package.json > temp.json`, getShellConfig()); + sh.exec('mv temp.json package.json ', getShellConfig()); +} + +export function yarnInstall(repositoryPath: string): void { + LOGGER.debug(`Build ${repositoryPath}`); + sh.cd(repositoryPath); + fatalExec('yarn', 'Yarn build failed', getShellConfig({ silent: false })); +} + +export function upgradeCommand(pckName: string, version: string): string { + LOGGER.debug(`Upgrade '${pckName}' to version ${version}`); + return `lernaupdate --non-interactive --dependency ${pckName}@${version}`; +} + +export function updateVersion(...packages: { name: string; version: string }[]): void { + packages.forEach(pckg => { + const result = sh.exec(upgradeCommand(pckg.name, pckg.version), getShellConfig({ silent: false })).stdout.trim(); + if (result.includes('An error occurred:')) { + const errorMsg = result.substring(result.lastIndexOf('An error occurred:')).trim(); + throw new Error(errorMsg); + } + }); +} + +export function updateServerConfig(configFile: string, version: string, isSnapShot: boolean): void { + LOGGER.info('Update example server download config'); + sh.exec(`jq '.isSnapShot=${isSnapShot}| .version="${version}"' ${configFile} > temp.json`, getShellConfig()); + sh.exec(`mv temp.json ${configFile}`, getShellConfig()); +} + +export function asMvnVersion(version: string): string { + LOGGER.debug(`Convert to maven conform version: ${version}`); + const mavenVersion = version.replace('-', '.'); + LOGGER.debug(`Maven version :${mavenVersion}`); + return mavenVersion; +} + +export async function checkIfNpmVersionExists(pckgName: string, newVersion: string): Promise { + LOGGER.debug(`Check if version exists on npm: ${newVersion}`); + + const response = await fetch(`https://registry.npmjs.org/${pckgName}/${newVersion}`); + const data = await response.json(); + if (typeof data === 'string' && data.includes('version not found:')) { + LOGGER.debug(`Version '${newVersion}' does not exist on NPM. Continue with release`); + return; + } + throw new Error(`Version '${newVersion} is already present on NPM!}`); +} + +export function checkIfMavenVersionExists(groupId: string, artifactId: string, newVersion: string): void { + LOGGER.debug('Check if maven version exists'); + if (isExistingMavenVersion(groupId, artifactId, newVersion)) { + throw new Error(`Version '${newVersion} is already present on maven central!}`); + } + LOGGER.debug(`Version '${newVersion}' does not exist on maven central. Continue with release`); +} + +export function isExistingMavenVersion(groupId: string, artifactId: string, version: string): boolean { + const versions = sh + .exec( + `wget -q -O - https://repo1.maven.org/maven2/${groupId.replace(/\./g, '/')}/${artifactId}/maven-metadata.xml`, + getShellConfig() + ) + .exec("grep -P '\\K[^<]*'", getShellConfig()) + .stdout.replace(/<\/?version>/g, '') + .split('\n') + .map(versionString => versionString.trim()); + LOGGER.debug(`${versions.length} released versions found:`, versions); + return versions.includes(version); +} + +export function checkJavaServerVersion(version: string, force = false): void { + const mvnVersion = asMvnVersion(version); + if (!isExistingMavenVersion('org.eclipse.glsp', 'org.eclipse.glsp.server', mvnVersion)) { + // eslint-disable-next-line max-len + const errorMsg = `No Java GLSP server with version ${mvnVersion} found on maven central!. Please release a new Java GLSP Server version before publishing this release!`; + LOGGER.warn(errorMsg); + if (force || readline.keyInYN('No Java GLSP server with corresponding version found. Do you want to continue anyways?')) { + return; + } + throw new Error(errorMsg); + } +} + +export function updateLernaForDryRun(): void { + LOGGER.debug('Update lerna.json to use local publish registry'); + sh.exec(`jq '.command.publish.registry="${VERDACCIO_REGISTRY}"' lerna.json > temp.json`, getShellConfig()); + sh.exec('mv temp.json lerna.json', getShellConfig()); + sh.exec(`npm set registry ${VERDACCIO_REGISTRY}`); +} diff --git a/dev-packages/cli/src/release/release-client.ts b/dev-packages/cli/src/release/release-client.ts new file mode 100644 index 0000000..20fc174 --- /dev/null +++ b/dev-packages/cli/src/release/release-client.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as sh from 'shelljs'; +import { LOGGER } from '../util/logger'; +import { + asMvnVersion, + checkJavaServerVersion, + checkoutAndCd, + commitAndTag, + lernaSetVersion, + publish, + ReleaseOptions, + updateLernaForDryRun, + updateServerConfig, + yarnInstall +} from './common'; + +let REPO_ROOT: string; + +export async function releaseClient(options: ReleaseOptions): Promise { + LOGGER.info('Prepare glsp-client release'); + LOGGER.debug('Release options: ', options.version); + REPO_ROOT = checkoutAndCd(options); + await updateDownloadServerScript(options.version, options.force); + generateChangeLog(); + lernaSetVersion(REPO_ROOT, options.version); + build(); + if (options.npmDryRun) { + updateLernaForDryRun(); + } + commitAndTag(options.version, REPO_ROOT); + publish(REPO_ROOT, options); +} + +async function updateDownloadServerScript(version: string, force: boolean): Promise { + LOGGER.info('Update example server download config'); + const mvnVersion = asMvnVersion(version); + checkJavaServerVersion(version, force); + sh.cd(`${REPO_ROOT}/examples/workflow-glsp/scripts`); + updateServerConfig('config.json', mvnVersion, false); +} + +function generateChangeLog(): void { + // do nothing for now +} + +function build(): void { + LOGGER.info('Install & Build with yarn'); + yarnInstall(REPO_ROOT); + LOGGER.debug('Build successful'); +} diff --git a/dev-packages/cli/src/release/release-eclipse-integration.ts b/dev-packages/cli/src/release/release-eclipse-integration.ts new file mode 100644 index 0000000..2b6eef1 --- /dev/null +++ b/dev-packages/cli/src/release/release-eclipse-integration.ts @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as sh from 'shelljs'; +import { fatalExec, getShellConfig } from '../util/command-util'; +import { LOGGER } from '../util/logger'; +import { + asMvnVersion, + checkJavaServerVersion, + checkoutAndCd, + commitAndTag, + lernaSetVersion, + publish, + ReleaseOptions, + ReleaseType, + updateLernaForDryRun, + updateVersion, + yarnInstall +} from './common'; + +let REPO_ROOT: string; + +export async function releaseEclipseIntegration(options: ReleaseOptions): Promise { + LOGGER.info('Prepare glsp-eclipse-integration release'); + LOGGER.debug('Release options: ', options); + checkJavaServerVersion(options.version); + + REPO_ROOT = checkoutAndCd(options); + prepareClient(options); + prepareServer(options); + generateChangeLog(); + commitAndTag(asMvnVersion(options.version), REPO_ROOT); + publish(REPO_ROOT, options); + LOGGER.info('glsp-eclipse-integration release successful!'); +} + +function prepareClient(options: ReleaseOptions): void { + LOGGER.info('Prepare client'); + updateExternalGLSPDependencies(options.version); + lernaSetVersion(`${REPO_ROOT}/client`, options.version); + buildClient(); + if (options.npmDryRun) { + updateLernaForDryRun(); + } +} + +function updateExternalGLSPDependencies(version: string): void { + LOGGER.info('Update external GLSP dependencies (Client and workflow example)'); + sh.cd(`${REPO_ROOT}/client`); + updateVersion({ name: '@eclipse-glsp/client', version }, { name: '@eclipse-glsp-examples/workflow-glsp', version }); +} + +function buildClient(): void { + LOGGER.info('[Client] Install & Build with yarn'); + yarnInstall(`${REPO_ROOT}/client`); + LOGGER.debug('Build successful'); +} + +function generateChangeLog(): void { + // do nothing for now +} + +function prepareServer(options: ReleaseOptions): void { + const mvnVersion = asMvnVersion(options.version); + setServerVersion(mvnVersion); + updateTarget(mvnVersion, options.releaseType); + buildServer(); +} + +function setServerVersion(version: string): void { + LOGGER.info(`Set pom version to ${version}`); + sh.cd(`${REPO_ROOT}/server`); + // Execute tycho-versions plugin + fatalExec(`mvn tycho-versions:set-version -DnewVersion=${version}`, 'Mvn set-versions failed', getShellConfig({ silent: false })); + LOGGER.debug('Version update complete!'); +} + +function buildServer(): void { + sh.cd(`${REPO_ROOT}/server`); + LOGGER.info('Build Server(P2)'); + fatalExec('mvn clean install', 'P2 build failed', getShellConfig({ silent: false })); +} +function updateTarget(mvnVersion: string, releaseType: ReleaseType): void { + const p2SubLocation = releaseType === 'rc' ? 'staging' : 'releases'; + const p2Location = `https://download.eclipse.org/glsp/server/p2/${p2SubLocation}/${mvnVersion}/`; + LOGGER.info(`Update glsp server p2 repository to ${p2Location}`); + sh.cd(`${REPO_ROOT}/server/releng/org.eclipse.glsp.ide.releng.target`); + LOGGER.debug('Update r2021-03.tpd file'); + sh.exec( + `sed -i 's_location "https://download.eclipse.org/glsp/server/p2/.*"_location "${p2Location}"_' r2021-03.tpd`, + getShellConfig() + ); + LOGGER.debug('Update r2021-03.target file'); + sh.exec( + // eslint-disable-next-line max-len + `sed -i 's_ { + LOGGER.info('Prepare glsp-server release'); + LOGGER.debug('Release options: ', options); + const mvnVersion = asMvnVersion(options.version); + REPO_ROOT = checkoutAndCd(options); + setVersion(mvnVersion); + build(); + generateChangeLog(); + commitAndTag(mvnVersion, REPO_ROOT); + publish(REPO_ROOT, options); + LOGGER.info('glsp-server release successful!'); +} + +function setVersion(version: string): void { + LOGGER.info(`Set pom version to ${version}`); + sh.cd(REPO_ROOT); + LOGGER.debug('Preprocessing eclipse-plugins poms'); + // Capture all poms with a `${package.type}` property + + const pluginPoms = sh + .exec('grep -ril --include pom.xml \\${package-type}', getShellConfig()) // + .stdout.trim() + .split('\n') + .map(file => `${REPO_ROOT}/${file}`); + + // Replace `${package.type}` property with `eclipse-plugin` + sh.sed('-i', /\${package-type}/, 'eclipse-plugin', pluginPoms); + LOGGER.debug('Preprocessing complete'); + + // Execute tycho-versions plugin + fatalExec(`mvn tycho-versions:set-version -DnewVersion=${version}`, 'Mvn set-versions failed', getShellConfig({ silent: false })); + + LOGGER.debug('Restore eclipse-plugin poms'); + sh.sed('-i', /eclipse-plugin/, '${package-type}', pluginPoms); + + LOGGER.debug('Version update complete!'); +} + +function generateChangeLog(): void { + // do nothing for now +} + +function build(): void { + LOGGER.info('Build M2 & P2'); + LOGGER.debug('M2'); + fatalExec('mvn clean install -Pm2', 'M2 build failed', getShellConfig({ silent: false })); + console.log(''); + LOGGER.debug('P2'); + fatalExec('mvn clean install -Pp2', 'P2 build failed', getShellConfig({ silent: false })); +} diff --git a/dev-packages/cli/src/release/release-server-node.ts b/dev-packages/cli/src/release/release-server-node.ts new file mode 100644 index 0000000..f79181a --- /dev/null +++ b/dev-packages/cli/src/release/release-server-node.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as sh from 'shelljs'; +import { LOGGER } from '../util/logger'; +import { + checkoutAndCd, + commitAndTag, + lernaSetVersion, + publish, + ReleaseOptions, + updateLernaForDryRun, + updateVersion, + yarnInstall +} from './common'; + +let REPO_ROOT: string; + +export async function releaseServerNode(options: ReleaseOptions): Promise { + LOGGER.info('Prepare glsp-server-node release'); + LOGGER.debug('Release options: ', options); + REPO_ROOT = checkoutAndCd(options); + updateExternalGLSPDependencies(options.version); + generateChangeLog(); + lernaSetVersion(REPO_ROOT, options.version); + build(); + if (options.npmDryRun) { + updateLernaForDryRun(); + } + commitAndTag(options.version, REPO_ROOT); + publish(REPO_ROOT, options); +} + +function updateExternalGLSPDependencies(version: string): void { + LOGGER.info('Update external GLSP dependencies (Protocol)'); + sh.cd(REPO_ROOT); + updateVersion({ name: '@eclipse-glsp/protocol', version }); +} + +function build(): void { + LOGGER.info('Install & Build with yarn'); + yarnInstall(REPO_ROOT); + LOGGER.debug('Build successful'); +} + +function generateChangeLog(): void { + // do nothing for now +} diff --git a/dev-packages/cli/src/release/release-theia-integration.ts b/dev-packages/cli/src/release/release-theia-integration.ts new file mode 100644 index 0000000..d3babcd --- /dev/null +++ b/dev-packages/cli/src/release/release-theia-integration.ts @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as sh from 'shelljs'; +import { LOGGER } from '../util/logger'; +import { + asMvnVersion, + checkoutAndCd, + commitAndTag, + isExistingMavenVersion, + lernaSetVersion, + publish, + ReleaseOptions, + updateLernaForDryRun, + updateServerConfig, + updateVersion, + yarnInstall +} from './common'; + +let REPO_ROOT: string; + +export async function releaseTheiaIntegration(options: ReleaseOptions): Promise { + LOGGER.info('Prepare glsp-theia-integration release'); + LOGGER.debug('Release options: ', options); + REPO_ROOT = checkoutAndCd(options); + updateExternalGLSPDependencies(options.version); + await updateDownloadServerScript(options.version); + generateChangeLog(); + lernaSetVersion(REPO_ROOT, options.version); + build(); + if (options.npmDryRun) { + updateLernaForDryRun(); + } + commitAndTag(options.version, REPO_ROOT); + publish(REPO_ROOT, options); +} + +function updateExternalGLSPDependencies(version: string): void { + LOGGER.info('Update external GLSP dependencies (Client and workflow example)'); + sh.cd(REPO_ROOT); + updateVersion({ name: '@eclipse-glsp/client', version }, { name: '@eclipse-glsp-examples/workflow-glsp', version }); +} + +async function updateDownloadServerScript(version: string): Promise { + LOGGER.info('Update example server download config'); + const mvnVersion = asMvnVersion(version); + if (!isExistingMavenVersion('org.eclipse.glsp', 'org.eclipse.glsp.server', mvnVersion)) { + LOGGER.warn(`No Java GLSP server with version ${mvnVersion} found on maven central!. Please release a new Java GLSP Server version + before publishing this release!`); + } + + sh.cd(`${REPO_ROOT}/examples/workflow-theia/src/node`); + updateServerConfig('server-config.json', mvnVersion, false); +} + +function build(): void { + LOGGER.info('Install & Build with yarn'); + yarnInstall(REPO_ROOT); + LOGGER.debug('Build successful'); +} + +function generateChangeLog(): void { + // do nothing for now +} diff --git a/dev-packages/cli/src/release/release-vscode-integration.ts b/dev-packages/cli/src/release/release-vscode-integration.ts new file mode 100644 index 0000000..59d1423 --- /dev/null +++ b/dev-packages/cli/src/release/release-vscode-integration.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as sh from 'shelljs'; +import { LOGGER } from '../util/logger'; +import { + checkoutAndCd, + commitAndTag, + lernaSetVersion, + publish, + ReleaseOptions, + updateLernaForDryRun, + updateVersion, + yarnInstall +} from './common'; + +let REPO_ROOT: string; + +export async function releaseVscodeIntegration(options: ReleaseOptions): Promise { + LOGGER.info('Prepare glsp-vscode-integration release'); + LOGGER.debug('Release options: ', options); + REPO_ROOT = checkoutAndCd(options); + updateExternalGLSPDependencies(options.version); + generateChangeLog(); + lernaSetVersion(REPO_ROOT, options.version); + build(); + if (options.npmDryRun) { + updateLernaForDryRun(); + } + commitAndTag(options.version, REPO_ROOT); + publish(REPO_ROOT, options); +} + +function updateExternalGLSPDependencies(version: string): void { + LOGGER.info('Update external GLSP dependencies (Protocol)'); + sh.cd(REPO_ROOT); + updateVersion( + { name: '@eclipse-glsp/protocol', version }, + { name: '@eclipse-glsp/client', version }, + { name: '@eclipse-glsp-examples/workflow-glsp', version } + ); +} + +function build(): void { + LOGGER.info('Install & Build with yarn'); + yarnInstall(REPO_ROOT); + LOGGER.debug('Build successful'); +} + +function generateChangeLog(): void { + // do nothing for now +} diff --git a/dev-packages/cli/src/release/release.ts b/dev-packages/cli/src/release/release.ts new file mode 100644 index 0000000..cb3da2c --- /dev/null +++ b/dev-packages/cli/src/release/release.ts @@ -0,0 +1,174 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ChildProcess } from 'child_process'; +import { exit } from 'process'; +import { createInterface } from 'readline'; +import * as readline from 'readline-sync'; +import * as semver from 'semver'; +import * as sh from 'shelljs'; +import { BaseCmdOptions, fatalExec, getShellConfig, initialConfiguration } from '../util/command-util'; +import { isGithubCLIAuthenticated } from '../util/git-util'; +import { LOGGER } from '../util/logger'; +import { validateVersion } from '../util/validation-util'; +import { + asMvnVersion, + checkIfMavenVersionExists, + checkIfNpmVersionExists, + Component, + ReleaseOptions, + ReleaseType, + VERDACCIO_REGISTRY +} from './common'; +import { releaseClient } from './release-client'; +import { releaseEclipseIntegration } from './release-eclipse-integration'; +import { releaseJavaServer } from './release-java-server'; +import { releaseServerNode } from './release-server-node'; +import { releaseTheiaIntegration } from './release-theia-integration'; +import { releaseVscodeIntegration } from './release-vscode-integration'; +interface ReleaseCmdOptions extends BaseCmdOptions { + checkoutDir: string; + branch: string; + force: boolean; + publish: boolean; + npmDryRun: boolean; + draft: boolean; +} + +let verdaccioChildProcess: ChildProcess | undefined = undefined; + +export async function release( + component: Component, + releaseType: ReleaseType, + customVersion: string | undefined, + cliOptions: ReleaseCmdOptions +): Promise { + try { + LOGGER.debug('Cli options:', cliOptions); + initialConfiguration(cliOptions.verbose); + checkGHCli(); + const version = deriveVersion(releaseType, customVersion); + const options: ReleaseOptions = { ...cliOptions, component, releaseType, version }; + if (cliOptions.npmDryRun && options.component.releaseRepo === 'npm') { + await launchVerdaccio().catch(error => LOGGER.error('Error occurred during verdaccio launch', error)); + } + switch (component.type) { + case 'server-java': + checkIfMavenVersionExists('org.eclipse.glsp', 'org.eclipse.glsp.server', asMvnVersion(version)); + return releaseJavaServer(options); + case 'server-node': + await checkIfNpmVersionExists('@eclipse-glsp/server-node', version); + return releaseServerNode(options); + case 'client': + await checkIfNpmVersionExists('@eclipse-glsp/client', version); + return releaseClient(options); + case 'theia-integration': + await checkIfNpmVersionExists('@eclipse-glsp/theia-integration', version); + return releaseTheiaIntegration(options); + case 'vscode-integration': + await checkIfNpmVersionExists('@eclipse-glsp/vscode-integration', version); + return releaseVscodeIntegration(options); + case 'eclipse-integration': + await checkIfNpmVersionExists('@eclipse-glsp/ide', version); + return releaseEclipseIntegration(options); + } + } catch (err) { + console.error('An error occurred during command execution:', err); + exit(1); + } finally { + if (verdaccioChildProcess) { + verdaccioChildProcess.kill(); + } + } +} + +function checkGHCli(): void { + LOGGER.debug('Verify that Github CLI is configured correctly'); + if (!isGithubCLIAuthenticated()) { + throw new Error("Github CLI is not configured properly. No user is logged in for host 'github.com'"); + } +} + +function launchVerdaccio(): Promise { + LOGGER.debug('Verify that verdaccio is installed and start if necessary'); + fatalExec('which verdaccio', 'Verdaccio is not installed!'); + // Check if verdaccio is alreaddy running + const result = sh.exec(`curl -i ${VERDACCIO_REGISTRY}`, getShellConfig()); + if (result.code !== 0) { + LOGGER.info('Starting local verdaccio registry'); + verdaccioChildProcess = sh.exec('verdaccio', { async: true, silent: true }); + return new Promise(resolve => { + createInterface(verdaccioChildProcess!.stdout!).on('line', line => { + if (line.includes(`http address - ${VERDACCIO_REGISTRY}`)) { + resolve(); + } + }); + }); + } + return Promise.resolve(); +} + +function deriveVersion(release: ReleaseType, customVersion?: string): string { + LOGGER.debug(`Derive version from release type: ${release}`); + switch (release) { + case 'custom': + return getCustomVersion(customVersion); + case 'rc': + return getRCVersion(); + case 'patch': + case 'major': + case 'minor': + return semverInc(release); + } +} + +const REFERENCE_NPM_PACKAGE = '@eclipse-glsp/ide'; + +function getRCVersion(): string { + LOGGER.debug('Retrieve and new RC version'); + const newBaseVersion = semverInc('minor'); + const currentRcVersion = sh.exec(`npm view ${REFERENCE_NPM_PACKAGE} dist-tags.rc`, getShellConfig()).stdout.trim(); + const currentRcBaseVersion = currentRcVersion.split('-')[0]; + if (currentRcBaseVersion !== newBaseVersion) { + return `${newBaseVersion}-RC01`; + } + let rcNumber = Number.parseInt(currentRcVersion.replace(`${currentRcBaseVersion}-RC`, ''), 10); + rcNumber++; + + return `${newBaseVersion}-RC` + `${rcNumber}`.padStart(2, '0'); +} + +function getCustomVersion(customVersion?: string): string { + LOGGER.debug('Retrieve and validate custom version'); + LOGGER.debug(customVersion ? `Custom version has been passed as argument: ${customVersion}` : 'Prompt custom version from user'); + const version = customVersion ?? readline.question('> Please enter the new version'); + return validateVersion(version); +} + +function getCurrentVersion(): string { + LOGGER.debug('Retrieve base version by querying the latest tag of the reference npm package'); + const version = sh.exec(`npm view ${REFERENCE_NPM_PACKAGE} dist-tags.latest`, getShellConfig()).stdout.trim(); + return validateVersion(version); +} + +function semverInc(releaseType: semver.ReleaseType, identifier?: string): string { + const currentVersion = getCurrentVersion(); + LOGGER.debug(`Execute: semver.inc("${currentVersion}", ${releaseType}, ${identifier})`); + const newVersion = semver.inc(currentVersion, releaseType, identifier); + if (!newVersion) { + throw new Error(`Could not increment version: ${currentVersion} `); + } + return newVersion; +} diff --git a/dev-packages/cli/src/util/command-util.ts b/dev-packages/cli/src/util/command-util.ts new file mode 100644 index 0000000..7d31fba --- /dev/null +++ b/dev-packages/cli/src/util/command-util.ts @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Command } from 'commander'; +import * as sh from 'shelljs'; +import { configureLogger } from './logger'; +export const COMMAND_VERSION = '1.1.0-next'; + +export function baseConfiguration(cmd: Command): Command { + return cmd + .version(COMMAND_VERSION) // + .showSuggestionAfterError(true) + .showHelpAfterError(true) + .allowUnknownOption(false); +} + +export interface BaseCmdOptions { + verbose: boolean; +} + +export const SH_CONFIG: sh.ExecOptions & { async: false } = { + async: false, + fatal: true, + silent: false +}; + +export function getShellConfig(options: Partial> = {}): sh.ExecOptions & { async: false } { + return { + ...SH_CONFIG, + ...options + }; +} + +export function initialConfiguration(verbose: boolean): void { + sh.config.reset(); + + SH_CONFIG.silent = !verbose; + configureLogger(verbose); +} + +export function fatalExec( + command: string, + fatalErrorMessage: string, + options: Partial> = {} +): sh.ShellString { + const result = sh.exec(command, getShellConfig(options)); + if (result.code !== 0) { + throw new Error(fatalErrorMessage); + } + return result; +} diff --git a/dev-packages/cli/src/util/git-util.ts b/dev-packages/cli/src/util/git-util.ts new file mode 100644 index 0000000..e283ff0 --- /dev/null +++ b/dev-packages/cli/src/util/git-util.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as sh from 'shelljs'; +import { fatalExec, getShellConfig } from './command-util'; +import { LOGGER } from './logger'; +import { validateGitDirectory } from './validation-util'; + +export function isGithubCLIAuthenticated(): boolean { + LOGGER.debug('Verify that Github CLI is installed'); + fatalExec('which gh', 'Github CLI is not installed!'); + + const status = sh.exec('gh auth status', getShellConfig()); + if (status.code !== 0) { + if (status.stderr.includes('You are not logged into any GitHub hosts')) { + return false; + } + throw new Error(status.stderr); + } + if (!status.stderr.trim().includes('Logged in to github.com')) { + LOGGER.debug("No user is logged in for host 'github.com'"); + return false; + } + LOGGER.debug('Github CLI is authenticated and ready to use'); + return true; +} + +export function isGitRepository(path: string): boolean { + LOGGER.debug(`Check if the given directory is a git repo: ${path}`); + sh.cd(path); + return sh.exec('git rev-parse --is-inside-work-tree', getShellConfig()).stdout.trim().toLocaleLowerCase() === 'true'; +} + +export function hasGitChanges(path?: string): boolean { + LOGGER.debug(`Check if the directory has git changes: ${asDebugArg(path)}`); + cdIfPresent(path); + return sh.exec('git status --porcelain').stdout.trim().length !== 0; +} + +export function getLatestRelease(path?: string): string { + LOGGER.debug(`Retrieve latest release from repo: ${asDebugArg(path)}`); + cdIfPresent(path); + const release = sh.exec('gh release list --exclude-drafts -L 1', getShellConfig()).stdout.trim().split('\t'); + return release[release.length - 2]; +} + +export function getLatestTag(path?: string): string { + LOGGER.debug(`Retrieve latest tag from local repo : ${asDebugArg(path)}`); + cdIfPresent(path); + return sh.exec('git describe --abbrev=0 --tags', getShellConfig()).stdout.trim(); +} + +export function hasBranch(branch: string, path?: string): boolean { + LOGGER.debug(`Check if branch exists: ${asDebugArg(path)}`); + cdIfPresent(path); + return sh.exec(`git branch --list ${branch}`, getShellConfig()).stdout.trim().length !== 0; +} + +export function getRemoteUrl(path?: string): string { + LOGGER.debug(`Retrieve remote git url for: ${asDebugArg(path)}`); + cdIfPresent(path); + return sh.exec('git config --get remote.origin.url', getShellConfig()).stdout.trim(); +} + +function cdIfPresent(path?: string): void { + if (path) { + validateGitDirectory(path); + sh.cd(path); + } +} + +function asDebugArg(path?: string): string { + return path ?? sh.pwd().stdout; +} diff --git a/dev-packages/cli/src/util/logger.ts b/dev-packages/cli/src/util/logger.ts new file mode 100644 index 0000000..a18560c --- /dev/null +++ b/dev-packages/cli/src/util/logger.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +let verbose = false; + +export const LOGGER: Logger = { + info: (message, ...args) => console.info(`[INFO] ${message}`, ...args), + error: (message, ...args) => console.error(`[ERROR] ${message}`, ...args), + warn: (message, ...args) => console.warn(`[WARNING] ${message}`, ...args), + debug: (message, ...args) => { + if (verbose) { + console.log(`[DEBUG] ${message}`, ...args); + } + } +}; + +export function configureLogger(isVerbose: boolean): void { + verbose = isVerbose; +} + +export interface Logger { + info(message: string, ...args: any[]): void; + error(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + debug(message: string, ...args: any[]): void; +} diff --git a/dev-packages/cli/src/util/validation-util.ts b/dev-packages/cli/src/util/validation-util.ts new file mode 100644 index 0000000..76b9daf --- /dev/null +++ b/dev-packages/cli/src/util/validation-util.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { InvalidArgumentError } from 'commander'; +import * as fs from 'fs'; +import { resolve } from 'path'; +import * as semver from 'semver'; +import { isGitRepository } from './git-util'; +import { LOGGER } from './logger'; +export const COMMAND_VERSION = '1.1.0-next'; + +export function validateDirectory(rootDir: string): string { + const path = resolve(rootDir); + if (!fs.existsSync(path)) { + throw new InvalidArgumentError('Not a valid file path!'); + } + + if (!fs.statSync(path).isDirectory()) { + throw new InvalidArgumentError('Not a directory!'); + } + return path; +} + +export function validateVersion(version: string): string { + LOGGER.debug(`Validate version format of: ${version}`); + if (!semver.valid(version)) { + throw new Error(`Not a valid version: ${version}`); + } + return version; +} + +export function validateGitDirectory(repository: string): string { + const repoPath = validateDirectory(repository); + if (!isGitRepository(repoPath)) { + throw new Error('Not a valid git repository'); + } + return repoPath; +} diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json new file mode 100644 index 0000000..ecf9295 --- /dev/null +++ b/dev-packages/cli/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "baseUrl": "." + }, + + "include": ["src"] +} diff --git a/dev-packages/config/LICENSE b/dev-packages/config/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/dev-packages/config/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/dev-packages/config/README.md b/dev-packages/config/README.md new file mode 100644 index 0000000..b300989 --- /dev/null +++ b/dev-packages/config/README.md @@ -0,0 +1,75 @@ +# Eclipse GLSP - Shared configuration + +Common shared configuration for Eclipse GLSP components that are implemented with Typescript. +Provides a meta package that export common configuration objects for: + +- [Typescript](https://www.typescriptlang.org/) (`tsconfig.json`) +- [ESLint](https://eslint.org/) (`.eslintrc`) +- [Prettier](https://prettier.io/) (`.prettierrc`). + +The package is available via npm and are used by all GLSP components implemented with Typescript. ESLint and prettier are included as direct dependencies. + +## Components + +- `@eclipse-glsp/ts-config`: Shared Typescript configuration for GLSP projects +- `@eclipse-glsp/eslint-config`: Shared ESLint configuration for GLSP projects +- `@eclipse-glsp/prettier-config`: Shared Prettier configuration for GLSP projects + +## Install + +```bash +yarn add --dev @eclipse-glsp/config +``` + +## Usage + +### TSConfig + +**Create a `tsconfig.json`**: + +```json +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + } +} +``` + +### ESLint + +**Create a `.eslintrc.js`**: + +```javascript +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '@eclipse-glsp', + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; +``` + +### Prettier + +**Add to the `package.json`**: + +```json +{ + // ... + "prettier": "@eclipse-glsp/prettier-config" +} +``` + +**Or add a `.prettierrc.json` to the workspace root**: + +```json +"@eclipse-glsp/prettier-config" +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json new file mode 100644 index 0000000..7c2d883 --- /dev/null +++ b/dev-packages/config/package.json @@ -0,0 +1,41 @@ +{ + "name": "@eclipse-glsp/config", + "version": "1.0.0-next", + "description": "Wrapper package that provides Typescript, ESLint, Mocha and prettier configurations for GLSP projects", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "tsconfig" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@eclipse-glsp/eslint-config": "1.0.0-next", + "@eclipse-glsp/mocha-config": "1.0.0-next", + "@eclipse-glsp/prettier-config": "1.0.0-next", + "@eclipse-glsp/ts-config": "1.0.0-next", + "prettier": "^2.8.0" + }, + "devDependencies": { + "@types/node": ">=14.x", + "reflect-metadata": ">=0.1.13", + "typescript": ">=4.5.5" + } +} diff --git a/dev-packages/eslint-config/LICENSE b/dev-packages/eslint-config/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/dev-packages/eslint-config/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/dev-packages/eslint-config/README.md b/dev-packages/eslint-config/README.md new file mode 100644 index 0000000..2b0f1ff --- /dev/null +++ b/dev-packages/eslint-config/README.md @@ -0,0 +1,29 @@ +# Eclipse GLSP - Shared ESLint configuration + +Common shared configuration for Eclipse GLSP components that are using ESLint for linting. + +## Install + +```bash +yarn add --dev @eclipse-glsp/eslint-config +``` + +## Usage + +**Create a `.eslintrc.js`**: + +```javascript +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '@eclipse-glsp', + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/eslint-config/configs/base.eslintrc.js b/dev-packages/eslint-config/configs/base.eslintrc.js new file mode 100644 index 0000000..1f93bda --- /dev/null +++ b/dev-packages/eslint-config/configs/base.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + ecmaVersion: 6, + ecmaFeatures: { + jsx: true + } + }, + plugins: ['@typescript-eslint', 'header', 'import', 'no-null', 'chai-friendly', 'deprecation'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript' + ], + env: { + browser: true, + mocha: true, + es6: true + }, + ignorePatterns: ['node_modules', '*.d.ts'] +}; diff --git a/dev-packages/eslint-config/configs/errors.eslintrc.js b/dev-packages/eslint-config/configs/errors.eslintrc.js new file mode 100644 index 0000000..1da7077 --- /dev/null +++ b/dev-packages/eslint-config/configs/errors.eslintrc.js @@ -0,0 +1,84 @@ +const year = new Date().getFullYear(); +module.exports = { + rules: { + // https://eslint.org/docs/rules/ + // Possible Errors + 'no-inner-declarations': 'off', + // Best Practices + eqeqeq: ['error', 'smart'], + 'guard-for-in': 'error', + 'no-caller': 'error', + 'no-eval': 'error', + 'no-restricted-imports': ['error', '..', '../index', '../..', '../../index', 'src'], + 'no-sequences': 'error', + 'no-throw-literal': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTernary: true + } + ], + // Variables + 'no-unused-vars': 'off', // typescript-eslint rule activated instead + 'no-use-before-define': 'off', // typescript-eslint rule activated instead + 'no-underscore-dangle': 'off', + quotes: 'off', // typescript-eslint rule activated instead + 'one-var': ['error', 'never'], + // ECMAScript6 + 'arrow-body-style': ['error', 'as-needed'], + 'no-var': 'error', + 'prefer-const': [ + 'error', + { + destructuring: 'all' + } + ], + // @typescript-eslint/eslint-plugin + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/consistent-type-definitions': 'error', + '@typescript-eslint/no-misused-new': 'error', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'none' + } + ], + // eslint-plugin-header + 'header/header': [ + 2, + 'block', + [ + { + pattern: '[\n\r]+ \\* Copyright \\([cC]\\) \\d{4}(-\\d{4})? .*[\n\r]+', + template: `******************************************************************************* + * Copyright (c) ${year} EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + *******************************************************************************` + } + ] + ], + // eslint-plugin-import + 'import/export': 'off', // we have multiple exports due to namespaces, enums and classes that share the same name + 'import/no-deprecated': 'error', + // eslint-plugin-no-null + 'no-null/no-null': 'error', + // chai friendly + 'no-unused-expressions': 'off', + 'chai-friendly/no-unused-expressions': 'error' + } +}; diff --git a/dev-packages/eslint-config/configs/warnings.eslintrc.js b/dev-packages/eslint-config/configs/warnings.eslintrc.js new file mode 100644 index 0000000..f882a32 --- /dev/null +++ b/dev-packages/eslint-config/configs/warnings.eslintrc.js @@ -0,0 +1,83 @@ +module.exports = { + rules: { + // https://eslint.org/docs/rules/ + 'brace-style': 'off', + 'comma-dangle': 'warn', + curly: 'warn', + 'eol-last': 'warn', + 'no-invalid-this': 'warn', + 'no-new-wrappers': 'warn', + 'no-return-await': 'warn', + 'no-redeclare': 'off', + 'no-shadow': [ + 'warn', + { + hoist: 'all' + } + ], + 'no-multiple-empty-lines': [ + 'warn', + { + max: 1 + } + ], + 'no-trailing-spaces': 'warn', + 'no-void': 'warn', + 'prefer-const': [ + 'warn', + { + destructuring: 'all' + } + ], + 'prefer-object-spread': 'warn', + radix: 'warn', + 'spaced-comment': [ + 'warn', + 'always', + { + exceptions: ['*', '+', '-', '/', '!'] + } + ], + 'space-before-function-paren': [ + 'warn', + { + anonymous: 'always', + named: 'never', + asyncArrow: 'always' + } + ], + // Stylistic Issues + 'max-len': [ + 'warn', + { + code: 140 + } + ], + 'use-isnan': 'warn', + 'arrow-parens': ['warn', 'as-needed'], + + // @typescript-eslint/eslint-plugin + '@typescript-eslint/semi': ['warn', 'always'], + '@typescript-eslint/quotes': [ + 'warn', + 'single', + { + avoidEscape: true + } + ], + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { + allowExpressions: true + } + ], + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/type-annotation-spacing': 'warn', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-this-alias': 'off', + + /// eslint-plugin-deprecation plugin + 'deprecation/deprecation': 'warn' + } +}; diff --git a/dev-packages/eslint-config/index.js b/dev-packages/eslint-config/index.js new file mode 100644 index 0000000..393847c --- /dev/null +++ b/dev-packages/eslint-config/index.js @@ -0,0 +1,9 @@ +module.exports = require('./configs/base.eslintrc'); +module.exports = require('./configs/errors.eslintrc'); +module.exports = require('./configs/warnings.eslintrc'); +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: ['prettier', './configs/base.eslintrc', './configs/warnings.eslintrc', './configs/errors.eslintrc'], + ignorePatterns: ['**/{css,node_modules,lib}'], + root: true +}; diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json new file mode 100644 index 0000000..d3a1e6e --- /dev/null +++ b/dev-packages/eslint-config/package.json @@ -0,0 +1,41 @@ +{ + "name": "@eclipse-glsp/eslint-config", + "version": "1.0.0-next", + "description": "Shared ESLint configuration for GLSP projects", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "tsconfig" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-chai-friendly": "^0.7.2", + "eslint-plugin-deprecation": "^1.3.3", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-no-null": "^1.0.2" + }, + "main": "index.js" +} diff --git a/dev-packages/mocha-config/.mocharc.json b/dev-packages/mocha-config/.mocharc.json new file mode 100644 index 0000000..989a419 --- /dev/null +++ b/dev-packages/mocha-config/.mocharc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/mocharc", + "require": ["ts-node/register", "reflect-metadata/Reflect", "ignore-styles"], + "reporter": "spec", + "color": true, + "watch-files": ["*.ts", "*.tsx"], + "timeout": 2000 +} diff --git a/dev-packages/mocha-config/LICENSE b/dev-packages/mocha-config/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/dev-packages/mocha-config/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/dev-packages/mocha-config/README.md b/dev-packages/mocha-config/README.md new file mode 100644 index 0000000..209c156 --- /dev/null +++ b/dev-packages/mocha-config/README.md @@ -0,0 +1,25 @@ +# Eclipse GLSP - Shared mocha configuration + +Common shared configuration for Eclipse GLSP components that are using mocha as test framework. + +## Install + +```bash +yarn add --dev @eclipse-glsp/mocha-config +``` + +## Usage + +**Create a `.mocharc.json`**: + +```json +{ + "$schema": "https://json.schemastore.org/mocharc", + "extends": "@eclipse-glsp/mocha-config" +} +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json new file mode 100644 index 0000000..a33adcc --- /dev/null +++ b/dev-packages/mocha-config/package.json @@ -0,0 +1,53 @@ +{ + "name": "@eclipse-glsp/mocha-config", + "version": "1.0.0-next", + "description": "Shared Mocha test configuration for GLSP projects", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "mocha", + "testing" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "dependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "@types/sinon": "^10.0.13", + "chai": "^4.3.7", + "ignore-styles": "^5.0.1", + "mocha": "^10.1.0", + "mocha-jenkins-reporter": "^0.4.8", + "sinon": "^15.0.0", + "ts-node": "^10.9.1" + }, + "devDependencies": { + "reflect-metadata": "^0.1.13" + }, + "peerDependencies": { + "@types/node": ">=14.x", + "reflect-metadata": ">=0.1.13", + "typescript": ">=4.5.5" + }, + "files": [ + ".mocharc.json" + ], + "publishConfig": { + "access": "public" + }, + "main": ".mocharc.json" +} diff --git a/dev-packages/prettier-config/LICENSE b/dev-packages/prettier-config/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/dev-packages/prettier-config/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/dev-packages/prettier-config/README.md b/dev-packages/prettier-config/README.md new file mode 100644 index 0000000..229ab1f --- /dev/null +++ b/dev-packages/prettier-config/README.md @@ -0,0 +1,31 @@ +# Eclipse GLSP - Shared Pretier configuration + +Common shared configuration for Eclipse GLSP components that use Prettier for code formatting. + +## Install + +```bash +yarn add --dev @eclipse-glsp/prettier-config +``` + +## Usage + +**Add to the `package.json`**: + +```json +{ + // ... + "prettier": "@eclipse-glsp/prettier-config" +} +``` + +**Or add a `.prettierrc.json` to the workspace root**: + +```json +"@eclipse-glsp/prettier-config" +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/prettier-config/index.json b/dev-packages/prettier-config/index.json new file mode 100644 index 0000000..847f707 --- /dev/null +++ b/dev-packages/prettier-config/index.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "singleQuote": true, + "jsxSingleQuote": true, + "arrowParens": "avoid", + "trailingComma": "none", + "endOfLine": "lf", + "printWidth": 140, + "tabWidth": 4, + "overrides": [ + { + "files": ["*.json", "*.yml"], + "options": { + "printWidth": 100, + "tabWidth": 2 + } + } + ] +} diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json new file mode 100644 index 0000000..bbe34dc --- /dev/null +++ b/dev-packages/prettier-config/package.json @@ -0,0 +1,30 @@ +{ + "name": "@eclipse-glsp/prettier-config", + "version": "1.0.0-next", + "description": "Shared Prettier configuration for GLSP projects", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "tsconfig" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "publishConfig": { + "access": "public" + }, + "main": "index.json" +} diff --git a/dev-packages/ts-config/LICENSE b/dev-packages/ts-config/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/dev-packages/ts-config/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/dev-packages/ts-config/README.md b/dev-packages/ts-config/README.md new file mode 100644 index 0000000..c4f0e0d --- /dev/null +++ b/dev-packages/ts-config/README.md @@ -0,0 +1,28 @@ +# Eclipse GLSP - Shared Typescript configuration + +Common shared configuration for Eclipse GLSP components that are based on Typescript. + +## Install + +```bash +yarn add --dev @eclipse-glsp/ts-config +``` + +## Usage + +**Create a `tsconfig.json`**: + +```json +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + } +} +``` + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json new file mode 100644 index 0000000..a74c3a5 --- /dev/null +++ b/dev-packages/ts-config/package.json @@ -0,0 +1,36 @@ +{ + "name": "@eclipse-glsp/ts-config", + "version": "1.0.0-next", + "description": "Shared Typescript configuration for GLSP projects", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "tsconfig" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "peerDependencies": { + "typescript": ">=4.5.5" + }, + "files": [ + "tsconfig.json" + ], + "publishConfig": { + "access": "public" + }, + "main": "tsconfig.json" +} diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json new file mode 100644 index 0000000..915e9f9 --- /dev/null +++ b/dev-packages/ts-config/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "noImplicitOverride": true, + "noEmitOnError": false, + "noUnusedLocals": true, + "noImplicitReturns": true, + "strict": true, + "incremental": true, + // Needs to be disabled to support property injection + "strictPropertyInitialization": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "downlevelIteration": true, + "resolveJsonModule": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "ES2017", + "jsx": "react", + "lib": ["ES2017", "dom"], + "sourceMap": true, + "types": ["node", "mocha", "reflect-metadata"] + } +} From 339afafe84493c8a632addc661d1feef40782cb7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 6 Dec 2022 16:57:22 +0100 Subject: [PATCH 137/566] Fix deploy job (#807) Ensure that the @eclipse-glsp/cli package is published with public access --- dev-packages/cli/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 56aa4c1..a09988c 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -31,6 +31,9 @@ "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "test": "node --enable-source-maps lib/app.js" }, + "publishConfig": { + "access": "public" + }, "dependencies": { "commander": "^9.4.0", "shelljs": "0.8.5", From ed17a4e48221e56fc0b11c9b3eb76872984b3a40 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 6 Dec 2022 17:48:03 +0100 Subject: [PATCH 138/566] #738 Implement routing points feature (#27) Implement routing points support for the node glsp server and ensure that the API mirrors the latest changes of the java glsp server (https://github.com/eclipse-glsp/glsp-server/pull/181) Fixes https://github.com/eclipse-glsp/glsp/issues/738 --- ...change-routing-points-operation-handler.ts | 39 +++++ .../computed-bounds-action-handler.ts | 21 +-- .../src/gmodel-lib/gmodel-diagram-module.ts | 2 + packages/server-node/src/gmodel-lib/index.ts | 1 + .../operations/create-operation-handler.ts | 47 +---- packages/server-node/src/utils/args-util.ts | 62 ++++++- packages/server-node/src/utils/layout-util.ts | 164 ++++++++++++++++++ 7 files changed, 269 insertions(+), 67 deletions(-) create mode 100644 packages/server-node/src/gmodel-lib/change-routing-points-operation-handler.ts create mode 100644 packages/server-node/src/utils/layout-util.ts diff --git a/packages/server-node/src/gmodel-lib/change-routing-points-operation-handler.ts b/packages/server-node/src/gmodel-lib/change-routing-points-operation-handler.ts new file mode 100644 index 0000000..fede6d5 --- /dev/null +++ b/packages/server-node/src/gmodel-lib/change-routing-points-operation-handler.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ChangeRoutingPointsOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ModelState } from '../features/model/model-state'; +import { OperationHandler } from '../operations/operation-handler'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { applyRoutingPoints } from '../utils/layout-util'; + +@injectable() +export class ChangeRoutingPointsOperationHandler implements OperationHandler { + operationType = ChangeRoutingPointsOperation.KIND; + + @inject(ModelState) + protected modelState: ModelState; + + execute(operation: ChangeRoutingPointsOperation): void { + if (!operation.newRoutingPoints) { + throw new GLSPServerError('Incomplete change routingPoints action'); + } + + const index = this.modelState.index; + operation.newRoutingPoints.forEach(routingPoints => applyRoutingPoints(routingPoints, index)); + } +} diff --git a/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts b/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts index 71ada93..8ee89ec 100644 --- a/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts +++ b/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts @@ -13,12 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph'; +import { GModelRoot } from '@eclipse-glsp/graph'; import { Action, ComputedBoundsAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { ModelState } from '../features/model/model-state'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; +import { applyAlignment, applyBounds, applyRoute } from '../utils/layout-util'; @injectable() export class ComputedBoundsActionHandler implements ActionHandler { @@ -40,20 +41,10 @@ export class ComputedBoundsActionHandler implements ActionHandler { } protected applyBounds(root: GModelRoot, action: ComputedBoundsAction): void { - action.bounds.forEach(b => { - const element = this.modelState.index.get(b.elementId); - if (isGBoundsAware(element)) { - element.position = b.newPosition ?? element.position; - element.size = b.newSize; - } - }); - - action.alignments?.forEach(a => { - const element = this.modelState.index.get(a.elementId); - if (isGAlignable(element)) { - element.alignment = a.newAlignment; - } - }); + const index = this.modelState.index; + action.bounds.forEach(bounds => applyBounds(bounds, index)); + (action.alignments ?? []).forEach(alignment => applyAlignment(alignment, index)); + (action.routes ?? []).forEach(route => applyRoute(route, index)); } priority?: number | undefined; diff --git a/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts index 54a4e41..5d20e6c 100644 --- a/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts +++ b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts @@ -28,6 +28,7 @@ import { CutOperationHandler } from '../operations/cut-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; import { ChangeBoundsOperationHandler } from './change-bounds-operation-handler'; +import { ChangeRoutingPointsOperationHandler } from './change-routing-points-operation-handler'; import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; import { GModelDeleteOperationHandler } from './delete-operation-handler'; import { GModelStorage } from './gmodel-storage'; @@ -82,5 +83,6 @@ export abstract class GModelDiagramModule extends DiagramModule { binding.add(PasteOperationHandler); binding.add(ReconnectEdgeOperationHandler); binding.add(LayoutOperationHandler); + binding.add(ChangeRoutingPointsOperationHandler); } } diff --git a/packages/server-node/src/gmodel-lib/index.ts b/packages/server-node/src/gmodel-lib/index.ts index 6870911..568e979 100644 --- a/packages/server-node/src/gmodel-lib/index.ts +++ b/packages/server-node/src/gmodel-lib/index.ts @@ -16,6 +16,7 @@ export * from './apply-label-edit-operation-handler'; export * from './change-bounds-operation-handler'; +export * from './change-routing-points-operation-handler'; export * from './computed-bounds-action-handler'; export * from './delete-operation-handler'; export * from './gmodel-create-edge-operation-handler'; diff --git a/packages/server-node/src/operations/create-operation-handler.ts b/packages/server-node/src/operations/create-operation-handler.ts index 4f5a27e..312831b 100644 --- a/packages/server-node/src/operations/create-operation-handler.ts +++ b/packages/server-node/src/operations/create-operation-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GBoundsAware, GGraph, GModelElement, GModelRoot, isGBoundsAware } from '@eclipse-glsp/graph'; +import { GModelElement } from '@eclipse-glsp/graph'; import { CreateEdgeOperation, CreateNodeOperation, @@ -25,6 +25,7 @@ import { } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ModelState } from '../features/model/model-state'; +import { getRelativeLocation } from '../utils/layout-util'; import { OperationHandler } from './operation-handler'; /** @@ -99,48 +100,6 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler getRelativeLocation(operation: CreateNodeOperation): Point | undefined { const container = this.getContainer(operation) ?? this.modelState.root; const absoluteLocation = this.getLocation(operation) ?? Point.ORIGIN; - const allowNegativeCoordinates = container instanceof GGraph; - if (isGBoundsAware(container)) { - const relativePosition = absoluteToRelative(absoluteLocation, container); - const relativeLocation = allowNegativeCoordinates - ? relativePosition - : { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) }; - return relativeLocation; - } - return undefined; - } -} - -/** - * Convert a point in absolute coordinates to a point relative to the specified GBoundsAware. - * Note: this method only works if the specified {@link GBoundsAware} is part of a - * hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement - * {@link GBoundsAware}, this method will throw an exception. - * - * @param absolutePoint - * @param modelElement - * @returns - * A new point, relative to the coordinates space of the specified {@link GBoundsAware} - * @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy - */ -export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point { - let parentElement; - if (!(modelElement instanceof GModelRoot)) { - parentElement = modelElement.parent; - } - - let relativeToParent: Point; - if (!parentElement) { - relativeToParent = { x: absolutePoint.x, y: absolutePoint.y }; - } else { - if (!isGBoundsAware(parentElement)) { - throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`); - } - relativeToParent = absoluteToRelative(absolutePoint, parentElement); + return getRelativeLocation(absoluteLocation, container); } - - const x = modelElement.position?.x ?? 0; - const y = modelElement.position?.y ?? 0; - - return { x: relativeToParent.x - x, y: relativeToParent.y - y }; } diff --git a/packages/server-node/src/utils/args-util.ts b/packages/server-node/src/utils/args-util.ts index 0532111..c6c9b61 100644 --- a/packages/server-node/src/utils/args-util.ts +++ b/packages/server-node/src/utils/args-util.ts @@ -13,26 +13,72 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args } from '@eclipse-glsp/protocol'; +import { Args, Point } from '@eclipse-glsp/protocol'; export namespace ArgsUtil { export const KEY_EDGE_PADDING = 'edgePadding'; + export const KEY_EDGE_SOURCE_POINT_X = 'edgeSourcePointX'; + export const KEY_EDGE_SOURCE_POINT_Y = 'edgeSourcePointY'; + export const KEY_EDGE_TARGET_POINT_X = 'edgeTargetPointX'; + export const KEY_EDGE_TARGET_POINT_Y = 'edgeTargetPointY'; + export const KEY_RADIUS_TOP_LEFT = 'radiusTopLeft'; export const KEY_RADIUS_TOP_RIGHT = 'radiusTopRight'; export const KEY_RADIUS_BOTTOM_RIGHT = 'radiusBottomRight'; export const KEY_RADIUS_BOTTOM_LEFT = 'radiusBottomLeft'; + export function cornerRadius(radius: number): Args; + export function cornerRadius(topLeftBottomRight: number, topRightBottomLeft: number): Args; + export function cornerRadius(topLeft: number, topRight: number, bottomRight: number, bottomLeft: number): Args; + export function cornerRadius(radiusOrTopLeft: number, topRightOpt?: number, bottomRightOpt?: number, bottomLeftOpt?: number): Args { + const topLeft = radiusOrTopLeft; + let topRight = radiusOrTopLeft; + let bottomLeft = radiusOrTopLeft; + let bottomRight = radiusOrTopLeft; + + if (topRightOpt) { + topRight = topRightOpt; + if (bottomRightOpt && bottomLeftOpt) { + bottomLeft = bottomLeftOpt; + bottomRight = bottomRightOpt; + } else { + bottomRight = radiusOrTopLeft; + bottomLeft = topRight; + } + } + + return { + [KEY_RADIUS_TOP_RIGHT]: topLeft, + [KEY_RADIUS_BOTTOM_LEFT]: bottomLeft, + [KEY_RADIUS_BOTTOM_RIGHT]: bottomRight, + [KEY_RADIUS_TOP_RIGHT]: topRight + }; + } + + export function edgePadding(edgePadding: number): Args { + return { [KEY_EDGE_PADDING]: edgePadding }; + } + export function getEdgePadding(args: Args): number | undefined { return getNumber(args, KEY_EDGE_PADDING); } - export function cornerRadius(radius: number): Args { - return { - [KEY_RADIUS_TOP_RIGHT]: radius, - [KEY_RADIUS_BOTTOM_LEFT]: radius, - [KEY_RADIUS_BOTTOM_RIGHT]: radius, - [KEY_RADIUS_TOP_RIGHT]: radius - }; + export function getEdgeSourcePoint(args: Args = {}): Point | undefined { + const x = getNumber(args, KEY_EDGE_SOURCE_POINT_X); + const y = getNumber(args, KEY_EDGE_SOURCE_POINT_Y); + if (x && y) { + return { x, y }; + } + return undefined; + } + + export function getEdgeTargetPoint(args: Args = {}): Point | undefined { + const x = getNumber(args, KEY_EDGE_TARGET_POINT_X); + const y = getNumber(args, KEY_EDGE_TARGET_POINT_Y); + if (x && y) { + return { x, y }; + } + return undefined; } export function get(args: Args = {}, key: string): string | undefined { diff --git a/packages/server-node/src/utils/layout-util.ts b/packages/server-node/src/utils/layout-util.ts new file mode 100644 index 0000000..562ce19 --- /dev/null +++ b/packages/server-node/src/utils/layout-util.ts @@ -0,0 +1,164 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GAlignable, GBoundsAware, GEdge, GGraph, GModelElement, GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph'; +import { ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints, Point } from '@eclipse-glsp/protocol'; +import { GModelIndex } from '../features/model/gmodel-index'; +import { ArgsUtil } from './args-util'; +import { getOrThrow, GLSPServerError } from './glsp-server-error'; + +/** + * Applies the new bounds to the model. + * + * @param bounds The new bounds. + * @param index The model index. + * @returns The changed element. + */ +export function applyBounds(bounds: ElementAndBounds, index: GModelIndex): GBoundsAware | undefined { + const element = getOrThrow(index.get(bounds.elementId), 'Model element not found! ID: ' + bounds.elementId); + if (isGBoundsAware(element)) { + if (bounds.newPosition !== undefined) { + element.position = bounds.newPosition; + } + element.size = bounds.newSize; + return element; + } + return undefined; +} + +/** + * Applies the new alignment to the model. + * + * @param alignment The new alignment. + * @param index The model index. + * @returns The changed element. + */ +export function applyAlignment(alignment: ElementAndAlignment, index: GModelIndex): GAlignable | undefined { + const element = getOrThrow(index.get(alignment.elementId), 'Model element not found! ID: ' + alignment.elementId); + if (isGAlignable(element)) { + element.alignment = alignment.newAlignment; + return element; + } + return undefined; +} + +/** + * Applies the new route to the model. + * + * @param route The new route. + * @param index The model index. + * @returns The changed element. + */ +export function applyRoute(route: ElementAndRoutingPoints, index: GModelIndex): GEdge { + const routingPoints = route.newRoutingPoints ?? []; + if (routingPoints.length < 2) { + throw new GLSPServerError('Invalid Route!'); + } + // first and last point mark the source and target point + const edge = applyRoutingPoints(route, index); + const edgeRoutingPoints = edge.routingPoints; + + const args = edge.args ?? {}; + const source = edgeRoutingPoints.shift()!; + const target = edgeRoutingPoints.pop()!; + args[ArgsUtil.KEY_EDGE_SOURCE_POINT_X] = source.x; + args[ArgsUtil.KEY_EDGE_SOURCE_POINT_Y] = source.y; + args[ArgsUtil.KEY_EDGE_TARGET_POINT_X] = target.x; + args[ArgsUtil.KEY_EDGE_TARGET_POINT_Y] = target.y; + return edge; +} + +/** + * Returns the complete route of the given edge. The route, as opposed to the routing points, also contain the source + * and target point. + * + * @param edge The edge from which we get the route + * @returns complete edge route + */ +export function getRoute(edge: GEdge): ElementAndRoutingPoints { + const sourcePoint = ArgsUtil.getEdgeSourcePoint(edge.args); + if (!sourcePoint) { + throw new GLSPServerError('Cannot get route without source point!'); + } + const targetPoint = ArgsUtil.getEdgeTargetPoint(edge.args); + if (!targetPoint) { + throw new GLSPServerError('Cannot get route without target point!'); + } + const route: Point[] = []; + route.push(sourcePoint); + route.push(...edge.routingPoints); + route.push(targetPoint); + return { elementId: edge.id, newRoutingPoints: route }; +} + +export function applyRoutingPoints(routingPoints: ElementAndRoutingPoints, index: GModelIndex): GEdge { + const edge = getOrThrow(index.findByClass(routingPoints.elementId, GEdge), 'Model element not found! ID: ' + routingPoints.elementId); + edge.routingPoints = routingPoints.newRoutingPoints ?? []; + return edge; +} +/** + * Returns the relative location of the given absolute location within the container. + * + * @param absoluteLocation absolute location + * @param container container + * @return relative location if it can be determined, absolute location in case of error and null if the container + * cannot contain any location. + */ +export function getRelativeLocation(absoluteLocation: Point, container: GModelElement): Point | undefined { + const allowNegativeCoordinates = container instanceof GGraph; + if (isGBoundsAware(container)) { + const relativePosition = absoluteToRelative(absoluteLocation, container); + const relativeLocation = allowNegativeCoordinates + ? relativePosition + : { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) }; + return relativeLocation; + } + return undefined; +} + +/** + * Convert a point in absolute coordinates to a point relative to the specified GBoundsAware. + * Note: this method only works if the specified {@link GBoundsAware} is part of a + * hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement + * {@link GBoundsAware}, this method will throw an exception. + * + * @param absolutePoint + * @param modelElement + * @returns + * A new point, relative to the coordinates space of the specified {@link GBoundsAware} + * @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy + */ +export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point { + let parentElement; + if (!(modelElement instanceof GModelRoot)) { + parentElement = modelElement.parent; + } + + let relativeToParent: Point; + if (!parentElement) { + relativeToParent = { x: absolutePoint.x, y: absolutePoint.y }; + } else { + if (!isGBoundsAware(parentElement)) { + throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`); + } + relativeToParent = absoluteToRelative(absolutePoint, parentElement); + } + + const x = modelElement.position?.x ?? 0; + const y = modelElement.position?.y ?? 0; + + return { x: relativeToParent.x - x, y: relativeToParent.y - y }; +} From 5a28d6b6b084bc8f26d8b4ee8d9162e07578edff Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 9 Dec 2022 14:59:39 +0100 Subject: [PATCH 139/566] Start with current element (#29) I think we should include the current element in the search for an element or parent that matches the guard. This would then be inline with how it is in Sprotty and also better matches the current typedoc (the element to start the search from). --- packages/server-node/src/features/model/gmodel-index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-node/src/features/model/gmodel-index.ts b/packages/server-node/src/features/model/gmodel-index.ts index 7f08705..05d391e 100644 --- a/packages/server-node/src/features/model/gmodel-index.ts +++ b/packages/server-node/src/features/model/gmodel-index.ts @@ -177,7 +177,7 @@ export class GModelIndex { */ findParentElement(elementId: string, guard: TypeGuard): T | undefined { const element = this.get(elementId); - return element ? this._findElement(element.parent, guard) : undefined; + return element ? this._findElement(element, guard) : undefined; } protected _findElement(element: GModelElement, guard: TypeGuard): T | undefined { From 560a9959d81f73e33634d7f94d9337b3b54163b3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 13 Dec 2022 10:53:16 +0100 Subject: [PATCH 140/566] Monorepo housekeeping (#205) - https://github.com/eclipse-glsp/glsp/issues/676: Update to lerna 6 and set node 14 as minimum engine version - https://github.com/eclipse-glsp/glsp/issues/812: Improve yarn scripts - Remove linting from prepare step - Add dedicated :ci scrips for testing and linting - Add 'all' script on root level - Consistently sort scripts ascending - https://github.com/eclipse-glsp/glsp/issues/680 update to latest `@eclipse-glsp/config` version - https://github.com/eclipse-glsp/glsp/issues/794: Update Jenkinsfile to - directly include deploy step instead of spawning a new dedicated job - enable error reporting when master builds fail --- examples/workflow-glsp/package.json | 6 +++--- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 14 ++++++-------- packages/protocol/package.json | 6 +++--- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index bf2fecc..8aa11cc 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -33,11 +33,11 @@ "mvn-artifact-download": "5.1.0" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "tsc", + "build": "tsc -b", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build ", "watch": "tsc -w" }, "files": [ diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 204f55d..40e861e 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -36,11 +36,11 @@ "access": "public" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", + "build": "tsc -b && webpack", "clean": "rimraf lib tsconfig.tsbuildinfo app/bundle.js app/bundle.js.map app/css", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", - "build": "tsc && webpack", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", "watch": "tsc -w -p ./tsconfig.json" }, "types": "lib", diff --git a/packages/client/package.json b/packages/client/package.json index e8aa2f7..25f9b2a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -39,19 +39,17 @@ "sprotty": "0.13.0-next.1c4343c.328" }, "devDependencies": { - "@vscode/codicons": "^0.0.25", - "eslint-import-resolver-typescript": "2.7.0", - "typescript": "^4.5.5" + "@vscode/codicons": "^0.0.25" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", + "build": "tsc -b", "clean": "rimraf lib tsconfig.tsbuildinfo ", - "build": "tsc", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", - "watch": "tsc -w", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", - "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter" + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", + "watch": "tsc -w" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/package.json b/packages/protocol/package.json index aead7b6..e47701d 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -41,13 +41,13 @@ "@types/uuid": "3.4.5" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", "clean": "rimraf lib tsconfig.tsbuildinfo ", - "build": "tsc", + "build": "tsc -b", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", + "prepare": "yarn clean && yarn build", "watch": "tsc -w" }, "publishConfig": { From 527e30f950fe9527fccfec31a52e399e2469aced Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 13 Dec 2022 14:43:26 +0100 Subject: [PATCH 141/566] Repo housekeeping (#819) - https://github.com/eclipse-glsp/glsp/issues/812: Improve yarn scripts - Add scripts for linting and watching - Add 'all' script on root level - Consistently sort scripts ascending - https://github.com/eclipse-glsp/glsp/issues/794: Update Jenkinsfile to - directly include deploy step instead of spawning a new dedicated job - enable error reporting when master builds fail --- dev-packages/cli/package.json | 15 ++++++--------- dev-packages/cli/src/release/release.ts | 6 +++--- dev-packages/config/package.json | 5 ----- dev-packages/mocha-config/package.json | 3 --- dev-packages/ts-config/package.json | 2 +- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index a09988c..f09481c 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -24,12 +24,13 @@ } ], "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf lib tsconfig.tsbuildinfo ", - "build": "tsc", + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", - "test": "node --enable-source-maps lib/app.js" + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build ", + "start": "node --enable-source-maps lib/app.js", + "watch": "tsc -w" }, "publishConfig": { "access": "public" @@ -43,14 +44,10 @@ }, "devDependencies": { "ts-node": "^10.9.1", - "rimraf": "3.0.2", "@types/shelljs": "0.8.11", "@types/semver": "7.3.12", - "typescript": "^4.5.0", - "@types/node": "14.x", "@types/readline-sync": "1.4.4", "@eclipse-glsp/config": "1.0.0-next", - "reflect-metadata": "0.1.13", "@types/node-fetch": "2.6.2" }, "bin": { diff --git a/dev-packages/cli/src/release/release.ts b/dev-packages/cli/src/release/release.ts index cb3da2c..9ea605a 100644 --- a/dev-packages/cli/src/release/release.ts +++ b/dev-packages/cli/src/release/release.ts @@ -120,9 +120,9 @@ function launchVerdaccio(): Promise { return Promise.resolve(); } -function deriveVersion(release: ReleaseType, customVersion?: string): string { +function deriveVersion(releaseType: ReleaseType, customVersion?: string): string { LOGGER.debug(`Derive version from release type: ${release}`); - switch (release) { + switch (releaseType) { case 'custom': return getCustomVersion(customVersion); case 'rc': @@ -130,7 +130,7 @@ function deriveVersion(release: ReleaseType, customVersion?: string): string { case 'patch': case 'major': case 'minor': - return semverInc(release); + return semverInc(releaseType); } } diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 7c2d883..bcde16e 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -32,10 +32,5 @@ "@eclipse-glsp/prettier-config": "1.0.0-next", "@eclipse-glsp/ts-config": "1.0.0-next", "prettier": "^2.8.0" - }, - "devDependencies": { - "@types/node": ">=14.x", - "reflect-metadata": ">=0.1.13", - "typescript": ">=4.5.5" } } diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index a33adcc..9425bb4 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -35,9 +35,6 @@ "sinon": "^15.0.0", "ts-node": "^10.9.1" }, - "devDependencies": { - "reflect-metadata": "^0.1.13" - }, "peerDependencies": { "@types/node": ">=14.x", "reflect-metadata": ">=0.1.13", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index a74c3a5..c6f6af0 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -24,7 +24,7 @@ } ], "peerDependencies": { - "typescript": ">=4.5.5" + "typescript": ">=4.9.3" }, "files": [ "tsconfig.json" From 4a95211d39a8cb167b0d32359af58c35c4ac9e37 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 13 Dec 2022 14:46:47 +0100 Subject: [PATCH 142/566] Repo housekeeping (#26) Update lerna and minimum node version - https://github.com/eclipse-glsp/glsp/issues/676: Update to lerna 6 and set node 14 as minimum engine version - https://github.com/eclipse-glsp/glsp/issues/812: Improve yarn scripts - Remove linting from prepare step - Add dedicated :ci scrips for testing and linting - Add 'all' script on root level - Consistently sort scripts ascending - https://github.com/eclipse-glsp/glsp/issues/680: Update to latest `@eclipse-glsp/config` version - https://github.com/eclipse-glsp/glsp/issues/794: Update Jenkinsfile to - directly include deploy step instead of spawning a new dedicated job - enable error reporting when master builds fail --- examples/workflow-server/package.json | 12 ++++++------ packages/graph/package.json | 10 +++++----- packages/layout-elk/package.json | 10 +++++----- packages/server-node/package.json | 10 +++++----- .../src/features/model/gmodel-serializer.spec.ts | 8 ++++---- packages/server-node/src/launch/cli-parser.ts | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index d22ddb4..993ef6c 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -41,13 +41,13 @@ "@eclipse-glsp/server-node": "1.1.0-next" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "tsc", + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo ", "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", - "watch": "tsc -w", - "start:server": "yarn ts-node src/index.ts" + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", + "start:server": "yarn ts-node src/index.ts", + "watch": "tsc -w" }, "publishConfig": { "access": "public" diff --git a/packages/graph/package.json b/packages/graph/package.json index ed269de..9bb67cf 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -44,13 +44,13 @@ "@types/uuid": "8.3.1" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "tsc", + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo ", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index eb3d5bf..cb57aba 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -46,13 +46,13 @@ "inversify": "^5.1.1" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "tsc", + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo ", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/server-node/package.json b/packages/server-node/package.json index b170010..a0f5044 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -48,13 +48,13 @@ "@types/fs-extra": "^9.0.13" }, "scripts": { - "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf tsconfig.tsbuildinfo lib", - "build": "tsc", + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo ", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", - "lint": "eslint --ext .ts,.tsx ./src", - "lint:fix": "eslint --fix --ext .ts,.tsx ./src", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/server-node/src/features/model/gmodel-serializer.spec.ts b/packages/server-node/src/features/model/gmodel-serializer.spec.ts index bf49dc3..c4ee590 100644 --- a/packages/server-node/src/features/model/gmodel-serializer.spec.ts +++ b/packages/server-node/src/features/model/gmodel-serializer.spec.ts @@ -82,7 +82,7 @@ describe('test DefaultGModelSerializer', () => { it('createElement - with node schema', () => { const node = serializer.createElement(testNodeSchema); expect(node).to.be.an.instanceOf(TestNode); - expect(node).to.be.deep.equal(testNodeSchema); + expect(node).to.be.deep.include(testNodeSchema); expect((node as TestNode).foo).to.not.be.undefined; }); @@ -90,7 +90,7 @@ describe('test DefaultGModelSerializer', () => { testNodeSchema.type = 'node:rectangular'; const node = serializer.createElement(testNodeSchema); expect(node).to.be.an.instanceOf(TestNode); - expect(node).to.be.deep.equal(testNodeSchema); + expect(node).to.be.deep.include(testNodeSchema); expect((node as TestNode).foo).to.not.be.undefined; }); @@ -98,7 +98,7 @@ describe('test DefaultGModelSerializer', () => { const parent = new GNode(); const child = serializer.createElement(testNodeSchema, parent); expect(child).to.be.an.instanceOf(TestNode); - expect(child).to.be.deep.equal(testNodeSchemaWithParent); + expect(child).to.be.deep.include(testNodeSchemaWithParent); expect(child.parent).to.be.equal(parent); }); @@ -136,6 +136,6 @@ describe('test DefaultGModelSerializer', () => { testNode.layoutOptions = { ['my']: 'Options' }; const schema = serializer.createSchema(testNode); delete (testNode as Partial).foo; - expect(schema).to.be.deep.equal(testNode); + expect(schema).to.be.deep.include(testNode); }); }); diff --git a/packages/server-node/src/launch/cli-parser.ts b/packages/server-node/src/launch/cli-parser.ts index 5551051..85754ef 100644 --- a/packages/server-node/src/launch/cli-parser.ts +++ b/packages/server-node/src/launch/cli-parser.ts @@ -55,7 +55,7 @@ export function createCliParser(options return { command, parse: argv => parse(command, options as Partial, argv) }; } -export function parse(command: cmd.Command, defaultOptions: Partial, argv?: string[]): T { +export function parse(command: cmd.Command, defaultOptions: Partial, argv?: string[]): T { command.parse(argv); return { ...defaultOptions, ...command.opts() }; } From 06818aa86e1357086dc6a9235528cd8579348109 Mon Sep 17 00:00:00 2001 From: Remi Schnekenburger Date: Wed, 14 Dec 2022 11:52:49 +0100 Subject: [PATCH 143/566] Return port with startup complete message (#30) --- .../src/launch/glsp-server-launcher.ts | 2 +- .../src/launch/socket-server-launcher.ts | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/server-node/src/launch/glsp-server-launcher.ts b/packages/server-node/src/launch/glsp-server-launcher.ts index 3de2aaf..cb1ddcc 100644 --- a/packages/server-node/src/launch/glsp-server-launcher.ts +++ b/packages/server-node/src/launch/glsp-server-launcher.ts @@ -40,8 +40,8 @@ export abstract class GLSPServerLauncher { start(startParams: T): MaybePromise { if (!this.running) { + this.running = true; const result = this.run(startParams); - this.running = false; return result; } this.logger.warn('Could not start launcher. Launcher is already running!'); diff --git a/packages/server-node/src/launch/socket-server-launcher.ts b/packages/server-node/src/launch/socket-server-launcher.ts index fdf9aaf..4eec5f5 100644 --- a/packages/server-node/src/launch/socket-server-launcher.ts +++ b/packages/server-node/src/launch/socket-server-launcher.ts @@ -19,7 +19,7 @@ import * as jsonrpc from 'vscode-jsonrpc'; import { GLSPServer, JsonRpcGLSPServer } from '../protocol/glsp-server'; import { Logger } from '../utils/logger'; import { GLSPServerLauncher } from './glsp-server-launcher'; -const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed'; +const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed. Accepting requests on port:'; @injectable() export class SocketServerLauncher extends GLSPServerLauncher { @@ -30,11 +30,25 @@ export class SocketServerLauncher extends GLSPServerLauncher { const netServer = net.createServer(socket => this.createClientConnection(socket)); + netServer.listen(opts.port, opts.host); - this.logger.info(`The GLSP server is ready to accept new client requests on port: ${opts.port}`); - // Print a message to the output stream that indicates that the start is completed. - // This indicates to the client that the sever process is ready (in an embedded scenario). - console.log(this.startupCompleteMessage); + netServer.on('listening', () => { + const addressInfo = netServer.address(); + if (!addressInfo) { + this.logger.error('Could not resolve GLSP Server address info. Shutting down.'); + this.shutdown(); + return; + } else if (typeof addressInfo === 'string') { + this.logger.error(`GLSP Server is unexpectedly listening to pipe or domain socket "${addressInfo}". Shutting down.`); + this.shutdown(); + return; + } + const currentPort = addressInfo.port; + this.logger.info(`The GLSP server is ready to accept new client requests on port: ${currentPort}`); + // Print a message to the output stream that indicates that the start is completed. + // This indicates to the client that the server process is ready (in an embedded scenario). + console.log(this.startupCompleteMessage.concat(currentPort.toString())); + }); netServer.on('error', () => this.shutdown()); return new Promise((resolve, reject) => { netServer.on('close', () => resolve(undefined)); From a2aeaeaa30aa64fe6a43f6c428a4583ef8609fc1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 16 Dec 2022 00:12:45 +0100 Subject: [PATCH 144/566] Add nyc configuration package (#821) - Add a new package for the nyc coverage - Extract a second wrapper package for test-config. This way test dependencies are opt-in and can be omitted if not required - Switch dedicated config packages to pure peer dependencies. This improves flexibility and re-usability. The wrapper config packages still ship with all required dependencies. - Minor restructuring of cli package - Add `generateReport` command. This commands creates a nyc report for each package of the mono repo and then combibes them into one single report. This will be used in CI for publishing coverage results Part of https://github.com/eclipse-glsp/glsp/issues/820 Co-authored-by: Philip Langer --- dev-packages/cli/README.md | 25 +- dev-packages/cli/src/app.ts | 37 +- .../cli/src/commands/coverage-report.ts | 113 +++ .../cli/src/{ => commands}/release/common.ts | 8 +- .../{ => commands}/release/release-client.ts | 2 +- .../release/release-eclipse-integration.ts | 4 +- .../release/release-java-server.ts | 5 +- .../release/release-server-node.ts | 2 +- .../release/release-theia-integration.ts | 2 +- .../release/release-vscode-integration.ts | 2 +- .../cli/src/{ => commands}/release/release.ts | 29 +- dev-packages/cli/src/util/command-util.ts | 6 +- dev-packages/config-test/LICENSE | 642 ++++++++++++++++++ dev-packages/config-test/README.md | 50 ++ dev-packages/config-test/package.json | 46 ++ dev-packages/config/README.md | 4 +- dev-packages/config/package.json | 20 +- dev-packages/eslint-config/package.json | 10 +- dev-packages/mocha-config/package.json | 15 +- dev-packages/nyc-config/LICENSE | 642 ++++++++++++++++++ dev-packages/nyc-config/README.md | 24 + dev-packages/nyc-config/index.json | 11 + dev-packages/nyc-config/package.json | 35 + dev-packages/prettier-config/README.md | 2 +- dev-packages/prettier-config/package.json | 3 +- dev-packages/ts-config/package.json | 1 + 26 files changed, 1662 insertions(+), 78 deletions(-) create mode 100644 dev-packages/cli/src/commands/coverage-report.ts rename dev-packages/cli/src/{ => commands}/release/common.ts (98%) rename dev-packages/cli/src/{ => commands}/release/release-client.ts (98%) rename dev-packages/cli/src/{ => commands}/release/release-eclipse-integration.ts (97%) rename dev-packages/cli/src/{ => commands}/release/release-java-server.ts (95%) rename dev-packages/cli/src/{ => commands}/release/release-server-node.ts (97%) rename dev-packages/cli/src/{ => commands}/release/release-theia-integration.ts (98%) rename dev-packages/cli/src/{ => commands}/release/release-vscode-integration.ts (97%) rename dev-packages/cli/src/{ => commands}/release/release.ts (82%) create mode 100644 dev-packages/config-test/LICENSE create mode 100644 dev-packages/config-test/README.md create mode 100644 dev-packages/config-test/package.json create mode 100644 dev-packages/nyc-config/LICENSE create mode 100644 dev-packages/nyc-config/README.md create mode 100644 dev-packages/nyc-config/index.json create mode 100644 dev-packages/nyc-config/package.json diff --git a/dev-packages/cli/README.md b/dev-packages/cli/README.md index ff146db..ddd1647 100644 --- a/dev-packages/cli/README.md +++ b/dev-packages/cli/README.md @@ -11,10 +11,26 @@ Install `@eclipse-glsp/cli` as a dev dependency in your application. yarn add @eclipse-glsp/cli --dev ``` -## Commands +## coverageReport -Eclipse GLSP committers can use the `release` following command to prepare & publish a new Github release -for a specific GLSP component. +The `coverageReport` command can be used to create a full nyc test coverage report for a lerna/yarn mono repository. +Individual coverage reports for each package are created and then combined to a full report. + +```console +$ glsp coverageReport -h +Usage: glsp coverageReport [options] + +Generate a test coverage report for a glsp component + +Options: + -p, --projectRoot The root directory of the GLSP component (default: "") + -c, --coverageScript - + #sprotty { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } - -
- - - + svg { + width: 100%; + height: 100%; + } + + + + +
+ + + + \ No newline at end of file diff --git a/packages/client/css/decoration.css b/packages/client/css/decoration.css index 9927b94..ba09b53 100644 --- a/packages/client/css/decoration.css +++ b/packages/client/css/decoration.css @@ -39,4 +39,4 @@ .sprotty-issue.sprotty-info { fill: var(--glsp-info-foreground); background-color: var(--glsp-info-foreground); -} \ No newline at end of file +} diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 35d4746..7fd03cd 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -223,7 +223,7 @@ export class AutoCompleteWidget { protected renderSuggestions(item: LabeledAction, value: string): HTMLDivElement { const itemElement = document.createElement('div'); - const wordMatcher = this.espaceForRegExp(value).split(' ').join('|'); + const wordMatcher = this.escapeForRegExp(value).split(' ').join('|'); const regex = new RegExp(wordMatcher, 'gi'); if (item.icon) { this.renderIcon(itemElement, item.icon); @@ -232,7 +232,7 @@ export class AutoCompleteWidget { return itemElement; } - protected espaceForRegExp(value: string): string { + protected escapeForRegExp(value: string): string { return value.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 3566c28..82e12ac 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { RedoAction, UndoAction } from '@eclipse-glsp/protocol'; -import { inject, injectable } from 'inversify'; -import { CommandStack, IActionDispatcher, SModelRoot } from 'sprotty'; -import { TYPES } from './types'; +import { injectable } from 'inversify'; +import { CommandStack, SModelRoot } from 'sprotty'; @injectable() export class GLSPCommandStack extends CommandStack { - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - override undo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(UndoAction.create())); - return this.thenUpdate(); + this.logger.warn( + this, + 'GLSPCommandStack.undo() was called. This should never happen as the GLSP server is responsible for handling undo requests' + ); + return this.currentModel; } override redo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(RedoAction.create())); - return this.thenUpdate(); + this.logger.warn( + this, + 'GLSPCommandStack.redo() was called. This should never happen as the GLSP server is responsible for handling redo requests' + ); + return this.currentModel; } } diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index dea9fef..bf8afb5 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -40,7 +40,7 @@ import modelHintsModule from '../features/hints/di.config'; import glspHoverModule from '../features/hover/di.config'; import layoutModule from '../features/layout/di.config'; import glspMouseToolModule from '../features/mouse-tool/di.config'; -import { navigationModule } from '../features/navigation/di.config'; +import navigationModule from '../features/navigation/di.config'; import glspSelectModule from '../features/select/di.config'; import sourceModelWatcherModule from '../features/source-model-watcher/di.config'; import toolFeedbackModule from '../features/tool-feedback/di.config'; diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 94ed8df..ad03390 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,7 +21,7 @@ import { isSourceUriAware } from './source-uri-aware'; import { TYPES } from './types'; export interface EditModeListener { - editModeChanged(newValue: string, oldvalue: string): void; + editModeChanged(newValue: string, oldValue: string): void; } /** @@ -81,11 +81,11 @@ export class EditorContextService implements IActionHandler { if (SetEditModeAction.is(action)) { const oldValue = this._editMode; this._editMode = action.editMode; - this.notifiyEditModeListeners(oldValue); + this.notifyEditModeListeners(oldValue); } } - protected notifiyEditModeListeners(oldValue: string): void { + protected notifyEditModeListeners(oldValue: string): void { this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); } diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index c41cccf..c4571af 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,19 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { distinctAdd, EditMode } from '@eclipse-glsp/protocol'; +import { AnyObject, distinctAdd, EditMode, hasBooleanProp, hasFunctionProp, hasStringProp } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { Tool, ToolManager } from 'sprotty'; import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; import { TYPES } from '../types'; -export interface IGLSPToolManager extends ToolManager { - /* Disables all actives tools and activates only default tool with non-edit function*/ - disableEditTools(): void; -} - @injectable() -export class GLSPToolManager extends ToolManager implements IGLSPToolManager, EditModeListener { +export class GLSPToolManager extends ToolManager implements EditModeListener { protected editorContext?: EditorContextService; @multiInject(TYPES.ITool) @optional() override tools: Tool[]; @@ -59,7 +54,7 @@ export class GLSPToolManager extends ToolManager implements IGLSPToolManager, Ed this.disableActiveTools(); let tools = toolIds.map(id => this.tool(id)); if (this.editorContext && this.editorContext.isReadonly) { - tools = tools.filter(tool => tool && (!isGLSPTool(tool) || tool.isEditTool === false)); + tools = tools.filter(tool => tool && (!GLSPTool.is(tool) || tool.isEditTool === false)); } tools.forEach(tool => { if (tool !== undefined) { @@ -71,7 +66,7 @@ export class GLSPToolManager extends ToolManager implements IGLSPToolManager, Ed disableEditTools(): void { this.disableActiveTools(); - this.enable(this.defaultTools.filter(tool => !isGLSPTool(tool) || tool.isEditTool === false).map(tool => tool.id)); + this.enable(this.defaultTools.filter(tool => !GLSPTool.is(tool) || tool.isEditTool === false).map(tool => tool.id)); } editModeChanged(oldValue: string, newValue: string): void { @@ -90,6 +85,14 @@ export interface GLSPTool extends Tool { isEditTool: boolean; } -export function isGLSPTool(tool: any): tool is GLSPTool { - return 'isEditTool' in tool && typeof tool['isEditTool'] === 'boolean'; +export namespace GLSPTool { + export function is(object: unknown): object is GLSPTool { + return ( + AnyObject.is(object) && + hasStringProp(object, 'id') && + hasFunctionProp(object, 'enable') && + hasFunctionProp(object, 'disable') && + hasBooleanProp(object, 'isEditTool') + ); + } } diff --git a/packages/client/src/features/bounds/di.config.ts b/packages/client/src/features/bounds/di.config.ts index e94eb9f..17e0484 100644 --- a/packages/client/src/features/bounds/di.config.ts +++ b/packages/client/src/features/bounds/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,12 +15,21 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { - configureCommand, configureLayout, HBoxLayouter, HiddenBoundsUpdater, Layouter, - LayoutRegistry, RequestBoundsCommand, SetBoundsCommand, StackLayouter, VBoxLayouter + configureCommand, + configureLayout, + HBoxLayouter, + HiddenBoundsUpdater, + Layouter, + LayoutRegistry, + RequestBoundsCommand, + SetBoundsCommand, + VBoxLayouter } from 'sprotty'; -import '../../../css/command-palette.css'; import { TYPES } from '../../base/types'; +import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; +import { HBoxLayouterExt } from './hbox-layout'; +import { VBoxLayouterExt } from './vbox-layout'; const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { configureCommand({ bind, isBound }, SetBoundsCommand); @@ -31,9 +40,9 @@ const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) = bind(TYPES.Layouter).to(Layouter).inSingletonScope(); bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); - configureLayout({ bind, isBound }, VBoxLayouter.KIND, VBoxLayouter); - configureLayout({ bind, isBound }, HBoxLayouter.KIND, HBoxLayouter); - configureLayout({ bind, isBound }, StackLayouter.KIND, StackLayouter); + configureLayout({ bind, isBound }, VBoxLayouter.KIND, VBoxLayouterExt); + configureLayout({ bind, isBound }, HBoxLayouter.KIND, HBoxLayouterExt); + configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); }); export default glspBoundsModule; diff --git a/packages/client/src/features/layout/tests/freeform-layout.spec.ts b/packages/client/src/features/bounds/freeform-layout.spec.ts similarity index 99% rename from packages/client/src/features/layout/tests/freeform-layout.spec.ts rename to packages/client/src/features/bounds/freeform-layout.spec.ts index 490af55..163f97c 100644 --- a/packages/client/src/features/layout/tests/freeform-layout.spec.ts +++ b/packages/client/src/features/bounds/freeform-layout.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,7 +17,7 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; import { BoundsData, ConsoleLogger, SModelElement } from 'sprotty'; -import { gModel, layout, sComp, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; +import { gModel, layout, sComp, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; describe('FreeFormLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/layout/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts similarity index 97% rename from packages/client/src/features/layout/freeform-layout.ts rename to packages/client/src/features/bounds/freeform-layout.ts index 712979a..ddaafd4 100644 --- a/packages/client/src/features/layout/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,7 +20,7 @@ import { AbstractLayout } from 'sprotty/lib/features/bounds/abstract-layout'; import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; /** - * Layouts children of a container with explit X/Y positions + * Layouts children of a container with explicit X/Y positions */ @injectable() export class FreeFormLayouter extends AbstractLayout { diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 69cfc1b..b891dc4 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,10 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, ComputedBoundsAction, Deferred, ElementAndRoutingPoints, RequestAction, - ResponseAction -} from '@eclipse-glsp/protocol'; +import { Action, ComputedBoundsAction, Deferred, ElementAndRoutingPoints, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { EdgeRouterRegistry, HiddenBoundsUpdater, IActionDispatcher, SModelElement, SRoutableElement } from 'sprotty'; @@ -30,7 +27,6 @@ import { calcElementAndRoute, isRoutable } from '../../utils/smodel-util'; */ @injectable() export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { - @inject(EdgeRouterRegistry) @optional() protected readonly edgeRouterRegistry?: EdgeRouterRegistry; protected element2route: ElementAndRoutingPoints[] = []; @@ -47,7 +43,8 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { override postUpdate(cause?: Action): void { const actions = this.captureActions(() => super.postUpdate(cause)); - actions.filter(action => ComputedBoundsAction.is(action)) + actions + .filter(action => ComputedBoundsAction.is(action)) .forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction))); this.element2route = []; } diff --git a/packages/client/src/features/layout/tests/hbox-layout.spec.ts b/packages/client/src/features/bounds/hbox-layout.spec.ts similarity index 98% rename from packages/client/src/features/layout/tests/hbox-layout.spec.ts rename to packages/client/src/features/bounds/hbox-layout.spec.ts index d10e1af..58d4dc2 100644 --- a/packages/client/src/features/layout/tests/hbox-layout.spec.ts +++ b/packages/client/src/features/bounds/hbox-layout.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 TypeFox, STMicroelectronics and others. + * Copyright (c) 2022-2023 TypeFox, STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,7 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; -import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; +import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; describe('HBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/layout/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts similarity index 99% rename from packages/client/src/features/layout/hbox-layout.ts rename to packages/client/src/features/bounds/hbox-layout.ts index 3618172..e1f0760 100644 --- a/packages/client/src/features/layout/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/tests/layouter-test-util.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts similarity index 94% rename from packages/client/src/features/layout/tests/layouter-test-util.ts rename to packages/client/src/features/bounds/layouter-test-util.spec.ts index 577f790..106f93a 100644 --- a/packages/client/src/features/layout/tests/layouter-test-util.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -16,7 +16,6 @@ import { Dimension, Point } from '@eclipse-glsp/protocol'; import 'mocha'; -import 'reflect-metadata'; import { BoundsData, ConsoleLogger, @@ -30,9 +29,9 @@ import { SParentElement, TYPES } from 'sprotty'; -import { createDiagramContainer } from '../../../base/container-modules'; -import { GLSPGraph } from '../../../lib/model'; -import { StatefulLayouterExt } from '../layouter'; +import { createDiagramContainer } from '../../base/container-modules'; +import { GLSPGraph } from '../../lib/model'; +import { StatefulLayouterExt } from './layouter'; export function gModel(): GLSPGraph { return new GLSPGraph(); diff --git a/packages/client/src/features/layout/layouter.ts b/packages/client/src/features/bounds/layouter.ts similarity index 98% rename from packages/client/src/features/layout/layouter.ts rename to packages/client/src/features/bounds/layouter.ts index f063a8e..fd328fe 100644 --- a/packages/client/src/features/layout/layouter.ts +++ b/packages/client/src/features/bounds/layouter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/tests/vbox-layout.spec.ts b/packages/client/src/features/bounds/vbox-layout.spec.ts similarity index 98% rename from packages/client/src/features/layout/tests/vbox-layout.spec.ts rename to packages/client/src/features/bounds/vbox-layout.spec.ts index 23bbf70..cf04e1f 100644 --- a/packages/client/src/features/layout/tests/vbox-layout.spec.ts +++ b/packages/client/src/features/bounds/vbox-layout.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 TypeFox, STMicroelectronics and others. + * Copyright (c) 2022-2023 TypeFox, STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,7 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; -import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util'; +import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; describe('VBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts similarity index 99% rename from packages/client/src/features/layout/vbox-layout.ts rename to packages/client/src/features/bounds/vbox-layout.ts index eb0f393..0467dc8 100644 --- a/packages/client/src/features/layout/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index 45d7100..6d9e107 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,7 +25,7 @@ export class CommandPaletteTool implements Tool { @postConstruct() protected postConstruct(): void { - this.commandPaletteKeyListener = this.createCommandPaleteKeyListener(); + this.commandPaletteKeyListener = this.createCommandPaletteKeyListener(); } get id(): string { @@ -40,7 +40,7 @@ export class CommandPaletteTool implements Tool { this.keyTool.deregister(this.commandPaletteKeyListener); } - protected createCommandPaleteKeyListener(): KeyListener { + protected createCommandPaletteKeyListener(): KeyListener { return new CommandPaletteKeyListener(); } } diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 205b822..c13cf25 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -96,7 +96,7 @@ const CLIPBOARD_DATA_FORMAT = 'text/plain'; export class ServerCopyPasteHandler implements ICopyPasteHandler { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; - @inject(TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; + @inject(TYPES.IAsyncClipboardService) protected clipboardService: IAsyncClipboardService; @inject(EditorContextService) protected editorContext: EditorContextService; handleCopy(event: ClipboardEvent): void { @@ -105,13 +105,13 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { event.clipboardData.setData(CLIPBOARD_DATA_FORMAT, toClipboardId(clipboardId)); this.actionDispatcher .request(RequestClipboardDataAction.create(this.editorContext.get())) - .then(action => this.clipboadService.put(action.clipboardData, clipboardId)); + .then(action => this.clipboardService.put(action.clipboardData, clipboardId)); event.preventDefault(); } else { if (event.clipboardData) { event.clipboardData.clearData(); } - this.clipboadService.clear(); + this.clipboardService.clear(); } } @@ -126,7 +126,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { handlePaste(event: ClipboardEvent): void { if (event.clipboardData && this.shouldPaste(event)) { const clipboardId = getClipboardIdFromDataTransfer(event.clipboardData); - const clipboardData = this.clipboadService.get(clipboardId); + const clipboardData = this.clipboardService.get(clipboardId); if (clipboardData) { this.actionDispatcher.dispatch(PasteOperation.create({ clipboardData, editorContext: this.editorContext.get() })); } diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index a427b5d..82e44a1 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,8 +18,8 @@ import { injectable } from 'inversify'; import { ExportSvgAction, SModelRoot, SvgExporter } from 'sprotty'; import { v4 as uuid } from 'uuid'; - @injectable() - export class GLSPSvgExporter extends SvgExporter { +@injectable() +export class GLSPSvgExporter extends SvgExporter { override export(root: SModelRoot, _request?: RequestAction): void { if (typeof document !== 'undefined') { const svgElement = this.findSvgElement(); @@ -30,9 +30,10 @@ import { v4 as uuid } from 'uuid'; svgElement.id = originalId || uuid(); // provide generated svg code with respective sizing for proper viewing in browser and remove undesired border const bounds = this.getBounds(root); - const svg = this.createSvg(svgElement, root) - .replace('style="', - `style="width: ${bounds.width}px !important;height: ${bounds.height}px !important;border: none !important;`); + const svg = this.createSvg(svgElement, root).replace( + 'style="', + `style="width: ${bounds.width}px !important;height: ${bounds.height}px !important;border: none !important;` + ); // do not give request/response id here as otherwise the action is treated as an unrequested response this.actionDispatcher.dispatch(ExportSvgAction.create(svg, '')); } finally { @@ -47,4 +48,4 @@ import { v4 as uuid } from 'uuid'; // search for first svg element as hierarchy within Sprotty might change return div && div.querySelector('svg'); } - } +} diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 6311950..62ef6bb 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,27 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureLayout, HBoxLayouter, TYPES, VBoxLayouter } from 'sprotty'; -import { FreeFormLayouter } from './freeform-layout'; -import { HBoxLayouterExt } from './hbox-layout'; +import { configureActionHandler } from 'sprotty'; import { AlignElementsAction, AlignElementsActionHandler, ResizeElementsAction, ResizeElementsActionHandler } from './layout-elements-action'; -import { LayouterExt } from './layouter'; -import { VBoxLayouterExt } from './vbox-layout'; const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound }; configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); - - rebind(VBoxLayouter).to(VBoxLayouterExt); - rebind(HBoxLayouter).to(HBoxLayouterExt); - rebind(TYPES.Layouter).to(LayouterExt); - configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); }); export default layoutModule; diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts index ff9376f..9f8f6c4 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,7 @@ import { configureActionHandler } from 'sprotty'; import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; -export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { +const navigationModule = new ContainerModule((bind, _unbind, isBound) => { bind(NavigationTargetResolver).toSelf().inSingletonScope(); bind(NavigationActionHandler).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); @@ -27,3 +27,5 @@ export const navigationModule = new ContainerModule((bind, _unbind, isBound) => configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); configureActionHandler({ bind, isBound }, NavigateToExternalTargetAction.KIND, NavigationActionHandler); }); + +export default navigationModule; diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index e030e23..eaad0b4 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -123,7 +123,7 @@ export namespace ProcessNavigationArgumentsAction { * *(a)* the navigation target already specifies element IDs, in which case this action handler navigates * to the specified elements directly, by the selecting them and centering them in the viewport. * *(b)* the arguments of the navigation targets don't contain element IDs, but other arguments, the - * navigation target needs to be resolved into actual elment IDs by the `NavigationTargetResolver`. + * navigation target needs to be resolved into actual element IDs by the `NavigationTargetResolver`. * This can for instance be useful, if the navigation deals with queries or some other more complex * logic that can't be dealt with on the client. * *(c)* the target isn't resolved by the `NavigationTargetResolver`, e.g. because the `uri` doesn't match diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index 0a346dd..259c265 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -200,7 +200,7 @@ describe('SelectionService', () => { selectionService.modelRootChanged(newRoot); assertSelectionAndFeedback(['node1'], ['node2']); }); - it('Changing root with new selection correctly selects matchting elements and deselects not matching elements.', () => { + it('Changing root with new selection correctly selects matching elements and deselects not matching elements.', () => { assertSelectionAndFeedback([], []); selectionService.updateSelection(root, ['node1', 'node2'], []); assertSelectionAndFeedback(['node1', 'node2'], []); diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 999f854..e4e06cb 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -221,7 +221,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } } - protected validateMove(startPostion: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { + protected validateMove(startPosition: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { let newPosition = toPosition; if (this.tool.movementRestrictor) { const valid = this.tool.movementRestrictor.validate(element, toPosition); @@ -230,7 +230,7 @@ export class FeedbackMoveMouseListener extends MouseListener { action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); if (isFinished) { - newPosition = startPostion; + newPosition = startPosition; } } else { action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index 85f783d..d6ccd84 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -43,7 +43,7 @@ const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { configureCommand({ bind, isBound }, RemoveMarqueeCommand); configureView({ bind, isBound }, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); - // move tool feedback: we use sprotties MoveCommand as client-side visual feedback + // move tool feedback: we use sprotty's MoveCommand as client-side visual feedback configureCommand({ bind, isBound }, MoveCommand); // resize tool feedback diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 3ab11cc..06031ca 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - import { +import { Action, Bounds, ChangeBoundsOperation, @@ -243,16 +243,16 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele // If client routing is enabled -> delegate routingpoints of connected edges to server forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { if (element instanceof SConnectableElement) { - element.incomingEdges.map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) + element.incomingEdges + .map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) .forEach(ear => newRoutingPoints.push(ear)); - element.outgoingEdges.map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) + element.outgoingEdges + .map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) .forEach(ear => newRoutingPoints.push(ear)); } }); } - return newRoutingPoints.length > 0 - ? [ChangeRoutingPointsOperation.create(newRoutingPoints)] - : []; + return newRoutingPoints.length > 0 ? [ChangeRoutingPointsOperation.create(newRoutingPoints)] : []; } protected handleResize(activeResizeHandle: SResizeHandle): Action[] { diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 9413fda..77921a8 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -24,7 +24,7 @@ import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; import { EdgeCreationTool } from './edge-creation-tool'; import { EdgeEditTool } from './edge-edit-tool'; import { EnableDefaultToolsOnFocusLossHandler } from './enable-default-tools-on-focus-loss'; -import { GSLPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; +import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; import { MarqueeMouseTool } from './marquee-mouse-tool'; import { MarqueeTool } from './marquee-tool'; import { MarqueeNode } from './model'; @@ -52,8 +52,8 @@ export const toolsModule = new ContainerModule((bind, _unbind, isBound, rebind) configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); - bind(GSLPManhattanEdgeRouter).toSelf().inSingletonScope(); - rebind(ManhattanEdgeRouter).toService(GSLPManhattanEdgeRouter); + bind(GLSPManhattanEdgeRouter).toSelf().inSingletonScope(); + rebind(ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); }); export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { diff --git a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts index 451459f..a562ed3 100644 --- a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts +++ b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,7 +17,7 @@ import { almostEquals } from '@eclipse-glsp/protocol'; import { ManhattanEdgeRouter, ResolvedHandleMove, SRoutableElement } from 'sprotty'; -export class GSLPManhattanEdgeRouter extends ManhattanEdgeRouter { +export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { protected override applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { const route = this.route(edge); const routingPoints = edge.routingPoints; @@ -33,9 +33,11 @@ export class GSLPManhattanEdgeRouter extends ManhattanEdgeRouter { if (routingPoints.length === 0) { routingPoints.push({ x: correctedX, y: correctedY }); move.handle.pointIndex = 0; + } else if (almostEquals(route[0].x, route[1].x)) { + this.alignX(routingPoints, 0, correctedX); + } else { + this.alignY(routingPoints, 0, correctedY); } - else if (almostEquals(route[0].x, route[1].x)) { this.alignX(routingPoints, 0, correctedX); } - else { this.alignY(routingPoints, 0, correctedY); } } else if (index < routingPoints.length - 1) { if (almostEquals(routingPoints[index].x, routingPoints[index + 1].x)) { this.alignX(routingPoints, index, correctedX); @@ -47,8 +49,9 @@ export class GSLPManhattanEdgeRouter extends ManhattanEdgeRouter { } else { if (almostEquals(route[route.length - 2].x, route[route.length - 1].x)) { this.alignX(routingPoints, routingPoints.length - 1, correctedX); + } else { + this.alignY(routingPoints, routingPoints.length - 1, correctedY); } - else { this.alignY(routingPoints, routingPoints.length - 1, correctedY); } } break; } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index add34db..ba786bd 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,16 +23,17 @@ import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; import layoutModule from './features/layout/di.config'; import glspMouseToolModule from './features/mouse-tool/di.config'; -import { navigationModule } from './features/navigation/di.config'; +import navigationModule from './features/navigation/di.config'; import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; import sourceModelWatcherModule from './features/source-model-watcher/di.config'; import toolFeedbackModule from './features/tool-feedback/di.config'; import paletteModule from './features/tool-palette/di.config'; import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; -import { markerNavigatorModule, validationModule } from './features/validation/di.config'; +import { markerNavigatorContextMenuModule, markerNavigatorModule, validationModule } from './features/validation/di.config'; import glspViewportModule from './features/viewport/di.config'; +// ------------------ Base ------------------ export * from './base/action-dispatcher'; export * from './base/actions/focus-change-action'; export * from './base/argumentable'; @@ -50,18 +51,31 @@ export * from './base/model/model-registry'; export * from './base/model/update-model-command'; export * from './base/selection-clearing-mouse-listener'; export * from './base/source-uri-aware'; +export * from './base/tool-manager/glsp-tool-manager'; export * from './base/tool-manager/tool-actions'; export * from './base/types'; export * from './base/view/view-registry'; +// +// ------------------ Features ------------------ +export * from './features/bounds/freeform-layout'; +export * from './features/bounds/glsp-hidden-bounds-updater'; +export * from './features/bounds/hbox-layout'; +export * from './features/bounds/layouter'; +export * from './features/bounds/vbox-layout'; export * from './features/change-bounds/model'; export * from './features/change-bounds/movement-restrictor'; export * from './features/change-bounds/snap'; +export * from './features/command-palette/command-palette-tool'; export * from './features/command-palette/server-command-palette-provider'; export * from './features/context-menu/delete-element-context-menu'; +export * from './features/context-menu/selection-service-aware-context-menu-mouse-listener'; +export * from './features/context-menu/server-context-menu-provider'; +export * from './features/copy-paste/copy-paste-context-menu'; export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; +export * from './features/export/glsp-svg-exporter'; export * from './features/hints/model'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; @@ -73,33 +87,47 @@ export * from './features/rank/model'; export * from './features/reconnect/model'; export * from './features/save/model'; export * from './features/save/save-keylistener'; +export * from './features/select/select-feedback-action'; +export * from './features/select/select-mouse-listener'; +export * from './features/select/selection-service'; export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/tool-feedback/change-bounds-tool-feedback'; export * from './features/tool-feedback/creation-tool-feedback'; export * from './features/tool-feedback/css-feedback'; export * from './features/tool-feedback/edge-edit-tool-feedback'; export * from './features/tool-feedback/feedback-action-dispatcher'; +export * from './features/tool-feedback/marquee-tool-feedback'; export * from './features/tool-feedback/model'; +export * from './features/tool-feedback/view'; export * from './features/tool-palette/tool-palette'; export * from './features/tools/base-glsp-tool'; export * from './features/tools/change-bounds-tool'; export * from './features/tools/delete-tool'; +export { configureMarqueeTool } from './features/tools/di.config'; export * from './features/tools/edge-creation-tool'; export * from './features/tools/edge-edit-tool'; +export * from './features/tools/enable-default-tools-on-focus-loss'; +export * from './features/tools/glsp-manhattan-edge-router'; export * from './features/tools/marquee-behavior'; +export * from './features/tools/model'; export * from './features/tools/node-creation-tool'; export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; +// +// ------------------ Misc ------------------ export * from './lib/model'; export * from './model-source/glsp-diagram-server'; export * from './sprotty-reexport'; export * from './utils/argument-utils'; +export * from './utils/html-utils'; +export * from './utils/layout-utils'; export * from './utils/marker'; export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; +// ------------------ DI Modules ------------------ export { validationModule, saveModule, @@ -121,6 +149,7 @@ export { navigationModule, markerNavigatorModule, glspDecorationModule, - sourceModelWatcherModule as modelSourceWatcherModule, + sourceModelWatcherModule, + markerNavigatorContextMenuModule, glspViewportModule }; diff --git a/packages/client/src/sprotty-reexport.ts b/packages/client/src/sprotty-reexport.ts index 8c3ec55..f83c418 100644 --- a/packages/client/src/sprotty-reexport.ts +++ b/packages/client/src/sprotty-reexport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,8 +23,13 @@ export * from '@eclipse-glsp/protocol'; // sprotty-protocol export { - BringToFrontAction, CollapseExpandAction, CollapseExpandAllAction, - ElementAndAlignment, HoverFeedbackAction, MoveAction, SetBoundsAction + BringToFrontAction, + CollapseExpandAction, + CollapseExpandAllAction, + ElementAndAlignment, + HoverFeedbackAction, + MoveAction, + SetBoundsAction } from 'sprotty-protocol/lib/actions'; export { Viewport } from 'sprotty-protocol/lib/model'; // ------------------ Base ------------------ @@ -157,8 +162,14 @@ export { export * from 'sprotty/lib/features/hover/model'; export * from 'sprotty/lib/features/move/model'; export { - ElementMove, LocationPostprocessor, MorphEdgesAnimation, MoveAnimation, - MoveCommand, MoveMouseListener, ResolvedElementMove, ResolvedHandleMove + ElementMove, + LocationPostprocessor, + MorphEdgesAnimation, + MoveAnimation, + MoveCommand, + MoveMouseListener, + ResolvedElementMove, + ResolvedHandleMove } from 'sprotty/lib/features/move/move'; export * from 'sprotty/lib/features/move/snap'; export * from 'sprotty/lib/features/nameable/model'; @@ -181,8 +192,8 @@ export * from 'sprotty/lib/features/select/model'; export { GetSelectionAction, GetSelectionCommand, - SelectAllCommand, - SelectCommand, + SelectAllCommand as SprottySelectAllCommand, + SelectCommand as SprottySelectCommand, SelectionResult, SelectKeyboardListener, SelectMouseListener @@ -291,4 +302,3 @@ import viewportModule from 'sprotty/lib/features/viewport/di.config'; import zorderModule from 'sprotty/lib/features/zorder/di.config'; import graphModule from 'sprotty/lib/graph/di.config'; import modelSourceModule from 'sprotty/lib/model-source/di.config'; - diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index e45552b..b7a1b27 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - distinctAdd, ElementAndBounds, ElementAndRoutingPoints, Point, remove, SModelElementSchema, + distinctAdd, + ElementAndBounds, + ElementAndRoutingPoints, + Point, + remove, + SModelElementSchema, TypeGuard } from '@eclipse-glsp/protocol'; import { @@ -23,7 +28,8 @@ import { isBoundsAware, isMoveable, isSelectable, - isSelected, ModelIndexImpl, + isSelected, + ModelIndexImpl, RoutedPoint, Selectable, SModelElement, @@ -275,7 +281,9 @@ export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: * @returns The corresponding route for the given element. */ export function calcRoute(element: SRoutableElement, routerRegistry: EdgeRouterRegistry, pointKinds?: string[]): RoutedPoint[] | undefined { - return routerRegistry?.get(element.routerKind).route(element) + return routerRegistry + ?.get(element.routerKind) + .route(element) .filter((point, idx, fullRoute) => idx === fullRoute.findIndex(otherPoint => otherPoint.x === point.x && otherPoint.y === point.y)) .filter(point => !pointKinds || pointKinds.includes(point.kind)); } @@ -302,4 +310,3 @@ export function getElementTypeId(input: SModelElement | SModelElementSchema | st return (input as any)['type'] as string; } } - diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index 4677b90..506aab8 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -32,7 +32,7 @@ export class GEdgeView extends PolylineEdgeView { } return ( - + {this.renderLine(edge, route, context)} {this.renderAdditionals(edge, route, context)} {context.renderChildren(edge, { route })} @@ -40,7 +40,7 @@ export class GEdgeView extends PolylineEdgeView { ); } - protected addtionalClasses(_edge: Readonly, _context: RenderingContext): Classes { + protected additionalClasses(_edge: Readonly, _context: RenderingContext): Classes { return {}; } diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index 2e79fe7..055b107 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,4 +20,3 @@ export * from './glsp-projection-view'; export * from './issue-marker-view'; export * from './rounded-corner'; export * from './rounded-corner-view'; - diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 30767c8..041fd69 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -70,7 +70,7 @@ export class RoundedCornerNodeView extends RectangularNodeView { } protected renderPath(wrapper: Readonly, _context: RenderingContext, inset: number): string { - // Calcualte length of straight line segments + // Calculate length of straight line segments const topLineLength = Math.max(0, wrapper.size.width - wrapper.cornerRadius.topLeft - wrapper.cornerRadius.topRight); const rightLineLength = Math.max(0, wrapper.size.height - wrapper.cornerRadius.topRight - wrapper.cornerRadius.bottomRight); const bottomLineLength = Math.max(0, wrapper.size.width - wrapper.cornerRadius.bottomLeft - wrapper.cornerRadius.bottomRight); diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index d62c19b..4592461 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -76,7 +76,7 @@ export class RoundedCornerWrapper { } /* - * Scales the radius if necessary. (Percentual downscaling if the radius is bigger then the maximal allowed length) + * Scales the radius if necessary. (Percentile down scaling if the radius is bigger then the maximal allowed length) */ export function scaledRadius(radius: number, maximalLength: number): number { if (radius <= maximalLength) { diff --git a/packages/protocol/src/action-protocol/element-text-editing.spec.ts b/packages/protocol/src/action-protocol/element-text-editing.spec.ts index f31214d..2fc7325 100644 --- a/packages/protocol/src/action-protocol/element-text-editing.spec.ts +++ b/packages/protocol/src/action-protocol/element-text-editing.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -104,7 +104,7 @@ describe('Element text editing actions', () => { it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { const expected: SetEditValidationResultAction = { kind: 'setEditValidationResult', - responseId: 'myRespsone', + responseId: 'myResponse', args: { some: 'args' }, status: { severity: ValidationStatus.Severity.OK } }; diff --git a/packages/protocol/src/action-protocol/tool-palette.spec.ts b/packages/protocol/src/action-protocol/tool-palette.spec.ts index b0e0fdb..ee710d2 100644 --- a/packages/protocol/src/action-protocol/tool-palette.spec.ts +++ b/packages/protocol/src/action-protocol/tool-palette.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { expect } from 'chai'; import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from './tool-palette'; describe('Tool palette Actions', () => { - describe('TrigerNodeCreationAction', () => { + describe('TriggerNodeCreationAction', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { const action: TriggerNodeCreationAction = { From 812093bb98dbcddcffaab021eadb69b0d2df6940 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 16 Mar 2023 14:15:18 -0700 Subject: [PATCH 186/566] GLSP-954 Add DI utility to protocol package (#236) - Create a `di-util` module that provides - A configurable function for initializing new DI containers with a specific module configuration (`initializeDiContainer`) - The `bindAsService` helper function which can be used as shorthand for the common pattern of binding classes to self before binding them to another service - The `bindOrRebind` helper function - Double check all multiinjected service bindings and ensure that they can be easily rebound if necessary. This is achieved with the new `bindAsService` helper function Part of https://github.com/eclipse-glsp/glsp/issues/954 Co-authored-by: Martin Fleck --- examples/workflow-glsp/src/di.config.ts | 30 ++-- .../src/direct-task-editing/di.config.ts | 9 +- examples/workflow-glsp/src/index.ts | 8 +- packages/client/src/base/container-modules.ts | 43 +---- packages/client/src/base/di.config.ts | 16 +- .../client/src/features/bounds/di.config.ts | 15 +- .../bounds/layouter-test-util.spec.ts | 5 +- .../src/features/command-palette/di.config.ts | 13 +- .../src/features/context-menu/di.config.ts | 7 +- .../src/features/decoration/di.config.ts | 8 +- .../src/features/edit-label/di.config.ts | 5 +- .../client/src/features/export/di.config.ts | 11 +- .../client/src/features/hover/di.config.ts | 15 +- .../client/src/features/save/di.config.ts | 5 +- .../client/src/features/select/di.config.ts | 15 +- .../features/select/selection-service.spec.ts | 5 +- .../src/features/tool-feedback/di.config.ts | 34 ++-- .../src/features/tool-palette/di.config.ts | 8 +- .../client/src/features/tools/di.config.ts | 33 ++-- .../src/features/validation/di.config.ts | 10 +- .../client/src/features/viewport/di.config.ts | 24 +-- packages/client/src/views/base-view-module.ts | 13 +- packages/protocol/README.md | 1 + packages/protocol/package.json | 3 + .../{utils => disposable}/disposable.spec.ts | 0 .../src/{utils => disposable}/disposable.ts | 4 +- packages/protocol/src/index.ts | 3 +- packages/protocol/src/utils/di-util.ts | 166 ++++++++++++++++++ 28 files changed, 333 insertions(+), 176 deletions(-) rename packages/protocol/src/{utils => disposable}/disposable.spec.ts (100%) rename packages/protocol/src/{utils => disposable}/disposable.ts (96%) create mode 100644 packages/protocol/src/utils/di-util.ts diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 1bef1ed..d1e26d8 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -14,10 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + bindAsService, configureDefaultModelElements, configureModelElement, ConsoleLogger, - createDiagramContainer, DeleteElementContextMenuItemProvider, DiamondNodeView, editLabelFeature, @@ -25,6 +25,7 @@ import { GLSPGraph, GLSPProjectionView, GridSnapper, + initializeDiagramContainer, LogLevel, overrideViewerOptions, RectangularNodeView, @@ -37,7 +38,7 @@ import { StructureCompartmentView, TYPES } from '@eclipse-glsp/client'; -import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { bindOrRebind, ContainerConfiguration, DefaultTypes } from '@eclipse-glsp/protocol'; import 'balloon-css/balloon.min.css'; import { Container, ContainerModule } from 'inversify'; import 'sprotty/css/edit-label.css'; @@ -46,14 +47,15 @@ import { directTaskEditor } from './direct-task-editing/di.config'; import { ActivityNode, CategoryNode, Icon, TaskNode, WeightedEdge } from './model'; import { IconView, WorkflowEdgeView } from './workflow-views'; -const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { - rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); - rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); - bind(TYPES.ISnapper).to(GridSnapper); - bind(TYPES.ICommandPaletteActionProvider).to(RevealNamedElementActionProvider); - bind(TYPES.IContextMenuItemProvider).to(DeleteElementContextMenuItemProvider); +export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; + bindOrRebind(context, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + bindOrRebind(context, TYPES.LogLevel).toConstantValue(LogLevel.warn); + bind(TYPES.ISnapper).to(GridSnapper); + bindAsService(context, TYPES.ICommandPaletteActionProvider, RevealNamedElementActionProvider); + bindAsService(context, TYPES.IContextMenuItemProvider, DeleteElementContextMenuItemProvider); + configureDefaultModelElements(context); configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); @@ -73,8 +75,16 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind configureModelElement(context, 'struct', SCompartment, StructureCompartmentView); }); -export default function createContainer(widgetId: string): Container { - const container = createDiagramContainer(workflowDiagramModule, directTaskEditor); +export function createWorkflowDiagramContainer(widgetId: string, ...containerConfiguration: ContainerConfiguration): Container { + return initializeWorkflowDiagramContainer(new Container(), widgetId, ...containerConfiguration); +} + +export function initializeWorkflowDiagramContainer( + container: Container, + widgetId: string, + ...containerConfiguration: ContainerConfiguration +): Container { + initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, ...containerConfiguration); overrideViewerOptions(container, { baseDiv: widgetId, hiddenDiv: widgetId + '_hidden' diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts index ec0d138..96a1367 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { TYPES } from '@eclipse-glsp/client'; +import { bindAsService, TYPES } from '@eclipse-glsp/client'; import { ContainerModule } from 'inversify'; import { TaskEditor } from './direct-task-editor'; -export const directTaskEditor = new ContainerModule((bind, _unbind, isBound) => { - bind(TaskEditor).toSelf().inSingletonScope(); - bind(TYPES.IUIExtension).toService(TaskEditor); +export const directTaskEditor = new ContainerModule((bind, _unbind, _isBound) => { + bindAsService(bind, TYPES.IUIExtension, TaskEditor); }); diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index 76e7aff..194be94 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import createWorkflowDiagramContainer from './di.config'; - +export * from './di.config'; +export * from './direct-task-editing/direct-task-editor'; export * from './model'; export * from './workflow-views'; -export * from './direct-task-editing/direct-task-editor'; -export { createWorkflowDiagramContainer }; diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index bf8afb5..6a6b026 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { asArray, distinctAdd, MaybeArray, remove } from '@eclipse-glsp/protocol'; +import { ContainerConfiguration, initializeContainer } from '@eclipse-glsp/protocol'; import { Container, ContainerModule } from 'inversify'; import { buttonModule, @@ -87,7 +87,7 @@ export const DEFAULT_MODULES = [ ] as const; /** - * Creates a GLSP Diagram container with the GLSP default modules and the specified custom `modules`. + * Initializes a GLSP Diagram container with the GLSP default modules and the specified custom `modules`. * Additional modules can be passed as direct arguments or as part of a {@link ModuleConfiguration}. * ```typescript * const container= createDiagramContainer(myModule1, myModule2) @@ -109,37 +109,12 @@ export const DEFAULT_MODULES = [ * rebind(NavigationTargetResolver).to(MyNavigationTargetResolver); * ``` * - * @param customModules Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. - * @param options Options to customize the module loading - * @returns The created container. - */ -export function createDiagramContainer(...customModules: Array): Container { - const container = new Container(); - - const modules = [...DEFAULT_MODULES]; - customModules.forEach(customModule => { - if (customModule instanceof ContainerModule) { - modules.push(customModule); - } else { - if (customModule.remove) { - remove(modules, ...asArray(customModule.remove)); - } - if (customModule.add) { - distinctAdd(modules, ...asArray(customModule.add)); - } - } - }); - container.load(...modules); - return container; -} - -/** - * Can be passed to the {@link createDiagramContainer} utility function to configure additional modules or - * remove (i.e. not load) default modules. + * @param containerConfiguration + * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. + * @returns The initialized container. */ -export interface ModuleConfiguration { - add?: MaybeArray; - remove?: MaybeArray; +export function initializeDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { + return initializeContainer(container, ...DEFAULT_MODULES, ...containerConfiguration); } /** @@ -162,8 +137,8 @@ export interface ModuleConfiguration { * ``` * @param modules Custom modules to be loaded in addition to the default modules. * @returns The created container. - * @deprecated Please use `createDiagramContainer` from `@eclipse-glsp/client` instead + * @deprecated Please use `initializeDiagramContainer` from `@eclipse-glsp/client` instead */ export function createClientContainer(...modules: ContainerModule[]): Container { - return createDiagramContainer(...modules); + return initializeDiagramContainer(new Container(), ...modules); } diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 78183df..3155b33 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { InitializeResult, SetEditModeAction } from '@eclipse-glsp/protocol'; +import { bindAsService, bindOrRebind, InitializeResult, SetEditModeAction } from '@eclipse-glsp/protocol'; import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand } from 'sprotty'; @@ -54,19 +54,19 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = configureCommand(context, FeedbackAwareUpdateModelCommand); configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); - bind(TYPES.MouseListener).to(SelectionClearingMouseListener); + bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); - rebind(TYPES.ICommandStack).to(GLSPCommandStack); + bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack); bind(GLSPToolManager).toSelf().inSingletonScope(); - rebind(TYPES.IToolManager).toService(GLSPToolManager); + bindOrRebind(context, TYPES.IToolManager).toService(GLSPToolManager); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); - rebind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); // support re-registration of model elements and views - rebind(TYPES.SModelRegistry).to(GLSPModelRegistry).inSingletonScope(); - rebind(TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); + bindOrRebind(context, TYPES.SModelRegistry).to(GLSPModelRegistry).inSingletonScope(); + bindOrRebind(context, TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); }); export default defaultGLSPModule; diff --git a/packages/client/src/features/bounds/di.config.ts b/packages/client/src/features/bounds/di.config.ts index 17e0484..14bfde7 100644 --- a/packages/client/src/features/bounds/di.config.ts +++ b/packages/client/src/features/bounds/di.config.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureCommand, @@ -32,17 +33,17 @@ import { HBoxLayouterExt } from './hbox-layout'; import { VBoxLayouterExt } from './vbox-layout'; const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { - configureCommand({ bind, isBound }, SetBoundsCommand); - configureCommand({ bind, isBound }, RequestBoundsCommand); + const context = { bind, isBound }; + configureCommand(context, SetBoundsCommand); + configureCommand(context, RequestBoundsCommand); bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); - bind(GLSPHiddenBoundsUpdater).toSelf().inSingletonScope(); - bind(TYPES.HiddenVNodePostprocessor).toService(GLSPHiddenBoundsUpdater); + bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); bind(TYPES.Layouter).to(Layouter).inSingletonScope(); bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); - configureLayout({ bind, isBound }, VBoxLayouter.KIND, VBoxLayouterExt); - configureLayout({ bind, isBound }, HBoxLayouter.KIND, HBoxLayouterExt); - configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); + configureLayout(context, VBoxLayouter.KIND, VBoxLayouterExt); + configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); + configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); }); export default glspBoundsModule; diff --git a/packages/client/src/features/bounds/layouter-test-util.spec.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts index 106f93a..7020f7c 100644 --- a/packages/client/src/features/bounds/layouter-test-util.spec.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { Dimension, Point } from '@eclipse-glsp/protocol'; +import { Container } from 'inversify'; import 'mocha'; import { BoundsData, @@ -29,7 +30,7 @@ import { SParentElement, TYPES } from 'sprotty'; -import { createDiagramContainer } from '../../base/container-modules'; +import { initializeDiagramContainer } from '../../base/container-modules'; import { GLSPGraph } from '../../lib/model'; import { StatefulLayouterExt } from './layouter'; @@ -116,6 +117,6 @@ export function layout( export function setupLayoutRegistry(): LayoutRegistry { // Generic Test setup // create client container that registers all default modules including the layoutModule - const layoutContainer = createDiagramContainer(); + const layoutContainer = initializeDiagramContainer(new Container()); return layoutContainer.get(TYPES.LayoutRegistry); } diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/di.config.ts index b6d60c3..6f3c8a0 100644 --- a/packages/client/src/features/command-palette/di.config.ts +++ b/packages/client/src/features/command-palette/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { CommandPalette, CommandPaletteActionProviderRegistry } from 'sprotty'; import '../../../css/command-palette.css'; @@ -21,12 +22,10 @@ import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; const glspCommandPaletteModule = new ContainerModule(bind => { - bind(CommandPalette).toSelf().inSingletonScope(); - bind(TYPES.IUIExtension).toService(CommandPalette); - bind(CommandPaletteActionProviderRegistry).toSelf().inSingletonScope(); - bind(TYPES.ICommandPaletteActionProviderRegistry).toService(CommandPaletteActionProviderRegistry); - bind(TYPES.ICommandPaletteActionProvider).to(ServerCommandPaletteActionProvider); - bind(TYPES.IDefaultTool).to(CommandPaletteTool); + bindAsService(bind, TYPES.IUIExtension, CommandPalette); + bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); + bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); + bindAsService(bind, TYPES.IDefaultTool, CommandPaletteTool); }); export default glspCommandPaletteModule; diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts index 0d1b74c..684bcc0 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { ContextMenuProviderRegistry, IContextMenuService } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -30,9 +31,9 @@ const glspContextMenuModule = new ContainerModule(bind => { } }) ); - bind(TYPES.MouseListener).to(SelectionServiceAwareContextMenuMouseListener); + bindAsService(bind, TYPES.MouseListener, SelectionServiceAwareContextMenuMouseListener); bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); - bind(TYPES.IContextMenuItemProvider).to(ServerContextMenuItemProvider); + bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); }); export default glspContextMenuModule; diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts index 336ee31..977002f 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import '../../../css/decoration.css'; import { TYPES } from '../../base/types'; import { GlspDecorationPlacer } from './decoration-placer'; -const glspDecorationModule = new ContainerModule((bind, _unbind, isBound) => { - bind(GlspDecorationPlacer).toSelf().inSingletonScope(); - bind(TYPES.IVNodePostprocessor).toService(GlspDecorationPlacer); +const glspDecorationModule = new ContainerModule(bind => { + bindAsService(bind, TYPES.IVNodePostprocessor, GlspDecorationPlacer); }); export default glspDecorationModule; diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/edit-label/di.config.ts index a6b96a0..df7860b 100644 --- a/packages/client/src/features/edit-label/di.config.ts +++ b/packages/client/src/features/edit-label/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { ApplyLabelEditCommand, configureCommand } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -22,7 +23,7 @@ import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edi const glspEditLabelModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); - bind(TYPES.IDefaultTool).to(DirectLabelEditTool); + bindAsService(bind, TYPES.IDefaultTool, DirectLabelEditTool); configureCommand({ bind, isBound }, ApplyLabelEditCommand); }); diff --git a/packages/client/src/features/export/di.config.ts b/packages/client/src/features/export/di.config.ts index 2630afb..1f2498e 100644 --- a/packages/client/src/features/export/di.config.ts +++ b/packages/client/src/features/export/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,15 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureCommand, ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, TYPES } from 'sprotty'; import { GLSPSvgExporter } from './glsp-svg-exporter'; const glspExportSvgModule = new ContainerModule((bind, _unbind, isBound) => { - bind(ExportSvgKeyListener).toSelf().inSingletonScope(); - bind(TYPES.KeyListener).toService(ExportSvgKeyListener); - bind(TYPES.HiddenVNodePostprocessor).to(ExportSvgPostprocessor).inSingletonScope(); - configureCommand({ bind, isBound }, ExportSvgCommand); + const context = { bind, isBound }; + bindAsService(context, TYPES.KeyListener, ExportSvgKeyListener); + bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); + configureCommand(context, ExportSvgCommand); bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); }); diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index d2d848d..0ec146f 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { CenterCommand, @@ -36,11 +37,12 @@ import { FocusStateChangedAction } from '../../base/actions/focus-change-action' import { GlspHoverMouseListener } from './hover'; const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { - bind(TYPES.PopupVNodePostprocessor).to(PopupPositionUpdater).inSingletonScope(); - bind(GlspHoverMouseListener).toSelf().inSingletonScope(); - bind(TYPES.MouseListener).toService(GlspHoverMouseListener); - bind(TYPES.PopupMouseListener).to(PopupHoverMouseListener); - bind(TYPES.KeyListener).to(HoverKeyListener); + const context = { bind, isBound }; + bindAsService(context, TYPES.PopupVNodePostprocessor, PopupPositionUpdater); + bindAsService(context, TYPES.MouseListener, GlspHoverMouseListener); + bindAsService(context, TYPES.PopupMouseListener, PopupHoverMouseListener); + bindAsService(context, TYPES.KeyListener, HoverKeyListener); + bind(TYPES.HoverState).toConstantValue({ mouseOverTimer: undefined, mouseOutTimer: undefined, @@ -49,7 +51,6 @@ const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { }); bind(ClosePopupActionHandler).toSelf().inSingletonScope(); - const context = { bind, isBound }; configureCommand(context, HoverFeedbackCommand); configureCommand(context, SetPopupModelCommand); configureActionHandler(context, SetPopupModelCommand.KIND, ClosePopupActionHandler); diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index fb5504f..e130bca 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { TYPES } from '../../base/types'; import { SaveModelKeyboardListener } from './save-keylistener'; @@ -22,7 +23,7 @@ import { SaveModelKeyboardListener } from './save-keylistener'; * but only intended to be used in a standalone deployment of GLSP. */ const saveModule = new ContainerModule(bind => { - bind(TYPES.KeyListener).to(SaveModelKeyboardListener); + bindAsService(bind, TYPES.KeyListener, SaveModelKeyboardListener); }); export default saveModule; diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts index 0afea17..9a52195 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureCommand } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -21,12 +22,12 @@ import { RankedSelectMouseListener } from './select-mouse-listener'; import { SelectAllCommand, SelectCommand, SelectionService } from './selection-service'; const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { - bind(SelectionService).toSelf().inSingletonScope(); - bind(TYPES.SelectionService).toService(SelectionService); - configureCommand({ bind, isBound }, SelectCommand); - configureCommand({ bind, isBound }, SelectAllCommand); - configureCommand({ bind, isBound }, SelectFeedbackCommand); - bind(TYPES.MouseListener).to(RankedSelectMouseListener); + const context = { bind, isBound }; + bindAsService(context, TYPES.SelectionService, SelectionService); + configureCommand(context, SelectCommand); + configureCommand(context, SelectAllCommand); + configureCommand(context, SelectFeedbackCommand); + bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); bind(TYPES.SModelRootListener).toService(SelectionService); }); diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index 259c265..c7c1711 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ -import { Action, SModelElementSchema } from '@eclipse-glsp/protocol'; +import { Action, initializeContainer, SModelElementSchema } from '@eclipse-glsp/protocol'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import { defaultModule, SGraphFactory, SModelRoot } from 'sprotty'; @@ -72,8 +72,7 @@ class MockSelectionListener implements SelectionListener { } function createContainer(): Container { - const container = new Container(); - container.load(defaultModule); + const container = initializeContainer(new Container(), defaultModule); // eslint-disable-next-line deprecation/deprecation container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); container.bind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index d6ccd84..dfa292e 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureCommand, configureView, LocationPostprocessor, MoveCommand } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -31,35 +32,36 @@ import { DrawMarqueeCommand, RemoveMarqueeCommand } from './marquee-tool-feedbac import { FeedbackEdgeEndView, SResizeHandleView } from './view'; const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { + const context = { bind, isBound }; bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); - configureCommand({ bind, isBound }, ModifyCssFeedbackCommand); + configureCommand(context, ModifyCssFeedbackCommand); // create node and edge tool feedback - configureCommand({ bind, isBound }, DrawFeedbackEdgeCommand); - configureCommand({ bind, isBound }, RemoveFeedbackEdgeCommand); + configureCommand(context, DrawFeedbackEdgeCommand); + configureCommand(context, RemoveFeedbackEdgeCommand); - configureCommand({ bind, isBound }, DrawMarqueeCommand); - configureCommand({ bind, isBound }, RemoveMarqueeCommand); + configureCommand(context, DrawMarqueeCommand); + configureCommand(context, RemoveMarqueeCommand); - configureView({ bind, isBound }, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); + configureView(context, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); // move tool feedback: we use sprotty's MoveCommand as client-side visual feedback - configureCommand({ bind, isBound }, MoveCommand); + configureCommand(context, MoveCommand); // resize tool feedback - configureCommand({ bind, isBound }, ShowChangeBoundsToolResizeFeedbackCommand); - configureCommand({ bind, isBound }, HideChangeBoundsToolResizeFeedbackCommand); - configureView({ bind, isBound }, SResizeHandle.TYPE, SResizeHandleView); + configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); + configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); + configureView(context, SResizeHandle.TYPE, SResizeHandleView); // reconnect edge tool feedback - configureCommand({ bind, isBound }, ShowEdgeReconnectHandlesFeedbackCommand); - configureCommand({ bind, isBound }, HideEdgeReconnectHandlesFeedbackCommand); - configureCommand({ bind, isBound }, DrawFeedbackEdgeSourceCommand); + configureCommand(context, ShowEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, HideEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, DrawFeedbackEdgeSourceCommand); - configureCommand({ bind, isBound }, SwitchRoutingModeCommand); + configureCommand(context, SwitchRoutingModeCommand); - bind(TYPES.IVNodePostprocessor).to(LocationPostprocessor); - bind(TYPES.HiddenVNodePostprocessor).to(LocationPostprocessor); + bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); + bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); }); export default toolFeedbackModule; diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts index 7625dba..6867962 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureActionHandler, EnableDefaultToolsAction } from 'sprotty'; import '../../../css/tool-palette.css'; import { TYPES } from '../../base/types'; import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; -const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - bind(ToolPalette).toSelf().inSingletonScope(); - bind(TYPES.IUIExtension).toService(ToolPalette); +const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { + bindAsService(bind, TYPES.IUIExtension, ToolPalette); configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); }); diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 77921a8..4f7fe25 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; -import { ContainerModule, interfaces } from 'inversify'; +import { bindAsService, BindingContext, bindOrRebind, TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; import { configureActionHandler, configureModelElement, ManhattanEdgeRouter } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { TYPES } from '../../base/types'; @@ -36,30 +36,29 @@ import { MarqueeView } from './view'; * and adds the marquee selection tool. */ export const toolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; // Register default tools - bind(TYPES.IDefaultTool).to(ChangeBoundsTool); - bind(TYPES.IDefaultTool).to(EdgeEditTool); - bind(TYPES.IDefaultTool).to(DelKeyDeleteTool); + bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); + bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); + bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); // Register tools - bind(TYPES.ITool).to(MouseDeleteTool); - bind(NodeCreationTool).toSelf().inSingletonScope(); - bind(EdgeCreationTool).toSelf().inSingletonScope(); - bind(TYPES.ITool).toService(EdgeCreationTool); - bind(TYPES.ITool).toService(NodeCreationTool); + bindAsService(context, TYPES.ITool, MouseDeleteTool); + bindAsService(context, TYPES.ITool, NodeCreationTool); + bindAsService(context, TYPES.ITool, EdgeCreationTool); - configureMarqueeTool({ bind, isBound }); - configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); - configureActionHandler({ bind, isBound }, TriggerEdgeCreationAction.KIND, EdgeCreationTool); + configureMarqueeTool(context); + configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); + configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); bind(GLSPManhattanEdgeRouter).toSelf().inSingletonScope(); - rebind(ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); + bindOrRebind(context, ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); }); -export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { +export function configureMarqueeTool(context: Pick): void { configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); - context.bind(TYPES.IDefaultTool).to(MarqueeTool); - context.bind(TYPES.ITool).to(MarqueeMouseTool); + bindAsService(context, TYPES.IDefaultTool, MarqueeTool); + bindAsService(context, TYPES.ITool, MarqueeMouseTool); } /** diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index 99b94df..ba733a6 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SetMarkersAction } from '@eclipse-glsp/protocol'; +import { bindAsService, SetMarkersAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureActionHandler, configureCommand } from 'sprotty'; import { TYPES } from '../../base/types'; @@ -47,7 +47,7 @@ export const markerNavigatorModule = new ContainerModule((bind, _unbind, isBound * in a standalone deployment of GLSP. If the GLSP diagram is in Theia use the Theia-native * `registerMarkerNavigationCommands()` in `glsp-theia-integration` instead. */ -export const markerNavigatorContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { - bind(TYPES.IContextMenuProvider).to(MarkerNavigatorContextMenuItemProvider).inSingletonScope(); - bind(TYPES.KeyListener).to(MarkerNavigatorKeyListener).inSingletonScope(); +export const markerNavigatorContextMenuModule = new ContainerModule(bind => { + bindAsService(bind, TYPES.IContextMenuProvider, MarkerNavigatorContextMenuItemProvider); + bindAsService(bind, TYPES.KeyListener, MarkerNavigatorKeyListener); }); diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/di.config.ts index 64871c5..7056ec7 100644 --- a/packages/client/src/features/viewport/di.config.ts +++ b/packages/client/src/features/viewport/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { CenterCommand, @@ -30,17 +31,18 @@ import { import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { - configureCommand({ bind, isBound }, CenterCommand); - configureCommand({ bind, isBound }, FitToScreenCommand); - configureCommand({ bind, isBound }, GetViewportCommand); - configureCommand({ bind, isBound }, SetViewportCommand); - bind(TYPES.KeyListener).to(CenterKeyboardListener); - bind(TYPES.MouseListener).to(ZoomMouseListener); - bind(GLSPScrollMouseListener).toSelf().inSingletonScope(); - bind(TYPES.MouseListener).toService(GLSPScrollMouseListener); + const context = { bind, isBound }; + configureCommand(context, CenterCommand); + configureCommand(context, FitToScreenCommand); + configureCommand(context, GetViewportCommand); + configureCommand(context, SetViewportCommand); - configureActionHandler({ bind, isBound }, EnableToolsAction.KIND, GLSPScrollMouseListener); - configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); + bindAsService(context, TYPES.KeyListener, CenterKeyboardListener); + bindAsService(context, TYPES.MouseListener, ZoomMouseListener); + bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); + + configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); + configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); }); export default glspViewportModule; diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 09f1307..2f6b900 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DefaultTypes } from '@eclipse-glsp/protocol'; -import { ContainerModule, interfaces } from 'inversify'; +import { BindingContext, DefaultTypes } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; import { CircularNode, CircularNodeView, @@ -53,17 +53,12 @@ import { GEdgeView } from './glsp-edge-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; -interface ContainerContext { - bind: interfaces.Bind; - isBound: interfaces.IsBound; -} - const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); }); -export function configureDefaultModelElements(context: ContainerContext): void { +export function configureDefaultModelElements(context: Pick): void { // HTML elements configureModelElement(context, DefaultTypes.HTML, HtmlRoot, HtmlRootView); diff --git a/packages/protocol/README.md b/packages/protocol/README.md index 64db32c..07c3f14 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -1,6 +1,7 @@ # Eclipse GLSP - Protocol The generic client-server communication protocol for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) and a json-rpc based default implementation. +In addition, this package provides shared common code and utility libraries for GLSP components. This project is built with `yarn` and is available from npm via [@eclipse-glsp/protocol](https://www.npmjs.com/package/@eclipse-glsp/protocol). diff --git a/packages/protocol/package.json b/packages/protocol/package.json index f910276..9cc16bc 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -53,6 +53,9 @@ "devDependencies": { "@types/uuid": "3.4.5" }, + "peerDependencies": { + "inversify": "^5.1.1" + }, "publishConfig": { "access": "public" } diff --git a/packages/protocol/src/utils/disposable.spec.ts b/packages/protocol/src/disposable/disposable.spec.ts similarity index 100% rename from packages/protocol/src/utils/disposable.spec.ts rename to packages/protocol/src/disposable/disposable.spec.ts diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/disposable/disposable.ts similarity index 96% rename from packages/protocol/src/utils/disposable.ts rename to packages/protocol/src/disposable/disposable.ts index dc6e85f..077fcf3 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/disposable/disposable.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { remove } from './array-util'; -import { AnyObject, hasFunctionProp } from './type-util'; +import { remove } from '../utils/array-util'; +import { AnyObject, hasFunctionProp } from '../utils/type-util'; /** * Interface for objects that can or need to be disposed properly. diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index f8b3226..e5b7176 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -47,6 +47,7 @@ export * from 'sprotty-protocol/lib/utils/json'; export * from 'sprotty-protocol/lib/utils/model-utils'; // Default export of @eclipse-glsp/protocol export * from './action-protocol'; +export * from './disposable/disposable'; export * from './glsp-client'; export * from './jsonrpc/base-jsonrpc-glsp-client'; export * from './jsonrpc/glsp-jsonrpc-client'; @@ -54,6 +55,6 @@ export * from './jsonrpc/websocket-connection'; export * from './model/default-types'; export * from './model/model-schema'; export * from './utils/array-util'; -export * from './utils/disposable'; +export * from './utils/di-util'; export * from './utils/type-util'; export { SetBoundsAction, SetViewportAction }; diff --git a/packages/protocol/src/utils/di-util.ts b/packages/protocol/src/utils/di-util.ts new file mode 100644 index 0000000..44b8113 --- /dev/null +++ b/packages/protocol/src/utils/di-util.ts @@ -0,0 +1,166 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule, interfaces } from 'inversify'; +import { asArray, distinctAdd, MaybeArray, remove } from './array-util'; +import { hasFunctionProp, hasNumberProp } from './type-util'; + +/** + * A wrapper interface to get access to the binding related functions + * for an inversify container. + */ +export interface BindingContext { + bind: interfaces.Bind; + unbind: interfaces.Unbind; + isBound: interfaces.IsBound; + rebind: interfaces.Rebind; +} + +/** + * Initializes a container with the given {@link ContainerConfiguration}. The container configuration + * consists of the set of {@link ContainerModule}s that should be loaded in the container and/or + * In addition, for more fine-grained control {@link ModuleConfiguration}s can be passed as part fo the container configuration + * Module loading is distinct,this means each module will only get loaded once even if it is configured multiple times. + @param containerConfiguration + * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. + @returns The initialized container. + */ +export function initializeContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { + const modules: ContainerModule[] = []; + containerConfiguration.forEach(config => { + if (isContainerModule(config)) { + distinctAdd(modules, config); + } else { + if (config.remove) { + remove(modules, ...asArray(config.remove)); + } + if (config.add) { + distinctAdd(modules, ...asArray(config.add)); + } + } + }); + container.load(...modules); + return container; +} + +/** + * The container modules might originate form different inversify contexts (e.g. `inversify` vs. `@theia/core/shared/inversify`). + * If this is the case an instanceof check can return false negative. + * => use a simple typeguard instead. + */ +function isContainerModule(config: ContainerModule | ModuleConfiguration): config is ContainerModule { + return hasNumberProp(config, 'id') && hasFunctionProp(config, 'registry'); +} + +/** + * Union type for the set of {@link ContainerModule}s and addition {@link ModuleConfiguration}s + * used to configure a DI container. + */ +export type ContainerConfiguration = Array; + +/** + * Can be passed to create DI container utility functions to configure additional modules or + * remove (i.e. not load) default modules. + */ +export interface ModuleConfiguration { + /** Set of modules that should be loaded into the container. Loading */ + add?: MaybeArray; + /** Set of modules that should be loaded into the container */ + remove?: MaybeArray; +} + +/** + * Checks wether the given service identifier is already bound in the given context + * then either calls the `bind` or `rebind` function respectively. + * + * As this is just a convenience function + * ```ts + * bindOrRebind({bind,isBound,rebind}, MyService).to(SomeOtherService); + * ``` + * is equivalent to: + * ``` + * if (isBound(MyService)) { + * rebind(MyService).to(SomeOtherService); + * } else { + * bind(MyService).to(SomeOtherService); + * } + * ``` + * @param context The binding context + * @param serviceIdentifier The service identifier + * @returns The result of the `bind` or `rebind` function + */ +export function bindOrRebind( + context: Omit, + serviceIdentifier: interfaces.ServiceIdentifier +): interfaces.BindingToSyntax { + if (context.isBound(serviceIdentifier)) { + return context.rebind(serviceIdentifier); + } + return context.bind(serviceIdentifier); +} + +/** + * Only binds the given service identifier if it's not already bound in the given context. + * + * As this is just a convenience function + * ```ts + * lazyBind({bind,isBound}, MyService)?.to(SomeOtherService); + * ``` + * is equivalent to: + * ``` + * if (!isBound(MyService)) { + * bind(MyService).to(SomeOtherService); + * } + * ``` + * @param context The binding context + * @param serviceIdentifier The service identifier + * @returns The result of the `bind` function or `undefined` if the service was already bound + */ +export function lazyBind( + context: Pick, + serviceIdentifier: interfaces.ServiceIdentifier +): interfaces.BindingToSyntax | undefined { + if (context.isBound(serviceIdentifier)) { + return undefined; + } + return context.bind(serviceIdentifier); +} + +/** + * Binds the given service identifier to the given target service in the given context. + * In addition, the target service is bound to itself in singleton scope. This ensures + * that services can be rebound individually even if they are multi-injected. + * + * As this is just a convenience function + * ```ts + * bindAsService(bind,SomeOtherService,MyServiceImpl); + * ``` + * is equivalent to: + * ```ts + * bind(MyServiceImpl).toSelf.inSingletonScope(): + * bind(SomeOtherService).toService(MyServiceImpl); + * ``` + * @param serviceIdentifier + * @param toServiceIdentifier + */ +export function bindAsService( + context: Pick | interfaces.Bind, + serviceIdentifier: interfaces.ServiceIdentifier, + targetService: interfaces.ServiceIdentifier +): void { + const bind = typeof context === 'object' ? context.bind : context; + bind(targetService).toSelf().inSingletonScope(); + bind(serviceIdentifier).toService(targetService); +} From 2b621a76ae62921f1b7664559fad20bffb7c357f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 17 Mar 2023 08:17:32 -0700 Subject: [PATCH 187/566] GLSP-954 Ensure that bindAsAService also work with the DI container as context (#237) The `bindAsService` method failed when the DI container was used as context. To avoid this we have to make sure that the bind function receives the correct `this` arg. Use utility functions in standalone di config --- examples/workflow-standalone/src/di.config.ts | 10 +++++----- packages/protocol/src/utils/di-util.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 2aee8e8..27e1d3c 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -14,16 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; -import { ConsoleLogger, GLSPDiagramServer, LogLevel, TYPES } from '@eclipse-glsp/client'; +import { bindAsService, bindOrRebind, ConsoleLogger, GLSPDiagramServer, LogLevel, TYPES } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import '../css/diagram.css'; export default function createContainer(): Container { const container = createWorkflowDiagramContainer('sprotty'); - container.bind(GLSPDiagramServer).toSelf().inSingletonScope(); - container.bind(TYPES.ModelSource).toService(GLSPDiagramServer); - container.rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); - container.rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); + bindAsService(container, TYPES.ModelSource, GLSPDiagramServer); + bindOrRebind(container, TYPES.ModelSource).toService(GLSPDiagramServer); + bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); return container; } diff --git a/packages/protocol/src/utils/di-util.ts b/packages/protocol/src/utils/di-util.ts index 44b8113..097feb5 100644 --- a/packages/protocol/src/utils/di-util.ts +++ b/packages/protocol/src/utils/di-util.ts @@ -160,7 +160,7 @@ export function bindAsService( serviceIdentifier: interfaces.ServiceIdentifier, targetService: interfaces.ServiceIdentifier ): void { - const bind = typeof context === 'object' ? context.bind : context; + const bind = typeof context === 'object' ? context.bind.bind(context) : context; bind(targetService).toSelf().inSingletonScope(); bind(serviceIdentifier).toService(targetService); } From 7c7942761d9c89a146f0952b409fd776204f4bcf Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 21 Apr 2023 14:47:08 +0200 Subject: [PATCH 188/566] Add a reason for requesting and setting markers (#243) Also track feedback by reason to replace only those on `SetMarkersAction` that have the same reason. https://github.com/eclipse-glsp/glsp/issues/980 --- .../src/features/tool-palette/tool-palette.ts | 8 ++--- .../src/features/validation/validate.ts | 35 +++++++++---------- .../element-validation.spec.ts | 6 ++-- .../src/action-protocol/element-validation.ts | 23 +++++++++++- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index a35de54..3bb4177 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, PaletteItem, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; +import { Action, MarkersReason, PaletteItem, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, @@ -22,8 +22,8 @@ import { IActionHandler, ICommand, IToolManager, - SetUIExtensionVisibilityAction, SModelRoot, + SetUIExtensionVisibilityAction, TYPES } from 'sprotty'; import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; @@ -222,7 +222,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, validateActionButton.title = 'Validate model'; validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; - this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds)); + this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds, { reason: MarkersReason.BATCH })); }; return validateActionButton; } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index efaef7c..2915645 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,28 +33,25 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - private registeredAction: ApplyMarkersAction; - - // eslint-disable-next-line @typescript-eslint/no-empty-function - private constructor() {} + protected registeredFeedbackByReason: Map = new Map(); /** * Register the action that should be emitted for visualizing validation feedback. * @param action the action that should be emitted when the model is updated and that will visualize the model validation feedback. + * @param reason the reason for this validation feedback. */ - registerValidationFeedbackAction(action: ApplyMarkersAction): void { - // De-register old action responsible for applying markers and re-applying them when the model is updated - this.feedbackActionDispatcher.deregisterFeedback(this, []); - - // Clear existing markers - if (this.registeredAction !== undefined) { - const deleteMarkersAction = DeleteMarkersAction.create(this.registeredAction.markers); + registerValidationFeedbackAction(action: ApplyMarkersAction, reason = ''): void { + // De-register feedback and clear existing markers with the same reason + const previousFeedbackWithSameReason = this.registeredFeedbackByReason.get(reason); + if (previousFeedbackWithSameReason) { + this.feedbackActionDispatcher.deregisterFeedback(this, [previousFeedbackWithSameReason]); + const deleteMarkersAction = DeleteMarkersAction.create(previousFeedbackWithSameReason.markers); this.actionDispatcher().then(dispatcher => dispatcher.dispatch(deleteMarkersAction)); } // Register new action responsible for applying markers and re-applying them when the model is updated - this.feedbackActionDispatcher.registerFeedback(this, [action]); - this.registeredAction = action; + this.registeredFeedbackByReason.set(reason, action); + this.feedbackActionDispatcher.registerFeedback(this, [...this.registeredFeedbackByReason.values()]); } } @@ -80,7 +77,7 @@ export abstract class ExternalMarkerManager { } } - abstract setMarkers(markers: Marker[], sourceUri?: string): void; + abstract setMarkers(markers: Marker[], reason?: string, sourceUri?: string): void; } @injectable() @@ -97,14 +94,14 @@ export class SetMarkersActionHandler implements IActionHandler { handle(action: SetMarkersAction): void | Action | ICommand { const markers: Marker[] = action.markers; - this.setMarkers(markers); + this.setMarkers(markers, action.reason); } - async setMarkers(markers: Marker[]): Promise { + async setMarkers(markers: Marker[], reason: string | undefined): Promise { const uri = await this.editorContextService.getSourceUri(); - this.externalMarkerManager?.setMarkers(markers, uri); + this.externalMarkerManager?.setMarkers(markers, reason, uri); const applyMarkersAction = ApplyMarkersAction.create(markers); - this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); + this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction, reason); } } diff --git a/packages/protocol/src/action-protocol/element-validation.spec.ts b/packages/protocol/src/action-protocol/element-validation.spec.ts index 71de59a..6da9a3c 100644 --- a/packages/protocol/src/action-protocol/element-validation.spec.ts +++ b/packages/protocol/src/action-protocol/element-validation.spec.ts @@ -45,7 +45,8 @@ describe('Element validation actions', () => { const expected: RequestMarkersAction = { kind: 'requestMarkers', requestId: '', - elementsIDs: ['myIds'] + elementsIDs: ['myIds'], + reason: 'batch' }; const { elementsIDs } = expected; expect(RequestMarkersAction.create(elementsIDs)).to.deep.equals(expected); @@ -54,7 +55,8 @@ describe('Element validation actions', () => { const expected: RequestMarkersAction = { kind: 'requestMarkers', requestId: 'myRequest', - elementsIDs: ['myIds'] + elementsIDs: ['myIds'], + reason: 'batch' }; const { elementsIDs, requestId } = expected; expect(RequestMarkersAction.create(elementsIDs, { requestId })).to.deep.equals(expected); diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index 1a0087b..dcfb8de 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -47,6 +47,16 @@ export namespace MarkerKind { export const ERROR = 'error'; } +/** + * The default reasons for markers. + */ +export namespace MarkersReason { + /** Markers resulting from a batch validation */ + export const BATCH = 'batch'; + /** Markers resulting from a live validation */ + export const LIVE = 'live'; +} + /** * Action to retrieve markers for the specified model elements. Sent from the client to the server. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks @@ -59,6 +69,11 @@ export interface RequestMarkersAction extends RequestAction { * The elements for which markers are requested, may be just the root element. */ elementsIDs: string[]; + + /** + * The reason for this request, such as `batch` or `live` validation. `batch` by default. + */ + reason?: string; } export namespace RequestMarkersAction { @@ -68,11 +83,12 @@ export namespace RequestMarkersAction { return RequestAction.hasKind(object, KIND) && hasArrayProp(object, 'elementsIDs'); } - export function create(elementsIDs: string[], options: { requestId?: string } = {}): RequestMarkersAction { + export function create(elementsIDs: string[], options: { requestId?: string; reason?: string } = {}): RequestMarkersAction { return { kind: KIND, requestId: '', elementsIDs, + reason: MarkersReason.BATCH, ...options }; } @@ -92,6 +108,11 @@ export interface SetMarkersAction extends ResponseAction { * The list of markers to be added to the diagram. */ readonly markers: Marker[]; + + /** + * The reason for message, such as `batch` or `live` validation. + */ + reason?: string; } export namespace SetMarkersAction { From 5a44deb256a7bb34d7083cdabbebc8afa59268db Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 24 Apr 2023 15:32:13 +0200 Subject: [PATCH 189/566] Add missing `reason` to `SetMarkersAction` https://github.com/eclipse-glsp/glsp/issues/980 --- .../protocol/src/action-protocol/element-validation.spec.ts | 2 ++ packages/protocol/src/action-protocol/element-validation.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/protocol/src/action-protocol/element-validation.spec.ts b/packages/protocol/src/action-protocol/element-validation.spec.ts index 6da9a3c..78534c3 100644 --- a/packages/protocol/src/action-protocol/element-validation.spec.ts +++ b/packages/protocol/src/action-protocol/element-validation.spec.ts @@ -87,6 +87,7 @@ describe('Element validation actions', () => { const expected: SetMarkersAction = { kind: 'setMarkers', responseId: '', + reason: 'batch', markers: [{ description: 'desc', elementId: 'myId', kind: 'info', label: 'string' }] }; const { markers } = expected; @@ -96,6 +97,7 @@ describe('Element validation actions', () => { const expected: SetMarkersAction = { kind: 'setMarkers', responseId: 'myResponse', + reason: 'batch', markers: [{ description: 'desc', elementId: 'myId', kind: 'info', label: 'string' }] }; const { markers, responseId } = expected; diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index dcfb8de..b873eca 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -122,11 +122,12 @@ export namespace SetMarkersAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); } - export function create(markers: Marker[], options: { responseId?: string } = {}): SetMarkersAction { + export function create(markers: Marker[], options: { responseId?: string; reason?: string } = {}): SetMarkersAction { return { kind: KIND, responseId: '', markers, + reason: MarkersReason.BATCH, ...options }; } From c5641a8b4306a708f467869026bf646d2fd83ed4 Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Thu, 27 Apr 2023 09:17:35 +0200 Subject: [PATCH 190/566] Add SVGMetadata API (#239) Support adding metadata to DOM elements --- packages/client/src/base/container-modules.ts | 4 +- .../src/features/svg-metadata/di.config.ts | 26 ++++++++++ .../features/svg-metadata/metadata-placer.ts | 51 +++++++++++++++++++ packages/client/src/index.ts | 5 +- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/features/svg-metadata/di.config.ts create mode 100644 packages/client/src/features/svg-metadata/metadata-placer.ts diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 6a6b026..9ab3d15 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -29,6 +29,7 @@ import { routingModule, zorderModule } from 'sprotty'; +import svgMetadataModule from '../features/svg-metadata/di.config'; import glspBoundsModule from '../features/bounds/di.config'; import glspCommandPaletteModule from '../features/command-palette/di.config'; import glspContextMenuModule from '../features/context-menu/di.config'; @@ -83,7 +84,8 @@ export const DEFAULT_MODULES = [ toolsModule, enableDefaultToolsOnFocusLossModule, validationModule, - zorderModule + zorderModule, + svgMetadataModule ] as const; /** diff --git a/packages/client/src/features/svg-metadata/di.config.ts b/packages/client/src/features/svg-metadata/di.config.ts new file mode 100644 index 0000000..b9204b6 --- /dev/null +++ b/packages/client/src/features/svg-metadata/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { TYPES } from '../../base/types'; +import { MetadataPlacer } from './metadata-placer'; + +const svgMetadataModule = new ContainerModule(bind => { + bind(MetadataPlacer).toSelf().inSingletonScope(); + bind(TYPES.IVNodePostprocessor).toService(MetadataPlacer); +}); + +export default svgMetadataModule; diff --git a/packages/client/src/features/svg-metadata/metadata-placer.ts b/packages/client/src/features/svg-metadata/metadata-placer.ts new file mode 100644 index 0000000..1ffd8e9 --- /dev/null +++ b/packages/client/src/features/svg-metadata/metadata-placer.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject } from 'inversify'; +import { VNode } from 'snabbdom'; +import { IVNodePostprocessor, SChildElement, SEdge, setAttr, SModelElement, SModelRoot } from 'sprotty'; +import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; +import { TYPES } from '../../base/types'; + +@injectable() +export class MetadataPlacer implements IVNodePostprocessor { + @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; + + decorate(vnode: VNode, element: SModelElement): VNode { + if (element instanceof SModelRoot) { + setAttr(vnode, 'data-svg-metadata-api', true); + } + + setAttr(vnode, 'data-svg-metadata-type', element.type); + + if (element instanceof SChildElement) { + setAttr(vnode, 'data-svg-metadata-parent-id', this.domHelper.createUniqueDOMElementId(element.parent)); + } + if (element instanceof SEdge) { + if (element.source !== undefined) { + setAttr(vnode, 'data-svg-metadata-edge-source-id', this.domHelper.createUniqueDOMElementId(element.source)); + } + if (element.target !== undefined) { + setAttr(vnode, 'data-svg-metadata-edge-target-id', this.domHelper.createUniqueDOMElementId(element.target)); + } + } + return vnode; + } + + postUpdate(): void { + // empty + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index ba786bd..a9cbe10 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -18,6 +18,7 @@ import glspCommandPaletteModule from './features/command-palette/di.config'; import glspContextMenuModule from './features/context-menu/di.config'; import { copyPasteContextMenuModule, glspServerCopyPasteModule } from './features/copy-paste/di.config'; import glspDecorationModule from './features/decoration/di.config'; +import svgMetadataModule from './features/svg-metadata/di.config'; import glspEditLabelModule from './features/edit-label/di.config'; import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; @@ -73,6 +74,7 @@ export * from './features/context-menu/server-context-menu-provider'; export * from './features/copy-paste/copy-paste-context-menu'; export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; +export * from './features/svg-metadata/metadata-placer'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; export * from './features/export/glsp-svg-exporter'; @@ -151,5 +153,6 @@ export { glspDecorationModule, sourceModelWatcherModule, markerNavigatorContextMenuModule, - glspViewportModule + glspViewportModule, + svgMetadataModule }; From c57a07da8989c29dfed0d60e95fe6a188a63cd9e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 3 May 2023 02:57:42 -0700 Subject: [PATCH 191/566] GLSP-975 Move glsp-server types into protocol packages (#245) Ensures that the `@eclipse-glsp/protocol` package also provides the glsp server interfaces. This allows type reusage in usecases where the GLSP server is not connected via JSON-RPC and is instead accessed directly. Contributed on behalf of STMicroelectronics Part of https://github.com/eclipse-glsp/glsp/issues/975 --- .../glsp-client.ts | 74 +-------- .../src/client-server-protocol/glsp-server.ts | 149 ++++++++++++++++++ .../src/client-server-protocol/types.ts | 85 ++++++++++ packages/protocol/src/index.ts | 5 +- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 31 +++- .../src/jsonrpc/glsp-jsonrpc-client.ts | 4 +- .../src/jsonrpc/glsp-jsonrpc-server.ts | 38 +++++ 7 files changed, 307 insertions(+), 79 deletions(-) rename packages/protocol/src/{ => client-server-protocol}/glsp-client.ts (74%) create mode 100644 packages/protocol/src/client-server-protocol/glsp-server.ts create mode 100644 packages/protocol/src/client-server-protocol/types.ts create mode 100644 packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts diff --git a/packages/protocol/src/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts similarity index 74% rename from packages/protocol/src/glsp-client.ts rename to packages/protocol/src/client-server-protocol/glsp-client.ts index 95c2cab..8de8111 100644 --- a/packages/protocol/src/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,76 +15,8 @@ ********************************************************************************/ import * as uuid from 'uuid'; -import { ActionMessage } from './action-protocol'; -import { Args } from './action-protocol/types'; - -/** - * A key-value pair structure to map a diagramType to its server-handled action kinds. - */ -export interface ServerActions { - [key: string]: string[]; -} - -export interface InitializeParameters { - /** - * Unique identifier for the current client application. - */ - applicationId: string; - - /** - * GLSP protocol version that this client is implementing. - */ - protocolVersion: string; - - /** - * Additional custom arguments e.g. application specific parameters. - */ - args?: Args; -} - -export interface InitializeResult { - /** - * GLSP protocol version that the server is implementing. - */ - protocolVersion: string; - - /** - * The actions (grouped by diagramType) that the server can handle. - */ - serverActions: ServerActions; -} - -/** - * Known server actions i.e. action kinds that the server can handle for a specific diagram type. - */ -export interface InitializeClientSessionParameters { - /** - * Unique identifier for the new client session. - */ - clientSessionId: string; - - /** - * Unique identifier of the diagram type for which the session should be configured. - */ - diagramType: string; - - /** - * Additional custom arguments. - */ - args?: Args; -} - -export interface DisposeClientSessionParameters { - /** - * Unique identifier of the client session that should be disposed. - */ - clientSessionId: string; - - /** - * Additional custom arguments. - */ - args?: Args; -} +import { ActionMessage } from '../action-protocol'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export class ApplicationIdProvider { private static _applicationId?: string; diff --git a/packages/protocol/src/client-server-protocol/glsp-server.ts b/packages/protocol/src/client-server-protocol/glsp-server.ts new file mode 100644 index 0000000..5708548 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/glsp-server.ts @@ -0,0 +1,149 @@ +/******************************************************************************** + * Copyright (c) 2022-2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ActionMessage } from '../action-protocol'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; + +/** + * Interface for implementations of a server component using json-rpc for client-server communication. + * Based on the specification of the Graphical Language Server Protocol: + * https://github.com/eclipse-glsp/glsp/blob/master/PROTOCOL.md + */ +export interface GLSPServer { + /** + * + * The `initialize` request has to be the first request from the client to the server. Until the server has responded + * with an {@link InitializeResult} no other request or notification can be handled and is expected to throw an + * error. A client is uniquely identified by an `applicationId` and has to specify on which `protocolVersion` it is + * based on. In addition, custom arguments can be provided in the `args` map to allow for custom initialization + * behavior on the server. + * + * After successfully initialization all {@link GLSPServerListener}s are notified via the + * {@link GLSPServerListener.serverInitialized} method. + * + * @param params the {@link InitializeParameters}. + * @returns A promise of the {@link InitializeResult} . + * + * @throws {@link Error} Subsequent initialize requests return the {@link InitializeResult} of the initial request + * if the given application id and protocol version are matching, otherwise the promise rejects with an error. + * + */ + initialize(params: InitializeParameters): Promise; + + /** + * The `initializeClientSession` request is sent to the server whenever a new graphical representation (diagram) is + * created. Each individual diagram on the client side counts as one session and has to provide a unique + * `clientSessionId` and its `diagramType`. In addition, custom arguments can be provided in the `args` map to allow + * for custom initialization behavior on the server. Subsequent `initializeClientSession` requests for the same + * client id and diagram type are expected to resolve successfully but don't have an actual effect because the + * corresponding client session is already initialized. + * + * @param params the {@link InitializeClientSessionParameters}. + * @returns A promise that completes when the initialization was successful. + */ + initializeClientSession(params: InitializeClientSessionParameters): Promise; + + /** + * The 'DisposeClientSession' request is sent to the server when a graphical representation (diagram) is no longer + * needed, e.g. the tab containing the diagram widget has been closed. The session is identified by its unique + * `clientSessionId`. In addition, custom arguments can be provided in the `args` map to allow for custom dispose + * behavior on the server. + * + * @param params the {@link DisposeClientSessionParameters}. + * @returns A `void` promise that completes if the disposal was successful. + * + */ + disposeClientSession(params: DisposeClientSessionParameters): Promise; + + /** + * A `process` notification is sent from the client to server when the server should handle i.e. process a specific + * {@link ActionMessage}. Any communication that is performed between initialization and shutdown is handled by + * sending action messages, either from the client to the server or from the server to the client. This is the core + * part of the Graphical Language Server Protocol. + * + * @param message The {@link ActionMessage} that should be processed. + */ + process(message: ActionMessage): void; + + /** + * The `shutdown` notification is sent from the client to the server if the client disconnects from the server (e.g. + * the client application has been closed). + * This gives the server a chance to clean up and dispose any resources dedicated to the client and its sessions. + * All {@link GLSPServerListener}s are notified via the {@link GLSPServerListener.serverShutDown} method. + * Afterwards the server instance is considered to be disposed and can no longer be used for handling requests. + * + */ + shutdown(): void; + + /** + * Register a new {@link GLSPServerListener}. + * + * @param listener The listener that should be registered. + * @returns `true` if the listener was registered successfully, `false` otherwise (e.g. listener is already + * registered). + */ + addListener(listener: GLSPServerListener): boolean; + + /** + * Unregister a {@link GLSPServerListener}. + * + * @param listener The listener that should be removed + * @returns 'true' if the listener was unregistered successfully, `false` otherwise (e.g. listener is was not + * registered in the first place). + */ + removeListener(listener: GLSPServerListener): boolean; +} + +export const GLSPServer = Symbol('GLSPServer'); + +/** + * A listener to track the connection status of {@link GLSPClient}s (i.e. client applications). + * Gets notified when a new GLSP client connects or disconnects. + */ +export interface GLSPServerListener { + /** + * Triggered after a GLSPServer has been initialized via the {@link GLSPServer.initialize()} + * method. + * + * @param server The GLSPServer which has been initialized. + */ + serverInitialized?(server: GLSPServer): void; + + /** + * Triggered after the {@link GLSPServer.shutdown()} method has been invoked. + * + * @param glspServer The glspServer which has been shut down. + */ + serverShutDown?(server: GLSPServer): void; +} +export const GLSPServerListener = Symbol('GLSPServerListener'); + +/** + * Communication proxy interface used by the GLSP servers to send action messages to clients. + * The default `JsonrpcClientProxy` used an underlying jsonrpc message connection for sending the action messages. + */ +export interface GLSPClientProxy { + /** + * A `process` notification is sent from the server to server to the client when the client should handle i.e. + * process a specific {@link ActionMessage}. Any communication that is performed between initialization and shutdown + * is handled by sending action messages, either from the client to the server or from the server to the client. This + * is the core part of the Graphical Language Server Protocol. + * + * @param message The {@link ActionMessage} that should be processed. + */ + process(message: ActionMessage): void; +} +export const GLSPClientProxy = Symbol('GLSPClientProxy'); diff --git a/packages/protocol/src/client-server-protocol/types.ts b/packages/protocol/src/client-server-protocol/types.ts new file mode 100644 index 0000000..44287d7 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/types.ts @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Args } from '../action-protocol'; + +/** + * A key-value pair structure to map a diagramType to its server-handled action kinds. + */ +export interface ServerActions { + [key: string]: string[]; +} + +export interface InitializeParameters { + /** + * Unique identifier for the current client application. + */ + applicationId: string; + + /** + * GLSP protocol version that this client is implementing. + */ + protocolVersion: string; + + /** + * Additional custom arguments e.g. application specific parameters. + */ + args?: Args; +} + +export interface InitializeResult { + /** + * GLSP protocol version that the server is implementing. + */ + protocolVersion: string; + + /** + * The actions (grouped by diagramType) that the server can handle. + */ + serverActions: ServerActions; +} + +/** + * Known server actions i.e. action kinds that the server can handle for a specific diagram type. + */ +export interface InitializeClientSessionParameters { + /** + * Unique identifier for the new client session. + */ + clientSessionId: string; + + /** + * Unique identifier of the diagram type for which the session should be configured. + */ + diagramType: string; + + /** + * Additional custom arguments. + */ + args?: Args; +} + +export interface DisposeClientSessionParameters { + /** + * Unique identifier of the client session that should be disposed. + */ + clientSessionId: string; + + /** + * Additional custom arguments. + */ + args?: Args; +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e5b7176..246903b 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -47,10 +47,13 @@ export * from 'sprotty-protocol/lib/utils/json'; export * from 'sprotty-protocol/lib/utils/model-utils'; // Default export of @eclipse-glsp/protocol export * from './action-protocol'; +export * from './client-server-protocol/glsp-client'; +export * from './client-server-protocol/glsp-server'; +export * from './client-server-protocol/types'; export * from './disposable/disposable'; -export * from './glsp-client'; export * from './jsonrpc/base-jsonrpc-glsp-client'; export * from './jsonrpc/glsp-jsonrpc-client'; +export * from './jsonrpc/glsp-jsonrpc-server'; export * from './jsonrpc/websocket-connection'; export * from './model/default-types'; export * from './model/model-schema'; diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index dbe38ee..c0f6657 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,17 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; import { Message, MessageConnection } from 'vscode-jsonrpc'; +import { GLSPClientProxy } from '..'; import { ActionMessage } from '../action-protocol'; +import { ActionMessageHandler, ClientState, GLSPClient } from '../client-server-protocol/glsp-client'; import { - ActionMessageHandler, - ClientState, DisposeClientSessionParameters, - GLSPClient, InitializeClientSessionParameters, InitializeParameters, InitializeResult -} from '../glsp-client'; +} from '../client-server-protocol/types'; import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { @@ -162,3 +162,24 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.state; } } + +/** + * Default {@link GLSPClientProxy} implementation for jsonrpc-based client-server communication with typescript based servers. + */ +@injectable() +export class JsonrpcClientProxy implements GLSPClientProxy { + protected clientConnection?: MessageConnection; + protected enableLogging: boolean; + + initialize(clientConnection: MessageConnection, enableLogging = true): void { + this.clientConnection = clientConnection; + this.enableLogging = enableLogging; + } + + process(message: ActionMessage): void { + if (this.enableLogging) { + console.log(`Send action '${message.action.kind}' to client '${message.clientId}'`); + } + this.clientConnection?.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); + } +} diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts index 0d07f2c..3785181 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -15,13 +15,13 @@ ********************************************************************************/ import { MessageConnection, NotificationType, NotificationType0, RequestType } from 'vscode-jsonrpc'; import { ActionMessage } from '../action-protocol/base-protocol'; +import { GLSPClient } from '../client-server-protocol/glsp-client'; import { DisposeClientSessionParameters, - GLSPClient, InitializeClientSessionParameters, InitializeParameters, InitializeResult -} from '../glsp-client'; +} from '../client-server-protocol/types'; import { MaybePromise } from '../utils/type-util'; export type ConnectionProvider = MessageConnection | (() => MaybePromise); diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts new file mode 100644 index 0000000..9ebf56c --- /dev/null +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { MessageConnection } from 'vscode-jsonrpc'; +import { GLSPServer } from '../client-server-protocol/glsp-server'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '../client-server-protocol/types'; +import { JsonrpcGLSPClient } from './glsp-jsonrpc-client'; + +/** + * Configure the given client connection to forward the requests and notifications to the given {@link GLSPServer} instance. + * @param clientConnection JSON-RPC client connection. + * @param glspServer The GLSP Server which should react to requests & notifications. + */ +export function configureClientConnection(clientConnection: MessageConnection, glspServer: GLSPServer): void { + clientConnection.onRequest(JsonrpcGLSPClient.InitializeRequest.method, (params: InitializeParameters) => glspServer.initialize(params)); + clientConnection.onRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, (params: InitializeClientSessionParameters) => + glspServer.initializeClientSession(params) + ); + clientConnection.onRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, (params: DisposeClientSessionParameters) => + glspServer.disposeClientSession(params) + ); + clientConnection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, message => glspServer.process(message)); + + clientConnection.listen(); +} From 0a8b3dda001076ecde8e181551a311566a6e35a8 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Wed, 3 May 2023 13:48:10 +0200 Subject: [PATCH 192/566] feat: adjust server startup to use autoassigned ports (#42) This sets the default port to 0, which will cause the server to automatically assign a port. The port is then printed to the console in the startup message and can be parsed by clients. Signed-off-by: Olaf Lessenich --- examples/workflow-server/package.json | 2 +- packages/server/src/node/launch/socket-cli-parser.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index b541b13..52919bf 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -49,7 +49,7 @@ "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "prepare": "yarn clean && yarn build", - "start": "node --enable-source-maps bundle/wf-glsp-server-node.js", + "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" }, diff --git a/packages/server/src/node/launch/socket-cli-parser.ts b/packages/server/src/node/launch/socket-cli-parser.ts index e6456db..561b261 100644 --- a/packages/server/src/node/launch/socket-cli-parser.ts +++ b/packages/server/src/node/launch/socket-cli-parser.ts @@ -23,7 +23,7 @@ export interface SocketLaunchOptions extends LaunchOptions { export const defaultSocketLaunchOptions: Required = { ...defaultLaunchOptions, - port: 5007, + port: 0, host: 'localhost' }; From d770d746f32f1eafbe098e2bfe5b3b37fae3cf3a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 3 May 2023 08:05:56 -0700 Subject: [PATCH 193/566] GLSP-975 Ensure generic GLSP Server implementation (#44) - Introduce a common subclass for jsonrpc based ServerLaunchers - Move out jsonrpc implementation details from `GLSPServer` implementation into the `JsonRpcGLSPServerLauncher`. This ensures that the Server implementation itself is independent from the chosen communication channel and can reused for other use cases like direct invocation in a node backend or orthogonal communication channels like REST or gRPC. - Update to latest @eclipse-glsp/protocol version and consume shared server interface types from there - Ensure that the `@eclipse-glsp-examples/workflow-server` package is properly indexed - Add entry points for browser and node - Move rpc-server initialization out of index files into app files. Also: Update example applications to ensure that promise rejections/errors are properly catched. Part of https://github.com/eclipse-glsp/glsp/issues/975 Requires https://github.com/eclipse-glsp/glsp-client/pull/245 Contributed on behalf of STMicroelectronics --- examples/workflow-server/browser.d.ts | 16 ++ examples/workflow-server/browser.js | 18 +++ examples/workflow-server/node.d.ts | 16 ++ examples/workflow-server/node.js | 18 +++ examples/workflow-server/src/browser/app.ts | 33 ++-- examples/workflow-server/src/browser/index.ts | 6 +- examples/workflow-server/src/common/index.ts | 50 ++++++ examples/workflow-server/src/node/app.ts | 52 +++--- examples/workflow-server/src/node/index.ts | 6 +- examples/workflow-server/webpack.config.js | 2 +- packages/graph/src/gmodel-util.ts | 4 +- packages/layout-elk/src/di.config.spec.ts | 3 +- packages/layout-elk/src/di.config.ts | 7 +- packages/server/src/browser/di/app-module.ts | 36 +---- packages/server/src/browser/index.ts | 1 - .../browser/launch/worker-server-launcher.ts | 20 +-- .../common/actions/client-action-handler.ts | 3 +- .../src/common/command/command-stack.ts | 2 +- .../src/common/di/client-session-module.ts | 2 +- packages/server/src/common/di/glsp-module.ts | 4 +- .../server/src/common/di/multi-binding.ts | 9 +- .../server/src/common/di/server-module.ts | 27 +--- packages/server/src/common/index.ts | 3 +- .../src/common/launch/glsp-server-launcher.ts | 4 +- .../common/launch/jsonrpc-server-launcher.ts | 95 +++++++++++ .../src/common/protocol/glsp-client-proxy.ts | 65 -------- .../common/protocol/glsp-server-listener.ts | 39 ----- .../src/common/protocol/glsp-server.spec.ts | 11 +- .../server/src/common/protocol/glsp-server.ts | 149 ++---------------- .../session/client-session-factory.spec.ts | 2 +- .../common/session/client-session-factory.ts | 3 +- .../common/session/client-session-manager.ts | 4 +- packages/server/src/common/test/mock-util.ts | 10 +- .../di => common/utils}/console-logger.ts | 22 ++- packages/server/src/common/utils/logger.ts | 17 +- packages/server/src/node/di/app-module.ts | 17 +- packages/server/src/node/launch/cli-parser.ts | 5 +- .../launch/socket-server-launcher.spec.ts | 6 + .../src/node/launch/socket-server-launcher.ts | 38 +---- .../node/launch/websocket-server-launcher.ts | 36 +---- 40 files changed, 382 insertions(+), 479 deletions(-) create mode 100644 examples/workflow-server/browser.d.ts create mode 100644 examples/workflow-server/browser.js create mode 100644 examples/workflow-server/node.d.ts create mode 100644 examples/workflow-server/node.js create mode 100644 examples/workflow-server/src/common/index.ts create mode 100644 packages/server/src/common/launch/jsonrpc-server-launcher.ts delete mode 100644 packages/server/src/common/protocol/glsp-client-proxy.ts delete mode 100644 packages/server/src/common/protocol/glsp-server-listener.ts rename packages/server/src/{browser/di => common/utils}/console-logger.ts (72%) diff --git a/examples/workflow-server/browser.d.ts b/examples/workflow-server/browser.d.ts new file mode 100644 index 0000000..02f39a7 --- /dev/null +++ b/examples/workflow-server/browser.d.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './lib/browser/index'; diff --git a/examples/workflow-server/browser.js b/examples/workflow-server/browser.js new file mode 100644 index 0000000..5f3c029 --- /dev/null +++ b/examples/workflow-server/browser.js @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +'use strict'; + +module.exports = require('./lib/browser/index'); diff --git a/examples/workflow-server/node.d.ts b/examples/workflow-server/node.d.ts new file mode 100644 index 0000000..a37c4b4 --- /dev/null +++ b/examples/workflow-server/node.d.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './lib/node/index'; diff --git a/examples/workflow-server/node.js b/examples/workflow-server/node.js new file mode 100644 index 0000000..a667cdd --- /dev/null +++ b/examples/workflow-server/node.js @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +'use strict'; + +module.exports = require('./lib/node/index'); diff --git a/examples/workflow-server/src/browser/app.ts b/examples/workflow-server/src/browser/app.ts index e47af6c..a4a6e63 100644 --- a/examples/workflow-server/src/browser/app.ts +++ b/examples/workflow-server/src/browser/app.ts @@ -13,33 +13,30 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import 'reflect-metadata'; + import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk'; -import { createAppModule, Logger, LoggerFactory, LogLevel, WorkerServerLauncher } from '@eclipse-glsp/server/browser'; +import { createAppModule, LogLevel, WorkerServerLauncher } from '@eclipse-glsp/server/browser'; import { Container } from 'inversify'; import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator'; import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module'; import { WorkflowMockModelStorage } from './mock-model-storage'; export async function launch(argv?: string[]): Promise { - let logger: Logger | undefined; - - try { - const appContainer = new Container(); - appContainer.load(createAppModule({ logLevel: LogLevel.info })); + const appContainer = new Container(); + appContainer.load(createAppModule({ logLevel: LogLevel.info })); - logger = appContainer.get(LoggerFactory)('WorkflowServerApp'); - const launcher = appContainer.resolve(WorkerServerLauncher); - const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); + const launcher = appContainer.resolve(WorkerServerLauncher); + const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); - const serverModule = new WorkflowServerModule().configureDiagramModule( - new WorkflowDiagramModule(() => WorkflowMockModelStorage), - elkLayoutModule - ); + const serverModule = new WorkflowServerModule().configureDiagramModule( + new WorkflowDiagramModule(() => WorkflowMockModelStorage), + elkLayoutModule + ); - launcher.configure(serverModule); + launcher.configure(serverModule); - await launcher.start({}); - } catch (error) { - (logger ?? console).error('Error in workflow server launcher:', error); - } + await launcher.start({}); } + +launch().catch(error => console.error('Error in workflow server launcher:', error)); diff --git a/examples/workflow-server/src/browser/index.ts b/examples/workflow-server/src/browser/index.ts index 2171f81..764900b 100644 --- a/examples/workflow-server/src/browser/index.ts +++ b/examples/workflow-server/src/browser/index.ts @@ -13,7 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import 'reflect-metadata'; -import { launch } from './app'; - -launch(); +export * from '../common/index'; +export * from './mock-model-storage'; diff --git a/examples/workflow-server/src/common/index.ts b/examples/workflow-server/src/common/index.ts new file mode 100644 index 0000000..646634d --- /dev/null +++ b/examples/workflow-server/src/common/index.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2023 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from './graph-extension'; +export * from './handler/create-activity-node-handler'; +export * from './handler/create-automated-task-handler'; +export * from './handler/create-category-handler'; +export * from './handler/create-decision-node-handler'; +export * from './handler/create-edge-handler'; +export * from './handler/create-fork-node-handler'; +export * from './handler/create-fork-or-join-node-handler'; +export * from './handler/create-join-node-handler'; +export * from './handler/create-manual-task-handler'; +export * from './handler/create-merge-node-handler'; +export * from './handler/create-task-handler'; +export * from './handler/create-weighted-edge-handler'; +export * from './handler/create-workflow-node-operation-handler'; +export * from './handler/grid-snapper'; +export * from './index'; +export * from './labeledit/workflow-label-edit-validator'; +export * from './layout/workflow-layout-configurator'; +export * from './marker/workflow-model-validator'; +export * from './model/workflow-navigation-target-resolver'; +export * from './provider/abstract-next-or-previous-navigation-target-provider'; +export * from './provider/next-node-navigation-target-provider'; +export * from './provider/node-documentation-navigation-target-provider'; +export * from './provider/previous-node-navigation-target-provider'; +export * from './provider/workflow-command-palette-action-provider'; +export * from './provider/workflow-context-menu-item-provider'; +export * from './taskedit/edit-task-operation-handler'; +export * from './taskedit/task-edit-context-provider'; +export * from './taskedit/task-edit-validator'; +export * from './util/model-types'; +export * from './workflow-diagram-configuration'; +export * from './workflow-diagram-module'; +export * from './workflow-glsp-server'; +export * from './workflow-popup-factory'; diff --git a/examples/workflow-server/src/node/app.ts b/examples/workflow-server/src/node/app.ts index 546cada..de1c26f 100644 --- a/examples/workflow-server/src/node/app.ts +++ b/examples/workflow-server/src/node/app.ts @@ -13,45 +13,33 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import 'reflect-metadata'; + import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk'; -import { - createAppModule, - GModelStorage, - Logger, - LoggerFactory, - SocketServerLauncher, - WebSocketServerLauncher -} from '@eclipse-glsp/server/node'; +import { createAppModule, GModelStorage, SocketServerLauncher, WebSocketServerLauncher } from '@eclipse-glsp/server/node'; import { Container } from 'inversify'; + import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator'; import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module'; import { createWorkflowCliParser } from './workflow-cli-parser'; -export async function launch(argv?: string[]): Promise { - let logger: Logger | undefined; - try { - const options = createWorkflowCliParser().parse(argv); - const appContainer = new Container(); - appContainer.load(createAppModule(options)); +async function launch(argv?: string[]): Promise { + const options = createWorkflowCliParser().parse(argv); + const appContainer = new Container(); + appContainer.load(createAppModule(options)); - logger = appContainer.get(LoggerFactory)('WorkflowServerApp'); + const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); + const serverModule = new WorkflowServerModule().configureDiagramModule(new WorkflowDiagramModule(() => GModelStorage), elkLayoutModule); - const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); - const serverModule = new WorkflowServerModule().configureDiagramModule( - new WorkflowDiagramModule(() => GModelStorage), - elkLayoutModule - ); - - if (options.webSocket) { - const launcher = appContainer.resolve(WebSocketServerLauncher); - launcher.configure(serverModule); - launcher.start({ port: options.port, host: options.host, path: 'workflow' }); - } else { - const launcher = appContainer.resolve(SocketServerLauncher); - launcher.configure(serverModule); - launcher.start({ port: options.port, host: options.host }); - } - } catch (error) { - (logger ?? console).error('Error in workflow server launcher:', error); + if (options.webSocket) { + const launcher = appContainer.resolve(WebSocketServerLauncher); + launcher.configure(serverModule); + launcher.start({ port: options.port, host: options.host, path: 'workflow' }); + } else { + const launcher = appContainer.resolve(SocketServerLauncher); + launcher.configure(serverModule); + launcher.start({ port: options.port, host: options.host }); } } + +launch(process.argv).catch(error => console.error('Error in workflow server launcher:', error)); diff --git a/examples/workflow-server/src/node/index.ts b/examples/workflow-server/src/node/index.ts index 282a9f3..051f52a 100644 --- a/examples/workflow-server/src/node/index.ts +++ b/examples/workflow-server/src/node/index.ts @@ -13,7 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import 'reflect-metadata'; -import { launch } from './app'; - -launch(process.argv); +export * from '../common/index'; +export * from './workflow-cli-parser'; diff --git a/examples/workflow-server/webpack.config.js b/examples/workflow-server/webpack.config.js index 00be390..b2d1b67 100644 --- a/examples/workflow-server/webpack.config.js +++ b/examples/workflow-server/webpack.config.js @@ -20,7 +20,7 @@ const appRoot = path.resolve(__dirname, 'bundle'); module.exports = env => { const target = env.target ?? 'node'; - const pathToIndex = target !== 'node' ? 'browser/index' : 'node/index'; + const pathToIndex = target !== 'node' ? 'browser/app' : 'node/app'; const filename = `wf-glsp-server-${target}.js`; return { entry: [path.resolve(buildRoot, pathToIndex)], diff --git a/packages/graph/src/gmodel-util.ts b/packages/graph/src/gmodel-util.ts index 6b8fd6c..4a7cd02 100644 --- a/packages/graph/src/gmodel-util.ts +++ b/packages/graph/src/gmodel-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, GModelElementConstructor } from '.'; +import { GModelElement, GModelElementConstructor } from './gmodel-element'; export type Predicate = (element: T) => boolean; diff --git a/packages/layout-elk/src/di.config.spec.ts b/packages/layout-elk/src/di.config.spec.ts index 40dabd3..af6a1be 100644 --- a/packages/layout-elk/src/di.config.spec.ts +++ b/packages/layout-elk/src/di.config.spec.ts @@ -18,10 +18,9 @@ import { StubDiagramConfiguration } from '@eclipse-glsp/server/lib/common/test/m import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { AbstractLayoutConfigurator } from '.'; import { configureELKLayoutModule } from './di.config'; import { DefaultElementFilter, ElementFilter } from './element-filter'; -import { FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator'; +import { AbstractLayoutConfigurator, FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator'; @injectable() class CustomLayoutConfigurator extends AbstractLayoutConfigurator {} diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 197078e..d5cd6f7 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,10 +17,9 @@ import { LayoutEngine, ModelState } from '@eclipse-glsp/server'; import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled'; import { ContainerModule } from 'inversify'; import { ElkFactory } from 'sprotty-elk'; -import { ElementFilter, LayoutConfigurator } from '.'; -import { DefaultElementFilter } from './element-filter'; +import { DefaultElementFilter, ElementFilter } from './element-filter'; import { GlspElkLayoutEngine } from './glsp-elk-layout-engine'; -import { FallbackLayoutConfigurator } from './layout-configurator'; +import { FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator'; type Constructor = new (...args: any[]) => T; diff --git a/packages/server/src/browser/di/app-module.ts b/packages/server/src/browser/di/app-module.ts index e37a1f6..004e77b 100644 --- a/packages/server/src/browser/di/app-module.ts +++ b/packages/server/src/browser/di/app-module.ts @@ -14,42 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule, interfaces } from 'inversify'; -import { InjectionContainer, Logger, LoggerFactory, LogLevel, ModuleContext, NullLogger } from '../../common/index'; -import { ConsoleLogger } from './console-logger'; +import { ContainerModule } from 'inversify'; +import { InjectionContainer, LogLevel, LoggerConfigOptions, configureConsoleLogger } from '../../common/'; -export interface LaunchOptions { - consoleLog?: boolean; - logLevel?: LogLevel; -} -export function createAppModule(options: LaunchOptions = {}): ContainerModule { - const resolvedOptions: LaunchOptions = { consoleLog: true, logLevel: LogLevel.info, ...options }; +export function createAppModule(options: LoggerConfigOptions = {}): ContainerModule { + const resolvedOptions: LoggerConfigOptions = { consoleLog: true, logLevel: LogLevel.info, ...options }; return new ContainerModule((bind, unbind, isBound, rebind) => { bind(InjectionContainer).toDynamicValue(dynamicContext => dynamicContext.container); const context = { bind, unbind, isBound, rebind }; configureConsoleLogger(context, resolvedOptions); }); } - -export function configureConsoleLogger(context: ModuleContext, options: T): void { - if (!options.consoleLog) { - context.bind(Logger).to(NullLogger).inSingletonScope(); - } else { - context.bind(Logger).toDynamicValue(ctx => new ConsoleLogger(options.logLevel, getRequestParentName(ctx))); - } - context.bind(LoggerFactory).toFactory(dynamicContext => (caller: string) => { - const logger = dynamicContext.container.get(Logger); - logger.caller = caller; - return logger; - }); -} - -function getRequestParentName(context: interfaces.Context): string | undefined { - if (context.currentRequest.parentRequest) { - const bindings = context.currentRequest.parentRequest.bindings; - if (bindings.length > 0) { - return bindings[0].implementationType?.name; - } - } - return undefined; -} diff --git a/packages/server/src/browser/index.ts b/packages/server/src/browser/index.ts index a0ecbd4..066dec9 100644 --- a/packages/server/src/browser/index.ts +++ b/packages/server/src/browser/index.ts @@ -15,5 +15,4 @@ ********************************************************************************/ export * from '../common/index'; export * from './di/app-module'; -export * from './di/console-logger'; export * from './launch/worker-server-launcher'; diff --git a/packages/server/src/browser/launch/worker-server-launcher.ts b/packages/server/src/browser/launch/worker-server-launcher.ts index 96d4339..ede3e6a 100644 --- a/packages/server/src/browser/launch/worker-server-launcher.ts +++ b/packages/server/src/browser/launch/worker-server-launcher.ts @@ -14,9 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Container, injectable } from 'inversify'; +import { injectable } from 'inversify'; import * as jsonrpc from 'vscode-jsonrpc/browser'; -import { GLSPServer, GLSPServerLauncher, JsonRpcGLSPServer, MaybePromise } from '../../common/index'; +import { MaybePromise } from '../../common/index'; +import { JsonRpcGLSPServerLauncher, JsonRpcServerInstance } from '../../common/launch/jsonrpc-server-launcher'; export interface WorkerLaunchOptions { context?: Worker; } @@ -24,22 +25,16 @@ export interface WorkerLaunchOptions { export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed.'; @injectable() -export class WorkerServerLauncher extends GLSPServerLauncher { +export class WorkerServerLauncher extends JsonRpcGLSPServerLauncher { protected connection?: jsonrpc.MessageConnection; protected run(options: WorkerLaunchOptions): MaybePromise { if (this.connection) { throw new Error('Error during launch. Server already has an active client connection'); } - const container = this.createContainer(); this.connection = this.createConnection(options); - const glspServer = container.get(JsonRpcGLSPServer); - glspServer.connect(this.connection); - this.connection.onDispose(() => this.disposeClientConnection(container, glspServer)); - + this.createServerInstance(this.connection); this.logger.info('GLSP server worker connection established'); - this.connection.listen(); - this.toDispose.push(this.connection); postMessage(START_UP_COMPLETE_MSG); return new Promise((resolve, rejects) => { this.connection?.onClose(() => resolve(undefined)); @@ -47,9 +42,8 @@ export class WorkerServerLauncher extends GLSPServerLauncher { diff --git a/packages/server/src/common/di/multi-binding.ts b/packages/server/src/common/di/multi-binding.ts index fe94377..e822a2c 100644 --- a/packages/server/src/common/di/multi-binding.ts +++ b/packages/server/src/common/di/multi-binding.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; +import { BindingContext, distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; import { applyBindingTarget, BindingTarget } from './binding-target'; -import { ModuleContext } from './glsp-module'; /** * A helper class used in `GLSPModules` to ease the configuration of multi-injected service identifiers. @@ -29,7 +28,7 @@ export abstract class AbstractMultiBinding { constructor(readonly identifier: string | symbol) {} - abstract applyBindings(context: ModuleContext): void; + abstract applyBindings(context: BindingContext): void; add(newBinding: T): void { distinctAdd(this.bindings, newBinding); @@ -71,7 +70,7 @@ export abstract class AbstractMultiBinding { * @typeparam T the base type of {@link BindingTarget}. */ export class MultiBinding extends AbstractMultiBinding> { - applyBindings(context: ModuleContext): void { + applyBindings(context: BindingContext): void { this.bindings.forEach(binding => applyBindingTarget(context, this.identifier, binding)); } } @@ -83,7 +82,7 @@ export class MultiBinding extends AbstractMultiBinding> { * * @typeparam T the type of the instances */ export class InstanceMultiBinding extends AbstractMultiBinding { - applyBindings(context: ModuleContext): void { + applyBindings(context: BindingContext): void { context.bind(this.identifier).toConstantValue(this.bindings); } } diff --git a/packages/server/src/common/di/server-module.ts b/packages/server/src/common/di/server-module.ts index 092687b..dfaec07 100644 --- a/packages/server/src/common/di/server-module.ts +++ b/packages/server/src/common/di/server-module.ts @@ -13,14 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GLSPServer, GLSPServerListener } from '@eclipse-glsp/protocol'; import { ContainerModule, interfaces } from 'inversify'; import { DefaultGlobalActionProvider, GlobalActionProvider } from '../actions/global-action-provider'; -import { DefaultGLSPClientProxy, GLSPClientProxy, JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; -import { DefaultGLSPServer, GLSPServer, JsonRpcGLSPServer } from '../protocol/glsp-server'; -import { GLSPServerListener } from '../protocol/glsp-server-listener'; +import { DefaultGLSPServer } from '../protocol/glsp-server'; import { ClientSessionFactory, DefaultClientSessionFactory } from '../session/client-session-factory'; import { ClientSessionManager, DefaultClientSessionManager } from '../session/client-session-manager'; -import { applyBindingTarget, BindingTarget } from './binding-target'; +import { BindingTarget, applyBindingTarget } from './binding-target'; import { DiagramModule } from './diagram-module'; import { GLSPModule } from './glsp-module'; import { MultiBinding } from './multi-binding'; @@ -74,7 +73,6 @@ export class ServerModule extends GLSPModule { applyBindingTarget(context, DiagramModules, this.bindDiagramModules()); applyBindingTarget(context, GLSPServer, this.bindGLSPServer()).inSingletonScope(); - applyBindingTarget(context, JsonRpcGLSPServer, this.bindJsonRpcGLSPServer()); applyBindingTarget(context, ClientSessionFactory, this.bindClientSessionFactory()).inSingletonScope(); applyBindingTarget(context, ClientSessionManager, this.bindClientSessionManager()).inSingletonScope(); this.configureMultiBinding(new MultiBinding(GLSPServerListener), binding => @@ -83,10 +81,9 @@ export class ServerModule extends GLSPModule { applyBindingTarget(context, GlobalActionProvider, this.bindGlobalActionProvider()).inSingletonScope(); - applyBindingTarget(context, InjectionContainer, this.bindInjectionContainer()); - - applyBindingTarget(context, GLSPClientProxy, this.bindGLSPClientProxy()).inSingletonScope(); - applyBindingTarget(context, JsonRpcGLSPClientProxy, this.bindJsonRpcGLSPClientProxy()); + if (!isBound(InjectionContainer)) { + applyBindingTarget(context, InjectionContainer, this.bindInjectionContainer()); + } } protected bindDiagramModules(): BindingTarget> { @@ -97,10 +94,6 @@ export class ServerModule extends GLSPModule { return DefaultGLSPServer; } - protected bindJsonRpcGLSPServer(): BindingTarget { - return { service: GLSPServer }; - } - protected bindClientSessionFactory(): BindingTarget { return DefaultClientSessionFactory; } @@ -117,14 +110,6 @@ export class ServerModule extends GLSPModule { return { dynamicValue: context => context.container }; } - protected bindGLSPClientProxy(): BindingTarget { - return DefaultGLSPClientProxy; - } - - protected bindJsonRpcGLSPClientProxy(): BindingTarget { - return { service: GLSPClientProxy }; - } - protected configureGLSPServerListeners(binding: MultiBinding): void { binding.add({ service: ClientSessionManager }); } diff --git a/packages/server/src/common/index.ts b/packages/server/src/common/index.ts index a82f835..8cbe4e9 100644 --- a/packages/server/src/common/index.ts +++ b/packages/server/src/common/index.ts @@ -74,9 +74,7 @@ export * from './operations/create-operation-handler'; export * from './operations/operation-action-handler'; export * from './operations/operation-handler'; export * from './operations/operation-handler-registry'; -export * from './protocol/glsp-client-proxy'; export * from './protocol/glsp-server'; -export * from './protocol/glsp-server-listener'; export * from './session/client-session'; export * from './session/client-session-factory'; export * from './session/client-session-initializer'; @@ -84,6 +82,7 @@ export * from './session/client-session-listener'; export * from './session/client-session-manager'; export * from './utils/args-util'; export * from './utils/client-options-util'; +export * from './utils/console-logger'; export * from './utils/glsp-server-error'; export * from './utils/layout-util'; export * from './utils/logger'; diff --git a/packages/server/src/common/launch/glsp-server-launcher.ts b/packages/server/src/common/launch/glsp-server-launcher.ts index 8b1ac01..f3dbd05 100644 --- a/packages/server/src/common/launch/glsp-server-launcher.ts +++ b/packages/server/src/common/launch/glsp-server-launcher.ts @@ -33,9 +33,9 @@ export abstract class GLSPServerLauncher implements Disposable { this._modules.push(serverModule, ...additionalModules); } - createContainer(): Container { + createContainer(...additionalModules: ContainerModule[]): Container { const container = this.parentContainer ? this.parentContainer.createChild() : new Container(); - container.load(...this._modules); + container.load(...this._modules, ...additionalModules); return container; } diff --git a/packages/server/src/common/launch/jsonrpc-server-launcher.ts b/packages/server/src/common/launch/jsonrpc-server-launcher.ts new file mode 100644 index 0000000..8e777be --- /dev/null +++ b/packages/server/src/common/launch/jsonrpc-server-launcher.ts @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Disposable, + GLSPClientProxy, + GLSPServer, + JsonrpcClientProxy, + JsonrpcGLSPClient, + configureClientConnection +} from '@eclipse-glsp/protocol'; +import { Container, ContainerModule, inject, injectable } from 'inversify'; +import * as jsonrpc from 'vscode-jsonrpc'; +import { Logger } from '../utils/logger'; +import { GLSPServerLauncher } from './glsp-server-launcher'; + +export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed. Accepting requests on port:'; + +export interface JsonRpcServerInstance { + server: GLSPServer; + clientConnection: jsonrpc.MessageConnection; + container: Container; +} + +@injectable() +export abstract class JsonRpcGLSPServerLauncher extends GLSPServerLauncher { + @inject(Logger) + protected override logger: Logger; + + protected serverInstances = new Map(); + protected startupCompleteMessage = START_UP_COMPLETE_MSG; + + constructor() { + super(); + this.toDispose.push( + Disposable.create(() => { + this.serverInstances.forEach(instance => this.disposeServerInstance(instance)); + }) + ); + } + + protected disposeServerInstance(instance: JsonRpcServerInstance): void { + this.serverInstances.delete(instance.clientConnection); + instance.server.shutdown(); + instance.container.unbindAll(); + instance.clientConnection.dispose(); + } + + protected createServerInstance(clientConnection: jsonrpc.MessageConnection): void { + const container = this.createContainer(this.createJsonRpcModule(clientConnection)); + const server = container.get(GLSPServer); + const instance = { container, clientConnection, server }; + this.serverInstances.set(clientConnection, instance); + this.configureClientConnection(instance); + } + + protected createJsonRpcModule(clientConnection: jsonrpc.MessageConnection): ContainerModule { + return new ContainerModule(bind => { + bind(GLSPClientProxy).toDynamicValue(ctx => { + const proxy = ctx.container.resolve(JsonrpcClientProxy); + proxy.initialize(clientConnection); + return proxy; + }); + }); + } + + protected configureClientConnection(serverInstance: JsonRpcServerInstance): void { + configureClientConnection(serverInstance.clientConnection, serverInstance.server); + + serverInstance.clientConnection.onNotification(JsonrpcGLSPClient.ShutdownNotification, () => + this.disposeServerInstance(serverInstance) + ); + + this.logger.info('Starting GLSP server connection'); + } + + protected createClientProxy(serverInstance: JsonRpcServerInstance): GLSPClientProxy { + const proxy = serverInstance.container.resolve(JsonrpcClientProxy); + proxy.initialize(serverInstance.clientConnection); + return proxy; + } +} diff --git a/packages/server/src/common/protocol/glsp-client-proxy.ts b/packages/server/src/common/protocol/glsp-client-proxy.ts deleted file mode 100644 index 03bb4ce..0000000 --- a/packages/server/src/common/protocol/glsp-client-proxy.ts +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { ActionMessage, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; -import { inject, injectable } from 'inversify'; -import * as jsonrpc from 'vscode-jsonrpc'; -import { Logger } from '../utils/logger'; - -export const GLSPClientProxyFactory = Symbol('GLSPClientProxyFactory'); - -export type GLSPClientProxyFactory = (connection: jsonrpc.MessageConnection) => GLSPClientProxy; - -export const GLSPClientProxy = Symbol('GLSPClientProxy'); - -/** - * Json-rpc client proxy interface to hide the underlying json-rpc logic. - */ -export interface GLSPClientProxy { - /** - * A `process` notification is sent from the server to server to the client when the client should handle i.e. - * process a specific {@link ActionMessage}. Any communication that is performed between initialization and shutdown - * is handled by sending action messages, either from the client to the server or from the server to the client. This - * is the core part of the Graphical Language Server Protocol. - * - * @param message The {@link ActionMessage} that should be processed. - */ - process(message: ActionMessage): void; -} - -export const JsonRpcGLSPClientProxy = Symbol('JsonRpcGLSPClientProxy'); - -export interface JsonRpcGLSPClientProxy extends GLSPClientProxy { - connect(connection: jsonrpc.MessageConnection): void; -} - -@injectable() -export class DefaultGLSPClientProxy implements JsonRpcGLSPClientProxy { - @inject(Logger) - private logger: Logger; - - protected connection: jsonrpc.MessageConnection; - - connect(connection: jsonrpc.MessageConnection): void { - if (!this.connection) { - this.connection = connection; - } - } - - process(message: ActionMessage): void { - this.logger.debug(`Send action '${message.action.kind}' to client '${message.clientId}'`); - this.connection.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); - } -} diff --git a/packages/server/src/common/protocol/glsp-server-listener.ts b/packages/server/src/common/protocol/glsp-server-listener.ts deleted file mode 100644 index 91645e2..0000000 --- a/packages/server/src/common/protocol/glsp-server-listener.ts +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { GLSPServer } from './glsp-server'; - -export const GLSPServerListener = Symbol('GLSPServerListener'); - -/** - * A listener to track the connection status of {@link GLSPClient}s (i.e. client applications). - * Gets notified when a new GLSP client connects or disconnects. - */ -export interface GLSPServerListener { - /** - * Triggered after a GLSPServer has been initialized via the {@link GLSPServer.initialize()} - * method. - * - * @param server The GLSPServer which has been initialized. - */ - serverInitialized?(server: GLSPServer): void; - - /** - * Triggered after the {@link GLSPServer.shutdown()} method has been invoked. - * - * @param glspServer The glspServer which has been shut down. - */ - serverShutDown?(server: GLSPServer): void; -} diff --git a/packages/server/src/common/protocol/glsp-server.spec.ts b/packages/server/src/common/protocol/glsp-server.spec.ts index 0938b4c..d72a96d 100644 --- a/packages/server/src/common/protocol/glsp-server.spec.ts +++ b/packages/server/src/common/protocol/glsp-server.spec.ts @@ -13,7 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '@eclipse-glsp/protocol'; +import { + DisposeClientSessionParameters, + GLSPClientProxy, + GLSPServerListener, + InitializeClientSessionParameters, + InitializeParameters +} from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import * as sinon from 'sinon'; @@ -21,9 +27,7 @@ import { GlobalActionProvider } from '../actions/global-action-provider'; import { ClientSessionManager } from '../session/client-session-manager'; import * as mock from '../test/mock-util'; import { Logger } from '../utils/logger'; -import { GLSPClientProxy, JsonRpcGLSPClientProxy } from './glsp-client-proxy'; import { DefaultGLSPServer } from './glsp-server'; -import { GLSPServerListener } from './glsp-server-listener'; import assert = require('assert'); describe('test DefaultGLSPServer', () => { @@ -48,7 +52,6 @@ describe('test DefaultGLSPServer', () => { new ContainerModule(bind => { bind(Logger).toConstantValue(new mock.StubLogger()); bind(GLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); - bind(JsonRpcGLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); bind(ClientSessionManager).toConstantValue(sessionManager); bind(GlobalActionProvider).toConstantValue({ clientActionKinds: new Map(), serverActionKinds }); bind(GLSPServerListener).toConstantValue(listener1); diff --git a/packages/server/src/common/protocol/glsp-server.ts b/packages/server/src/common/protocol/glsp-server.ts index 9e6ab1d..a87cb4e 100644 --- a/packages/server/src/common/protocol/glsp-server.ts +++ b/packages/server/src/common/protocol/glsp-server.ts @@ -18,134 +18,26 @@ import { Args, DisposeClientSessionParameters, distinctAdd, + GLSPClientProxy, + GLSPServer, + GLSPServerListener, InitializeClientSessionParameters, InitializeParameters, InitializeResult, - JsonrpcGLSPClient, MaybePromise, remove, ServerActions, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; -import * as jsonrpc from 'vscode-jsonrpc'; -import { MessageConnection } from 'vscode-jsonrpc'; import { GlobalActionProvider } from '../actions/global-action-provider'; import { ClientSession } from '../session/client-session'; import { ClientSessionManager } from '../session/client-session-manager'; import { GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; -import { JsonRpcGLSPClientProxy } from './glsp-client-proxy'; -import { GLSPServerListener } from './glsp-server-listener'; - -export const GLSPServer = Symbol('GLSPServer'); - -/** - * Interface for implementations of a server component using json-rpc for client-server communication. - * Based on the specification of the Graphical Language Server Protocol: - * https://github.com/eclipse-glsp/glsp/blob/master/PROTOCOL.md - */ -export interface GLSPServer { - /** - * - * The `initialize` request has to be the first request from the client to the server. Until the server has responded - * with an {@link InitializeResult} no other request or notification can be handled and is expected to throw an - * error. A client is uniquely identified by an `applicationId` and has to specify on which `protocolVersion` it is - * based on. In addition, custom arguments can be provided in the `args` map to allow for custom initialization - * behavior on the server. - * - * After successfully initialization all {@link GLSPServerListener}s are notified via the - * {@link GLSPServerListener.serverInitialized} method. - * - * @param params the {@link InitializeParameters}. - * @returns A promise of the {@link InitializeResult} . - * - * @throws {@link Error} Subsequent initialize requests return the {@link InitializeResult} of the initial request - * if the given application id and protocol version are matching, otherwise the promise rejects with an error. - * - */ - initialize(params: InitializeParameters): Promise; - - /** - * The `initializeClientSession` request is sent to the server whenever a new graphical representation (diagram) is - * created. Each individual diagram on the client side counts as one session and has to provide a unique - * `clientSessionId` and its `diagramType`. In addition, custom arguments can be provided in the `args` map to allow - * for custom initialization behavior on the server. Subsequent `initializeClientSession` requests for the same - * client id and diagram type are expected to resolve successfully but don't have an actual effect because the - * corresponding client session is already initialized. - * - * @param params the {@link InitializeClientSessionParameters}. - * @returns A promise that completes when the initialization was successful. - */ - initializeClientSession(params: InitializeClientSessionParameters): Promise; - - /** - * The 'DisposeClientSession' request is sent to the server when a graphical representation (diagram) is no longer - * needed, e.g. the tab containing the diagram widget has been closed. The session is identified by its unique - * `clientSessionId`. In addition, custom arguments can be provided in the `args` map to allow for custom dispose - * behavior on the server. - * - * @param params the {@link DisposeClientSessionParameters}. - * @returns A `void` promise that completes if the disposal was successful. - * - */ - disposeClientSession(params: DisposeClientSessionParameters): Promise; - - /** - * A `process` notification is sent from the client to server when the server should handle i.e. process a specific - * {@link ActionMessage}. Any communication that is performed between initialization and shutdown is handled by - * sending action messages, either from the client to the server or from the server to the client. This is the core - * part of the Graphical Language Server Protocol. - * - * @param message The {@link ActionMessage} that should be processed. - */ - process(message: ActionMessage): void; - - /** - * The `shutdown` notification is sent from the client to the server if the client disconnects from the server (e.g. - * the client application has been closed). - * This gives the server a chance to clean up and dispose any resources dedicated to the client and its sessions. - * All {@link GLSPServerListener}s are notified via the {@link GLSPServerListener.serverShutDown} method. - * Afterwards the server instance is considered to be disposed and can no longer be used for handling requests. - * - */ - shutdown(): void; - - /** - * Register a new {@link GLSPServerListener}. - * - * @param listener The listener that should be registered. - * @returns `true` if the listener was registered successfully, `false` otherwise (e.g. listener is already - * registered). - */ - addListener(listener: GLSPServerListener): boolean; - - /** - * Unregister a {@link GLSPServerListener}. - * - * @param listener The listener that should be removed - * @returns 'true' if the listener was unregistered successfully, `false` otherwise (e.g. listener is was not - * registered in the first place). - */ - removeListener(listener: GLSPServerListener): boolean; - - /** - * get a {@link ClientSession}. - * - * @param sessionId The id of the session to get - * @returns The either the ClientSession or undefined, if no ClientSession was found for the given id. - */ - getClientSession(sessionId: string): ClientSession | undefined; -} - -export const JsonRpcGLSPServer = Symbol('JsonRpcGLSPServer'); - -export interface JsonRpcGLSPServer extends GLSPServer { - connect(connection: jsonrpc.MessageConnection): void; -} @injectable() -export class DefaultGLSPServer implements JsonRpcGLSPServer { +export class DefaultGLSPServer implements GLSPServer { public static readonly PROTOCOL_VERSION = '1.0.0'; @inject(Logger) @@ -157,8 +49,9 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { @inject(GlobalActionProvider) protected actionProvider: GlobalActionProvider; - @inject(JsonRpcGLSPClientProxy) - protected glspClient: JsonRpcGLSPClientProxy; + @inject(GLSPClientProxy) + @optional() + protected glspClientProxy?: GLSPClientProxy; protected initializeResult?: InitializeResult; @@ -172,18 +65,6 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { serverListeners.forEach(listener => this.addListener(listener)); } - protected setupJsonRpc(connection: MessageConnection): void { - connection.onRequest(JsonrpcGLSPClient.InitializeRequest.method, (params: InitializeParameters) => this.initialize(params)); - connection.onRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, (params: InitializeClientSessionParameters) => - this.initializeClientSession(params) - ); - connection.onRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, (params: DisposeClientSessionParameters) => - this.disposeClientSession(params) - ); - connection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, message => this.process(message)); - connection.onNotification(JsonrpcGLSPClient.ShutdownNotification, () => this.shutdown()); - } - protected validateProtocolVersion(params: InitializeParameters): void { if (params.protocolVersion !== DefaultGLSPServer.PROTOCOL_VERSION) { throw new Error( @@ -237,6 +118,7 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { this.validateServerInitialized(); const session = this.sessionManager.getOrCreateClientSession(params); + this.clientSessions.set(params.clientSessionId, session); return this.handleInitializeClientSessionArgs(params.args); } @@ -281,7 +163,15 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { errorMsg = reason.message; } const errorAction = ServerMessageAction.create(errorMsg, { severity: 'ERROR', details }); - this.glspClient.process({ clientId: message.clientId, action: errorAction }); + this.sendToClient({ clientId: message.clientId, action: errorAction }); + } + + protected sendToClient(message: ActionMessage): void { + if (this.glspClientProxy) { + this.glspClientProxy.process(message); + return; + } + throw new Error("Could not send message to client. No 'GLSPClientProxy' is connected"); } getClientSession(sessionId: string): ClientSession | undefined { @@ -295,11 +185,6 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { this.initializeResult = undefined; } - public connect(connection: jsonrpc.MessageConnection): void { - this.setupJsonRpc(connection); - this.glspClient.connect(connection); - } - protected isInitialized(): boolean { return this.initializeResult !== undefined; } diff --git a/packages/server/src/common/session/client-session-factory.spec.ts b/packages/server/src/common/session/client-session-factory.spec.ts index aab9d2d..957e1da 100644 --- a/packages/server/src/common/session/client-session-factory.spec.ts +++ b/packages/server/src/common/session/client-session-factory.spec.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GLSPClientProxy } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; -import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; import * as mock from '../test/mock-util'; import { GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; diff --git a/packages/server/src/common/session/client-session-factory.ts b/packages/server/src/common/session/client-session-factory.ts index e3e4761..1d11bdc 100644 --- a/packages/server/src/common/session/client-session-factory.ts +++ b/packages/server/src/common/session/client-session-factory.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { InitializeClientSessionParameters } from '@eclipse-glsp/protocol'; +import { GLSPClientProxy, InitializeClientSessionParameters } from '@eclipse-glsp/protocol'; import { Container, ContainerModule, inject, injectable } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { createClientSessionModule } from '../di/client-session-module'; import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; -import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; import { GLSPServerError } from '../utils/glsp-server-error'; import { ClientSession, DefaultClientSession } from './client-session'; import { ClientSessionInitializer } from './client-session-initializer'; diff --git a/packages/server/src/common/session/client-session-manager.ts b/packages/server/src/common/session/client-session-manager.ts index 82ddc4c..0a3714e 100644 --- a/packages/server/src/common/session/client-session-manager.ts +++ b/packages/server/src/common/session/client-session-manager.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { InitializeClientSessionParameters, remove } from '@eclipse-glsp/protocol'; +import { GLSPServer, GLSPServerListener, InitializeClientSessionParameters, remove } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { GLSPServer } from '../protocol/glsp-server'; -import { GLSPServerListener } from '../protocol/glsp-server-listener'; import { GLSPServerError } from '../utils/glsp-server-error'; import { ClientSession } from './client-session'; import { ClientSessionFactory } from './client-session-factory'; diff --git a/packages/server/src/common/test/mock-util.ts b/packages/server/src/common/test/mock-util.ts index 02323ce..8ec0bc3 100644 --- a/packages/server/src/common/test/mock-util.ts +++ b/packages/server/src/common/test/mock-util.ts @@ -23,6 +23,9 @@ import { Args, CreateNodeOperation, EdgeTypeHint, + GLSPClientProxy, + GLSPServer, + GLSPServerListener, InitializeClientSessionParameters, MaybeArray, MaybePromise, @@ -41,15 +44,12 @@ import { DiagramConfiguration, ServerLayoutKind } from '../diagram/diagram-confi import { ContextEditValidator } from '../features/directediting/context-edit-validator'; import { LabelEditValidator } from '../features/directediting/label-edit-validator'; import { GModelCreateEdgeOperationHandler, GModelCreateNodeOperationHandler } from '../gmodel/index'; -import { JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; -import { GLSPServer } from '../protocol/glsp-server'; -import { GLSPServerListener } from '../protocol/glsp-server-listener'; import { ClientSession } from '../session/client-session'; import { ClientSessionFactory } from '../session/client-session-factory'; import { ClientSessionInitializer } from '../session/client-session-initializer'; import { ClientSessionListener } from '../session/client-session-listener'; import { ClientSessionManager } from '../session/client-session-manager'; -import { Logger, LogLevel } from '../utils/logger'; +import { LogLevel, Logger } from '../utils/logger'; export async function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); @@ -194,7 +194,7 @@ export class StubClientSessionListener implements ClientSessionListener { sessionDisposed(clientSession: ClientSession): void {} } -export class StubGLSPClientProxy implements JsonRpcGLSPClientProxy { +export class StubGLSPClientProxy implements GLSPClientProxy { connect(connection: MessageConnection): void {} process(message: ActionMessage): void {} diff --git a/packages/server/src/browser/di/console-logger.ts b/packages/server/src/common/utils/console-logger.ts similarity index 72% rename from packages/server/src/browser/di/console-logger.ts rename to packages/server/src/common/utils/console-logger.ts index 3bd81a4..971a7b0 100644 --- a/packages/server/src/browser/di/console-logger.ts +++ b/packages/server/src/common/utils/console-logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { BindingContext } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Logger, LogLevel } from '../../common/index'; +import { LogLevel, Logger, LoggerConfigOptions, LoggerFactory, NullLogger, getRequestParentName } from './logger'; +/** + * Simple logger implementation that forwards logging calls to the `console` and + * can be used in both the browser and node context. + */ @injectable() export class ConsoleLogger extends Logger { constructor(public logLevel: LogLevel = LogLevel.none, public caller?: string) { @@ -69,3 +74,16 @@ export class ConsoleLogger extends Logger { return JSON.stringify(param, undefined, 4); } } + +export function configureConsoleLogger(context: BindingContext, options: T): void { + if (!options.consoleLog) { + context.bind(Logger).to(NullLogger).inSingletonScope(); + } else { + context.bind(Logger).toDynamicValue(ctx => new ConsoleLogger(options.logLevel, getRequestParentName(ctx))); + } + context.bind(LoggerFactory).toFactory(dynamicContext => (caller: string) => { + const logger = dynamicContext.container.get(Logger); + logger.caller = caller; + return logger; + }); +} diff --git a/packages/server/src/common/utils/logger.ts b/packages/server/src/common/utils/logger.ts index 83594ef..460e47b 100644 --- a/packages/server/src/common/utils/logger.ts +++ b/packages/server/src/common/utils/logger.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; +import { injectable, interfaces } from 'inversify'; @injectable() export abstract class Logger { @@ -70,3 +70,18 @@ export function asLogLevel(level: string | number): LogLevel | undefined { } return undefined; } + +export interface LoggerConfigOptions { + consoleLog?: boolean; + logLevel?: LogLevel; +} + +export function getRequestParentName(context: interfaces.Context): string | undefined { + if (context.currentRequest.parentRequest) { + const bindings = context.currentRequest.parentRequest.bindings; + if (bindings.length > 0) { + return bindings[0].implementationType?.name; + } + } + return undefined; +} diff --git a/packages/server/src/node/di/app-module.ts b/packages/server/src/node/di/app-module.ts index 43acd51..c566939 100644 --- a/packages/server/src/node/di/app-module.ts +++ b/packages/server/src/node/di/app-module.ts @@ -13,9 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule, interfaces } from 'inversify'; +import { BindingContext } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; import * as winston from 'winston'; -import { InjectionContainer, Logger, LoggerFactory, LogLevel, ModuleContext } from '../../common/index'; +import { InjectionContainer, LogLevel, Logger, LoggerFactory, getRequestParentName } from '../../common'; import { LaunchOptions } from '../launch/cli-parser'; import { WinstonLogger } from './winston-logger'; @@ -68,7 +69,7 @@ export function createWinstonInstance(options: T): wins * @param baseLoggerCreator The underling global {@link winston.Logger} instance. */ export function configureWinstonLogger( - context: ModuleContext, + context: BindingContext, options: T, rebind = true, baseLoggerCreator: (launchOptions: T) => winston.Logger = createWinstonInstance @@ -90,13 +91,3 @@ export function configureWinstonLogger( return logger; }); } - -function getRequestParentName(context: interfaces.Context): string | undefined { - if (context.currentRequest.parentRequest) { - const bindings = context.currentRequest.parentRequest.bindings; - if (bindings.length > 0) { - return bindings[0].implementationType?.name; - } - } - return undefined; -} diff --git a/packages/server/src/node/launch/cli-parser.ts b/packages/server/src/node/launch/cli-parser.ts index c709653..cd1ca4b 100644 --- a/packages/server/src/node/launch/cli-parser.ts +++ b/packages/server/src/node/launch/cli-parser.ts @@ -16,13 +16,12 @@ import * as cmd from 'commander'; import * as fs from 'fs-extra'; import * as path from 'path'; -import { asLogLevel, LogLevel } from '../../common/utils/logger'; +import { LogLevel, LoggerConfigOptions, asLogLevel } from '../../common/utils/logger'; -export interface LaunchOptions { +export interface LaunchOptions extends LoggerConfigOptions { logLevel: LogLevel; logDir?: string; fileLog: boolean; - consoleLog: boolean; } export interface CliParser { diff --git a/packages/server/src/node/launch/socket-server-launcher.spec.ts b/packages/server/src/node/launch/socket-server-launcher.spec.ts index 309264c..3cad01f 100644 --- a/packages/server/src/node/launch/socket-server-launcher.spec.ts +++ b/packages/server/src/node/launch/socket-server-launcher.spec.ts @@ -14,9 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GLSPServer } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; import * as net from 'net'; +import * as sinon from 'sinon'; +import { DefaultGLSPServer } from '../../common/protocol/glsp-server'; import { createAppModule } from '../di/app-module'; import { defaultSocketLaunchOptions } from './socket-cli-parser'; import { SocketServerLauncher } from './socket-server-launcher'; @@ -24,7 +27,10 @@ const severPort = 5008; describe('test SocketServerLauncher', () => { it('starts and stops', async () => { const appContainer = new Container(); + const serverStub = sinon.createStubInstance(DefaultGLSPServer); appContainer.load(createAppModule(defaultSocketLaunchOptions)); + + appContainer.bind(GLSPServer).toConstantValue(serverStub); const launcher = appContainer.resolve(SocketServerLauncher); launcher.start({ port: severPort }); const sockStart = new net.Socket(); diff --git a/packages/server/src/node/launch/socket-server-launcher.ts b/packages/server/src/node/launch/socket-server-launcher.ts index 024f8fa..d56cb6d 100644 --- a/packages/server/src/node/launch/socket-server-launcher.ts +++ b/packages/server/src/node/launch/socket-server-launcher.ts @@ -13,36 +13,35 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Container, inject, injectable } from 'inversify'; +import { Disposable } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; import * as net from 'net'; import * as jsonrpc from 'vscode-jsonrpc/node'; -import { Disposable } from '..'; -import { GLSPServerLauncher } from '../../common/launch/glsp-server-launcher'; -import { GLSPServer, JsonRpcGLSPServer } from '../../common/protocol/glsp-server'; +import { JsonRpcGLSPServerLauncher } from '../../common/launch/jsonrpc-server-launcher'; import { Logger } from '../../common/utils/logger'; export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed. Accepting requests on port:'; @injectable() -export class SocketServerLauncher extends GLSPServerLauncher { +export class SocketServerLauncher extends JsonRpcGLSPServerLauncher { @inject(Logger) protected override logger: Logger; - protected currentConnections: jsonrpc.MessageConnection[] = []; - protected startupCompleteMessage = START_UP_COMPLETE_MSG; protected netServer: net.Server; constructor() { super(); this.toDispose.push( Disposable.create(() => { - this.currentConnections.forEach(connection => connection.dispose()); this.netServer.close(); }) ); } protected run(opts: net.TcpSocketConnectOpts): Promise { - this.netServer = net.createServer(socket => this.createClientConnection(socket)); + this.netServer = net.createServer(socket => { + const connection = this.createConnection(socket); + this.createServerInstance(connection); + }); this.netServer.listen(opts.port, opts.host); this.netServer.on('listening', () => { @@ -69,27 +68,6 @@ export class SocketServerLauncher extends GLSPServerLauncher { - const container = this.createContainer(); - const connection = this.createConnection(socket); - this.currentConnections.push(connection); - const glspServer = container.get(JsonRpcGLSPServer); - glspServer.connect(connection); - this.logger.info(`Starting GLSP server connection for client: '${socket.localAddress}'`); - connection.listen(); - connection.onDispose(() => this.disposeClientConnection(container, glspServer)); - socket.on('close', () => this.disposeClientConnection(container, glspServer)); - return new Promise((resolve, rejects) => { - connection.onClose(() => resolve(undefined)); - connection.onError(error => rejects(error)); - }); - } - - protected disposeClientConnection(container: Container, glspServer: GLSPServer): void { - glspServer.shutdown(); - container.unbindAll(); - } - protected createConnection(socket: net.Socket): jsonrpc.MessageConnection { return jsonrpc.createMessageConnection(new jsonrpc.SocketMessageReader(socket), new jsonrpc.SocketMessageWriter(socket), console); } diff --git a/packages/server/src/node/launch/websocket-server-launcher.ts b/packages/server/src/node/launch/websocket-server-launcher.ts index 188506f..c5fb784 100644 --- a/packages/server/src/node/launch/websocket-server-launcher.ts +++ b/packages/server/src/node/launch/websocket-server-launcher.ts @@ -16,14 +16,12 @@ import { createWebSocketConnection, Disposable, MaybePromise, WebSocketWrapper } from '@eclipse-glsp/protocol'; import * as http from 'http'; -import { Container, inject, injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import * as net from 'net'; import * as jsonrpc from 'vscode-jsonrpc'; import { Server, WebSocket } from 'ws'; -import { GLSPServerLauncher } from '../../common/launch/glsp-server-launcher'; -import { GLSPServer, JsonRpcGLSPServer } from '../../common/protocol/glsp-server'; +import { JsonRpcGLSPServerLauncher } from '../../common/launch/jsonrpc-server-launcher'; import { Logger } from '../../common/utils/logger'; -import { START_UP_COMPLETE_MSG } from './socket-server-launcher'; export interface WebSocketServerOptions { host?: string; @@ -46,19 +44,16 @@ export interface WebsocketConnectionData { const STATUS_UPGRADE_REQUIRED = 426; @injectable() -export class WebSocketServerLauncher extends GLSPServerLauncher { +export class WebSocketServerLauncher extends JsonRpcGLSPServerLauncher { @inject(Logger) protected override logger: Logger; protected server: Server; - protected startupCompleteMessage = START_UP_COMPLETE_MSG; - protected currentConnections: jsonrpc.MessageConnection[] = []; constructor() { super(); this.toDispose.push( Disposable.create(() => { - this.currentConnections.forEach(connection => connection.dispose()); this.server.close(); }) ); @@ -72,7 +67,8 @@ export class WebSocketServerLauncher extends GLSPServerLauncher { - this.createClientConnection(ws); + const connection = this.createConnection(ws); + this.createServerInstance(connection); }); return new Promise((resolve, reject) => { @@ -81,28 +77,6 @@ export class WebSocketServerLauncher extends GLSPServerLauncher { - const container = this.createContainer(); - const connection = this.createConnection(socket); - this.currentConnections.push(connection); - const glspServer = container.get(JsonRpcGLSPServer); - glspServer.connect(connection); - this.logger.info('Starting GLSP server connection'); - connection.listen(); - connection.onDispose(() => this.disposeClientConnection(container, glspServer)); - socket.on('close', () => this.disposeClientConnection(container, glspServer)); - connection.onClose(() => console.log('GOD dam')); - return new Promise((resolve, rejects) => { - connection.onClose(() => resolve(undefined)); - connection.onError(error => rejects(error)); - }); - } - - protected disposeClientConnection(container: Container, glspServer: GLSPServer): void { - glspServer.shutdown(); - container.unbindAll(); - } - protected createConnection(socket: WebSocket): jsonrpc.MessageConnection { return createWebSocketConnection(wrapWebSocket(socket)); } From 79fed6adb7df493e9759219b0506d7cb0b2cab33 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 4 May 2023 14:55:59 +0200 Subject: [PATCH 194/566] Add missing exports to `@eclipse-glsp-examples/workflow-server` --- examples/workflow-server/package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 52919bf..05b1c9c 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -39,7 +39,11 @@ "types": "lib/index", "files": [ "lib", - "src" + "src", + "node.js", + "node.d.ts", + "browser.d.ts", + "browser.js" ], "scripts": { "build": "tsc -b && yarn bundle", From 129fcb5ca8335b5d6792310cb364f5e71b7db8c8 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 May 2023 01:30:37 -0700 Subject: [PATCH 195/566] Fix wrong import (#246) --- packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index c0f6657..4434793 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -15,9 +15,9 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { Message, MessageConnection } from 'vscode-jsonrpc'; -import { GLSPClientProxy } from '..'; import { ActionMessage } from '../action-protocol'; import { ActionMessageHandler, ClientState, GLSPClient } from '../client-server-protocol/glsp-client'; +import { GLSPClientProxy } from '../client-server-protocol/glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, From ffb10b5f88776cf1c6b76dc4e885c7d0af3f68eb Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 May 2023 10:55:46 +0200 Subject: [PATCH 196/566] Update changelog - Update copyright headers - Update standalone example (Remove index and directly import reflect-metadata in app.ts) --- examples/workflow-standalone/src/app.ts | 2 ++ examples/workflow-standalone/src/di.config.ts | 2 +- examples/workflow-standalone/src/index.ts | 17 ----------------- examples/workflow-standalone/webpack.config.js | 2 +- .../tool-feedback/creation-tool-feedback.ts | 2 +- .../tool-feedback/edge-edit-tool-feedback.ts | 2 +- .../action-protocol/element-validation.spec.ts | 2 +- .../src/action-protocol/element-validation.ts | 2 +- 8 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 examples/workflow-standalone/src/index.ts diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index 5f681c7..b72a307 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -13,6 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import 'reflect-metadata'; + import { ApplicationIdProvider, BaseJsonrpcGLSPClient, diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 27e1d3c..b11840f 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/src/index.ts b/examples/workflow-standalone/src/index.ts deleted file mode 100644 index b2d6201..0000000 --- a/examples/workflow-standalone/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import 'reflect-metadata'; -import './app'; diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index dc413df..2483ba6 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -21,7 +21,7 @@ const appRoot = path.resolve(__dirname, 'app'); var CircularDependencyPlugin = require('circular-dependency-plugin'); module.exports = { - entry: [path.resolve(buildRoot, 'index')], + entry: [path.resolve(buildRoot, 'app')], output: { filename: 'bundle.js', path: appRoot diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 75290a4..a88a6b6 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 7c69577..b66d9e3 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-validation.spec.ts b/packages/protocol/src/action-protocol/element-validation.spec.ts index 78534c3..725fa8b 100644 --- a/packages/protocol/src/action-protocol/element-validation.spec.ts +++ b/packages/protocol/src/action-protocol/element-validation.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index b873eca..afa2dce 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 08fdec0a1c9c1e76dc46d8e629e0ccf79f494c15 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 May 2023 05:07:50 -0700 Subject: [PATCH 197/566] Update changelog (#45) Update copyright headers --- examples/workflow-server/browser.d.ts | 2 +- examples/workflow-server/node.d.ts | 2 +- examples/workflow-server/src/node/workflow-cli-parser.ts | 2 +- packages/layout-elk/src/element-filter.ts | 2 +- packages/layout-elk/src/glsp-elk-layout-engine.ts | 2 +- packages/layout-elk/src/layout-configurator.ts | 2 +- packages/server/src/common/utils/console-logger.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/workflow-server/browser.d.ts b/examples/workflow-server/browser.d.ts index 02f39a7..64126dd 100644 --- a/examples/workflow-server/browser.d.ts +++ b/examples/workflow-server/browser.d.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/node.d.ts b/examples/workflow-server/node.d.ts index a37c4b4..98f277b 100644 --- a/examples/workflow-server/node.d.ts +++ b/examples/workflow-server/node.d.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/node/workflow-cli-parser.ts b/examples/workflow-server/src/node/workflow-cli-parser.ts index 2d9f676..09c62b6 100644 --- a/examples/workflow-server/src/node/workflow-cli-parser.ts +++ b/examples/workflow-server/src/node/workflow-cli-parser.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/src/element-filter.ts b/packages/layout-elk/src/element-filter.ts index b1c7969..b2ea953 100644 --- a/packages/layout-elk/src/element-filter.ts +++ b/packages/layout-elk/src/element-filter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2018-2022 TypeFox and others. + * Copyright (c) 2018-2023 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index a3da86c..ab28c11 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2018-2022 TypeFox and others. + * Copyright (c) 2018-2023 TypeFox and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. diff --git a/packages/layout-elk/src/layout-configurator.ts b/packages/layout-elk/src/layout-configurator.ts index c0dcb2a..33779b2 100644 --- a/packages/layout-elk/src/layout-configurator.ts +++ b/packages/layout-elk/src/layout-configurator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/console-logger.ts b/packages/server/src/common/utils/console-logger.ts index 971a7b0..9d84031 100644 --- a/packages/server/src/common/utils/console-logger.ts +++ b/packages/server/src/common/utils/console-logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From f97b60c3b573c272e9e6f38e7b3c5f3b7d59de49 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 8 May 2023 04:28:09 -0700 Subject: [PATCH 198/566] Update release script (#993) -Ensure that script works for both `master` and `main` naming conventions - Use git checkout over `git switch` because switch is not available in all git installations --- dev-packages/cli/src/commands/release/common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index 7e9b692..ce6d5f6 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -134,8 +134,8 @@ export function checkoutAndCd(options: ReleaseOptions): string { sh.exec(`gh repo clone ${ghUrl}`, getShellConfig()); LOGGER.debug(`Successfully cloned to ${directory}`); sh.cd(directory); - if (options.checkoutDir !== 'master') { - sh.exec(`git switch ${options.branch} `); + if (options.branch !== 'master' && options.branch !== 'main') { + sh.exec(`git checkout ${options.branch} `); } return sh.pwd(); } From 49d5ec8d83952ca46ac2c13778be59565a096b69 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 11 May 2023 14:44:50 -0700 Subject: [PATCH 199/566] Update release script (#993) -Ensure that script works for both `master` and `main` naming conventions - Use git checkout over `git switch` because switch is not available in all git installations From 7c391643b9e6227254cef11f4af8c20ead11c4b7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 16 May 2023 08:56:24 -0700 Subject: [PATCH 200/566] GLSP-813 Also publish workflow-server bundle (#46) Ensure that bundled example-server is part of the published package contents so that it can be reused in the dev-examples https://github.com/eclipse-glsp/glsp/issues/813 --- examples/workflow-server/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 05b1c9c..93effb8 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -43,7 +43,8 @@ "node.js", "node.d.ts", "browser.d.ts", - "browser.js" + "browser.js", + "bundle/wf-glsp-server-node.js" ], "scripts": { "build": "tsc -b && yarn bundle", From 5b2e83c4005c0d1f6ecf41ee4b7461fa77966f0f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 21 May 2023 12:42:13 -0700 Subject: [PATCH 201/566] GLSP-975: Implement NodeGlspClient (#247) * GLSP-975: Implement NodeGlspClient In addition, to the classic json-rpc client the protocol package now also offers a `GLSPClient` that communicates directly with a `GLSPServer` without any communication layer between. This client can be used in cases where the (Node) server and client are running in the same context i.e. process. Part of https://github.com/eclipse-glsp/glsp/issues/975 In addition, this PR contains the following changes: - Refactor `onActionMessage` method of `GLSPClient` to return a disposable for deregistration - Refactor connection state checking in `BaseJsonrpcClient` - Add test classes for both GLSPClient implementations - Update tsconfig to ensure that spec files are not part of the published package Part of https://github.com/eclipse-glsp/glsp/issues/1004 * Address review feedback --- packages/client/tsconfig.json | 2 +- .../{contexts.spec..ts => contexts.spec.ts} | 2 +- .../src/client-server-protocol/glsp-client.ts | 4 +- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 238 ++++++++++++++++++ .../jsonrpc/base-jsonrpc-glsp-client.ts | 59 ++--- .../jsonrpc/glsp-jsonrpc-client.ts | 13 +- .../jsonrpc/glsp-jsonrpc-server.ts | 4 +- .../jsonrpc/websocket-connection.ts | 0 .../{ => node}/glsp-server.ts | 8 +- .../node/node-glsp-client.spec.ts | 237 +++++++++++++++++ .../node/node-glsp-client.ts | 162 ++++++++++++ packages/protocol/src/index.ts | 11 +- packages/protocol/src/utils/di-util.ts | 4 +- packages/protocol/src/utils/test-util.ts | 51 ++++ packages/protocol/tsconfig.json | 1 + 15 files changed, 735 insertions(+), 61 deletions(-) rename packages/protocol/src/action-protocol/{contexts.spec..ts => contexts.spec.ts} (98%) create mode 100644 packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts rename packages/protocol/src/{ => client-server-protocol}/jsonrpc/base-jsonrpc-glsp-client.ts (76%) rename packages/protocol/src/{ => client-server-protocol}/jsonrpc/glsp-jsonrpc-client.ts (86%) rename packages/protocol/src/{ => client-server-protocol}/jsonrpc/glsp-jsonrpc-server.ts (93%) rename packages/protocol/src/{ => client-server-protocol}/jsonrpc/websocket-connection.ts (100%) rename packages/protocol/src/client-server-protocol/{ => node}/glsp-server.ts (96%) create mode 100644 packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts create mode 100644 packages/protocol/src/client-server-protocol/node/node-glsp-client.ts create mode 100644 packages/protocol/src/utils/test-util.ts diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 6e56290..5752a13 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -6,6 +6,6 @@ "reactNamespace": "JSX", "baseUrl": "." }, - + "exclude": ["**/*.spec.ts"], "include": ["src"] } diff --git a/packages/protocol/src/action-protocol/contexts.spec..ts b/packages/protocol/src/action-protocol/contexts.spec.ts similarity index 98% rename from packages/protocol/src/action-protocol/contexts.spec..ts rename to packages/protocol/src/action-protocol/contexts.spec.ts index 5d7d491..9c56a0c 100644 --- a/packages/protocol/src/action-protocol/contexts.spec..ts +++ b/packages/protocol/src/action-protocol/contexts.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 8de8111..192698a 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -16,6 +16,7 @@ import * as uuid from 'uuid'; import { ActionMessage } from '../action-protocol'; +import { Disposable } from '../disposable/disposable'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export class ApplicationIdProvider { @@ -133,8 +134,9 @@ export interface GLSPClient { * Sets a handler/listener for action messages received from the server. * * @param handler The action message handler + * @returns A {@link Disposable} that can be used to unregister the handler */ - onActionMessage(handler: ActionMessageHandler): void; + onActionMessage(handler: ActionMessageHandler): Disposable; } export namespace GLSPClient { export interface Options { diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts new file mode 100644 index 0000000..65d75e0 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -0,0 +1,238 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { Disposable, Event, MessageConnection, NotificationHandler, ProgressType } from 'vscode-jsonrpc'; +import { ActionMessage } from '../../action-protocol/base-protocol'; +import { remove } from '../../utils/array-util'; +import { expectToThrowAsync } from '../../utils/test-util'; +import { ClientState } from '../glsp-client'; +import { BaseJsonrpcGLSPClient } from './base-jsonrpc-glsp-client'; +import { JsonrpcGLSPClient } from './glsp-jsonrpc-client'; + +class StubMessageConnection implements MessageConnection { + private mockEvent: Event = (listener: (e: any) => any, thisArgs?: any, disposables?: Disposable[]): Disposable => + Disposable.create(() => {}); + + sendRequest(...args: any[]): any { + throw new Error('Method not implemented.'); + } + + onRequest(...args: any[]): Disposable { + return Disposable.create(() => {}); + } + hasPendingResponse(): boolean { + return false; + } + sendNotification(...args: any[]): Promise { + return Promise.resolve(); + } + + onNotification(...args: any[]): Disposable { + return Disposable.create(() => {}); + } + + onProgress

(type: ProgressType

, token: string | number, handler: NotificationHandler

): Disposable { + throw new Error('Method not implemented.'); + } + sendProgress

(type: ProgressType

, token: string | number, value: P): Promise { + throw new Error('Method not implemented.'); + } + onUnhandledProgress = this.mockEvent; + + trace(...args: any[]): Promise { + return Promise.resolve(); + } + onError = this.mockEvent; + onClose = this.mockEvent; + listen(): void {} + onUnhandledNotification = this.mockEvent; + end(): void {} + onDispose = this.mockEvent; + dispose(): void {} + inspect(): void {} +} + +describe('Base JSON-RPC GLSP Client', () => { + const sandbox = sinon.createSandbox(); + const connection = sandbox.stub(new StubMessageConnection()); + let client = new BaseJsonrpcGLSPClient({ id: 'test', connectionProvider: connection }); + async function resetClient(setRunning = true): Promise { + sandbox.reset(); + client = new BaseJsonrpcGLSPClient({ id: 'test', connectionProvider: connection }); + if (setRunning) { + return client.start(); + } + } + + describe('start', () => { + it('should successfully start & activate the connection', async () => { + await resetClient(false); + expect(client.currentState).to.be.equal(ClientState.Initial); + client.start(); + expect(client.currentState).to.be.equal(ClientState.Starting); + const result = await client.start(); + expect(result).to.be.undefined; + expect(client.currentState).to.be.equal(ClientState.Running); + expect(client.isConnectionActive()).to.be.true; + }); + }); + + describe('stop', () => { + it('should successfully stop if the client was not running', async () => { + await resetClient(false); + expect(client.currentState).to.be.equal(ClientState.Initial); + const stopResult = await client.stop(); + expect(stopResult).to.be.undefined; + expect(client.currentState).to.be.equal(ClientState.Stopped); + }); + it('should successfully stop if the client was running', async () => { + await resetClient(); + client.stop(); + expect(client.currentState).to.be.equal(ClientState.Stopping); + const stopResult = await client.stop(); + expect(stopResult).to.be.undefined; + expect(client.currentState).to.be.equal(ClientState.Stopped); + }); + }); + + describe('initialize', () => { + it('should fail if client is not running', async () => { + await resetClient(false); + await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); + expect(connection.sendRequest.called).to.be.false; + }); + it('should forward the corresponding initialize request', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); + initializeMock.returns(expectedResult); + const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); + expect(result).to.deep.equals(expectedResult); + expect(initializeMock.calledOnce).to.be.true; + }); + }); + + describe('initializeClientSession', () => { + it('should fail if client is not running', async () => { + await resetClient(false); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + expect(connection.sendRequest.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + await resetClient(); + const params = { clientSessionId: '', diagramType: '' }; + const initializeSessionMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeClientSessionRequest, params); + const result = await client.initializeClientSession(params); + expect(result).to.be.undefined; + expect(initializeSessionMock.calledOnce).to.be.true; + }); + }); + + describe('disposeClientSession', () => { + it('should fail if client is not running', async () => { + await resetClient(false); + await expectToThrowAsync(() => client.disposeClientSession({ clientSessionId: '' })); + expect(connection.sendRequest.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + await resetClient(); + const params = { clientSessionId: 'someClient' }; + const disposeSessionMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.DisposeClientSessionRequest, params); + const result = await client.disposeClientSession(params); + expect(result).to.be.undefined; + expect(disposeSessionMock.calledOnce).to.be.true; + }); + }); + + describe('shutdownServer', () => { + it('should fail if client is not running', async () => { + await resetClient(false); + expect(() => client.shutdownServer()).to.throw(); + expect(connection.sendNotification.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + await resetClient(); + const shutdownMock = connection.sendNotification.withArgs(JsonrpcGLSPClient.ShutdownNotification); + const result = await client.shutdownServer(); + expect(result).to.be.undefined; + expect(shutdownMock.calledOnce).to.be.true; + }); + }); + + describe('sendActionMessage', () => { + it('should fail if client is not running', async () => { + await resetClient(false); + expect(() => client.sendActionMessage({ action: { kind: '' }, clientId: '' })).to.throw(); + expect(connection.sendNotification.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + await resetClient(); + const message = { action: { kind: '' }, clientId: '' }; + const messageMock = connection.sendNotification.withArgs(JsonrpcGLSPClient.ActionMessageNotification, message); + client.sendActionMessage({ action: { kind: '' }, clientId: '' }); + expect(messageMock.calledOnce).to.be.true; + }); + }); + + describe('onActionMessage', () => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + const handler = sandbox.spy((_message: ActionMessage): void => {}); + + it('should fail if client is not running', async () => { + await resetClient(false); + await expectToThrowAsync(() => client.onActionMessage(handler)); + expect(connection.onNotification.called).to.be.false; + }); + + it('should invoked the corresponding connection method', async () => { + await resetClient(); + client.onActionMessage(handler); + expect(connection.onNotification.withArgs(JsonrpcGLSPClient.ActionMessageNotification, handler).calledOnce).to.be.true; + }); + }); + + describe('Connection events', () => { + it('Should be in error state after connection error', async () => { + // mock setup + resetClient(false); + const listeners: ((e: any) => any)[] = []; + connection.onError.callsFake(listener => { + listeners.push(listener); + return Disposable.create(() => remove(listeners, listener)); + }); + + await client.start(); + listeners.forEach(listener => listener(new Error('SomeError'))); + expect(client.currentState).to.be.equal(ClientState.ServerError); + }); + it('Should be in error state after connection close while running', async () => { + // mock setup + resetClient(false); + const listeners: ((e: any) => any)[] = []; + connection.onClose.callsFake(listener => { + listeners.push(listener); + return Disposable.create(() => remove(listeners, listener)); + }); + + await client.start(); + listeners.forEach(listener => listener(undefined)); + expect(client.currentState).to.be.equal(ClientState.ServerError); + }); + }); +}); diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts similarity index 76% rename from packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts rename to packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 4434793..8dc91da 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -14,16 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Message, MessageConnection } from 'vscode-jsonrpc'; -import { ActionMessage } from '../action-protocol'; -import { ActionMessageHandler, ClientState, GLSPClient } from '../client-server-protocol/glsp-client'; -import { GLSPClientProxy } from '../client-server-protocol/glsp-server'; -import { - DisposeClientSessionParameters, - InitializeClientSessionParameters, - InitializeParameters, - InitializeResult -} from '../client-server-protocol/types'; +import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; +import { ActionMessage } from '../../action-protocol'; +import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; +import { GLSPClientProxy } from '../node/glsp-server'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; export class BaseJsonrpcGLSPClient implements GLSPClient { @@ -40,49 +35,41 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { } shutdownServer(): void { - if (this.checkConnectionState()) { - this.resolvedConnection!.sendNotification(JsonrpcGLSPClient.ShutdownNotification); - } + this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification); } initializeServer(params: InitializeParameters): Promise { - if (this.checkConnectionState()) { - return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); - } - return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); + return this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); } initializeClientSession(params: InitializeClientSessionParameters): Promise { - if (this.checkConnectionState()) { - return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, params); - } - return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); + return this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, params); } disposeClientSession(params: DisposeClientSessionParameters): Promise { - if (this.checkConnectionState()) { - return this.resolvedConnection!.sendRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, params); - } - return Promise.reject(JsonrpcGLSPClient.ClientNotReadyMsg); + return this.checkedConnection.sendRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, params); } - onActionMessage(handler: ActionMessageHandler): void { - if (this.checkConnectionState()) { - this.resolvedConnection!.onNotification(JsonrpcGLSPClient.ActionMessageNotification, handler); - } + onActionMessage(handler: ActionMessageHandler): Disposable { + return this.checkedConnection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, handler); } sendActionMessage(message: ActionMessage): void { - if (this.checkConnectionState()) { - this.resolvedConnection!.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); - } + this.checkedConnection.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); } - protected checkConnectionState(): boolean { + protected get checkedConnection(): MessageConnection { if (!this.isConnectionActive()) { throw new Error(JsonrpcGLSPClient.ClientNotReadyMsg); } - return true; + return this.resolvedConnection!; + } + + /** + * @Deprecated use {@link checkConnection} instead + */ + protected checkConnectionState(): boolean { + return this.checkedConnection !== undefined; } async start(): Promise { @@ -116,7 +103,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { })); } - private resolveConnection(): Promise { + protected resolveConnection(): Promise { if (!this.connectionPromise) { this.connectionPromise = this.doCreateConnection(); } @@ -154,7 +141,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { this.state = ClientState.ServerError; } - protected isConnectionActive(): boolean { + isConnectionActive(): boolean { return this.state === ClientState.Running && !!this.resolvedConnection; } diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts similarity index 86% rename from packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts rename to packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts index 3785181..db741fc 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts @@ -14,15 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { MessageConnection, NotificationType, NotificationType0, RequestType } from 'vscode-jsonrpc'; -import { ActionMessage } from '../action-protocol/base-protocol'; -import { GLSPClient } from '../client-server-protocol/glsp-client'; -import { - DisposeClientSessionParameters, - InitializeClientSessionParameters, - InitializeParameters, - InitializeResult -} from '../client-server-protocol/types'; -import { MaybePromise } from '../utils/type-util'; +import { ActionMessage } from '../../action-protocol/base-protocol'; +import { MaybePromise } from '../../utils/type-util'; +import { GLSPClient } from '../glsp-client'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; export type ConnectionProvider = MessageConnection | (() => MaybePromise); diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts similarity index 93% rename from packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts rename to packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts index 9ebf56c..22d9fab 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-server.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { MessageConnection } from 'vscode-jsonrpc'; -import { GLSPServer } from '../client-server-protocol/glsp-server'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '../client-server-protocol/types'; +import { GLSPServer } from '../node/glsp-server'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '../types'; import { JsonrpcGLSPClient } from './glsp-jsonrpc-client'; /** diff --git a/packages/protocol/src/jsonrpc/websocket-connection.ts b/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts similarity index 100% rename from packages/protocol/src/jsonrpc/websocket-connection.ts rename to packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts diff --git a/packages/protocol/src/client-server-protocol/glsp-server.ts b/packages/protocol/src/client-server-protocol/node/glsp-server.ts similarity index 96% rename from packages/protocol/src/client-server-protocol/glsp-server.ts rename to packages/protocol/src/client-server-protocol/node/glsp-server.ts index 5708548..bb56f46 100644 --- a/packages/protocol/src/client-server-protocol/glsp-server.ts +++ b/packages/protocol/src/client-server-protocol/node/glsp-server.ts @@ -14,13 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from '../action-protocol'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; +import { ActionMessage } from '../../action-protocol'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; /** - * Interface for implementations of a server component using json-rpc for client-server communication. + * Interface for implementations of a ts server component. * Based on the specification of the Graphical Language Server Protocol: - * https://github.com/eclipse-glsp/glsp/blob/master/PROTOCOL.md + * https://www.eclipse.org/glsp/documentation/protocol/ */ export interface GLSPServer { /** diff --git a/packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts new file mode 100644 index 0000000..b754dbe --- /dev/null +++ b/packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts @@ -0,0 +1,237 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as util from 'util'; +import { Action, ActionMessage } from '../..'; +import { expectToThrowAsync } from '../../utils/test-util'; +import { ClientState } from '../glsp-client'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; +import { GLSPServer, GLSPServerListener } from './glsp-server'; +import { NodeGLSPClient } from './node-glsp-client'; + +class StubGLSPServer implements GLSPServer { + initialize(params: InitializeParameters): Promise { + return Promise.resolve({ protocolVersion: '1.0.0', serverActions: {} }); + } + initializeClientSession(params: InitializeClientSessionParameters): Promise { + return Promise.resolve(); + } + disposeClientSession(params: DisposeClientSessionParameters): Promise { + return Promise.resolve(); + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + process(message: ActionMessage): void {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + shutdown(): void {} + addListener(listener: GLSPServerListener): boolean { + return false; + } + removeListener(listener: GLSPServerListener): boolean { + return false; + } +} + +describe('Node GLSP Client', () => { + const sandbox = sinon.createSandbox(); + + const server = sandbox.stub(new StubGLSPServer()); + + // Shared test client instance that is already in running state + let client = new NodeGLSPClient({ id: 'test' }); + function resetClient(setRunning = true): void { + sandbox.reset(); + client = new NodeGLSPClient({ id: 'test' }); + if (setRunning) { + client['_server'] = server; + client['state'] = ClientState.Running; + } + } + + it('Should be in initial state after construction', () => { + resetClient(false); + expect(client.currentState).to.be.equal(ClientState.Initial); + }); + + describe('start', () => { + it('should fail if no server is configured', async () => { + resetClient(false); + client.setStartupTimeout(5); + await expectToThrowAsync(() => client.start()); + expect(client.currentState).to.be.equal(ClientState.StartFailed); + }); + it('Should resolve when server is configured', async () => { + resetClient(false); + client.configureServer(server); + const result = await client.start(); + expect(result).to.be.undefined; + expect(client.currentState).to.be.equal(ClientState.Running); + }); + }); + + describe('stop & onStop', () => { + beforeEach(() => resetClient()); + it('onStop should not resolve if stop has not been called', () => { + expect(util.inspect(client.onStop())).to.include('pending'); + }); + it('should be in stopped state and onStop should resolve', async () => { + expect(client.currentState).to.be.not.equal(ClientState.Stopped); + const stopResult = await client.stop(); + expect(stopResult).to.be.undefined; + expect(client.currentState).to.be.equal(ClientState.Stopped); + const onStopResult = await client.onStop(); + expect(onStopResult).to.be.undefined; + expect(server.shutdown.calledOnce).to.be.true; + }); + }); + + describe('initialize', () => { + it('should fail if server is not configured', async () => { + resetClient(false); + await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); + expect(server.initialize.called).to.be.false; + }); + it('should fail if client is not running', async () => { + resetClient(false); + client.configureServer(server); + await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); + expect(server.initialize.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + server.initialize.returns(Promise.resolve(expectedResult)); + + const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); + expect(result).to.deep.equals(expectedResult); + expect(server.initialize.calledOnce).to.be.true; + }); + }); + + describe('initializeClientSession', () => { + it('should fail if server is not configured', async () => { + resetClient(false); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + expect(server.initializeClientSession.called).to.be.false; + }); + it('should fail if client is not running', async () => { + resetClient(false); + client.configureServer(server); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + expect(server.initializeClientSession.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + resetClient(); + const result = await client.initializeClientSession({ clientSessionId: '', diagramType: '' }); + expect(result).to.be.undefined; + expect(server.initializeClientSession.calledOnce).to.be.true; + }); + }); + + describe('disposeClientSession', () => { + it('should fail if server is not configured', async () => { + resetClient(false); + await expectToThrowAsync(() => client.disposeClientSession({ clientSessionId: '' })); + expect(server.disposeClientSession.called).to.be.false; + }); + it('should fail if client is not running', async () => { + resetClient(false); + client.configureServer(server); + await expectToThrowAsync(() => client.disposeClientSession({ clientSessionId: '' })); + expect(server.disposeClientSession.called).to.be.false; + }); + it('should invoke the corresponding server method', async () => { + resetClient(); + + const result = await client.disposeClientSession({ clientSessionId: '' }); + expect(result).to.be.undefined; + expect(server.disposeClientSession.calledOnce).to.be.true; + }); + }); + + describe('shutdownServer', () => { + it('should fail if server is not configured', () => { + resetClient(false); + expect(() => client.shutdownServer()).to.throw(); + expect(server.shutdown.called).to.be.false; + }); + it('should fail if client is not running', () => { + resetClient(false); + client.configureServer(server); + expect(() => client.shutdownServer()).to.throw(); + expect(server.shutdown.called).to.be.false; + }); + it('should invoke the corresponding server method', () => { + resetClient(); + client.shutdownServer(); + expect(server.shutdown.calledOnce).to.be.true; + }); + }); + + describe('sendActionMessage', () => { + it('should fail if server is not configured', () => { + resetClient(false); + expect(() => client.sendActionMessage({ action: { kind: '' }, clientId: '' })).to.throw(); + expect(server.process.called).to.be.false; + }); + it('should fail if client is not running', () => { + resetClient(false); + client.configureServer(server); + expect(() => client.sendActionMessage({ action: { kind: '' }, clientId: '' })).to.throw(); + expect(server.process.called).to.be.false; + }); + it('should invoke the corresponding server method', () => { + resetClient(); + client.sendActionMessage({ action: { kind: '' }, clientId: '' }); + expect(server.process.calledOnce).to.be.true; + }); + }); + + describe('onActionMessage', () => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + const handler = sandbox.spy((_message: ActionMessage): void => {}); + it('should be properly registered if server is not configured', () => { + resetClient(false); + client.onActionMessage(handler); + expect(client['actionMessageHandlers'].length).to.be.equal(1); + expect(handler.called).to.be.false; + }); + it('should be properly registered if client is not running', () => { + resetClient(false); + client.configureServer(server); + client.onActionMessage(handler); + expect(client['actionMessageHandlers'].length).to.be.equal(1); + expect(handler.called).to.be.false; + }); + it('should be unregistered if dispose is invoked', () => { + resetClient(false); + const toDispose = client.onActionMessage(handler); + + expect(client['actionMessageHandlers'].length).to.be.equal(1); + toDispose.dispose(); + expect(client['actionMessageHandlers'].length).to.be.equal(0); + }); + it('handler should be invoked when the an action message is sent via proxy', () => { + resetClient(); + client.onActionMessage(handler); + const expectedMessage = { action: { kind: 'someAction' }, clientId: 'someClientId' }; + client.proxy.process(expectedMessage); + expect(handler.calledOnce).to.be.true; + expect(handler.firstCall.args[0]).to.deep.equals(expectedMessage); + }); + }); +}); diff --git a/packages/protocol/src/client-server-protocol/node/node-glsp-client.ts b/packages/protocol/src/client-server-protocol/node/node-glsp-client.ts new file mode 100644 index 0000000..32c0506 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/node/node-glsp-client.ts @@ -0,0 +1,162 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Deferred } from 'sprotty-protocol'; +import { Disposable } from 'vscode-jsonrpc'; +import { Action, ActionMessage } from '../../action-protocol'; +import { distinctAdd, remove } from '../../utils/array-util'; +import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; +import { GLSPClientProxy, GLSPServer } from './glsp-server'; + +/** + * A simple {@link GLSPClient} implementation for use cases where the client & server are running + * in the same (node) context i.e. process without a communication layer (like json-rpc) between. + */ +export class NodeGLSPClient implements GLSPClient { + protected state: ClientState; + protected _server?: GLSPServer; + protected serverDeferred = new Deferred(); + protected onStartDeferred = new Deferred(); + protected onStopDeferred = new Deferred(); + readonly proxy: GLSPClientProxy; + protected startupTimeout = 1500; + protected actionMessageHandlers: ActionMessageHandler[] = []; + + constructor(protected options: GLSPClient.Options) { + this.state = ClientState.Initial; + this.proxy = this.createProxy(); + } + + protected createProxy(): GLSPClientProxy { + return { + process: message => { + if (this.actionMessageHandlers.length === 0) { + console.warn('No ActionMessageHandler is configured- Cannot process server message', message); + return; + } + [...this.actionMessageHandlers].forEach(handler => handler(message)); + } + }; + } + + configureServer(server: GLSPServer): void { + if (this.state === ClientState.Running) { + throw new Error('Could not configure new server. The GLSPClient is already running'); + } + this.serverDeferred.resolve(server); + } + + start(): Promise { + if (this.state !== ClientState.Initial) { + return this.onStartDeferred.promise; + } + + this.state = ClientState.Starting; + const timeOut = new Promise((_, reject) => + setTimeout(() => { + reject(new Error('Could not start client. No server is configured')); + }, this.startupTimeout) + ); + Promise.race([this.serverDeferred.promise, timeOut]) + .then(server => { + this._server = server; + this.state = ClientState.Running; + this.onStartDeferred.resolve(); + }) + .catch(error => { + this.state = ClientState.StartFailed; + this.onStartDeferred.reject(error); + }); + + return this.onStartDeferred.promise; + } + + initializeServer(params: InitializeParameters): Promise { + return this.checkedServer.initialize(params); + } + + initializeClientSession(params: InitializeClientSessionParameters): Promise { + return this.checkedServer.initializeClientSession(params); + } + + disposeClientSession(params: DisposeClientSessionParameters): Promise { + return this.checkedServer.disposeClientSession(params); + } + + shutdownServer(): void { + this.checkedServer.shutdown(); + } + + async stop(): Promise { + if (this.state === ClientState.Stopped || this.state === ClientState.Stopping) { + return this.onStop(); + } + + this.state = ClientState.Stopping; + try { + if (this._server) { + this._server.shutdown(); + } + } finally { + this.state = ClientState.Stopped; + this.onStopDeferred.resolve(); + } + } + + sendActionMessage(message: ActionMessage): void { + this.checkedServer.process(message); + } + + onActionMessage(handler: ActionMessageHandler): Disposable { + distinctAdd(this.actionMessageHandlers, handler); + return Disposable.create(() => remove(this.actionMessageHandlers, handler)); + } + + get currentState(): ClientState { + return this.state; + } + + onStart(): Promise { + return this.onStartDeferred.promise; + } + + onStop(): Promise { + return this.onStopDeferred.promise; + } + + get id(): string { + return this.options.id; + } + + protected checkState(): void | never { + if (this.state !== ClientState.Running) { + throw new Error(`Client with id '${this.id}' is not in 'Running' state`); + } + } + + protected get checkedServer(): GLSPServer { + this.checkState(); + if (!this._server) { + throw new Error(`No server is configured for GLSPClient with id '${this.id}'`); + } + return this._server; + } + + setStartupTimeout(ms: number): void { + this.startupTimeout = ms; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 246903b..9fd38f7 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -48,13 +48,14 @@ export * from 'sprotty-protocol/lib/utils/model-utils'; // Default export of @eclipse-glsp/protocol export * from './action-protocol'; export * from './client-server-protocol/glsp-client'; -export * from './client-server-protocol/glsp-server'; +export * from './client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; +export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; +export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; +export * from './client-server-protocol/jsonrpc/websocket-connection'; +export * from './client-server-protocol/node/glsp-server'; +export * from './client-server-protocol/node/node-glsp-client'; export * from './client-server-protocol/types'; export * from './disposable/disposable'; -export * from './jsonrpc/base-jsonrpc-glsp-client'; -export * from './jsonrpc/glsp-jsonrpc-client'; -export * from './jsonrpc/glsp-jsonrpc-server'; -export * from './jsonrpc/websocket-connection'; export * from './model/default-types'; export * from './model/model-schema'; export * from './utils/array-util'; diff --git a/packages/protocol/src/utils/di-util.ts b/packages/protocol/src/utils/di-util.ts index 097feb5..7bc0f29 100644 --- a/packages/protocol/src/utils/di-util.ts +++ b/packages/protocol/src/utils/di-util.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Container, ContainerModule, interfaces } from 'inversify'; -import { asArray, distinctAdd, MaybeArray, remove } from './array-util'; +import { MaybeArray, asArray, distinctAdd, remove } from './array-util'; import { hasFunctionProp, hasNumberProp } from './type-util'; /** @@ -30,7 +30,7 @@ export interface BindingContext { /** * Initializes a container with the given {@link ContainerConfiguration}. The container configuration - * consists of the set of {@link ContainerModule}s that should be loaded in the container and/or + * consists of the set of {@link ContainerModule}s that should be loaded in the container. * In addition, for more fine-grained control {@link ModuleConfiguration}s can be passed as part fo the container configuration * Module loading is distinct,this means each module will only get loaded once even if it is configured multiple times. @param containerConfiguration diff --git a/packages/protocol/src/utils/test-util.ts b/packages/protocol/src/utils/test-util.ts new file mode 100644 index 0000000..deaa74c --- /dev/null +++ b/packages/protocol/src/utils/test-util.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { expect } from 'chai'; +import { MaybePromise } from './type-util'; + +/* + * Utility classes used for testing. + * Only available in the testing context. Should not be used in production code. + */ + +/** + * Creates a promise that resolves after the given timeout + * @param timeout the timeout in milliseconds + * @returns + */ +export async function delay(timeout: number): Promise { + return new Promise(resolve => setTimeout(resolve, timeout)); +} + +/** + * Consumes a maybe async function and checks for error + * @param method - The function to check + * @param message - Optional message to match with error message + */ +export async function expectToThrowAsync(toEvaluate: () => MaybePromise, message?: string): Promise { + let err: Error | undefined = undefined; + try { + await toEvaluate(); + } catch (error: any) { + err = error; + } + if (message) { + expect(err?.message).to.be.equal(message); + } else { + expect(err).to.be.an('Error'); + } +} diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index 3fed2b4..8dc48d5 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -6,5 +6,6 @@ "reactNamespace": "JSX", "baseUrl": "." }, + "exclude": ["**/*.spec.ts", "src/utils/test-util.ts"], "include": ["src"] } From e44749b52beed9f9016c4e1747b38fa9eb4e3b41 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 21 May 2023 13:03:17 -0700 Subject: [PATCH 202/566] GLSP-813 Use Node example server instead of java variant (#248) Remove download script for Java Workflow Server and consume node variant via npm instead. Update README to reflect the changes. Part of https://github.com/eclipse-glsp/glsp/issues/813 --- examples/workflow-glsp/scripts/config.json | 9 ------ examples/workflow-glsp/scripts/download.ts | 35 ---------------------- examples/workflow-standalone/package.json | 1 + 3 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 examples/workflow-glsp/scripts/config.json delete mode 100644 examples/workflow-glsp/scripts/download.ts diff --git a/examples/workflow-glsp/scripts/config.json b/examples/workflow-glsp/scripts/config.json deleted file mode 100644 index 7a599ab..0000000 --- a/examples/workflow-glsp/scripts/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "releaseRepository": "https://repo1.maven.org/maven2/", - "snapshotRepository": "https://oss.sonatype.org/content/repositories/snapshots/", - "groupId": "org.eclipse.glsp.example", - "artifactId": "org.eclipse.glsp.example.workflow", - "version": "1.1.0", - "isSnapShot": true, - "classifier": "glsp" -} diff --git a/examples/workflow-glsp/scripts/download.ts b/examples/workflow-glsp/scripts/download.ts deleted file mode 100644 index cdf07f3..0000000 --- a/examples/workflow-glsp/scripts/download.ts +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import download from 'mvn-artifact-download'; -import { join, resolve } from 'path'; -import * as config from './config.json'; - -const downloadDir = resolve(join(__dirname)) + '/../../..'; - -const { groupId, artifactId, classifier, version, isSnapShot } = config; -const mavenRepository = isSnapShot ? config.snapshotRepository : config.releaseRepository; - -console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...'); -download({ groupId, artifactId, version, classifier, isSnapShot }, downloadDir, mavenRepository).then(() => - console.log( - 'Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-' + - version + - `${isSnapShot ? '-SNAPSHOT' : ''}` + - '-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher --port=8081 --websocket\n\n' + - 'After starting the server, access the following file locally in your browser to see the running example:\n' + - './examples/workflow-standalone/app/diagram.html' - ) -); diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 06ede15..309ce38 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -34,6 +34,7 @@ "@eclipse-glsp/client": "1.1.0-next" }, "devDependencies": { + "@eclipse-glsp-examples/workflow-server": "next", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", "path-browserify": "^1.0.1", From 8e5b7b007c6956aa04fc6db6cf7db74686a6bda4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 May 2023 12:49:51 +0200 Subject: [PATCH 203/566] GLSP-1004 Update publish process Exclude spec file from published content. Also include bundled browser workflow sever into published content Part of https://github.com/eclipse-glsp/glsp/issues/1004 --- examples/workflow-server/package.json | 4 +++- packages/graph/tsconfig.json | 4 ++-- packages/layout-elk/tsconfig.json | 1 + packages/server/tsconfig.json | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 93effb8..0952a37 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -44,7 +44,8 @@ "node.d.ts", "browser.d.ts", "browser.js", - "bundle/wf-glsp-server-node.js" + "bundle/wf-glsp-server-node.js", + "bundle/wf-glsp-server-webworker.js" ], "scripts": { "build": "tsc -b && yarn bundle", @@ -54,6 +55,7 @@ "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "prepare": "yarn clean && yarn build", + "prepublish": "yarn bundle && yarn bundle:browser", "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json index c28991c..183726a 100644 --- a/packages/graph/tsconfig.json +++ b/packages/graph/tsconfig.json @@ -7,6 +7,6 @@ "baseUrl": "src", "types": ["node", "mocha", "reflect-metadata"] }, - "include": ["src"], - "exclude": ["**/*.spec.ts"] + "exclude": ["**/*.spec.ts"], + "include": ["src"] } diff --git a/packages/layout-elk/tsconfig.json b/packages/layout-elk/tsconfig.json index 1fe0813..183726a 100644 --- a/packages/layout-elk/tsconfig.json +++ b/packages/layout-elk/tsconfig.json @@ -7,5 +7,6 @@ "baseUrl": "src", "types": ["node", "mocha", "reflect-metadata"] }, + "exclude": ["**/*.spec.ts"], "include": ["src"] } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 0ff9927..e7ecba5 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -5,5 +5,6 @@ "outDir": "lib", "baseUrl": "src" }, + "exclude": ["**/*.spec.ts", "src/common/test/mock-util.ts"], "include": ["src"] } From 75dde97584829c3414ff7d85667377793cbb95f8 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 May 2023 15:52:11 +0200 Subject: [PATCH 204/566] Revert web workflow example publishing The webbased example uses a mock file storage so there is no real purpose in publishing it as its not really reusable --- examples/workflow-server/package.json | 4 +--- packages/layout-elk/src/di.config.spec.ts | 26 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 0952a37..93effb8 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -44,8 +44,7 @@ "node.d.ts", "browser.d.ts", "browser.js", - "bundle/wf-glsp-server-node.js", - "bundle/wf-glsp-server-webworker.js" + "bundle/wf-glsp-server-node.js" ], "scripts": { "build": "tsc -b && yarn bundle", @@ -55,7 +54,6 @@ "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "prepare": "yarn clean && yarn build", - "prepublish": "yarn bundle && yarn bundle:browser", "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/packages/layout-elk/src/di.config.spec.ts b/packages/layout-elk/src/di.config.spec.ts index af6a1be..259c288 100644 --- a/packages/layout-elk/src/di.config.spec.ts +++ b/packages/layout-elk/src/di.config.spec.ts @@ -13,8 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DefaultModelState, GGraph, GModelElementConstructor, ModelState } from '@eclipse-glsp/server'; -import { StubDiagramConfiguration } from '@eclipse-glsp/server/lib/common/test/mock-util'; +import { + DefaultModelState, + DiagramConfiguration, + EdgeTypeHint, + GGraph, + GModelElementConstructor, + ModelState, + ServerLayoutKind, + ShapeTypeHint +} from '@eclipse-glsp/server'; import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; import * as sinon from 'sinon'; @@ -28,6 +36,20 @@ class CustomLayoutConfigurator extends AbstractLayoutConfigurator {} @injectable() class CustomElementFilter extends DefaultElementFilter {} +class StubDiagramConfiguration implements DiagramConfiguration { + typeMapping = new Map(); + + shapeTypeHints: ShapeTypeHint[] = []; + + edgeTypeHints: EdgeTypeHint[] = []; + + layoutKind = ServerLayoutKind.NONE; + + needsClientLayout = true; + + animatedUpdate = true; +} + describe('test configureELKLayoutModule', () => { const sandbox = sinon.createSandbox(); const mockDiagramConfiguration = new StubDiagramConfiguration(); From 281cffd258457b2db4938130f1620c4f1ff9cfdc Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 May 2023 12:37:56 -0700 Subject: [PATCH 205/566] GLSP-975: Rename `NodeGLSPClient` (#249) The `NodeGLSPClient` can also be used in a browser context => rename to `BaseGLSPClient`. This way we can introduce a consistent naming scheme for generic base client implementations: `BaseGLSPClient`. For instance if are going to provide a REST implementation in the future it would be called `BaseRestGLSPClient`. --- ...lsp-client.spec.ts => base-glsp-client.spec.ts} | 14 +++++++------- .../node-glsp-client.ts => base-glsp-client.ts} | 13 +++++++------ .../{node => }/glsp-server.ts | 4 ++-- .../jsonrpc/base-jsonrpc-glsp-client.ts | 2 +- .../jsonrpc/glsp-jsonrpc-server.ts | 2 +- packages/protocol/src/index.ts | 4 ++-- 6 files changed, 20 insertions(+), 19 deletions(-) rename packages/protocol/src/client-server-protocol/{node/node-glsp-client.spec.ts => base-glsp-client.spec.ts} (96%) rename packages/protocol/src/client-server-protocol/{node/node-glsp-client.ts => base-glsp-client.ts} (92%) rename packages/protocol/src/client-server-protocol/{node => }/glsp-server.ts (98%) diff --git a/packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts similarity index 96% rename from packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts rename to packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index b754dbe..db9a459 100644 --- a/packages/protocol/src/client-server-protocol/node/node-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -17,12 +17,12 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import * as util from 'util'; -import { Action, ActionMessage } from '../..'; -import { expectToThrowAsync } from '../../utils/test-util'; -import { ClientState } from '../glsp-client'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; +import { Action, ActionMessage } from '../action-protocol'; +import { expectToThrowAsync } from '../utils/test-util'; +import { BaseGLSPClient } from './base-glsp-client'; +import { ClientState } from './glsp-client'; import { GLSPServer, GLSPServerListener } from './glsp-server'; -import { NodeGLSPClient } from './node-glsp-client'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; class StubGLSPServer implements GLSPServer { initialize(params: InitializeParameters): Promise { @@ -52,10 +52,10 @@ describe('Node GLSP Client', () => { const server = sandbox.stub(new StubGLSPServer()); // Shared test client instance that is already in running state - let client = new NodeGLSPClient({ id: 'test' }); + let client = new BaseGLSPClient({ id: 'test' }); function resetClient(setRunning = true): void { sandbox.reset(); - client = new NodeGLSPClient({ id: 'test' }); + client = new BaseGLSPClient({ id: 'test' }); if (setRunning) { client['_server'] = server; client['state'] = ClientState.Running; diff --git a/packages/protocol/src/client-server-protocol/node/node-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts similarity index 92% rename from packages/protocol/src/client-server-protocol/node/node-glsp-client.ts rename to packages/protocol/src/client-server-protocol/base-glsp-client.ts index 32c0506..624eb14 100644 --- a/packages/protocol/src/client-server-protocol/node/node-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -16,17 +16,18 @@ import { Deferred } from 'sprotty-protocol'; import { Disposable } from 'vscode-jsonrpc'; -import { Action, ActionMessage } from '../../action-protocol'; -import { distinctAdd, remove } from '../../utils/array-util'; -import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; +import { Action, ActionMessage } from '../action-protocol'; +import { distinctAdd, remove } from '../utils/array-util'; +import { ActionMessageHandler, ClientState, GLSPClient } from './glsp-client'; import { GLSPClientProxy, GLSPServer } from './glsp-server'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; /** * A simple {@link GLSPClient} implementation for use cases where the client & server are running - * in the same (node) context i.e. process without a communication layer (like json-rpc) between. + * in the same context/process without a communication layer (like json-rpc) between. The client + * directly communicates with a given {@link GLSPServer} instance. */ -export class NodeGLSPClient implements GLSPClient { +export class BaseGLSPClient implements GLSPClient { protected state: ClientState; protected _server?: GLSPServer; protected serverDeferred = new Deferred(); diff --git a/packages/protocol/src/client-server-protocol/node/glsp-server.ts b/packages/protocol/src/client-server-protocol/glsp-server.ts similarity index 98% rename from packages/protocol/src/client-server-protocol/node/glsp-server.ts rename to packages/protocol/src/client-server-protocol/glsp-server.ts index bb56f46..a4e1d87 100644 --- a/packages/protocol/src/client-server-protocol/node/glsp-server.ts +++ b/packages/protocol/src/client-server-protocol/glsp-server.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from '../../action-protocol'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; +import { ActionMessage } from '../action-protocol'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; /** * Interface for implementations of a ts server component. diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 8dc91da..5dea44c 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -17,7 +17,7 @@ import { injectable } from 'inversify'; import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; import { ActionMessage } from '../../action-protocol'; import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; -import { GLSPClientProxy } from '../node/glsp-server'; +import { GLSPClientProxy } from '../glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; import { ConnectionProvider, JsonrpcGLSPClient } from './glsp-jsonrpc-client'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts index 22d9fab..48ec0a0 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-server.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { MessageConnection } from 'vscode-jsonrpc'; -import { GLSPServer } from '../node/glsp-server'; +import { GLSPServer } from '../glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '../types'; import { JsonrpcGLSPClient } from './glsp-jsonrpc-client'; diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 9fd38f7..f591976 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -47,13 +47,13 @@ export * from 'sprotty-protocol/lib/utils/json'; export * from 'sprotty-protocol/lib/utils/model-utils'; // Default export of @eclipse-glsp/protocol export * from './action-protocol'; +export * from './client-server-protocol/base-glsp-client'; export * from './client-server-protocol/glsp-client'; +export * from './client-server-protocol/glsp-server'; export * from './client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; export * from './client-server-protocol/jsonrpc/websocket-connection'; -export * from './client-server-protocol/node/glsp-server'; -export * from './client-server-protocol/node/node-glsp-client'; export * from './client-server-protocol/types'; export * from './disposable/disposable'; export * from './model/default-types'; From b27860a8885e34112867c9ecc46ee0d6a130e573 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 May 2023 16:36:15 -0700 Subject: [PATCH 206/566] Adjust WebSocketWrapper (#250) Adapt send() method of the `WebSocketWrapper` to support all types that are supported by the underlying websocket send method as well. --- .../src/client-server-protocol/jsonrpc/websocket-connection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts b/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts index cb037e5..7f1fa63 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts @@ -35,7 +35,7 @@ import { * independent of the underlying WebSocket implementation/library. e.g. one could use Socket.io instead of plain WebSockets */ export interface WebSocketWrapper extends Disposable { - send(content: string): void; + send(content: string | ArrayBufferLike | ArrayBufferView): void; onMessage(cb: (data: any) => void): void; onError(cb: (reason: any) => void): void; onClose(cb: (code: number, reason: string) => void): void; From 122c97bff0332ebed47230c0903a1d0fdc93638b Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sat, 27 May 2023 22:26:30 +0200 Subject: [PATCH 207/566] Update config dependencies (#1020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - In preparation for the 2.0.0 Release all config dependencies for the `@eclipse-glsp/config`, `@eclipse-glsp/config-test` and `@eclipse-glsp/dev` packages have been updated to the latest version. - Remove compile-time warnings regarding unfulfilled peerDependencies by adding a corresponding `devDependency` to the root package.json - Update `@eclipse-glsp/ts-config` configuration files. - Remove mocha types from default `ts-config.json` otherwise mocha is an indirect dependency even for projects that don't use mocha tests. - Add additional test config for mocha testing that can be consumed with `@eclipse-glsp/tsconfig/mocha´ - Update READMEs of config packages --- dev-packages/CHANGELOG.md | 28 ----------------------- dev-packages/cli/package.json | 13 +++++------ dev-packages/cli/src/app.ts | 1 + dev-packages/cli/tsconfig.json | 13 +++++++---- dev-packages/config-test/README.md | 2 +- dev-packages/config-test/package.json | 10 ++++---- dev-packages/config/README.md | 10 +++++--- dev-packages/config/package.json | 19 ++++++++------- dev-packages/dev/README.md | 16 +++++++++---- dev-packages/eslint-config/package.json | 1 + dev-packages/mocha-config/README.md | 2 +- dev-packages/mocha-config/package.json | 7 +++--- dev-packages/nyc-config/README.md | 6 +++-- dev-packages/prettier-config/README.md | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/README.md | 6 ++++- dev-packages/ts-config/mocha.json | 7 ++++++ dev-packages/ts-config/package.json | 7 +++--- dev-packages/ts-config/tsconfig.json | 3 ++- 19 files changed, 77 insertions(+), 78 deletions(-) delete mode 100644 dev-packages/CHANGELOG.md create mode 100644 dev-packages/ts-config/mocha.json diff --git a/dev-packages/CHANGELOG.md b/dev-packages/CHANGELOG.md deleted file mode 100644 index 4b3d7b3..0000000 --- a/dev-packages/CHANGELOG.md +++ /dev/null @@ -1,28 +0,0 @@ -# Eclipse GLSP Dev Packages Changelog - -## v1.1.0 - upcoming - -- [config] Introduce all-in-one [`@eclipse-glsp/dev`](https://www.npmjs.com/package/@eclipse-glsp/dev) meta package. [#842](https://github.com/eclipse-glsp/glsp/pull/842) -- [cli] Contribute the `checkHeaders` command to validate the copyright year (range) of license headers. [#834](https://github.com/eclipse-glsp/glsp/pull/834) -- [config] Introduce [`@eclipse-glsp/nyc-config`](https://www.npmjs.com/package/@eclipse-glsp/nyc-config) - package and the [`@eclipse-glsp/config-test`](https://www.npmjs.com/package/@eclipse-glsp/config-test) meta package. [#755](https://github.com/eclipse-glsp/glsp/pull/755) - - - Contribute the `coverageReport` command to create a full nyc test coverage report for a lerna/yarn mono repository - -- [cli] Introduce [`@eclipse-glsp/cli`](https://www.npmjs.com/package/@eclipse-glsp/cli) - package to offer CLI tooling & utility scripts for GLSP projects. [#755](https://github.com/eclipse-glsp/glsp/pull/755) - Contributed on behalf of STMicroelectronics - - Contribute the `release` command to prepare & publish a new Github release for a specific GLSP component - -### Breaking Changes - -- [node] Update minimum requirements for Node to >=16.11.0 [#829](https://github.com/eclipse-glsp/glsp/pull/829) - -## [v1.0.0 - 30/06/2022](https://github.com/eclipse-glsp/glsp/releases/tag/v1.0.0) - -Inception of the GLSP dev packages. -This project is part of the GLSP umbrella repository and provides common shared development packages for Eclipse GLSP components that are implemented with Typescript. - -- [`@eclipse-glsp/config`](https://www.npmjs.com/package/@eclipse-glsp/config): Meta package for shared build configuration - - [`@eclipse-glsp/ts-config`](https://www.npmjs.com/package/@eclipse-glsp/ts-config): Shared Typescript configuration for GLSP projects - - [`@eclipse-glsp/eslint-config`](https://www.npmjs.com/package/@eclipse-glsp/esling-config): Shared ESLint configuration for GLSP projects - - [`@eclipse-glsp/prettier-config`](https://www.npmjs.com/package/@eclipse-glsp/prettier-config): Shared Prettier configuration for GLSP projects diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index b8d86e0..7eee273 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -41,12 +41,12 @@ "watch": "tsc -w" }, "dependencies": { - "commander": "^9.4.0", - "glob": "^8.0.3", - "node-fetch": "2.6.7", + "commander": "^10.0.1", + "glob": "^10.2.6", + "node-fetch": "^2.6.11", "readline-sync": "^1.4.10", - "semver": "^7.3.7", - "shelljs": "0.8.5" + "semver": "^7.5.1", + "shelljs": "^0.8.5" }, "devDependencies": { "@eclipse-glsp/config": "1.0.0-next", @@ -54,8 +54,7 @@ "@types/node-fetch": "2.6.2", "@types/readline-sync": "1.4.4", "@types/semver": "7.3.12", - "@types/shelljs": "0.8.11", - "ts-node": "^10.9.1" + "@types/shelljs": "0.8.11" }, "publishConfig": { "access": "public" diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index cc78b3d..c373b1a 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -18,6 +18,7 @@ import { CheckHeaderCommand } from './commands/check-header'; import { CoverageReportCommand } from './commands/coverage-report'; import { ReleaseCommand } from './commands/release/release'; import { baseCommand } from './util/command-util'; + export const COMMAND_VERSION = '1.1.0-next'; const app = baseCommand() // diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index ecf9295..14c273a 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -1,10 +1,13 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": "." + "baseUrl": ".", + "esModuleInterop": true, + "resolveJsonModule": true }, - - "include": ["src"] -} + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/dev-packages/config-test/README.md b/dev-packages/config-test/README.md index 165ad1c..2acbca6 100644 --- a/dev-packages/config-test/README.md +++ b/dev-packages/config-test/README.md @@ -24,7 +24,7 @@ yarn add --dev @eclipse-glsp/config-test ### Mocha -**Create a `.mocharc.json`**: +**Create a `.mocharc`**: ```json { diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 1e97db0..d385b15 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -27,17 +27,17 @@ "dependencies": { "@eclipse-glsp/mocha-config": "1.0.0-next", "@eclipse-glsp/nyc-config": "1.0.0-next", - "@istanbuljs/nyc-config-typescript": "^1.0.0", - "@types/chai": "^4.3.4", + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.13", "chai": "^4.3.7", "ignore-styles": "^5.0.1", - "mocha": "^10.1.0", + "mocha": "^10.2.0", "mocha-jenkins-reporter": "^0.4.8", - "nyc": "^15.0.0", + "nyc": "^15.1.0", "reflect-metadata": "^0.1.13", - "sinon": "^15.0.0", + "sinon": "^15.1.0", "ts-node": "^10.9.1" }, "publishConfig": { diff --git a/dev-packages/config/README.md b/dev-packages/config/README.md index 59fbdfc..5c6859c 100644 --- a/dev-packages/config/README.md +++ b/dev-packages/config/README.md @@ -9,7 +9,7 @@ Provides a meta package that export common configuration objects for: The package is available via npm and is used by all GLSP components implemented with Typescript. ESLint and prettier are included as direct dependencies. -In addition, common GLSP dev dependencies like lerna,typescript,rimraf etc. are included. +In addition, common GLSP dev dependencies like `lerna`, `typescript`, `rimraf`, etc. are included. ## Components @@ -31,7 +31,7 @@ yarn add --dev @eclipse-glsp/config ```json { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -39,6 +39,10 @@ yarn add --dev @eclipse-glsp/config } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ### ESLint **Create a `.eslintrc.js`**: @@ -65,7 +69,7 @@ module.exports = { } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` file to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 2a220d1..064740c 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -27,20 +27,19 @@ "@eclipse-glsp/eslint-config": "1.0.0-next", "@eclipse-glsp/prettier-config": "1.0.0-next", "@eclipse-glsp/ts-config": "1.0.0-next", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.29.0", - "eslint-config-prettier": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-deprecation": "^1.3.3", + "eslint-plugin-deprecation": "^1.4.1", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.26.0", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-no-null": "^1.0.2", - "lerna": "^6.1.0", - "prettier": "^2.8.0", + "lerna": "^6.6.2", + "prettier": "^2.8.8", "reflect-metadata": "^0.1.13", - "rimraf": "^3.0.2", - "typescript": "^4.9.3" + "rimraf": "^5.0.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/dev/README.md b/dev-packages/dev/README.md index 7187f64..c5c7858 100644 --- a/dev-packages/dev/README.md +++ b/dev-packages/dev/README.md @@ -1,7 +1,7 @@ # Eclipse GLSP - All-in-one dev package A meta package that includes all shared configuration packages Eclipse GLSP components. -In addition, it also provides the GLSP CLI application +In addition, it also provides the GLSP CLI application. The package is available via npm and can be used by all GLSP components implemented with Typescript. @@ -38,6 +38,10 @@ yarn add --dev @eclipse-glsp/dev } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ### ESLint **Create a `.eslintrc.js`**: @@ -64,7 +68,7 @@ module.exports = { } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` to the workspace root**: ```json "@eclipse-glsp/prettier-config" @@ -72,7 +76,7 @@ module.exports = { ### Mocha -**Create a `.mocharc.json`**: +**Create a `.mocharc`**: ```json { @@ -83,10 +87,12 @@ module.exports = { ### Nyc -**Add a `.nycrc.json` to your project root**: +**Add a `.nycrc` to your project root**: ```json -"@eclipse-glsp/prettier-config" +{ + "extends": "@eclipse-glsp/nyc-config" +} ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 4bc72d5..b7f3dc4 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -26,6 +26,7 @@ ], "main": "index.js", "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", diff --git a/dev-packages/mocha-config/README.md b/dev-packages/mocha-config/README.md index 209c156..38cd9cd 100644 --- a/dev-packages/mocha-config/README.md +++ b/dev-packages/mocha-config/README.md @@ -10,7 +10,7 @@ yarn add --dev @eclipse-glsp/mocha-config ## Usage -**Create a `.mocharc.json`**: +**Create a `.mocharc` file **: ```json { diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index fd86e2a..a25b826 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -30,10 +30,9 @@ ".mocharc.json" ], "peerDependencies": { - "ignore-styles": "^5.0.1", - "mocha": "^10.1.0", - "reflect-metadata": ">=0.1.13", - "ts-node": "^10.9.1" + "ignore-styles": "^5.0.0", + "mocha": "^10.0.0", + "reflect-metadata": "^0.1.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/README.md b/dev-packages/nyc-config/README.md index 480d32b..7b03e57 100644 --- a/dev-packages/nyc-config/README.md +++ b/dev-packages/nyc-config/README.md @@ -10,10 +10,12 @@ yarn add --dev @eclipse-glsp/nyc-config ## Usage -**Add a `.nycrc.json` to your project root**: +**Add a `.nycrc` to your project root**: ```json -"@eclipse-glsp/nyc-config" +{ + "extends": "@eclipse-glsp/nyc-config" +} ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/prettier-config/README.md b/dev-packages/prettier-config/README.md index 80e7e7c..c6c56a8 100644 --- a/dev-packages/prettier-config/README.md +++ b/dev-packages/prettier-config/README.md @@ -19,7 +19,7 @@ yarn add --dev @eclipse-glsp/prettier-config } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 495e1a6..68b91d6 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "^2.3.0" + "prettier-plugin-packagejson": "~2.4.3" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/README.md b/dev-packages/ts-config/README.md index c4f0e0d..0e6d973 100644 --- a/dev-packages/ts-config/README.md +++ b/dev-packages/ts-config/README.md @@ -14,7 +14,7 @@ yarn add --dev @eclipse-glsp/ts-config ```json { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -22,6 +22,10 @@ yarn add --dev @eclipse-glsp/ts-config } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). diff --git a/dev-packages/ts-config/mocha.json b/dev-packages/ts-config/mocha.json new file mode 100644 index 0000000..fc29724 --- /dev/null +++ b/dev-packages/ts-config/mocha.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "mocha", "reflect-metadata"] + }, +} diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index a3c7d6d..4e2166f 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -24,12 +24,13 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], - "main": "tsconfig.json", + "main": "./tsconfig.json", "files": [ - "tsconfig.json" + "tsconfig.json", + "mocha.json" ], "peerDependencies": { - "typescript": ">=4.9.3" + "typescript": ">=4.9.x" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index b869a93..83dd56b 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "skipLibCheck": true, "declaration": true, @@ -24,6 +25,6 @@ "jsx": "react", "lib": ["ES2017", "dom"], "sourceMap": true, - "types": ["node", "mocha", "reflect-metadata"] + "types": ["node", "reflect-metadata"] } } From 2a78916b54a72bbda47afe122237cb2825dc42bc Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sat, 27 May 2023 22:32:22 +0200 Subject: [PATCH 208/566] Revert "Update config dependencies (#1020)" (#1021) This reverts commit 122c97bff0332ebed47230c0903a1d0fdc93638b. --- dev-packages/CHANGELOG.md | 28 +++++++++++++++++++++++ dev-packages/cli/package.json | 13 ++++++----- dev-packages/cli/src/app.ts | 1 - dev-packages/cli/tsconfig.json | 13 ++++------- dev-packages/config-test/README.md | 2 +- dev-packages/config-test/package.json | 10 ++++---- dev-packages/config/README.md | 10 +++----- dev-packages/config/package.json | 19 +++++++-------- dev-packages/dev/README.md | 16 ++++--------- dev-packages/eslint-config/package.json | 1 - dev-packages/mocha-config/README.md | 2 +- dev-packages/mocha-config/package.json | 7 +++--- dev-packages/nyc-config/README.md | 6 ++--- dev-packages/prettier-config/README.md | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/README.md | 6 +---- dev-packages/ts-config/mocha.json | 7 ------ dev-packages/ts-config/package.json | 7 +++--- dev-packages/ts-config/tsconfig.json | 3 +-- 19 files changed, 78 insertions(+), 77 deletions(-) create mode 100644 dev-packages/CHANGELOG.md delete mode 100644 dev-packages/ts-config/mocha.json diff --git a/dev-packages/CHANGELOG.md b/dev-packages/CHANGELOG.md new file mode 100644 index 0000000..4b3d7b3 --- /dev/null +++ b/dev-packages/CHANGELOG.md @@ -0,0 +1,28 @@ +# Eclipse GLSP Dev Packages Changelog + +## v1.1.0 - upcoming + +- [config] Introduce all-in-one [`@eclipse-glsp/dev`](https://www.npmjs.com/package/@eclipse-glsp/dev) meta package. [#842](https://github.com/eclipse-glsp/glsp/pull/842) +- [cli] Contribute the `checkHeaders` command to validate the copyright year (range) of license headers. [#834](https://github.com/eclipse-glsp/glsp/pull/834) +- [config] Introduce [`@eclipse-glsp/nyc-config`](https://www.npmjs.com/package/@eclipse-glsp/nyc-config) + package and the [`@eclipse-glsp/config-test`](https://www.npmjs.com/package/@eclipse-glsp/config-test) meta package. [#755](https://github.com/eclipse-glsp/glsp/pull/755) + + - Contribute the `coverageReport` command to create a full nyc test coverage report for a lerna/yarn mono repository + +- [cli] Introduce [`@eclipse-glsp/cli`](https://www.npmjs.com/package/@eclipse-glsp/cli) + package to offer CLI tooling & utility scripts for GLSP projects. [#755](https://github.com/eclipse-glsp/glsp/pull/755) - Contributed on behalf of STMicroelectronics + - Contribute the `release` command to prepare & publish a new Github release for a specific GLSP component + +### Breaking Changes + +- [node] Update minimum requirements for Node to >=16.11.0 [#829](https://github.com/eclipse-glsp/glsp/pull/829) + +## [v1.0.0 - 30/06/2022](https://github.com/eclipse-glsp/glsp/releases/tag/v1.0.0) + +Inception of the GLSP dev packages. +This project is part of the GLSP umbrella repository and provides common shared development packages for Eclipse GLSP components that are implemented with Typescript. + +- [`@eclipse-glsp/config`](https://www.npmjs.com/package/@eclipse-glsp/config): Meta package for shared build configuration + - [`@eclipse-glsp/ts-config`](https://www.npmjs.com/package/@eclipse-glsp/ts-config): Shared Typescript configuration for GLSP projects + - [`@eclipse-glsp/eslint-config`](https://www.npmjs.com/package/@eclipse-glsp/esling-config): Shared ESLint configuration for GLSP projects + - [`@eclipse-glsp/prettier-config`](https://www.npmjs.com/package/@eclipse-glsp/prettier-config): Shared Prettier configuration for GLSP projects diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 7eee273..b8d86e0 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -41,12 +41,12 @@ "watch": "tsc -w" }, "dependencies": { - "commander": "^10.0.1", - "glob": "^10.2.6", - "node-fetch": "^2.6.11", + "commander": "^9.4.0", + "glob": "^8.0.3", + "node-fetch": "2.6.7", "readline-sync": "^1.4.10", - "semver": "^7.5.1", - "shelljs": "^0.8.5" + "semver": "^7.3.7", + "shelljs": "0.8.5" }, "devDependencies": { "@eclipse-glsp/config": "1.0.0-next", @@ -54,7 +54,8 @@ "@types/node-fetch": "2.6.2", "@types/readline-sync": "1.4.4", "@types/semver": "7.3.12", - "@types/shelljs": "0.8.11" + "@types/shelljs": "0.8.11", + "ts-node": "^10.9.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index c373b1a..cc78b3d 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -18,7 +18,6 @@ import { CheckHeaderCommand } from './commands/check-header'; import { CoverageReportCommand } from './commands/coverage-report'; import { ReleaseCommand } from './commands/release/release'; import { baseCommand } from './util/command-util'; - export const COMMAND_VERSION = '1.1.0-next'; const app = baseCommand() // diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index 14c273a..ecf9295 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -1,13 +1,10 @@ { - "extends": "@eclipse-glsp/ts-config", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": ".", - "esModuleInterop": true, - "resolveJsonModule": true + "baseUrl": "." }, - "include": [ - "src" - ] -} \ No newline at end of file + + "include": ["src"] +} diff --git a/dev-packages/config-test/README.md b/dev-packages/config-test/README.md index 2acbca6..165ad1c 100644 --- a/dev-packages/config-test/README.md +++ b/dev-packages/config-test/README.md @@ -24,7 +24,7 @@ yarn add --dev @eclipse-glsp/config-test ### Mocha -**Create a `.mocharc`**: +**Create a `.mocharc.json`**: ```json { diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index d385b15..1e97db0 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -27,17 +27,17 @@ "dependencies": { "@eclipse-glsp/mocha-config": "1.0.0-next", "@eclipse-glsp/nyc-config": "1.0.0-next", - "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@types/chai": "^4.3.5", + "@istanbuljs/nyc-config-typescript": "^1.0.0", + "@types/chai": "^4.3.4", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.13", "chai": "^4.3.7", "ignore-styles": "^5.0.1", - "mocha": "^10.2.0", + "mocha": "^10.1.0", "mocha-jenkins-reporter": "^0.4.8", - "nyc": "^15.1.0", + "nyc": "^15.0.0", "reflect-metadata": "^0.1.13", - "sinon": "^15.1.0", + "sinon": "^15.0.0", "ts-node": "^10.9.1" }, "publishConfig": { diff --git a/dev-packages/config/README.md b/dev-packages/config/README.md index 5c6859c..59fbdfc 100644 --- a/dev-packages/config/README.md +++ b/dev-packages/config/README.md @@ -9,7 +9,7 @@ Provides a meta package that export common configuration objects for: The package is available via npm and is used by all GLSP components implemented with Typescript. ESLint and prettier are included as direct dependencies. -In addition, common GLSP dev dependencies like `lerna`, `typescript`, `rimraf`, etc. are included. +In addition, common GLSP dev dependencies like lerna,typescript,rimraf etc. are included. ## Components @@ -31,7 +31,7 @@ yarn add --dev @eclipse-glsp/config ```json { - "extends": "@eclipse-glsp/ts-config", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -39,10 +39,6 @@ yarn add --dev @eclipse-glsp/config } ``` -In addition, a custom configuration for projects that use `mocha` is available: - -- `@eclipse-glsp/ts-config/mocha` - ### ESLint **Create a `.eslintrc.js`**: @@ -69,7 +65,7 @@ module.exports = { } ``` -**Or add a `.prettierrc` file to the workspace root**: +**Or add a `.prettierrc.json` to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 064740c..2a220d1 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -27,19 +27,20 @@ "@eclipse-glsp/eslint-config": "1.0.0-next", "@eclipse-glsp/prettier-config": "1.0.0-next", "@eclipse-glsp/ts-config": "1.0.0-next", - "@typescript-eslint/eslint-plugin": "^5.59.7", - "@typescript-eslint/parser": "^5.59.7", - "eslint": "^8.41.0", - "eslint-config-prettier": "^8.8.0", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-deprecation": "^1.4.1", + "eslint-plugin-deprecation": "^1.3.3", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-no-null": "^1.0.2", - "lerna": "^6.6.2", - "prettier": "^2.8.8", + "lerna": "^6.1.0", + "prettier": "^2.8.0", "reflect-metadata": "^0.1.13", - "rimraf": "^5.0.1" + "rimraf": "^3.0.2", + "typescript": "^4.9.3" }, "publishConfig": { "access": "public" diff --git a/dev-packages/dev/README.md b/dev-packages/dev/README.md index c5c7858..7187f64 100644 --- a/dev-packages/dev/README.md +++ b/dev-packages/dev/README.md @@ -1,7 +1,7 @@ # Eclipse GLSP - All-in-one dev package A meta package that includes all shared configuration packages Eclipse GLSP components. -In addition, it also provides the GLSP CLI application. +In addition, it also provides the GLSP CLI application The package is available via npm and can be used by all GLSP components implemented with Typescript. @@ -38,10 +38,6 @@ yarn add --dev @eclipse-glsp/dev } ``` -In addition, a custom configuration for projects that use `mocha` is available: - -- `@eclipse-glsp/ts-config/mocha` - ### ESLint **Create a `.eslintrc.js`**: @@ -68,7 +64,7 @@ module.exports = { } ``` -**Or add a `.prettierrc` to the workspace root**: +**Or add a `.prettierrc.json` to the workspace root**: ```json "@eclipse-glsp/prettier-config" @@ -76,7 +72,7 @@ module.exports = { ### Mocha -**Create a `.mocharc`**: +**Create a `.mocharc.json`**: ```json { @@ -87,12 +83,10 @@ module.exports = { ### Nyc -**Add a `.nycrc` to your project root**: +**Add a `.nycrc.json` to your project root**: ```json -{ - "extends": "@eclipse-glsp/nyc-config" -} +"@eclipse-glsp/prettier-config" ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index b7f3dc4..4bc72d5 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -26,7 +26,6 @@ ], "main": "index.js", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", diff --git a/dev-packages/mocha-config/README.md b/dev-packages/mocha-config/README.md index 38cd9cd..209c156 100644 --- a/dev-packages/mocha-config/README.md +++ b/dev-packages/mocha-config/README.md @@ -10,7 +10,7 @@ yarn add --dev @eclipse-glsp/mocha-config ## Usage -**Create a `.mocharc` file **: +**Create a `.mocharc.json`**: ```json { diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index a25b826..fd86e2a 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -30,9 +30,10 @@ ".mocharc.json" ], "peerDependencies": { - "ignore-styles": "^5.0.0", - "mocha": "^10.0.0", - "reflect-metadata": "^0.1.0" + "ignore-styles": "^5.0.1", + "mocha": "^10.1.0", + "reflect-metadata": ">=0.1.13", + "ts-node": "^10.9.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/README.md b/dev-packages/nyc-config/README.md index 7b03e57..480d32b 100644 --- a/dev-packages/nyc-config/README.md +++ b/dev-packages/nyc-config/README.md @@ -10,12 +10,10 @@ yarn add --dev @eclipse-glsp/nyc-config ## Usage -**Add a `.nycrc` to your project root**: +**Add a `.nycrc.json` to your project root**: ```json -{ - "extends": "@eclipse-glsp/nyc-config" -} +"@eclipse-glsp/nyc-config" ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/prettier-config/README.md b/dev-packages/prettier-config/README.md index c6c56a8..80e7e7c 100644 --- a/dev-packages/prettier-config/README.md +++ b/dev-packages/prettier-config/README.md @@ -19,7 +19,7 @@ yarn add --dev @eclipse-glsp/prettier-config } ``` -**Or add a `.prettierrc` to the workspace root**: +**Or add a `.prettierrc.json` to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 68b91d6..495e1a6 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "~2.4.3" + "prettier-plugin-packagejson": "^2.3.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/README.md b/dev-packages/ts-config/README.md index 0e6d973..c4f0e0d 100644 --- a/dev-packages/ts-config/README.md +++ b/dev-packages/ts-config/README.md @@ -14,7 +14,7 @@ yarn add --dev @eclipse-glsp/ts-config ```json { - "extends": "@eclipse-glsp/ts-config", + "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -22,10 +22,6 @@ yarn add --dev @eclipse-glsp/ts-config } ``` -In addition, a custom configuration for projects that use `mocha` is available: - -- `@eclipse-glsp/ts-config/mocha` - ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). diff --git a/dev-packages/ts-config/mocha.json b/dev-packages/ts-config/mocha.json deleted file mode 100644 index fc29724..0000000 --- a/dev-packages/ts-config/mocha.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "types": ["node", "mocha", "reflect-metadata"] - }, -} diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 4e2166f..a3c7d6d 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -24,13 +24,12 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], - "main": "./tsconfig.json", + "main": "tsconfig.json", "files": [ - "tsconfig.json", - "mocha.json" + "tsconfig.json" ], "peerDependencies": { - "typescript": ">=4.9.x" + "typescript": ">=4.9.3" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index 83dd56b..b869a93 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -1,5 +1,4 @@ { - "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "skipLibCheck": true, "declaration": true, @@ -25,6 +24,6 @@ "jsx": "react", "lib": ["ES2017", "dom"], "sourceMap": true, - "types": ["node", "reflect-metadata"] + "types": ["node", "mocha", "reflect-metadata"] } } From eb55c205572884da2f260d43f742f06658085766 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 31 May 2023 09:41:42 +0200 Subject: [PATCH 209/566] GLSP-813: Update release scripts (#1024) The embedded example servers are now node based therefore: Remove function to update the java-server-download script from component release functions. Ensure that the used version of `@eclipse-glsp-examples/workflow-server` is updated as part of the release functions Closes https://github.com/eclipse-glsp/glsp/issues/813 --- .../cli/src/commands/release/common.ts | 4 ++-- .../src/commands/release/release-client.ts | 16 +++++-------- .../release/release-theia-integration.ts | 24 +++++-------------- .../release/release-vscode-integration.ts | 3 ++- .../cli/src/commands/release/release.ts | 20 ++++++++-------- 5 files changed, 26 insertions(+), 41 deletions(-) diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index ce6d5f6..08ee759 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -257,8 +257,8 @@ export function asMvnVersion(version: string): string { return mavenVersion; } -export async function checkIfNpmVersionExists(pckgName: string, newVersion: string): Promise { - LOGGER.debug(`Check if version exists on npm: ${newVersion}`); +export async function checkIfNpmVersionIsNew(pckgName: string, newVersion: string): Promise { + LOGGER.debug(`Check that the release version is new i.e. does not exist on npm: ${newVersion}`); const response = await fetch(`https://registry.npmjs.org/${pckgName}/${newVersion}`); const data = await response.json(); diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index d18288d..c556b0c 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -16,15 +16,13 @@ import * as sh from 'shelljs'; import { LOGGER } from '../../util/logger'; import { - asMvnVersion, - checkJavaServerVersion, checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, - updateServerConfig, + updateVersion, yarnInstall } from './common'; @@ -34,7 +32,7 @@ export async function releaseClient(options: ReleaseOptions): Promise { LOGGER.info('Prepare glsp-client release'); LOGGER.debug('Release options: ', options.version); REPO_ROOT = checkoutAndCd(options); - await updateDownloadServerScript(options.version, options.force); + updateExternalGLSPDependencies(options.version); generateChangeLog(); lernaSetVersion(REPO_ROOT, options.version); build(); @@ -45,12 +43,10 @@ export async function releaseClient(options: ReleaseOptions): Promise { publish(REPO_ROOT, options); } -async function updateDownloadServerScript(version: string, force: boolean): Promise { - LOGGER.info('Update example server download config'); - const mvnVersion = asMvnVersion(version); - checkJavaServerVersion(version, force); - sh.cd(`${REPO_ROOT}/examples/workflow-glsp/scripts`); - updateServerConfig('config.json', mvnVersion, false); +function updateExternalGLSPDependencies(version: string): void { + LOGGER.info('Update external GLSP dependencies (workflow example server)'); + sh.cd(REPO_ROOT); + updateVersion({ name: '@eclipse-glsp-examples/workflow-server', version }); } function generateChangeLog(): void { diff --git a/dev-packages/cli/src/commands/release/release-theia-integration.ts b/dev-packages/cli/src/commands/release/release-theia-integration.ts index 3c4c402..4403c00 100644 --- a/dev-packages/cli/src/commands/release/release-theia-integration.ts +++ b/dev-packages/cli/src/commands/release/release-theia-integration.ts @@ -17,15 +17,12 @@ import * as sh from 'shelljs'; import { LOGGER } from '../../util/logger'; import { - asMvnVersion, checkoutAndCd, commitAndTag, - isExistingMavenVersion, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, - updateServerConfig, updateVersion, yarnInstall } from './common'; @@ -37,7 +34,6 @@ export async function releaseTheiaIntegration(options: ReleaseOptions): Promise< LOGGER.debug('Release options: ', options); REPO_ROOT = checkoutAndCd(options); updateExternalGLSPDependencies(options.version); - await updateDownloadServerScript(options.version); generateChangeLog(); lernaSetVersion(REPO_ROOT, options.version); build(); @@ -49,21 +45,13 @@ export async function releaseTheiaIntegration(options: ReleaseOptions): Promise< } function updateExternalGLSPDependencies(version: string): void { - LOGGER.info('Update external GLSP dependencies (Client and workflow example)'); + LOGGER.info('Update external GLSP dependencies (Client and workflow example & server)'); sh.cd(REPO_ROOT); - updateVersion({ name: '@eclipse-glsp/client', version }, { name: '@eclipse-glsp-examples/workflow-glsp', version }); -} - -async function updateDownloadServerScript(version: string): Promise { - LOGGER.info('Update example server download config'); - const mvnVersion = asMvnVersion(version); - if (!isExistingMavenVersion('org.eclipse.glsp', 'org.eclipse.glsp.server', mvnVersion)) { - LOGGER.warn(`No Java GLSP server with version ${mvnVersion} found on maven central!. Please release a new Java GLSP Server version - before publishing this release!`); - } - - sh.cd(`${REPO_ROOT}/examples/workflow-theia/src/node`); - updateServerConfig('server-config.json', mvnVersion, false); + updateVersion( + { name: '@eclipse-glsp/client', version }, + { name: '@eclipse-glsp-examples/workflow-glsp', version }, + { name: '@eclipse-glsp-examples/workflow-server', version } + ); } function build(): void { diff --git a/dev-packages/cli/src/commands/release/release-vscode-integration.ts b/dev-packages/cli/src/commands/release/release-vscode-integration.ts index 366629d..0dee142 100644 --- a/dev-packages/cli/src/commands/release/release-vscode-integration.ts +++ b/dev-packages/cli/src/commands/release/release-vscode-integration.ts @@ -50,7 +50,8 @@ function updateExternalGLSPDependencies(version: string): void { updateVersion( { name: '@eclipse-glsp/protocol', version }, { name: '@eclipse-glsp/client', version }, - { name: '@eclipse-glsp-examples/workflow-glsp', version } + { name: '@eclipse-glsp-examples/workflow-glsp', version }, + { name: '@eclipse-glsp-examples/workflow-server', version } ); } diff --git a/dev-packages/cli/src/commands/release/release.ts b/dev-packages/cli/src/commands/release/release.ts index abc39a6..192dab1 100644 --- a/dev-packages/cli/src/commands/release/release.ts +++ b/dev-packages/cli/src/commands/release/release.ts @@ -21,16 +21,16 @@ import * as readline from 'readline-sync'; import * as semver from 'semver'; import * as sh from 'shelljs'; import { baseCommand, configureShell, fatalExec, getShellConfig } from '../../util/command-util'; -import { configureLogger, LOGGER } from '../../util/logger'; +import { LOGGER, configureLogger } from '../../util/logger'; import { validateDirectory, validateVersion } from '../../util/validation-util'; import { - asMvnVersion, - checkIfMavenVersionExists, - checkIfNpmVersionExists, Component, ReleaseOptions, ReleaseType, - VERDACCIO_REGISTRY + VERDACCIO_REGISTRY, + asMvnVersion, + checkIfMavenVersionExists, + checkIfNpmVersionIsNew } from './common'; import { releaseClient } from './release-client'; import { releaseEclipseIntegration } from './release-eclipse-integration'; @@ -91,19 +91,19 @@ export async function release( checkIfMavenVersionExists('org.eclipse.glsp', 'org.eclipse.glsp.server', asMvnVersion(version)); return releaseJavaServer(options); case 'server-node': - await checkIfNpmVersionExists('@eclipse-glsp/server-node', version); + await checkIfNpmVersionIsNew('@eclipse-glsp/server-node', version); return releaseServerNode(options); case 'client': - await checkIfNpmVersionExists('@eclipse-glsp/client', version); + await checkIfNpmVersionIsNew('@eclipse-glsp/client', version); return releaseClient(options); case 'theia-integration': - await checkIfNpmVersionExists('@eclipse-glsp/theia-integration', version); + await checkIfNpmVersionIsNew('@eclipse-glsp/theia-integration', version); return releaseTheiaIntegration(options); case 'vscode-integration': - await checkIfNpmVersionExists('@eclipse-glsp/vscode-integration', version); + await checkIfNpmVersionIsNew('@eclipse-glsp/vscode-integration', version); return releaseVscodeIntegration(options); case 'eclipse-integration': - await checkIfNpmVersionExists('@eclipse-glsp/ide', version); + await checkIfNpmVersionIsNew('@eclipse-glsp/ide', version); return releaseEclipseIntegration(options); } } catch (err) { From 44acbe1faca240ca99f11e457602adb5774f1ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:48:47 +0200 Subject: [PATCH 210/566] #1029 Introduce search palette that can find nodes and edges through autocomplete with the capability to visually highlight found elements. (#242) - Introduce base class for autocomplete palettes supporting flexible suggestions - Extend AutoCompleteWidget with visibleSuggestionsChanged and selectedSuggestionChanged callbacks - Provide reason if AutoCompleteWidget closes Part of https://github.com/eclipse-glsp/glsp/issues/1029 --- examples/workflow-glsp/src/di.config.ts | 3 +- packages/client/css/autocomplete-palette.css | 22 +++ packages/client/css/glsp-sprotty.css | 17 +- packages/client/package.json | 2 + .../auto-complete/auto-complete-widget.ts | 38 +++- .../src/features/accessibility/di.config.ts | 26 +++ .../accessibility/search/di.config.ts | 31 +++ .../accessibility/search/search-palette.ts | 181 ++++++++++++++++++ .../accessibility/search/search-tool.ts | 60 ++++++ .../autocomplete-suggestion-providers.ts | 37 ++++ .../base-autocomplete-palette.ts | 88 +++++++++ packages/client/src/index.ts | 10 +- 12 files changed, 503 insertions(+), 12 deletions(-) create mode 100644 packages/client/css/autocomplete-palette.css create mode 100644 packages/client/src/features/accessibility/di.config.ts create mode 100644 packages/client/src/features/accessibility/search/di.config.ts create mode 100644 packages/client/src/features/accessibility/search/search-palette.ts create mode 100644 packages/client/src/features/accessibility/search/search-tool.ts create mode 100644 packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts create mode 100644 packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index d1e26d8..955201a 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -22,6 +22,7 @@ import { DiamondNodeView, editLabelFeature, GEdge, + glspAccessibilityModule, GLSPGraph, GLSPProjectionView, GridSnapper, @@ -84,7 +85,7 @@ export function initializeWorkflowDiagramContainer( widgetId: string, ...containerConfiguration: ContainerConfiguration ): Container { - initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, ...containerConfiguration); + initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, glspAccessibilityModule, ...containerConfiguration); overrideViewerOptions(container, { baseDiv: widgetId, hiddenDiv: widgetId + '_hidden' diff --git a/packages/client/css/autocomplete-palette.css b/packages/client/css/autocomplete-palette.css new file mode 100644 index 0000000..4a5bf23 --- /dev/null +++ b/packages/client/css/autocomplete-palette.css @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.autocomplete-palette { + position: absolute; + left: 20px; + top: 20px; + width: 400px; +} diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 51ac118..b0b4278 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -254,3 +254,18 @@ fill: transparent; width: var(--theia-scrollbar-rail-width); } + +.search-hidden { + opacity: 0.2; +} + +.search-highlighted .sprotty-node, +.search-highlighted .sprotty-edge { + stroke-width: 1.5px; + stroke-width: 2px; + stroke-dashoffset: 5; + stroke-dasharray: 5, 5; + stroke-linecap: round; + stroke: rgb(218, 89, 15) !important; + stroke: #130bf7 !important; +} diff --git a/packages/client/package.json b/packages/client/package.json index 3b2d12f..51661f8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -49,9 +49,11 @@ "dependencies": { "@eclipse-glsp/protocol": "1.1.0-next", "autocompleter": "5.1.0", + "lodash": "4.17.21", "sprotty": "0.13.0-next.f4445dd.342" }, "devDependencies": { + "@types/lodash": "4.14.191", "@vscode/codicons": "^0.0.25" }, "publishConfig": { diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 7fd03cd..e0b7e77 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -28,6 +28,7 @@ export interface AutoCompleteSettings { readonly showOnFocus?: boolean; } +export type CloseReason = 'submission' | 'blur' | 'escape'; export interface InputValidator { validate(input: string): Promise; } @@ -48,6 +49,11 @@ export interface TextSubmitHandler { executeFromTextOnlyInput(input: string): void; } +export interface AutoCompleteWidgetOptions { + visibleSuggestionsChanged?: (suggestions: LabeledAction[]) => void; + selectedSuggestionChanged?: (suggestion?: LabeledAction) => void; +} + // eslint-disable-next-line @typescript-eslint/no-var-requires const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); @@ -78,8 +84,9 @@ export class AutoCompleteWidget { public suggestionProvider: SuggestionProvider, public suggestionSubmitHandler: SuggestionSubmitHandler, // eslint-disable-next-line @typescript-eslint/no-empty-function - protected notifyClose: () => void = () => {}, - protected logger?: ILogger + protected notifyClose: (reason: CloseReason) => void = () => {}, + protected logger?: ILogger, + protected options?: AutoCompleteWidgetOptions ) {} configureValidation( @@ -109,13 +116,18 @@ export class AutoCompleteWidget { inputElement.autocomplete = 'false'; inputElement.style.width = '100%'; inputElement.addEventListener('keydown', event => this.handleKeyDown(event)); - inputElement.addEventListener('blur', () => window.setTimeout(() => this.notifyClose(), 200)); + inputElement.addEventListener('blur', () => { + if (this.containerElement.style.visibility !== 'hidden') { + window.setTimeout(() => this.notifyClose('blur'), 200); + } + }); + return inputElement; } protected handleKeyDown(event: KeyboardEvent): void { if (matchesKeystroke(event, 'Escape')) { - this.notifyClose(); + this.notifyClose('escape'); return; } if (matchesKeystroke(event, 'Enter') && !this.isInputElementChanged() && this.isSuggestionAvailable()) { @@ -133,7 +145,7 @@ export class AutoCompleteWidget { } if (this.textSubmitHandler) { this.executeFromTextOnlyInput(); - this.notifyClose(); + this.notifyClose('submission'); } } @@ -175,6 +187,17 @@ export class AutoCompleteWidget { // move container into our UIExtension container as this is already positioned correctly if (this.containerElement) { this.containerElement.appendChild(container); + + if (this.options && this.options.selectedSuggestionChanged) { + const selectedElement = container.querySelector('.selected'); + // eslint-disable-next-line no-null/no-null + if (selectedElement !== null && selectedElement !== undefined) { + const index = Array.from(container.children).indexOf(selectedElement); + this.options.selectedSuggestionChanged(this.contextActions?.[index]); + } else { + this.options.selectedSuggestionChanged(undefined); + } + } } } @@ -189,6 +212,7 @@ export class AutoCompleteWidget { .then(actions => { this.contextActions = this.filterActions(text, actions); update(this.contextActions); + this.options?.visibleSuggestionsChanged?.(this.contextActions); this.onLoaded('success'); }) .catch(reason => { @@ -228,7 +252,7 @@ export class AutoCompleteWidget { if (item.icon) { this.renderIcon(itemElement, item.icon); } - itemElement.innerHTML += item.label.replace(regex, match => '' + match + ''); + itemElement.innerHTML += item.label.replace(regex, match => '' + match + '').replace(/ /g, ' '); return itemElement; } @@ -257,7 +281,7 @@ export class AutoCompleteWidget { window.setTimeout(() => this.inputElement.dispatchEvent(new Event('keyup'))); } else { this.executeFromSuggestion(item); - this.notifyClose(); + this.notifyClose('submission'); } } diff --git a/packages/client/src/features/accessibility/di.config.ts b/packages/client/src/features/accessibility/di.config.ts new file mode 100644 index 0000000..00b45f1 --- /dev/null +++ b/packages/client/src/features/accessibility/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { configureSearchPaletteModule } from './search/di.config'; + +/** + * Enables the accessibility tools for a keyboard-only-usage + */ +export const glspAccessibilityModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + configureSearchPaletteModule(context); +}); diff --git a/packages/client/src/features/accessibility/search/di.config.ts b/packages/client/src/features/accessibility/search/di.config.ts new file mode 100644 index 0000000..eb33331 --- /dev/null +++ b/packages/client/src/features/accessibility/search/di.config.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; +import { TYPES } from '../../../base/types'; +import { SearchAutocompletePalette } from './search-palette'; +import { SearchAutocompletePaletteTool } from './search-tool'; + +export const glspSearchPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + configureSearchPaletteModule(context); +}); + +export function configureSearchPaletteModule(context: Pick): void { + bindAsService(context, TYPES.IUIExtension, SearchAutocompletePalette); + bindAsService(context, TYPES.IDefaultTool, SearchAutocompletePaletteTool); +} diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts new file mode 100644 index 0000000..18211d3 --- /dev/null +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -0,0 +1,181 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, CenterAction, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { isEqual } from 'lodash'; +import { codiconCSSString, isNameable, LabeledAction, name, SEdge, SModelElement, SModelRoot, SNode } from 'sprotty'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; +import { BaseAutocompletePalette } from '../../autocomplete-palette/base-autocomplete-palette'; + +import { applyCssClasses, deleteCssClasses } from '../../tool-feedback/css-feedback'; + +const CSS_SEARCH_HIDDEN = 'search-hidden'; +const CSS_SEARCH_HIGHLIGHTED = 'search-highlighted'; + +export class RevealNamedElementAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { + async retrieveSuggestions(root: Readonly, text: string): Promise { + const nameables = toArray(root.index.all().filter(element => isNameable(element))); + return nameables.map(nameable => ({ + element: nameable, + action: new LabeledAction( + `[${nameable.type}] ${name(nameable) ?? ''}`, + this.getActions(nameable), + codiconCSSString('eye') + ) + })); + } + + protected getActions(nameable: SModelElement): Action[] { + return [SelectAction.create({ selectedElementsIDs: [nameable.id] }), CenterAction.create([nameable.id], { retainZoom: true })]; + } +} + +export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { + async retrieveSuggestions(root: Readonly, text: string): Promise { + const edges = toArray(root.index.all().filter(element => element instanceof SEdge)) as SEdge[]; + return edges.map(edge => ({ + element: edge, + action: new LabeledAction( + `[${edge.type}] ` + this.getEdgeLabel(root, edge), + this.getActions(edge), + codiconCSSString('arrow-both') + ) + })); + } + protected getActions(edge: SEdge): Action[] { + return [SelectAction.create({ selectedElementsIDs: [edge.id] }), CenterAction.create([edge.sourceId, edge.targetId])]; + } + protected getEdgeLabel(root: Readonly, edge: SEdge): string { + let sourceName = ''; + let targetName = ''; + + const sourceNode = root.index.getById(edge.sourceId); + const targetNode = root.index.getById(edge.targetId); + + if (sourceNode !== undefined) { + sourceName = name(sourceNode) ?? sourceNode.type; + } + if (targetNode !== undefined) { + targetName = name(targetNode) ?? targetNode.type; + } + + return sourceName + ' -> ' + targetName; + } +} +@injectable() +export class SearchAutocompletePalette extends BaseAutocompletePalette { + static readonly ID = 'search-autocomplete-palette'; + + protected cachedSuggestions: AutocompleteSuggestion[] = []; + + id(): string { + return SearchAutocompletePalette.ID; + } + + protected override initializeContents(containerElement: HTMLElement): void { + super.initializeContents(containerElement); + + this.autocompleteWidget.inputField.placeholder = 'Search for elements'; + } + protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + return [new RevealNamedElementAutocompleteSuggestionProvider(), new RevealEdgeElementAutocompleteSuggestionProvider()]; + } + protected async retrieveSuggestions(root: Readonly, input: string): Promise { + const providers = this.getSuggestionProviders(root, input); + const suggestions = (await Promise.all(providers.map(provider => provider.retrieveSuggestions(root, input)))).flat(1); + + this.cachedSuggestions = suggestions; + + return suggestions.map(s => s.action); + } + + protected override async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { + await this.applyCSS(this.getHiddenElements(root, this.getSuggestionsFromLabeledActions(labeledActions)), CSS_SEARCH_HIDDEN); + await this.deleteCSS( + this.getSuggestionsFromLabeledActions(labeledActions).map(s => s.element), + CSS_SEARCH_HIDDEN + ); + } + + protected override async selectedSuggestionChanged( + root: Readonly, + labeledAction?: LabeledAction | undefined + ): Promise { + await this.deleteAllCSS(root, CSS_SEARCH_HIGHLIGHTED); + if (labeledAction !== undefined) { + const suggestions = this.getSuggestionsFromLabeledActions([labeledAction]); + + const actions: CenterAction[] = []; + suggestions.map(currElem => actions.push(CenterAction.create([currElem.element.id], { animate: true, retainZoom: true }))); + + this.actionDispatcher.dispatchAll(actions); + await this.applyCSS( + suggestions.map(s => s.element), + CSS_SEARCH_HIGHLIGHTED + ); + } + } + + public override show(root: Readonly, ...contextElementIds: string[]): void { + this.actionDispatcher.dispatch(SelectAllAction.create(false)); + + super.show(root, ...contextElementIds); + } + + public override hide(): void { + if (this.root !== undefined) { + this.deleteAllCSS(this.root, CSS_SEARCH_HIDDEN); + this.deleteAllCSS(this.root, CSS_SEARCH_HIGHLIGHTED); + this.autocompleteWidget.inputField.value = ''; + } + + super.hide(); + } + + protected applyCSS(elements: SModelElement[], cssClass: string): Promise { + const actions = elements.map(element => applyCssClasses(element, cssClass)); + return this.actionDispatcher.dispatchAll(actions); + } + + protected deleteCSS(elements: SModelElement[], cssClass: string): Promise { + const actions = elements.map(element => deleteCssClasses(element, cssClass)); + return this.actionDispatcher.dispatchAll(actions); + } + + protected deleteAllCSS(root: Readonly, cssClass: string): Promise { + const actions = toArray(root.index.all().map(element => deleteCssClasses(element, cssClass))); + return this.actionDispatcher.dispatchAll(actions); + } + + protected getSuggestionsFromLabeledActions(labeledActions: LabeledAction[]): AutocompleteSuggestion[] { + return this.cachedSuggestions.filter(c => labeledActions.find(s => isEqual(s, c.action))); + } + + protected getHiddenSuggestionsFromLabeledActions(labeledActions: LabeledAction[]): AutocompleteSuggestion[] { + return this.cachedSuggestions.filter(c => !labeledActions.find(s => isEqual(s, c.action))); + } + + protected getHiddenElements(root: Readonly, suggestions: AutocompleteSuggestion[]): SModelElement[] { + return toArray( + root.index + .all() + .filter(element => element instanceof SNode || element instanceof SEdge) + .filter(element => suggestions.find(suggestion => suggestion.element.id === element.id) === undefined) + ); + } +} diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts new file mode 100644 index 0000000..a2ccbbd --- /dev/null +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { KeyListener, KeyTool, SetUIExtensionVisibilityAction, SModelElement, Tool } from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { SearchAutocompletePalette } from './search-palette'; + +@injectable() +export class SearchAutocompletePaletteTool implements Tool { + static readonly ID = 'glsp.search-autocomplete-palette-tool'; + + protected readonly keyListener = new SearchAutocompletePaletteKeyListener(); + + @inject(KeyTool) protected keyTool: KeyTool; + + get id(): string { + return SearchAutocompletePaletteTool.ID; + } + + enable(): void { + this.keyTool.register(this.keyListener); + } + + disable(): void { + this.keyTool.deregister(this.keyListener); + } +} + +export class SearchAutocompletePaletteKeyListener extends KeyListener { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (this.matchesSearchActivateKeystroke(event)) { + return [ + SetUIExtensionVisibilityAction.create({ + extensionId: SearchAutocompletePalette.ID, + visible: true + }) + ]; + } + return []; + } + + protected matchesSearchActivateKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyF', 'ctrl'); + } +} diff --git a/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts b/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts new file mode 100644 index 0000000..69a81f2 --- /dev/null +++ b/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { LabeledAction, SModelElement, SModelRoot } from 'sprotty'; + +/** + * Interface for a provider that provides suggestions that can be used to fill the autocomplete + */ +export interface IAutocompleteSuggestionProvider { + /** + * Returns the suggestions based on the provided parameters. + * @param root: The root model + * @param text: The text input provided by the user. + */ + retrieveSuggestions(root: Readonly, text: string): Promise; +} + +/** + * Interface for autocomplete suggestions. + */ +export interface AutocompleteSuggestion { + element: SModelElement; + action: LabeledAction; +} diff --git a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts new file mode 100644 index 0000000..1e4eb66 --- /dev/null +++ b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '@eclipse-glsp/protocol'; +import { inject } from 'inversify'; +import { AbstractUIExtension, LabeledAction, SModelRoot, TYPES } from 'sprotty'; +import '../../../css/autocomplete-palette.css'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { AutoCompleteWidget, CloseReason, toActionArray } from '../../base/auto-complete/auto-complete-widget'; + +export abstract class BaseAutocompletePalette extends AbstractUIExtension { + protected readonly autoSuggestionSettings = { + noSuggestionsMessage: 'No suggestions available', + suggestionsClass: 'command-palette-suggestions', + debounceWaitMs: 50, + showOnFocus: true + }; + + protected root?: Readonly; + protected autocompleteWidget: AutoCompleteWidget; + + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + containerClass(): string { + return 'autocomplete-palette'; + } + + override show(root: Readonly, ...contextElementIds: string[]): void { + super.show(root, ...contextElementIds); + this.root = root; + this.autocompleteWidget.open(root); + } + + override hide(): void { + this.autocompleteWidget.dispose(); + this.root = undefined; + super.hide(); + } + + protected initializeContents(containerElement: HTMLElement): void { + containerElement.classList.add('command-palette'); + + this.autocompleteWidget = new AutoCompleteWidget( + this.autoSuggestionSettings, + { provideSuggestions: input => this.retrieveSuggestions(this.root!, input) }, + { executeFromSuggestion: input => this.executeSuggestion(input) }, + reason => this.autocompleteHide(reason), + this.logger, + { + visibleSuggestionsChanged: suggestions => this.visibleSuggestionsChanged(this.root!, suggestions), + selectedSuggestionChanged: suggestion => this.selectedSuggestionChanged(this.root!, suggestion) + } + ); + this.autocompleteWidget.initialize(containerElement); + } + + protected abstract retrieveSuggestions(root: Readonly, input: string): Promise; + + protected async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { + return; + } + + protected async selectedSuggestionChanged(root: Readonly, labeledAction?: LabeledAction): Promise { + return; + } + + protected autocompleteHide(reason: CloseReason): void { + this.hide(); + } + + protected executeSuggestion(input: LabeledAction | Action[] | Action): void { + this.actionDispatcher.dispatchAll(toActionArray(input)); + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index a9cbe10..de87890 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -14,11 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import defaultGLSPModule from './base/di.config'; +import { glspAccessibilityModule } from './features/accessibility/di.config'; +import { glspSearchPaletteModule } from './features/accessibility/search/di.config'; import glspCommandPaletteModule from './features/command-palette/di.config'; import glspContextMenuModule from './features/context-menu/di.config'; import { copyPasteContextMenuModule, glspServerCopyPasteModule } from './features/copy-paste/di.config'; import glspDecorationModule from './features/decoration/di.config'; -import svgMetadataModule from './features/svg-metadata/di.config'; import glspEditLabelModule from './features/edit-label/di.config'; import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; @@ -28,6 +29,7 @@ import navigationModule from './features/navigation/di.config'; import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; import sourceModelWatcherModule from './features/source-model-watcher/di.config'; +import svgMetadataModule from './features/svg-metadata/di.config'; import toolFeedbackModule from './features/tool-feedback/di.config'; import paletteModule from './features/tool-palette/di.config'; import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; @@ -74,7 +76,6 @@ export * from './features/context-menu/server-context-menu-provider'; export * from './features/copy-paste/copy-paste-context-menu'; export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; -export * from './features/svg-metadata/metadata-placer'; export * from './features/edit-label/edit-label-tool'; export * from './features/edit-label/edit-label-validator'; export * from './features/export/glsp-svg-exporter'; @@ -93,6 +94,7 @@ export * from './features/select/select-feedback-action'; export * from './features/select/select-mouse-listener'; export * from './features/select/selection-service'; export * from './features/source-model-watcher/source-model-changed-action-handler'; +export * from './features/svg-metadata/metadata-placer'; export * from './features/tool-feedback/change-bounds-tool-feedback'; export * from './features/tool-feedback/creation-tool-feedback'; export * from './features/tool-feedback/css-feedback'; @@ -154,5 +156,7 @@ export { sourceModelWatcherModule, markerNavigatorContextMenuModule, glspViewportModule, - svgMetadataModule + svgMetadataModule, + glspSearchPaletteModule, + glspAccessibilityModule }; From cbb9697f11fc3bb99569fa2dd4e895c3d4c46e90 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 5 Jun 2023 04:12:11 -0700 Subject: [PATCH 211/566] Update workflow example README (#251) Update outdated information section in workflow example README --- examples/workflow-glsp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow-glsp/README.md b/examples/workflow-glsp/README.md index a554765..5cdc1fa 100644 --- a/examples/workflow-glsp/README.md +++ b/examples/workflow-glsp/README.md @@ -11,6 +11,6 @@ This package is built with `yarn` and is available from npm via [@eclipse-glsp-e ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). -If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) From 3ee5ed7090510eb81779ab6e59308eed53a93e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Tue, 6 Jun 2023 11:20:37 +0200 Subject: [PATCH 212/566] #1029 Introduce moving and zooming of the viewport and elements via the keyboard (#241) - Introduce tool and handler for moving viewport and elements via keyboard shortcut - Introduce tool and handler for zooming viewport and elements via keyboard shortcut - Provide tool for an improved implementation for deselecting elements Part of https://github.com/eclipse-glsp/glsp/issues/1029 --- .../src/features/accessibility/di.config.ts | 8 +- .../accessibility/move-zoom/di.config.ts | 43 ++++ .../accessibility/move-zoom/move-handler.ts | 198 ++++++++++++++++++ .../accessibility/move-zoom/zoom-handler.ts | 178 ++++++++++++++++ .../view-key-tools/deselect-key-tool.ts | 80 +++++++ .../accessibility/view-key-tools/di.config.ts | 33 +++ .../view-key-tools/movement-key-tool.ts | 115 ++++++++++ .../view-key-tools/zoom-key-tool.ts | 94 +++++++++ packages/client/src/index.ts | 11 +- 9 files changed, 756 insertions(+), 4 deletions(-) create mode 100644 packages/client/src/features/accessibility/move-zoom/di.config.ts create mode 100644 packages/client/src/features/accessibility/move-zoom/move-handler.ts create mode 100644 packages/client/src/features/accessibility/move-zoom/zoom-handler.ts create mode 100644 packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts create mode 100644 packages/client/src/features/accessibility/view-key-tools/di.config.ts create mode 100644 packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts create mode 100644 packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts diff --git a/packages/client/src/features/accessibility/di.config.ts b/packages/client/src/features/accessibility/di.config.ts index 00b45f1..d6e65c1 100644 --- a/packages/client/src/features/accessibility/di.config.ts +++ b/packages/client/src/features/accessibility/di.config.ts @@ -15,12 +15,16 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; +import { configureMoveZoom } from './move-zoom/di.config'; import { configureSearchPaletteModule } from './search/di.config'; +import { configureViewKeyTools } from './view-key-tools/di.config'; /** * Enables the accessibility tools for a keyboard-only-usage */ -export const glspAccessibilityModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - const context = { bind, isBound, rebind }; +export const glspAccessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureViewKeyTools(context); + configureMoveZoom(context); configureSearchPaletteModule(context); }); diff --git a/packages/client/src/features/accessibility/move-zoom/di.config.ts b/packages/client/src/features/accessibility/move-zoom/di.config.ts new file mode 100644 index 0000000..38a867e --- /dev/null +++ b/packages/client/src/features/accessibility/move-zoom/di.config.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { BindingContext } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; +import { configureActionHandler } from 'sprotty'; +import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler'; +import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler'; + +/** + * Handles move and zoom actions. + */ +export const glspMoveZoomModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureMoveZoom(context); +}); + +export function configureMoveZoom(context: BindingContext): void { + context.bind(MoveViewportHandler).toSelf().inSingletonScope(); + context.bind(MoveElementHandler).toSelf().inSingletonScope(); + + context.bind(ZoomViewportHandler).toSelf().inSingletonScope(); + context.bind(ZoomElementHandler).toSelf().inSingletonScope(); + + configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler); + configureActionHandler(context, MoveElementAction.KIND, MoveElementHandler); + + configureActionHandler(context, ZoomViewportAction.KIND, ZoomViewportHandler); + configureActionHandler(context, ZoomElementAction.KIND, ZoomElementHandler); +} diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts new file mode 100644 index 0000000..688b9c7 --- /dev/null +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, ChangeBoundsOperation, Point, SetViewportAction, Viewport } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { throttle } from 'lodash'; +import { findParentByFeature, IActionDispatcher, IActionHandler, ICommand, isViewport, SModelRoot } from 'sprotty'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { TYPES } from '../../../base/types'; +import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; + +/** + * Action for triggering moving of the viewport. + */ +export interface MoveViewportAction extends Action { + kind: typeof MoveViewportAction.KIND; + /** + * used to specify the amount to be moved in the x-axis + */ + moveX: number; + /** + * used to specify the amount to be moved in the y-axis + */ + moveY: number; +} + +export namespace MoveViewportAction { + export const KIND = 'moveViewportAction'; + + export function is(object: any): object is MoveViewportAction { + return Action.hasKind(object, KIND); + } + + export function create(moveX: number, moveY: number): MoveViewportAction { + return { kind: KIND, moveX, moveY }; + } +} + +/** + * Action for triggering moving of elements. + */ +export interface MoveElementAction extends Action { + kind: typeof MoveElementAction.KIND; + /** + * used to specify the elements to be zoomed in/out + */ + elementIds: string[]; + /** + * used to specify the amount to be moved in the x-axis + */ + moveX: number; + /** + * used to specify the amount to be moved in the y-axis + */ + moveY: number; +} + +export namespace MoveElementAction { + export const KIND = 'moveElementAction'; + + export function is(object: any): object is MoveElementAction { + return Action.hasKind(object, KIND); + } + + export function create(elementIds: string[], moveX: number, moveY: number): MoveElementAction { + return { kind: KIND, elementIds, moveX, moveY }; + } +} + +/* The MoveViewportHandler class is an implementation of the IActionHandler interface that handles +moving of the viewport. */ +@injectable() +export class MoveViewportHandler implements IActionHandler { + @inject(EditorContextService) + protected editorContextService: EditorContextService; + + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + protected readonly throttledHandleViewportMove = throttle((action: MoveViewportAction) => this.handleMoveViewport(action), 150); + + handle(action: Action): void | Action | ICommand { + if (MoveViewportAction.is(action)) { + this.throttledHandleViewportMove(action); + } + } + + handleMoveViewport(action: MoveViewportAction): void { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + this.dispatcher.dispatch(this.moveViewport(viewport, action.moveX, action.moveY)); + } + + protected moveViewport(viewport: SModelRoot & Viewport, offsetX: number, offSetY: number): SetViewportAction { + const newViewport: Viewport = { + scroll: { + x: viewport.scroll.x + offsetX, + y: viewport.scroll.y + offSetY + }, + zoom: viewport.zoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + } +} + +/* The MoveElementHandler class is an implementation of the IActionHandler interface that handles +moving elements. */ +@injectable() +export class MoveElementHandler implements IActionHandler { + @inject(EditorContextService) + protected editorContextService: EditorContextService; + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + protected readonly throttledHandleElementMove = throttle((action: MoveElementAction) => this.handleMoveElement(action), 150); + + handle(action: Action): void | Action | ICommand { + if (MoveElementAction.is(action)) { + this.throttledHandleElementMove(action); + } + } + + handleMoveElement(action: MoveElementAction): void { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + + const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + + this.dispatcher.dispatchAll(this.move(viewport, elements, action.moveX, action.moveY)); + } + + protected getBounds(element: SelectableBoundsAware, offSetX: number, offSetY: number): Point { + return { x: element.bounds.x + offSetX, y: element.bounds.y + offSetY }; + } + + protected adaptViewport( + viewport: SModelRoot & Viewport, + newPoint: Point, + moveX: number, + moveY: number + ): MoveViewportAction | undefined { + if ( + newPoint.x < viewport.scroll.x || + newPoint.x > viewport.scroll.x + viewport.canvasBounds.width || + newPoint.y < viewport.scroll.y || + newPoint.y > viewport.scroll.y + viewport.canvasBounds.height + ) { + return MoveViewportAction.create(moveX, moveY); + } + return; + } + + protected moveElement(element: SelectableBoundsAware, offSetX: number, offSetY: number): ChangeBoundsOperation { + return ChangeBoundsOperation.create([ + { + elementId: element.id, + newSize: { + width: element.bounds.width, + height: element.bounds.height + }, + newPosition: { + x: element.bounds.x + offSetX, + y: element.bounds.y + offSetY + } + } + ]); + } + + protected move(viewport: SModelRoot & Viewport, selectedElements: SelectableBoundsAware[], deltaX: number, deltaY: number): Action[] { + const results: Action[] = []; + + if (selectedElements.length !== 0) { + selectedElements.forEach(currentElement => { + results.push(this.moveElement(currentElement, deltaX, deltaY)); + const newPosition = this.getBounds(currentElement, deltaX, deltaY); + const viewportAction = this.adaptViewport(viewport, newPosition, deltaX, deltaY); + if (viewportAction) { + results.push(viewportAction); + } + }); + } + return results; + } +} diff --git a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts new file mode 100644 index 0000000..48656a2 --- /dev/null +++ b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts @@ -0,0 +1,178 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, Bounds, Point, SetViewportAction, Viewport } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { throttle } from 'lodash'; +import { + findParentByFeature, + IActionDispatcher, + IActionHandler, + ICommand, + isViewport, + SChildElement, + SModelElement, + SModelRoot +} from 'sprotty'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { TYPES } from '../../../base/types'; +import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; + +/** + * Action for triggering zooming of the viewport. + */ +export interface ZoomViewportAction extends Action { + kind: typeof ZoomViewportAction.KIND; + /** + * used to specify the amount by which the viewport should be zoomed + */ + zoomFactor: number; +} + +export namespace ZoomViewportAction { + export const KIND = 'zoomViewportAction'; + + export function is(object: any): object is ZoomViewportAction { + return Action.hasKind(object, KIND); + } + + export function create(zoomFactor: number): ZoomViewportAction { + return { kind: KIND, zoomFactor }; + } +} + +/** + * Action for triggering zooming of the elements.. + */ +export interface ZoomElementAction extends Action { + kind: typeof ZoomElementAction.KIND; + /** + * used to specify the elements to be zoomed in/out + */ + elementIds: string[]; + /** + * used to specify the amount by which the viewport should be zoomed + */ + zoomFactor: number; +} + +export namespace ZoomElementAction { + export const KIND = 'zoomElementAction'; + + export function is(object: any): object is ZoomElementAction { + return Action.hasKind(object, KIND); + } + + export function create(elementIds: string[], zoomFactor: number): ZoomElementAction { + return { kind: KIND, elementIds, zoomFactor }; + } +} + +/* The ZoomViewportHandler class is an implementation of the IActionHandler interface that handles +zooming in and out of a viewport. */ +@injectable() +export class ZoomViewportHandler implements IActionHandler { + @inject(EditorContextService) + protected editorContextService: EditorContextService; + + static readonly defaultZoomInFactor = 1.1; + static readonly defaultZoomOutFactor = 0.9; + + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + protected readonly throttledHandleViewportZoom = throttle((action: ZoomViewportAction) => this.handleZoomViewport(action), 150); + + handle(action: Action): void { + if (ZoomViewportAction.is(action)) { + this.throttledHandleViewportZoom(action); + } + } + + handleZoomViewport(action: ZoomViewportAction): void { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor)); + } + + protected setNewZoomFactor(viewport: SModelRoot & Viewport, zoomFactor: number): SetViewportAction { + const newZoom = viewport.zoom * zoomFactor; + + const newViewport = { + scroll: viewport.scroll, + zoom: newZoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + } +} + +/* The ZoomElementHandler class is an implementation of the IActionHandler interface that handles +zooming in and out of elements. */ +@injectable() +export class ZoomElementHandler implements IActionHandler { + @inject(EditorContextService) + protected editorContextService: EditorContextService; + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + protected readonly throttledHandleElementZoom = throttle((action: ZoomElementAction) => this.handleZoomElement(action), 150); + + handle(action: Action): void | Action | ICommand { + if (ZoomElementAction.is(action)) { + this.throttledHandleElementZoom(action); + } + } + + handleZoomElement(action: ZoomElementAction): void { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + + const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + const center = this.getCenter(viewport, elements); + this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor, center)); + } + + protected getCenter(viewport: SModelRoot & Viewport, selectedElements: SelectableBoundsAware[]): Point { + // Get bounds of elements based on the viewport + const allBounds = selectedElements.map(e => this.boundsInViewport(viewport, e, e.bounds)); + const mergedBounds = allBounds.reduce((b0, b1) => Bounds.combine(b0, b1)); + return Bounds.center(mergedBounds); + } + + // copy from center-fit.ts, translates the children bounds to the viewport bounds + protected boundsInViewport(viewport: SModelRoot & Viewport, element: SModelElement, bounds: Bounds): Bounds { + if (element instanceof SChildElement && element.parent !== viewport) { + return this.boundsInViewport(viewport, element.parent, element.parent.localToParent(bounds) as Bounds); + } else { + return bounds; + } + } + + protected setNewZoomFactor(viewport: SModelRoot & Viewport, zoomFactor: number, point: Point): SetViewportAction { + const newZoom = viewport.zoom * zoomFactor; + + const newViewport = { + scroll: { + x: point.x - (0.5 * viewport.canvasBounds.width) / newZoom, + y: point.y - (0.5 * viewport.canvasBounds.height) / newZoom + }, + zoom: newZoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + } +} diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts new file mode 100644 index 0000000..5b1f49d --- /dev/null +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, SelectAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { isSelectable, KeyListener, KeyTool, SModelElement, SRoutableElement, SwitchEditModeAction } from 'sprotty'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { SResizeHandle } from '../../change-bounds/model'; + +/** + * Deselects the element if there is no interaction possible with element. + */ +@injectable() +export class DeselectKeyTool implements GLSPTool { + static ID = 'glsp.deselect-key-tool'; + + isEditTool = true; + + protected readonly deselectKeyListener = new DeselectKeyListener(); + + @inject(KeyTool) protected readonly keytool: KeyTool; + + get id(): string { + return DeselectKeyTool.ID; + } + + enable(): void { + this.keytool.register(this.deselectKeyListener); + } + + disable(): void { + this.keytool.deregister(this.deselectKeyListener); + } +} + +export class DeselectKeyListener extends KeyListener { + override keyDown(target: SModelElement, event: KeyboardEvent): Action[] { + if (this.matchesDeselectKeystroke(event)) { + const isResizeHandleActive = toArray(target.root.index.all().filter(el => el instanceof SResizeHandle)).length > 0; + + if (isResizeHandleActive) { + return []; + } + + const deselect = toArray(target.root.index.all().filter(element => isSelectable(element) && element.selected)); + const actions: Action[] = []; + + if (deselect.length > 0) { + actions.push(SelectAction.create({ deselectedElementsIDs: deselect.map(e => e.id) })); + } + + const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); + if (routableDeselect.length > 0) { + actions.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); + } + + return actions; + } + return []; + } + + protected matchesDeselectKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } +} diff --git a/packages/client/src/features/accessibility/view-key-tools/di.config.ts b/packages/client/src/features/accessibility/view-key-tools/di.config.ts new file mode 100644 index 0000000..d9000eb --- /dev/null +++ b/packages/client/src/features/accessibility/view-key-tools/di.config.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; +import { TYPES } from '../../../base/types'; +import { DeselectKeyTool } from '../view-key-tools/deselect-key-tool'; +import { MovementKeyTool } from './movement-key-tool'; +import { ZoomKeyTool } from './zoom-key-tool'; + +export const glspViewKeyToolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + configureViewKeyTools(context); +}); + +export function configureViewKeyTools(context: Pick): void { + bindAsService(context, TYPES.IDefaultTool, MovementKeyTool); + bindAsService(context, TYPES.IDefaultTool, ZoomKeyTool); + bindAsService(context, TYPES.IDefaultTool, DeselectKeyTool); +} diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts new file mode 100644 index 0000000..c60a0c9 --- /dev/null +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -0,0 +1,115 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ISnapper, KeyListener, KeyTool, SModelElement } from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { TYPES } from '../../../base/types'; +import { GridSnapper } from '../../change-bounds/snap'; + +import { SelectionService } from '../../select/selection-service'; +import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; + +/** + * Moves viewport and elements when its focused and arrow keys are hit. + */ +@injectable() +export class MovementKeyTool implements GLSPTool { + static ID = 'glsp.movement-key-tool'; + + isEditTool = true; + + protected readonly movementKeyListener = new MoveKeyListener(this); + + @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.SelectionService) selectionService: SelectionService; + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + + get id(): string { + return MovementKeyTool.ID; + } + + enable(): void { + this.keytool.register(this.movementKeyListener); + } + + disable(): void { + this.keytool.deregister(this.movementKeyListener); + } +} + +export class MoveKeyListener extends KeyListener { + // Default x distance used if GridSnapper is not provided + static readonly defaultMoveX = 20; + + // Default y distance used if GridSnapper is not provided + static readonly defaultMoveY = 20; + + protected grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY }; + + constructor(protected readonly tool: MovementKeyTool) { + super(); + + if (this.tool.snapper instanceof GridSnapper) { + this.grid = this.tool.snapper.grid; + } + } + + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); + + if (selectedElementIds.length > 0) { + if (this.matchesMoveUpKeystroke(event)) { + return [MoveElementAction.create(selectedElementIds, 0, -this.grid.x)]; + } else if (this.matchesMoveDownKeystroke(event)) { + return [MoveElementAction.create(selectedElementIds, 0, this.grid.x)]; + } else if (this.matchesMoveRightKeystroke(event)) { + return [MoveElementAction.create(selectedElementIds, this.grid.x, 0)]; + } else if (this.matchesMoveLeftKeystroke(event)) { + return [MoveElementAction.create(selectedElementIds, -this.grid.x, 0)]; + } + } else { + if (this.matchesMoveUpKeystroke(event)) { + return [MoveViewportAction.create(0, -this.grid.x)]; + } else if (this.matchesMoveDownKeystroke(event)) { + return [MoveViewportAction.create(0, this.grid.x)]; + } else if (this.matchesMoveRightKeystroke(event)) { + return [MoveViewportAction.create(this.grid.x, 0)]; + } else if (this.matchesMoveLeftKeystroke(event)) { + return [MoveViewportAction.create(-this.grid.x, 0)]; + } + } + return []; + } + + protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowUp'); + } + + protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowDown'); + } + + protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowRight'); + } + + protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowLeft'); + } +} diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts new file mode 100644 index 0000000..681e590 --- /dev/null +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, CenterAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { KeyListener, KeyTool, SModelElement } from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { TYPES } from '../../../base/types'; +import { SelectionService } from '../../select/selection-service'; +import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; + +/** + * Zoom viewport and elements when its focused and arrow keys are hit. + */ +@injectable() +export class ZoomKeyTool implements GLSPTool { + static ID = 'glsp.zoom-key-tool'; + + isEditTool = false; + + protected readonly zoomKeyListener = new ZoomKeyListener(this); + + @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.SelectionService) selectionService: SelectionService; + + get id(): string { + return ZoomKeyTool.ID; + } + + enable(): void { + this.keytool.register(this.zoomKeyListener); + } + + disable(): void { + this.keytool.deregister(this.zoomKeyListener); + } +} + +export class ZoomKeyListener extends KeyListener { + static readonly defaultZoomInFactor = 1.1; + static readonly defaultZoomOutFactor = 0.9; + + constructor(protected tool: ZoomKeyTool) { + super(); + } + + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); + + if (this.matchesZoomOutKeystroke(event)) { + if (selectedElementIds.length > 0) { + return [ZoomElementAction.create(selectedElementIds, ZoomKeyListener.defaultZoomOutFactor)]; + } else { + return [ZoomViewportAction.create(ZoomKeyListener.defaultZoomOutFactor)]; + } + } else if (this.matchesZoomInKeystroke(event)) { + if (selectedElementIds.length > 0) { + return [ZoomElementAction.create(selectedElementIds, ZoomKeyListener.defaultZoomInFactor)]; + } else { + return [ZoomViewportAction.create(ZoomKeyListener.defaultZoomInFactor)]; + } + } else if (this.matchesMinZoomLevelKeystroke(event)) { + return [CenterAction.create(selectedElementIds)]; + } + return []; + } + + protected matchesZoomInKeystroke(event: KeyboardEvent): boolean { + /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ + return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); + } + + protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); + } + + protected matchesZoomOutKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract'); + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index de87890..b20c481 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -15,7 +15,9 @@ ********************************************************************************/ import defaultGLSPModule from './base/di.config'; import { glspAccessibilityModule } from './features/accessibility/di.config'; +import { glspMoveZoomModule } from './features/accessibility/move-zoom/di.config'; import { glspSearchPaletteModule } from './features/accessibility/search/di.config'; +import { glspViewKeyToolsModule } from './features/accessibility/view-key-tools/di.config'; import glspCommandPaletteModule from './features/command-palette/di.config'; import glspContextMenuModule from './features/context-menu/di.config'; import { copyPasteContextMenuModule, glspServerCopyPasteModule } from './features/copy-paste/di.config'; @@ -58,6 +60,9 @@ export * from './base/tool-manager/glsp-tool-manager'; export * from './base/tool-manager/tool-actions'; export * from './base/types'; export * from './base/view/view-registry'; +export * from './features/accessibility/view-key-tools/deselect-key-tool'; +export * from './features/accessibility/view-key-tools/movement-key-tool'; +export * from './features/accessibility/view-key-tools/zoom-key-tool'; // // ------------------ Features ------------------ export * from './features/bounds/freeform-layout'; @@ -157,6 +162,8 @@ export { markerNavigatorContextMenuModule, glspViewportModule, svgMetadataModule, - glspSearchPaletteModule, - glspAccessibilityModule + glspViewKeyToolsModule, + glspMoveZoomModule, + glspAccessibilityModule, + glspSearchPaletteModule }; From 3d9dc502f4de7bbc9af7d3f9eb6d7dc7b14ffa16 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 7 Jun 2023 15:06:13 +0200 Subject: [PATCH 213/566] GLSP-1026 Provide bundle on example server package (#49) Introduce a `@eclipse-glsp-examples/workflow-server-bundle` package. This package just contains the bundled workflowserver without any direct dependencies to server components. It can be consumed in the dev example of the client repository without introducing a dependecy loop between client & server. This fixes #1026. We can decide on a later point if want to move the protocol package into the umbrella repository. In regards to developer experience its probably better to keep it in the client repo. Changes to the protocol package typically also require changes in the client and server repos. By keeping the package in one of the repositories in question we can reduce the required overhead with regards to merging and consuming the changes. --- examples/workflow-server-bundled/LICENSE | 277 ++++++++++++++++++ examples/workflow-server-bundled/README.md | 20 ++ examples/workflow-server-bundled/package.json | 68 +++++ examples/workflow-server/README.md | 18 ++ examples/workflow-server/package.json | 3 +- examples/workflow-server/webpack.config.js | 2 +- 6 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 examples/workflow-server-bundled/LICENSE create mode 100644 examples/workflow-server-bundled/README.md create mode 100644 examples/workflow-server-bundled/package.json create mode 100644 examples/workflow-server/README.md diff --git a/examples/workflow-server-bundled/LICENSE b/examples/workflow-server-bundled/LICENSE new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/examples/workflow-server-bundled/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/examples/workflow-server-bundled/README.md b/examples/workflow-server-bundled/README.md new file mode 100644 index 0000000..2f51bf4 --- /dev/null +++ b/examples/workflow-server-bundled/README.md @@ -0,0 +1,20 @@ +# Eclipse GLSP - Workflow Example Server (bundled) + +Provides a bundled version of the Workflow example server for node. This example server is used as dev example in GLSP projects. +Note that this package provides the bundled file. If you are looking for the unbundled source code with type definitons check out +[`@eclipse-glsp-examples/workflow-server`](https://www.npmjs.com/package/@eclipse-glsp-examples/workflow-server). + +## Workflow Diagram Example + +The workflow diagram is a consistent example provided by all GLSP components. +The example implements a simple flow chart diagram editor with different types of nodes and edges (see screenshot below). +The example can be used to try out different GLSP features, as well as several available integrations with IDE platforms (Theia, VSCode, Eclipse, Standalone). +As the example is fully open source, you can also use it as a blueprint for a custom implementation of a GLSP diagram editor. +See [our project website](https://www.eclipse.org/glsp/documentation/#workflowoverview) for an overview of the workflow example and all components implementing it. + + + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json new file mode 100644 index 0000000..9dbbc9f --- /dev/null +++ b/examples/workflow-server-bundled/package.json @@ -0,0 +1,68 @@ +{ + "name": "@eclipse-glsp-examples/workflow-server-bundled", + "version": "1.1.0-next", + "description": "GLSP node server for the workflow example (bundled)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "homepage": "https://www.eclipse.org/glsp/", + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-server-node.git" + }, + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "author": { + "name": "Eclipse GLSP" + }, + "contributors": [ + { + "name": "STMicroelectronics", + "url": "https://www.st.com/" + }, + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "main": "./wf-glsp-server-node.js", + "browser": { + "./lib/node/index.js": "./lib/browser/index.js" + }, + "files": [ + "wf-glsp-server-node.js", + "wf-glsp-server-node.js.map" + ], + "scripts": { + "build": "tsc -b && yarn bundle", + "bundle": "webpack", + "bundle:browser": "webpack --env target=webworker ", + "clean": "rimraf lib tsconfig.tsbuildinfo bundle", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", + "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", + "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", + "watch": "tsc -w" + }, + "dependencies": { + "@eclipse-glsp/layout-elk": "1.1.0-next", + "@eclipse-glsp/server": "1.1.0-next", + "inversify": "^6.0.1" + }, + "devDependencies": { + "source-map-loader": "^4.0.1", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/examples/workflow-server/README.md b/examples/workflow-server/README.md new file mode 100644 index 0000000..a9c6488 --- /dev/null +++ b/examples/workflow-server/README.md @@ -0,0 +1,18 @@ +# Eclipse GLSP - Workflow Examples + +Provides the typescript implementation of the Workflow example server. This example server is used as dev example in GLSP projects. + +## Workflow Diagram Example + +The workflow diagram is a consistent example provided by all GLSP components. +The example implements a simple flow chart diagram editor with different types of nodes and edges (see screenshot below). +The example can be used to try out different GLSP features, as well as several available integrations with IDE platforms (Theia, VSCode, Eclipse, Standalone). +As the example is fully open source, you can also use it as a blueprint for a custom implementation of a GLSP diagram editor. +See [our project website](https://www.eclipse.org/glsp/documentation/#workflowoverview) for an overview of the workflow example and all components implementing it. + + + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 93effb8..05b1c9c 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -43,8 +43,7 @@ "node.js", "node.d.ts", "browser.d.ts", - "browser.js", - "bundle/wf-glsp-server-node.js" + "browser.js" ], "scripts": { "build": "tsc -b && yarn bundle", diff --git a/examples/workflow-server/webpack.config.js b/examples/workflow-server/webpack.config.js index b2d1b67..8a0a03e 100644 --- a/examples/workflow-server/webpack.config.js +++ b/examples/workflow-server/webpack.config.js @@ -16,7 +16,7 @@ const webpack = require('webpack'); const path = require('path'); const buildRoot = path.resolve(__dirname, 'lib'); -const appRoot = path.resolve(__dirname, 'bundle'); +const appRoot = path.resolve(__dirname, '..', 'workflow-server-bundled'); module.exports = env => { const target = env.target ?? 'node'; From 742e9212fe361c27b0dbc8a77b5d527513c02071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Wed, 7 Jun 2023 15:54:42 +0200 Subject: [PATCH 214/566] #1029 Introduce resizing elements via the keyboard (#240) - Introduce tool and handler for resizing elements via keyboard shortcut Part of https://github.com/eclipse-glsp/glsp/issues/1029 --- .../src/features/accessibility/di.config.ts | 2 + .../resize-key-tool/di.config.ts | 37 ++++++ .../resize-key-tool/resize-key-handler.ts | 120 ++++++++++++++++++ .../resize-key-tool/resize-key-tool.ts | 108 ++++++++++++++++ packages/client/src/index.ts | 9 +- 5 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 packages/client/src/features/accessibility/resize-key-tool/di.config.ts create mode 100644 packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts create mode 100644 packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts diff --git a/packages/client/src/features/accessibility/di.config.ts b/packages/client/src/features/accessibility/di.config.ts index d6e65c1..948d735 100644 --- a/packages/client/src/features/accessibility/di.config.ts +++ b/packages/client/src/features/accessibility/di.config.ts @@ -16,6 +16,7 @@ import { ContainerModule } from 'inversify'; import { configureMoveZoom } from './move-zoom/di.config'; +import { configureResizeTools } from './resize-key-tool/di.config'; import { configureSearchPaletteModule } from './search/di.config'; import { configureViewKeyTools } from './view-key-tools/di.config'; @@ -24,6 +25,7 @@ import { configureViewKeyTools } from './view-key-tools/di.config'; */ export const glspAccessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; + configureResizeTools(context); configureViewKeyTools(context); configureMoveZoom(context); configureSearchPaletteModule(context); diff --git a/packages/client/src/features/accessibility/resize-key-tool/di.config.ts b/packages/client/src/features/accessibility/resize-key-tool/di.config.ts new file mode 100644 index 0000000..6be1ecb --- /dev/null +++ b/packages/client/src/features/accessibility/resize-key-tool/di.config.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; +import { ContainerModule } from 'inversify'; +import { configureActionHandler } from 'sprotty'; +import { TYPES } from '../../../base/types'; +import { ResizeElementAction, ResizeElementHandler } from './resize-key-handler'; +import { ResizeKeyTool } from './resize-key-tool'; + +/** + * Handles resize actions. + */ +export const glspResizeKeyModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureResizeTools(context); +}); + +export function configureResizeTools(context: BindingContext): void { + context.bind(ResizeElementHandler).toSelf().inSingletonScope(); + + configureActionHandler(context, ResizeElementAction.KIND, ResizeElementHandler); + bindAsService(context, TYPES.IDefaultTool, ResizeKeyTool); +} diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts new file mode 100644 index 0000000..55bbfad --- /dev/null +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, ChangeBoundsOperation, Dimension, Point } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { IActionDispatcher, IActionHandler, ICommand, ISnapper, SModelElement, SParentElement } from 'sprotty'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { TYPES } from '../../../base/types'; +import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; +import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware, toElementAndBounds } from '../../../utils/smodel-util'; +import { Resizable } from '../../change-bounds/model'; +import { GridSnapper } from '../../change-bounds/snap'; + +// eslint-disable-next-line no-shadow +export enum ResizeType { + Increase, + Decrease, + MinSize +} + +/** + * Action for triggering resizing of elements. + */ +export interface ResizeElementAction extends Action { + kind: typeof ResizeElementAction.KIND; + elementIds: string[]; + resizeType: ResizeType; +} +export namespace ResizeElementAction { + export const KIND = 'resizeElementAction'; + + export function is(object: any): object is ResizeElementAction { + return Action.hasKind(object, KIND); + } + + export function create(elementIds: string[], resizeType: ResizeType): ResizeElementAction { + return { kind: KIND, elementIds, resizeType }; + } +} + +/* The ResizeElementHandler class is an implementation of the IActionHandler interface that handles +resizing of elements. */ +@injectable() +export class ResizeElementHandler implements IActionHandler { + @inject(EditorContextService) + protected editorContextService: EditorContextService; + + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + + // Default x resize used if GridSnapper is not provided + static readonly defaultResizeX = 20; + + // Default y resize used if GridSnapper is not provided + static readonly defaultResizeY = 20; + protected grid = { x: ResizeElementHandler.defaultResizeX, y: ResizeElementHandler.defaultResizeY }; + + protected isEditMode = false; + + constructor(@inject(TYPES.ISnapper) @optional() protected readonly snapper?: ISnapper) { + if (snapper instanceof GridSnapper) { + this.grid = snapper.grid; + } + } + + handle(action: Action): void | Action | ICommand { + if (ResizeElementAction.is(action)) { + this.handleResizeElement(action); + } + } + + handleResizeElement(action: ResizeElementAction): void { + const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + this.dispatcher.dispatchAll(this.resize(elements, action)); + } + + protected resize(elements: SelectableBoundsAware[], action: ResizeElementAction): Action[] { + const actions: Action[] = []; + + elements.forEach(element => { + const { x, y, width: oldWidth, height: oldHeight } = element.bounds; + let width = 0; + let height = 0; + + if (action.resizeType === ResizeType.Decrease) { + width = oldWidth - this.grid.x; + height = oldHeight - this.grid.y; + } else if (action.resizeType === ResizeType.Increase) { + width = oldWidth + this.grid.x; + height = oldHeight + this.grid.y; + } else if (action.resizeType === ResizeType.MinSize) { + width = minWidth(element); + height = minHeight(element); + } + + if (this.isValidBoundChange(element, { x, y }, { width, height })) { + const resizeElement = { id: element.id, bounds: { x, y, width, height } } as SModelElement & SParentElement & Resizable; + actions.push(ChangeBoundsOperation.create([toElementAndBounds(resizeElement)])); + } + }); + + return actions; + } + + protected isValidBoundChange(element: SelectableBoundsAware, newPosition: Point, newSize: Dimension): boolean { + return isValidSize(element, newSize) && isValidMove(element, newPosition); + } +} diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts new file mode 100644 index 0000000..abd8dce --- /dev/null +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement } from 'sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { TYPES } from '../../../base/types'; +import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; +import { SelectionService } from '../../select/selection-service'; +import { ResizeElementAction, ResizeType } from './resize-key-handler'; + +@injectable() +export class ResizeKeyTool implements GLSPTool { + static ID = 'glsp.resize-key-tool'; + + isEditTool = true; + + @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(TYPES.SelectionService) readonly selectionService: SelectionService; + + protected resizeKeyListener: ResizeKeyListener = new ResizeKeyListener(this); + + get id(): string { + return ResizeKeyTool.ID; + } + + enable(): void { + this.keytool.register(this.resizeKeyListener); + } + + disable(): void { + this.keytool.deregister(this.resizeKeyListener); + } +} +@injectable() +export class ResizeKeyListener extends KeyListener { + protected isEditMode = false; + + constructor(protected readonly tool: ResizeKeyTool) { + super(); + } + + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + const actions = []; + const selectedElementsIds = this.tool.selectionService.getSelectedElementIDs(); + + if (this.isEditMode && this.matchesDeactivateResizeModeKeystroke(event)) { + this.isEditMode = false; + actions.push(EnableDefaultToolsAction.create()); + } + + if (selectedElementsIds.length > 0) { + if (!this.isEditMode && this.matchesActivateResizeModeKeystroke(event)) { + this.isEditMode = true; + actions.push(EnableToolsAction.create([ResizeKeyTool.ID])); + } + + if (this.isEditMode) { + if (this.matchesIncreaseSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Increase)); + } else if (this.matchesDecreaseSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Decrease)); + } else if (this.matchesMinSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.MinSize)); + } + } + } + return actions; + } + + protected matchesIncreaseSizeKeystroke(event: KeyboardEvent): boolean { + /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ + return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); + } + + protected matchesActivateResizeModeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyA', 'alt'); + } + + protected matchesDeactivateResizeModeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } + + protected matchesMinSizeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); + } + + protected matchesDecreaseSizeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract'); + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index b20c481..4d3c6a9 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -16,6 +16,7 @@ import defaultGLSPModule from './base/di.config'; import { glspAccessibilityModule } from './features/accessibility/di.config'; import { glspMoveZoomModule } from './features/accessibility/move-zoom/di.config'; +import { glspResizeKeyModule } from './features/accessibility/resize-key-tool/di.config'; import { glspSearchPaletteModule } from './features/accessibility/search/di.config'; import { glspViewKeyToolsModule } from './features/accessibility/view-key-tools/di.config'; import glspCommandPaletteModule from './features/command-palette/di.config'; @@ -60,6 +61,7 @@ export * from './base/tool-manager/glsp-tool-manager'; export * from './base/tool-manager/tool-actions'; export * from './base/types'; export * from './base/view/view-registry'; +export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; @@ -162,8 +164,9 @@ export { markerNavigatorContextMenuModule, glspViewportModule, svgMetadataModule, - glspViewKeyToolsModule, - glspMoveZoomModule, + glspResizeKeyModule, + glspSearchPaletteModule, glspAccessibilityModule, - glspSearchPaletteModule + glspViewKeyToolsModule, + glspMoveZoomModule }; From 09d3c475642ae91c9c784dea469db35d01d8b20d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 7 Jun 2023 15:54:50 +0200 Subject: [PATCH 215/566] GLSP-1026 Fix launch configs &scripts (#50) Adapt launch configs and start scripts to use the new bundled workflow server package --- examples/workflow-server-bundled/package.json | 23 ++++--------------- examples/workflow-server/package.json | 2 -- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 9dbbc9f..6bf75cd 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -41,27 +41,12 @@ "wf-glsp-server-node.js.map" ], "scripts": { - "build": "tsc -b && yarn bundle", - "bundle": "webpack", - "bundle:browser": "webpack --env target=webworker ", - "clean": "rimraf lib tsconfig.tsbuildinfo bundle", - "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", - "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", - "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", + "clean": "rimraf ./*.js./*.js.map", + "prepare": "yarn clean", + "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", + "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" }, - "dependencies": { - "@eclipse-glsp/layout-elk": "1.1.0-next", - "@eclipse-glsp/server": "1.1.0-next", - "inversify": "^6.0.1" - }, - "devDependencies": { - "source-map-loader": "^4.0.1", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1" - }, "publishConfig": { "access": "public" } diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 05b1c9c..e7e23ab 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -53,8 +53,6 @@ "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "prepare": "yarn clean && yarn build", - "start": "node --enable-source-maps bundle/wf-glsp-server-node.js --port 5007", - "start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" }, "dependencies": { From 43e60c7b364778db3df8540698e214c96d31e5ca Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 7 Jun 2023 23:36:47 +0200 Subject: [PATCH 216/566] GLSP-1026 Use new bundled workflow-server package (#252) Also: Disable logging of base json-rpc-client proxy per default --- examples/workflow-standalone/package.json | 2 +- .../client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 309ce38..e1d20c7 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -34,7 +34,7 @@ "@eclipse-glsp/client": "1.1.0-next" }, "devDependencies": { - "@eclipse-glsp-examples/workflow-server": "next", + "@eclipse-glsp-examples/workflow-server-bundled": "next", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", "path-browserify": "^1.0.1", diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 5dea44c..4522240 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -158,7 +158,7 @@ export class JsonrpcClientProxy implements GLSPClientProxy { protected clientConnection?: MessageConnection; protected enableLogging: boolean; - initialize(clientConnection: MessageConnection, enableLogging = true): void { + initialize(clientConnection: MessageConnection, enableLogging = false): void { this.clientConnection = clientConnection; this.enableLogging = enableLogging; } From d084ccd9187b0ac9a8d4446c5ec864664aaf8191 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 6 Jun 2023 00:08:20 +0200 Subject: [PATCH 217/566] GLSP-1022: Update dependencies (#1023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - In preparation for the 2.0.0 Release all config dependencies for the `@eclipse-glsp/config`, `@eclipse-glsp/config-test` and `@eclipse-glsp/dev` packages have been updated to the latest version. - Remove compile-time warnings regarding unfulfilled peerDependencies by adding a corresponding `devDependency` to the root package.json - Update `@eclipse-glsp/ts-config` configuration files. - Remove mocha types from default `ts-config.json` otherwise mocha is an indirect dependency even for projects that don't use mocha tests. - Add additional test config for mocha testing that can be consumed with `@eclipse-glsp/tsconfig/mocha´ - Config package no longer includes a default typescript version - Update READMEs of config packages - Update eslint confiug. Use typscript corrsepondant rules for `no-shadow´ Part of https://github.com/eclipse-glsp/glsp/pull/1022 --- dev-packages/CHANGELOG.md | 28 ------------------- dev-packages/cli/package.json | 11 ++++---- dev-packages/cli/src/app.ts | 1 + dev-packages/cli/tsconfig.json | 13 +++++---- dev-packages/config-test/README.md | 2 +- dev-packages/config-test/package.json | 10 +++---- dev-packages/config/README.md | 9 ++++-- dev-packages/config/package.json | 18 ++++++------ dev-packages/dev/README.md | 16 +++++++---- .../configs/warnings.eslintrc.js | 9 ++---- dev-packages/eslint-config/package.json | 1 + dev-packages/mocha-config/README.md | 2 +- dev-packages/mocha-config/package.json | 7 ++--- dev-packages/nyc-config/README.md | 6 ++-- dev-packages/prettier-config/README.md | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/README.md | 6 +++- dev-packages/ts-config/mocha.json | 7 +++++ dev-packages/ts-config/package.json | 7 +++-- dev-packages/ts-config/tsconfig.json | 3 +- 20 files changed, 76 insertions(+), 84 deletions(-) delete mode 100644 dev-packages/CHANGELOG.md create mode 100644 dev-packages/ts-config/mocha.json diff --git a/dev-packages/CHANGELOG.md b/dev-packages/CHANGELOG.md deleted file mode 100644 index 4b3d7b3..0000000 --- a/dev-packages/CHANGELOG.md +++ /dev/null @@ -1,28 +0,0 @@ -# Eclipse GLSP Dev Packages Changelog - -## v1.1.0 - upcoming - -- [config] Introduce all-in-one [`@eclipse-glsp/dev`](https://www.npmjs.com/package/@eclipse-glsp/dev) meta package. [#842](https://github.com/eclipse-glsp/glsp/pull/842) -- [cli] Contribute the `checkHeaders` command to validate the copyright year (range) of license headers. [#834](https://github.com/eclipse-glsp/glsp/pull/834) -- [config] Introduce [`@eclipse-glsp/nyc-config`](https://www.npmjs.com/package/@eclipse-glsp/nyc-config) - package and the [`@eclipse-glsp/config-test`](https://www.npmjs.com/package/@eclipse-glsp/config-test) meta package. [#755](https://github.com/eclipse-glsp/glsp/pull/755) - - - Contribute the `coverageReport` command to create a full nyc test coverage report for a lerna/yarn mono repository - -- [cli] Introduce [`@eclipse-glsp/cli`](https://www.npmjs.com/package/@eclipse-glsp/cli) - package to offer CLI tooling & utility scripts for GLSP projects. [#755](https://github.com/eclipse-glsp/glsp/pull/755) - Contributed on behalf of STMicroelectronics - - Contribute the `release` command to prepare & publish a new Github release for a specific GLSP component - -### Breaking Changes - -- [node] Update minimum requirements for Node to >=16.11.0 [#829](https://github.com/eclipse-glsp/glsp/pull/829) - -## [v1.0.0 - 30/06/2022](https://github.com/eclipse-glsp/glsp/releases/tag/v1.0.0) - -Inception of the GLSP dev packages. -This project is part of the GLSP umbrella repository and provides common shared development packages for Eclipse GLSP components that are implemented with Typescript. - -- [`@eclipse-glsp/config`](https://www.npmjs.com/package/@eclipse-glsp/config): Meta package for shared build configuration - - [`@eclipse-glsp/ts-config`](https://www.npmjs.com/package/@eclipse-glsp/ts-config): Shared Typescript configuration for GLSP projects - - [`@eclipse-glsp/eslint-config`](https://www.npmjs.com/package/@eclipse-glsp/esling-config): Shared ESLint configuration for GLSP projects - - [`@eclipse-glsp/prettier-config`](https://www.npmjs.com/package/@eclipse-glsp/prettier-config): Shared Prettier configuration for GLSP projects diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index b8d86e0..c8406bb 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -41,12 +41,12 @@ "watch": "tsc -w" }, "dependencies": { - "commander": "^9.4.0", + "commander": "^10.0.1", "glob": "^8.0.3", - "node-fetch": "2.6.7", + "node-fetch": "^2.6.11", "readline-sync": "^1.4.10", - "semver": "^7.3.7", - "shelljs": "0.8.5" + "semver": "^7.5.1", + "shelljs": "^0.8.5" }, "devDependencies": { "@eclipse-glsp/config": "1.0.0-next", @@ -54,8 +54,7 @@ "@types/node-fetch": "2.6.2", "@types/readline-sync": "1.4.4", "@types/semver": "7.3.12", - "@types/shelljs": "0.8.11", - "ts-node": "^10.9.1" + "@types/shelljs": "0.8.11" }, "publishConfig": { "access": "public" diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index cc78b3d..c373b1a 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -18,6 +18,7 @@ import { CheckHeaderCommand } from './commands/check-header'; import { CoverageReportCommand } from './commands/coverage-report'; import { ReleaseCommand } from './commands/release/release'; import { baseCommand } from './util/command-util'; + export const COMMAND_VERSION = '1.1.0-next'; const app = baseCommand() // diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index ecf9295..14c273a 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -1,10 +1,13 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": "." + "baseUrl": ".", + "esModuleInterop": true, + "resolveJsonModule": true }, - - "include": ["src"] -} + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/dev-packages/config-test/README.md b/dev-packages/config-test/README.md index 165ad1c..2acbca6 100644 --- a/dev-packages/config-test/README.md +++ b/dev-packages/config-test/README.md @@ -24,7 +24,7 @@ yarn add --dev @eclipse-glsp/config-test ### Mocha -**Create a `.mocharc.json`**: +**Create a `.mocharc`**: ```json { diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 1e97db0..d385b15 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -27,17 +27,17 @@ "dependencies": { "@eclipse-glsp/mocha-config": "1.0.0-next", "@eclipse-glsp/nyc-config": "1.0.0-next", - "@istanbuljs/nyc-config-typescript": "^1.0.0", - "@types/chai": "^4.3.4", + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", "@types/sinon": "^10.0.13", "chai": "^4.3.7", "ignore-styles": "^5.0.1", - "mocha": "^10.1.0", + "mocha": "^10.2.0", "mocha-jenkins-reporter": "^0.4.8", - "nyc": "^15.0.0", + "nyc": "^15.1.0", "reflect-metadata": "^0.1.13", - "sinon": "^15.0.0", + "sinon": "^15.1.0", "ts-node": "^10.9.1" }, "publishConfig": { diff --git a/dev-packages/config/README.md b/dev-packages/config/README.md index 59fbdfc..e425cbb 100644 --- a/dev-packages/config/README.md +++ b/dev-packages/config/README.md @@ -9,7 +9,6 @@ Provides a meta package that export common configuration objects for: The package is available via npm and is used by all GLSP components implemented with Typescript. ESLint and prettier are included as direct dependencies. -In addition, common GLSP dev dependencies like lerna,typescript,rimraf etc. are included. ## Components @@ -31,7 +30,7 @@ yarn add --dev @eclipse-glsp/config ```json { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -39,6 +38,10 @@ yarn add --dev @eclipse-glsp/config } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ### ESLint **Create a `.eslintrc.js`**: @@ -65,7 +68,7 @@ module.exports = { } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` file to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 2a220d1..0668030 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -27,20 +27,18 @@ "@eclipse-glsp/eslint-config": "1.0.0-next", "@eclipse-glsp/prettier-config": "1.0.0-next", "@eclipse-glsp/ts-config": "1.0.0-next", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.29.0", - "eslint-config-prettier": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-deprecation": "^1.3.3", + "eslint-plugin-deprecation": "^1.4.1", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.26.0", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-no-null": "^1.0.2", - "lerna": "^6.1.0", - "prettier": "^2.8.0", + "prettier": "^2.8.8", "reflect-metadata": "^0.1.13", - "rimraf": "^3.0.2", - "typescript": "^4.9.3" + "rimraf": "^5.0.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/dev/README.md b/dev-packages/dev/README.md index 7187f64..c5c7858 100644 --- a/dev-packages/dev/README.md +++ b/dev-packages/dev/README.md @@ -1,7 +1,7 @@ # Eclipse GLSP - All-in-one dev package A meta package that includes all shared configuration packages Eclipse GLSP components. -In addition, it also provides the GLSP CLI application +In addition, it also provides the GLSP CLI application. The package is available via npm and can be used by all GLSP components implemented with Typescript. @@ -38,6 +38,10 @@ yarn add --dev @eclipse-glsp/dev } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ### ESLint **Create a `.eslintrc.js`**: @@ -64,7 +68,7 @@ module.exports = { } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` to the workspace root**: ```json "@eclipse-glsp/prettier-config" @@ -72,7 +76,7 @@ module.exports = { ### Mocha -**Create a `.mocharc.json`**: +**Create a `.mocharc`**: ```json { @@ -83,10 +87,12 @@ module.exports = { ### Nyc -**Add a `.nycrc.json` to your project root**: +**Add a `.nycrc` to your project root**: ```json -"@eclipse-glsp/prettier-config" +{ + "extends": "@eclipse-glsp/nyc-config" +} ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/eslint-config/configs/warnings.eslintrc.js b/dev-packages/eslint-config/configs/warnings.eslintrc.js index f4b2df1..61b3712 100644 --- a/dev-packages/eslint-config/configs/warnings.eslintrc.js +++ b/dev-packages/eslint-config/configs/warnings.eslintrc.js @@ -9,12 +9,7 @@ module.exports = { 'no-new-wrappers': 'warn', 'no-return-await': 'warn', 'no-redeclare': 'off', - 'no-shadow': [ - 'warn', - { - hoist: 'all' - } - ], + 'no-shadow': 'off', 'no-multiple-empty-lines': [ 'warn', { @@ -76,7 +71,7 @@ module.exports = { '@typescript-eslint/type-annotation-spacing': 'warn', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-this-alias': 'off', - + '@typescript-eslint/no-shadow': 'warn', /// eslint-plugin-deprecation plugin 'import/no-deprecated': 'warn', 'deprecation/deprecation': 'warn' diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 4bc72d5..b7f3dc4 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -26,6 +26,7 @@ ], "main": "index.js", "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", diff --git a/dev-packages/mocha-config/README.md b/dev-packages/mocha-config/README.md index 209c156..38cd9cd 100644 --- a/dev-packages/mocha-config/README.md +++ b/dev-packages/mocha-config/README.md @@ -10,7 +10,7 @@ yarn add --dev @eclipse-glsp/mocha-config ## Usage -**Create a `.mocharc.json`**: +**Create a `.mocharc` file **: ```json { diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index fd86e2a..a25b826 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -30,10 +30,9 @@ ".mocharc.json" ], "peerDependencies": { - "ignore-styles": "^5.0.1", - "mocha": "^10.1.0", - "reflect-metadata": ">=0.1.13", - "ts-node": "^10.9.1" + "ignore-styles": "^5.0.0", + "mocha": "^10.0.0", + "reflect-metadata": "^0.1.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/README.md b/dev-packages/nyc-config/README.md index 480d32b..7b03e57 100644 --- a/dev-packages/nyc-config/README.md +++ b/dev-packages/nyc-config/README.md @@ -10,10 +10,12 @@ yarn add --dev @eclipse-glsp/nyc-config ## Usage -**Add a `.nycrc.json` to your project root**: +**Add a `.nycrc` to your project root**: ```json -"@eclipse-glsp/nyc-config" +{ + "extends": "@eclipse-glsp/nyc-config" +} ``` Configuration can also be provided by `nyc.config.js` if programmed logic is required. diff --git a/dev-packages/prettier-config/README.md b/dev-packages/prettier-config/README.md index 80e7e7c..c6c56a8 100644 --- a/dev-packages/prettier-config/README.md +++ b/dev-packages/prettier-config/README.md @@ -19,7 +19,7 @@ yarn add --dev @eclipse-glsp/prettier-config } ``` -**Or add a `.prettierrc.json` to the workspace root**: +**Or add a `.prettierrc` to the workspace root**: ```json "@eclipse-glsp/prettier-config" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 495e1a6..68b91d6 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "^2.3.0" + "prettier-plugin-packagejson": "~2.4.3" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/README.md b/dev-packages/ts-config/README.md index c4f0e0d..0e6d973 100644 --- a/dev-packages/ts-config/README.md +++ b/dev-packages/ts-config/README.md @@ -14,7 +14,7 @@ yarn add --dev @eclipse-glsp/ts-config ```json { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib" @@ -22,6 +22,10 @@ yarn add --dev @eclipse-glsp/ts-config } ``` +In addition, a custom configuration for projects that use `mocha` is available: + +- `@eclipse-glsp/ts-config/mocha` + ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). diff --git a/dev-packages/ts-config/mocha.json b/dev-packages/ts-config/mocha.json new file mode 100644 index 0000000..fc29724 --- /dev/null +++ b/dev-packages/ts-config/mocha.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "mocha", "reflect-metadata"] + }, +} diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index a3c7d6d..4e2166f 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -24,12 +24,13 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], - "main": "tsconfig.json", + "main": "./tsconfig.json", "files": [ - "tsconfig.json" + "tsconfig.json", + "mocha.json" ], "peerDependencies": { - "typescript": ">=4.9.3" + "typescript": ">=4.9.x" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index b869a93..83dd56b 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "skipLibCheck": true, "declaration": true, @@ -24,6 +25,6 @@ "jsx": "react", "lib": ["ES2017", "dom"], "sourceMap": true, - "types": ["node", "mocha", "reflect-metadata"] + "types": ["node", "reflect-metadata"] } } From fe581f359fbd5688420478186885da4bf68cea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:10:58 +0200 Subject: [PATCH 218/566] #1029 Add documentation for accessibility features (#254) - Search - Move - Zoom - Resize Part of https://github.com/eclipse-glsp/glsp/issues/1029 --- .../src/features/accessibility/README.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/client/src/features/accessibility/README.md diff --git a/packages/client/src/features/accessibility/README.md b/packages/client/src/features/accessibility/README.md new file mode 100644 index 0000000..a9257e8 --- /dev/null +++ b/packages/client/src/features/accessibility/README.md @@ -0,0 +1,65 @@ +# Keyboard-Only: Accessibility Features + +The features presented in the following are introduced for disability-aware conceptual modeling and allow basic user model interactions to be performed only with the keyboard. + +## Getting started + +In order to activate and use all of the currently provided keyboard only accessibility features, the module `glspAccessibilityModule` needs to be in use. +To activate and use only specific accessibility features, the respective module (see list below) needs to be in use. + +## Features + +Below is a list of the keyboard only accessibility features:
+ +| Feature | Description | Shortcut | Module | +| ------- | :----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | :-----------------------: | +| Search | Search for elements or edges using labels, types, etc.
with respective diagram highlighting | CTRL+F | `glspSearchPaletteModule` | +| Move | Move elements, edges or viewport into the directions | \|\| \|\| \|\| | `glspMoveZoomModule` | +| Zoom | Zoom in or out gradually to elements or viewport or reset to default zoom level. | + \|\| - \|\| CTRL+0 | `glspMoveZoomModule` | +| Resize | Increase or decrease element sizes gradually or reset to default element size. | ALT+A then + \|\| - \|\| CTRL+0 | `glspResizeKeyModule` | + +## Usage + +#### Search + +The search palette allows to search labelled elements or edges that have a labelled node as source or target. The result set will be highlighted accordingly, to also be able to visually distinguish between the searched and the remaining parts of the given diagram. + +#### Read / Select via Search + +1. CTRL + F: Open the search palette +2. In the palette's input field enter either the **type** or the **label** of nodes or edges. The arrow keys can be used to navigate in the search result list. +3. ENTER: Select the node or edge that is highlighted in the search result list. + +### Model Exploration + +#### Move + +The move functionality can be used to move parts of the model, such as selected nodes or edges or the whole viewport in all directions. + +##### Steps + +1. Select one or multiple elements or the viewport. +2. Use arrow keys ( || || || ) to move element(s) or viewport to the desired location. + +#### Zoom + +This zoom functionality can be used to gradually adapt the zoom level of one element, a set of elements or the +viewport. + +#### Steps + +1. Select one or multiple elements or the viewport. +2. Use + or - to zoom in or out gradually. +3. CTRL+0: Set the zoom level to default. + +### Resize element + +The resize functionality helps to set the size of the nodes, by either increasing or decreasing the size of the nodes’ shape gradually. + +#### Steps + +1. Select one or multiple elements. +2. ALT+A: Activate the resize mode. +3. + or - to increase or decrease the size of the element(s) gradually. +4. CTRL+0: Set the size of the element(s) to default. +5. ESC: Deactivate the resize mode. From 07bd36fb9073d99a10909d1a016eb33502fb771d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 20 Jun 2023 13:30:24 +0200 Subject: [PATCH 219/566] GLSP-1022: Update dependencies (#253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GLSP-1022: Update dependencies - Update to latest next version of glsp dev packages and sprotty - This also requires an update to inversify 6 - Update to TS 6 ## Sprotty update - Move sprotty-reexport.ts into `glsp-sprotty´ directory and provide `~glsp-sprotty` path alias to ensure that client-internal we also only reuse the augmented `glsp-sprotty` reexport with having to use relative paths. - Counteract the breaking sprotty naming changes by rexporting them as the old types (SModelElementImpl-> SModelElement) - Adapt sprotty an glsp-protocol imports of `@eclipse-glsp/client` to consistently use `~glsp-sprotty` instead This reexport is a glsp-ified version of the sprotty main index that augments some modules of sprotty (actions,types) and replaces some modules with a specific glsp version (SModel API Impl -> GModel API) Part of https://github.com/eclipse-glsp/glsp/pull/1022 * Address review feedback --- examples/workflow-glsp/src/di.config.ts | 18 +-- .../direct-task-editing/direct-task-editor.ts | 14 +- examples/workflow-glsp/tsconfig.json | 2 +- examples/workflow-standalone/app/diagram.html | 56 ++++---- examples/workflow-standalone/package.json | 1 + packages/client/.eslintrc.js | 23 +++- packages/client/package.json | 7 +- packages/client/src/base/action-dispatcher.ts | 5 +- .../src/base/actions/focus-change-action.ts | 4 +- .../src/base/actions/sprotty-actions.ts | 26 ---- packages/client/src/base/argumentable.ts | 5 +- .../auto-complete/auto-complete-actions.ts | 4 +- .../auto-complete/auto-complete-widget.ts | 5 +- .../auto-complete/validation-decorator.ts | 5 +- packages/client/src/base/command-stack.ts | 2 +- packages/client/src/base/container-modules.ts | 7 +- packages/client/src/base/di.config.ts | 15 ++- .../src/base/drag-aware-mouse-listener.ts | 5 +- .../client/src/base/editor-context-service.ts | 18 ++- packages/client/src/base/focus-tracker.ts | 6 +- .../base/model-initialization-constraint.ts | 5 +- .../client/src/base/model/model-registry.ts | 4 +- .../src/base/model/update-model-command.ts | 13 +- .../base/selection-clearing-mouse-listener.ts | 4 +- packages/client/src/base/source-uri-aware.ts | 4 +- .../base/tool-manager/glsp-tool-manager.ts | 4 +- .../client/src/base/view/view-registry.ts | 4 +- .../accessibility/move-zoom/di.config.ts | 3 +- .../accessibility/move-zoom/move-handler.ts | 19 ++- .../accessibility/move-zoom/zoom-handler.ts | 18 ++- .../resize-key-tool/di.config.ts | 4 +- .../resize-key-tool/resize-key-handler.ts | 19 ++- .../resize-key-tool/resize-key-tool.ts | 4 +- .../accessibility/search/di.config.ts | 3 +- .../accessibility/search/search-palette.ts | 36 ++++-- .../accessibility/search/search-tool.ts | 3 +- .../view-key-tools/deselect-key-tool.ts | 12 +- .../accessibility/view-key-tools/di.config.ts | 3 +- .../view-key-tools/movement-key-tool.ts | 4 +- .../view-key-tools/zoom-key-tool.ts | 4 +- .../autocomplete-suggestion-providers.ts | 2 +- .../base-autocomplete-palette.ts | 3 +- .../client/src/features/bounds/di.config.ts | 14 +- .../features/bounds/freeform-layout.spec.ts | 4 +- .../src/features/bounds/freeform-layout.ts | 16 ++- .../bounds/glsp-hidden-bounds-updater.ts | 15 ++- .../src/features/bounds/hbox-layout.spec.ts | 5 +- .../client/src/features/bounds/hbox-layout.ts | 14 +- .../bounds/layouter-test-util.spec.ts | 11 +- .../client/src/features/bounds/layouter.ts | 12 +- .../src/features/bounds/vbox-layout.spec.ts | 5 +- .../client/src/features/bounds/vbox-layout.ts | 12 +- .../src/features/change-bounds/model.ts | 4 +- .../change-bounds/movement-restrictor.ts | 7 +- .../src/features/change-bounds/snap.spec.ts | 4 +- .../client/src/features/change-bounds/snap.ts | 5 +- .../command-palette/command-palette-tool.ts | 2 +- .../src/features/command-palette/di.config.ts | 4 +- .../server-command-palette-provider.ts | 15 ++- .../delete-element-context-menu.ts | 6 +- .../src/features/context-menu/di.config.ts | 4 +- ...rvice-aware-context-menu-mouse-listener.ts | 6 +- .../server-context-menu-provider.ts | 16 ++- .../copy-paste/copy-paste-context-menu.ts | 19 ++- .../features/copy-paste/copy-paste-handler.ts | 12 +- .../src/features/copy-paste/di.config.ts | 5 +- .../features/decoration/decoration-placer.ts | 5 +- .../src/features/decoration/di.config.ts | 3 +- .../src/features/edit-label/di.config.ts | 4 +- .../features/edit-label/edit-label-tool.ts | 4 +- .../edit-label/edit-label-validator.ts | 15 ++- .../client/src/features/export/di.config.ts | 3 +- .../src/features/export/glsp-svg-exporter.ts | 3 +- .../client/src/features/hints/di.config.ts | 6 +- packages/client/src/features/hints/model.ts | 5 +- .../client/src/features/hints/type-hints.ts | 22 ++-- .../client/src/features/hover/di.config.ts | 12 +- packages/client/src/features/hover/hover.ts | 23 ++-- .../client/src/features/layout/di.config.ts | 2 +- .../layout/layout-elements-action.spec.ts | 28 ++-- .../features/layout/layout-elements-action.ts | 24 ++-- .../src/features/mouse-tool/di.config.ts | 5 +- .../src/features/mouse-tool/mouse-tool.ts | 6 +- .../src/features/navigation/di.config.ts | 3 +- .../navigation/navigation-action-handler.ts | 18 ++- .../navigation-target-resolver.spec.ts | 5 +- .../navigation/navigation-target-resolver.ts | 14 +- .../client/src/features/reconnect/model.ts | 4 +- .../client/src/features/save/di.config.ts | 3 +- packages/client/src/features/save/model.ts | 4 +- .../src/features/save/save-keylistener.ts | 6 +- .../client/src/features/select/di.config.ts | 4 +- .../features/select/select-feedback-action.ts | 15 ++- .../features/select/select-mouse-listener.ts | 21 +-- .../features/select/selection-service.spec.ts | 5 +- .../src/features/select/selection-service.ts | 23 ++-- .../source-model-watcher/di.config.ts | 5 +- .../source-model-changed-action-handler.ts | 15 ++- .../src/features/svg-metadata/di.config.ts | 2 +- .../features/svg-metadata/metadata-placer.ts | 6 +- .../change-bounds-tool-feedback.ts | 20 +-- .../tool-feedback/creation-tool-feedback.ts | 19 +-- .../features/tool-feedback/css-feedback.ts | 7 +- .../src/features/tool-feedback/di.config.ts | 4 +- .../tool-feedback/edge-edit-tool-feedback.ts | 23 ++-- .../feedback-action-dispatcher.ts | 6 +- .../tool-feedback/marquee-tool-feedback.ts | 10 +- .../src/features/tool-feedback/model.ts | 4 +- .../src/features/tool-feedback/view.tsx | 5 +- .../src/features/tool-palette/di.config.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 15 ++- .../src/features/tools/base-glsp-tool.ts | 6 +- .../src/features/tools/change-bounds-tool.ts | 30 ++--- .../client/src/features/tools/delete-tool.ts | 19 +-- .../client/src/features/tools/di.config.ts | 14 +- .../src/features/tools/edge-creation-tool.ts | 16 ++- .../src/features/tools/edge-edit-tool.ts | 24 ++-- .../enable-default-tools-on-focus-loss.ts | 5 +- .../tools/glsp-manhattan-edge-router.ts | 3 +- .../src/features/tools/marquee-behavior.ts | 5 +- .../src/features/tools/marquee-mouse-tool.ts | 17 +-- .../client/src/features/tools/marquee-tool.ts | 5 +- packages/client/src/features/tools/model.ts | 5 +- .../src/features/tools/node-creation-tool.ts | 14 +- packages/client/src/features/tools/view.tsx | 4 +- .../src/features/validation/di.config.ts | 4 +- .../src/features/validation/issue-marker.ts | 16 ++- .../validation/marker-navigator.spec.ts | 15 +-- .../features/validation/marker-navigator.ts | 27 ++-- .../src/features/validation/validate.ts | 20 ++- .../client/src/features/viewport/di.config.ts | 4 +- .../viewport/glsp-scroll-mouse-listener.ts | 13 +- .../augmented-actions.ts} | 17 ++- .../index.ts} | 122 ++++++++++++------ .../src/{base => glsp-sprotty}/types.ts | 3 +- packages/client/src/index.ts | 5 +- packages/client/src/lib/model.ts | 14 +- .../src/model-source/glsp-diagram-server.ts | 15 ++- packages/client/src/utils/argument-utils.ts | 4 +- packages/client/src/utils/layout-utils.ts | 5 +- packages/client/src/utils/marker.ts | 5 +- packages/client/src/utils/smodel-util.ts | 20 ++- packages/client/src/utils/viewpoint-util.ts | 16 ++- packages/client/src/views/base-view-module.ts | 15 ++- packages/client/src/views/compartments.tsx | 4 +- packages/client/src/views/glsp-edge-view.tsx | 3 +- .../client/src/views/glsp-projection-view.tsx | 11 +- .../client/src/views/issue-marker-view.tsx | 4 +- .../client/src/views/rounded-corner-view.tsx | 2 +- packages/client/src/views/rounded-corner.ts | 3 +- packages/client/tsconfig.json | 7 +- packages/protocol/package.json | 2 +- .../action-protocol/element-text-editing.ts | 4 +- .../src/client-server-protocol/glsp-client.ts | 1 - packages/protocol/src/index.ts | 4 +- packages/protocol/tsconfig.json | 2 +- 156 files changed, 913 insertions(+), 701 deletions(-) delete mode 100644 packages/client/src/base/actions/sprotty-actions.ts rename packages/client/src/{base/tool-manager/tool-actions.ts => glsp-sprotty/augmented-actions.ts} (71%) rename packages/client/src/{sprotty-reexport.ts => glsp-sprotty/index.ts} (84%) rename packages/client/src/{base => glsp-sprotty}/types.ts (95%) diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/di.config.ts index 955201a..4e0b11e 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/di.config.ts @@ -14,21 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - bindAsService, - configureDefaultModelElements, - configureModelElement, ConsoleLogger, + ContainerConfiguration, + DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, - editLabelFeature, GEdge, glspAccessibilityModule, GLSPGraph, GLSPProjectionView, GridSnapper, - initializeDiagramContainer, LogLevel, - overrideViewerOptions, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, @@ -37,9 +33,15 @@ import { SLabel, SLabelView, StructureCompartmentView, - TYPES + TYPES, + bindAsService, + bindOrRebind, + configureDefaultModelElements, + configureModelElement, + editLabelFeature, + initializeDiagramContainer, + overrideViewerOptions } from '@eclipse-glsp/client'; -import { bindOrRebind, ContainerConfiguration, DefaultTypes } from '@eclipse-glsp/protocol'; import 'balloon-css/balloon.min.css'; import { Container, ContainerModule } from 'inversify'; import 'sprotty/css/edit-label.css'; diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index ae7678c..e2cc398 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -17,28 +17,28 @@ import { AbstractUIExtension, Action, AutoCompleteWidget, + DOMHelper, EditorContextService, - getAbsoluteClientBounds, GLSPActionDispatcher, - hasStringProp, ILogger, LabeledAction, ModelIndexImpl, Operation, RequestContextActions, RequestEditValidationAction, + SModelRoot, SetContextActions, SetEditValidationResultAction, - SModelRoot, - toActionArray, TYPES, ValidationDecorator, ValidationStatus, - ViewerOptions + ViewerOptions, + getAbsoluteClientBounds, + hasStringProp, + toActionArray } from '@eclipse-glsp/client'; import { inject, injectable } from 'inversify'; -import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; -import { isTaskNode, TaskNode } from '../model'; +import { TaskNode, isTaskNode } from '../model'; /** * Is send from the {@link TaskEditor} to the GLSP server diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index c139e00..b24158e 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config", "compilerOptions": { "rootDir": "src", "outDir": "lib", diff --git a/examples/workflow-standalone/app/diagram.html b/examples/workflow-standalone/app/diagram.html index 2b372a5..5907c58 100644 --- a/examples/workflow-standalone/app/diagram.html +++ b/examples/workflow-standalone/app/diagram.html @@ -1,34 +1,32 @@ + + + + - svg { - width: 100%; - height: 100%; - } - - - - -

- - - - \ No newline at end of file + +
+ + + diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index e1d20c7..449e1ae 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -37,6 +37,7 @@ "@eclipse-glsp-examples/workflow-server-bundled": "next", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", + "inversify": "^6.0.1", "path-browserify": "^1.0.1", "source-map-loader": "^4.0.0", "style-loader": "^3.3.1", diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index c35647a..0db6b82 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -4,14 +4,31 @@ module.exports = { rules: { 'no-restricted-imports': [ 'warn', + { + name: 'sprotty', + message: "Please use '~glsp-sprotty' instead" + }, + { + name: 'sprotty/*', + message: "Please use '~glsp-sprotty' instead" + }, { name: 'sprotty-protocol', - message: "Please use '@eclipse-glsp/protocol' instead" + message: "Please use '~glsp-sprotty' instead" }, { name: 'sprotty-protocol/*', - message: "Please use '@eclipse-glsp/protocol' instead" + message: "Please use '~glsp-sprotty' instead" + }, + { + name: '@eclipse-glsp/protocol', + message: "Please use '~glsp-sprotty' instead" + }, + { + name: '@eclipse-glsp/protocol/*', + message: "Please use '~glsp-sprotty' instead" } - ] + ], + 'import/no-unresolved': ['error', { ignore: ['~glsp-sprotty'] }] } }; diff --git a/packages/client/package.json b/packages/client/package.json index 51661f8..c8ef5f1 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -36,7 +36,7 @@ "css" ], "scripts": { - "build": "tsc -b", + "build": "tsc -b && tsc-alias", "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", @@ -50,11 +50,12 @@ "@eclipse-glsp/protocol": "1.1.0-next", "autocompleter": "5.1.0", "lodash": "4.17.21", - "sprotty": "0.13.0-next.f4445dd.342" + "sprotty": "0.14.0-next.02bbac0.26" }, "devDependencies": { "@types/lodash": "4.14.191", - "@vscode/codicons": "^0.0.25" + "@vscode/codicons": "^0.0.25", + "tsc-alias": "^1.8.2" }, "publishConfig": { "access": "public" diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index a78ae98..0417eaf 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { inject } from 'inversify'; -import { ActionDispatcher } from 'sprotty'; +import { Action, ActionDispatcher, RequestAction, ResponseAction } from '~glsp-sprotty'; import { ModelInitializationConstraint } from './model-initialization-constraint'; export class GLSPActionDispatcher extends ActionDispatcher { diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/actions/focus-change-action.ts index 86689a6..105c076 100644 --- a/packages/client/src/base/actions/focus-change-action.ts +++ b/packages/client/src/base/actions/focus-change-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasBooleanProp } from '@eclipse-glsp/protocol'; +import { Action, hasBooleanProp } from '~glsp-sprotty'; /** * A `FocusStateChangedAction` is dispatched by the client whenever the diff --git a/packages/client/src/base/actions/sprotty-actions.ts b/packages/client/src/base/actions/sprotty-actions.ts deleted file mode 100644 index 515d653..0000000 --- a/packages/client/src/base/actions/sprotty-actions.ts +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; -import { SetBoundsAction } from 'sprotty-protocol/lib/actions'; -declare module 'sprotty-protocol/lib/actions' { - // eslint-disable-next-line no-shadow - namespace SetBoundsAction { - export function is(object: any): object is SetBoundsAction; - } -} - -SetBoundsAction.is = (object: any): object is SetBoundsAction => - Action.hasKind(object, SetBoundsAction.KIND) && hasArrayProp(object, 'bounds'); diff --git a/packages/client/src/base/argumentable.ts b/packages/client/src/base/argumentable.ts index ce40175..e530414 100644 --- a/packages/client/src/base/argumentable.ts +++ b/packages/client/src/base/argumentable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args } from '@eclipse-glsp/protocol'; -import { SModelElement } from 'sprotty'; +import { Args, SModelElement } from '~glsp-sprotty'; export interface SArgumentable { args: Args; diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts index 53f0e6e..74fab61 100644 --- a/packages/client/src/base/auto-complete/auto-complete-actions.ts +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { hasStringProp, LabeledAction } from '@eclipse-glsp/protocol'; +import { hasStringProp, LabeledAction } from '~glsp-sprotty'; export interface AutoCompleteValue extends LabeledAction { readonly text: string; diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index e0b7e77..d164f78 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -13,11 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, LabeledAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; -import { codiconCSSClasses, ILogger, SModelRoot } from 'sprotty'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, ILogger, LabeledAction, SModelRoot, ValidationStatus, codiconCSSClasses, matchesKeystroke, toArray } from '~glsp-sprotty'; import { AutoCompleteValue } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index cca79e1..50415ec 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { codiconCSSString } from 'sprotty/lib/utils/codicon'; -import { ValidationStatus } from '@eclipse-glsp/protocol'; +import { ValidationStatus, codiconCSSString } from '~glsp-sprotty'; export interface IValidationDecorator { decorateValidationResult(status: ValidationStatus): void; diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 82e12ac..a6f5823 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { CommandStack, SModelRoot } from 'sprotty'; +import { CommandStack, SModelRoot } from '~glsp-sprotty'; @injectable() export class GLSPCommandStack extends CommandStack { diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 9ab3d15..43b4850 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -14,22 +14,22 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerConfiguration, initializeContainer } from '@eclipse-glsp/protocol'; import { Container, ContainerModule } from 'inversify'; import { + ContainerConfiguration, buttonModule, defaultModule, edgeIntersectionModule, edgeLayoutModule, expandModule, fadeModule, + initializeContainer, labelEditUiModule, modelSourceModule, openModule, routingModule, zorderModule -} from 'sprotty'; -import svgMetadataModule from '../features/svg-metadata/di.config'; +} from '~glsp-sprotty'; import glspBoundsModule from '../features/bounds/di.config'; import glspCommandPaletteModule from '../features/command-palette/di.config'; import glspContextMenuModule from '../features/context-menu/di.config'; @@ -44,6 +44,7 @@ import glspMouseToolModule from '../features/mouse-tool/di.config'; import navigationModule from '../features/navigation/di.config'; import glspSelectModule from '../features/select/di.config'; import sourceModelWatcherModule from '../features/source-model-watcher/di.config'; +import svgMetadataModule from '../features/svg-metadata/di.config'; import toolFeedbackModule from '../features/tool-feedback/di.config'; import toolPaletteModule from '../features/tool-palette/di.config'; import { enableDefaultToolsOnFocusLossModule, toolsModule } from '../features/tools/di.config'; diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 3155b33..695a1bf 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -13,10 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, bindOrRebind, InitializeResult, SetEditModeAction } from '@eclipse-glsp/protocol'; import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; -import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand } from 'sprotty'; +import { + ActionHandlerRegistry, + bindAsService, + bindOrRebind, + configureActionHandler, + configureCommand, + InitializeResult, + ModelSource, + SetEditModeAction, + SetModelCommand, + TYPES +} from '~glsp-sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; import { FocusStateChangedAction } from './actions/focus-change-action'; @@ -28,7 +38,6 @@ import { GLSPModelRegistry } from './model/model-registry'; import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from './model/update-model-command'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; -import { TYPES } from './types'; import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index f9e3eb2..6284a40 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; -import { MouseListener, SModelElement } from 'sprotty'; +import { Action, MouseListener, SModelElement } from '~glsp-sprotty'; /** * A mouse listener that is aware of prior mouse dragging. diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index ad03390..284c462 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -13,12 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Args, distinctAdd, EditMode, EditorContext, remove, SetEditModeAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; -import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot } from 'sprotty'; +import { + Action, + Args, + EditMode, + EditorContext, + IActionHandler, + ModelSource, + MousePositionTracker, + SModelElement, + SModelRoot, + SetEditModeAction, + TYPES, + distinctAdd, + remove +} from '~glsp-sprotty'; import { SelectionService } from '../features/select/selection-service'; import { isSourceUriAware } from './source-uri-aware'; -import { TYPES } from './types'; export interface EditModeListener { editModeChanged(newValue: string, oldValue: string): void; diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts index fc0c77d..2558c9f 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus-tracker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionHandler, ICommand, ViewerOptions } from 'sprotty'; +import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from '~glsp-sprotty'; import { FocusStateChangedAction } from './actions/focus-change-action'; -import { TYPES } from './types'; @injectable() export class FocusTracker implements IActionHandler { diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts index 64b70ed..67021a4 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Deferred, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { InitializeCanvasBoundsAction } from 'sprotty'; +import { Action, Deferred, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; /** * The constraint defining when the initialization of the GLSP model is completed. diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index cc85214..aea3bd1 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { SModelElement, SModelRegistry } from 'sprotty'; +import { SModelElement, SModelRegistry } from '~glsp-sprotty'; @injectable() export class GLSPModelRegistry extends SModelRegistry { diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index 4a7e481..473148d 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SetModelAction, toTypeGuard, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; import { + Action, ActionHandlerRegistry, Animation, Command, @@ -26,12 +26,15 @@ import { ILogger, MorphEdgesAnimation, SModelRoot, + SetModelAction, + TYPES, UpdateAnimationData, - UpdateModelCommand -} from 'sprotty'; + UpdateModelAction, + UpdateModelCommand, + toTypeGuard +} from '~glsp-sprotty'; import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../features/tool-feedback/model'; -import { TYPES } from '../types'; /** * ActionHandler that transforms a {@link SetModelAction} into an {@link UpdateModelAction} that can be handled diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts index 5999776..e57340f 100644 --- a/packages/client/src/base/selection-clearing-mouse-listener.ts +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { MouseListener, SModelElement } from 'sprotty'; +import { MouseListener, SModelElement } from '~glsp-sprotty'; /** * A mouse listener that clears the document selection on click diff --git a/packages/client/src/base/source-uri-aware.ts b/packages/client/src/base/source-uri-aware.ts index 4233e52..5cfbcfa 100644 --- a/packages/client/src/base/source-uri-aware.ts +++ b/packages/client/src/base/source-uri-aware.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AnyObject, hasStringProp } from '@eclipse-glsp/protocol'; +import { AnyObject, hasStringProp } from '~glsp-sprotty'; export interface SourceUriAware { sourceURI: string; } diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index c4571af..bd79d7c 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AnyObject, distinctAdd, EditMode, hasBooleanProp, hasFunctionProp, hasStringProp } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; -import { Tool, ToolManager } from 'sprotty'; +import { AnyObject, EditMode, TYPES, Tool, ToolManager, distinctAdd, hasBooleanProp, hasFunctionProp, hasStringProp } from '~glsp-sprotty'; import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; -import { TYPES } from '../types'; @injectable() export class GLSPToolManager extends ToolManager implements EditModeListener { diff --git a/packages/client/src/base/view/view-registry.ts b/packages/client/src/base/view/view-registry.ts index 10e1b63..72fb64d 100644 --- a/packages/client/src/base/view/view-registry.ts +++ b/packages/client/src/base/view/view-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { IView, ViewRegistry } from 'sprotty'; +import { IView, ViewRegistry } from '~glsp-sprotty'; @injectable() export class GLSPViewRegistry extends ViewRegistry { diff --git a/packages/client/src/features/accessibility/move-zoom/di.config.ts b/packages/client/src/features/accessibility/move-zoom/di.config.ts index 38a867e..599f170 100644 --- a/packages/client/src/features/accessibility/move-zoom/di.config.ts +++ b/packages/client/src/features/accessibility/move-zoom/di.config.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; +import { BindingContext, configureActionHandler } from '~glsp-sprotty'; import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler'; import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler'; diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index 688b9c7..60a74e7 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -14,13 +14,24 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ChangeBoundsOperation, Point, SetViewportAction, Viewport } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { throttle } from 'lodash'; -import { findParentByFeature, IActionDispatcher, IActionHandler, ICommand, isViewport, SModelRoot } from 'sprotty'; +import { + Action, + ChangeBoundsOperation, + IActionDispatcher, + IActionHandler, + ICommand, + Point, + SModelRoot, + SetViewportAction, + TYPES, + Viewport, + findParentByFeature, + isViewport +} from '~glsp-sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; -import { TYPES } from '../../../base/types'; -import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/smodel-util'; /** * Action for triggering moving of the viewport. diff --git a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts index 48656a2..9875022 100644 --- a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts @@ -14,22 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Bounds, Point, SetViewportAction, Viewport } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { throttle } from 'lodash'; import { - findParentByFeature, + Action, + Bounds, IActionDispatcher, IActionHandler, ICommand, - isViewport, + Point, SChildElement, SModelElement, - SModelRoot -} from 'sprotty'; + SModelRoot, + SetViewportAction, + TYPES, + Viewport, + findParentByFeature, + isViewport +} from '~glsp-sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; -import { TYPES } from '../../../base/types'; -import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/smodel-util'; /** * Action for triggering zooming of the viewport. diff --git a/packages/client/src/features/accessibility/resize-key-tool/di.config.ts b/packages/client/src/features/accessibility/resize-key-tool/di.config.ts index 6be1ecb..b0d6ece 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/di.config.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/di.config.ts @@ -14,10 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; -import { TYPES } from '../../../base/types'; +import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; import { ResizeElementAction, ResizeElementHandler } from './resize-key-handler'; import { ResizeKeyTool } from './resize-key-tool'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index 55bbfad..7869341 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -14,17 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ChangeBoundsOperation, Dimension, Point } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { IActionDispatcher, IActionHandler, ICommand, ISnapper, SModelElement, SParentElement } from 'sprotty'; +import { + Action, + ChangeBoundsOperation, + Dimension, + IActionDispatcher, + IActionHandler, + ICommand, + ISnapper, + Point, + SModelElement, + SParentElement, + TYPES +} from '~glsp-sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; -import { TYPES } from '../../../base/types'; import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; -import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware, toElementAndBounds } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/smodel-util'; import { Resizable } from '../../change-bounds/model'; import { GridSnapper } from '../../change-bounds/snap'; -// eslint-disable-next-line no-shadow export enum ResizeType { Increase, Decrease, diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index abd8dce..66c5a1d 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -14,12 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { TYPES } from '../../../base/types'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { SelectionService } from '../../select/selection-service'; import { ResizeElementAction, ResizeType } from './resize-key-handler'; diff --git a/packages/client/src/features/accessibility/search/di.config.ts b/packages/client/src/features/accessibility/search/di.config.ts index eb33331..75052dd 100644 --- a/packages/client/src/features/accessibility/search/di.config.ts +++ b/packages/client/src/features/accessibility/search/di.config.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { TYPES } from '../../../base/types'; +import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index 18211d3..56f6097 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -14,11 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CenterAction, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { isEqual } from 'lodash'; -import { codiconCSSString, isNameable, LabeledAction, name, SEdge, SModelElement, SModelRoot, SNode } from 'sprotty'; import { toArray } from 'sprotty/lib/utils/iterable'; +import { + Action, + CenterAction, + LabeledAction, + SEdge, + SModelElement, + SModelRoot, + SNode, + SelectAction, + SelectAllAction, + codiconCSSString, + isNameable, + name +} from '~glsp-sprotty'; import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; import { BaseAutocompletePalette } from '../../autocomplete-palette/base-autocomplete-palette'; @@ -32,11 +44,11 @@ export class RevealNamedElementAutocompleteSuggestionProvider implements IAutoco const nameables = toArray(root.index.all().filter(element => isNameable(element))); return nameables.map(nameable => ({ element: nameable, - action: new LabeledAction( - `[${nameable.type}] ${name(nameable) ?? ''}`, - this.getActions(nameable), - codiconCSSString('eye') - ) + action: { + label: `[${nameable.type}] ${name(nameable) ?? ''}`, + actions: this.getActions(nameable), + icon: codiconCSSString('eye') + } })); } @@ -50,11 +62,11 @@ export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocom const edges = toArray(root.index.all().filter(element => element instanceof SEdge)) as SEdge[]; return edges.map(edge => ({ element: edge, - action: new LabeledAction( - `[${edge.type}] ` + this.getEdgeLabel(root, edge), - this.getActions(edge), - codiconCSSString('arrow-both') - ) + action: { + label: `[${edge.type}] ${this.getEdgeLabel(root, edge)}`, + actions: this.getActions(edge), + icon: codiconCSSString('arrow-both') + } })); } protected getActions(edge: SEdge): Action[] { diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index a2ccbbd..db3fa8b 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -14,10 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { KeyListener, KeyTool, SetUIExtensionVisibilityAction, SModelElement, Tool } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, Tool } from '~glsp-sprotty'; import { SearchAutocompletePalette } from './search-palette'; @injectable() diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index 5b1f49d..af01e61 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -14,11 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { isSelectable, KeyListener, KeyTool, SModelElement, SRoutableElement, SwitchEditModeAction } from 'sprotty'; import { toArray } from 'sprotty/lib/utils/iterable'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { + Action, + KeyListener, + KeyTool, + SModelElement, + SRoutableElement, + SelectAction, + SwitchEditModeAction, + isSelectable +} from '~glsp-sprotty'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { SResizeHandle } from '../../change-bounds/model'; diff --git a/packages/client/src/features/accessibility/view-key-tools/di.config.ts b/packages/client/src/features/accessibility/view-key-tools/di.config.ts index d9000eb..86ef8eb 100644 --- a/packages/client/src/features/accessibility/view-key-tools/di.config.ts +++ b/packages/client/src/features/accessibility/view-key-tools/di.config.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { TYPES } from '../../../base/types'; +import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; import { DeselectKeyTool } from '../view-key-tools/deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index c60a0c9..0e1dc38 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -14,12 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { ISnapper, KeyListener, KeyTool, SModelElement } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { TYPES } from '../../../base/types'; import { GridSnapper } from '../../change-bounds/snap'; import { SelectionService } from '../../select/selection-service'; diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index 681e590..cf54e0d 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -14,12 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CenterAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { KeyListener, KeyTool, SModelElement } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, CenterAction, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { TYPES } from '../../../base/types'; import { SelectionService } from '../../select/selection-service'; import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; diff --git a/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts b/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts index 69a81f2..a10f03b 100644 --- a/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts +++ b/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LabeledAction, SModelElement, SModelRoot } from 'sprotty'; +import { LabeledAction, SModelElement, SModelRoot } from '~glsp-sprotty'; /** * Interface for a provider that provides suggestions that can be used to fill the autocomplete diff --git a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts index 1e4eb66..f992a7e 100644 --- a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts +++ b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject } from 'inversify'; -import { AbstractUIExtension, LabeledAction, SModelRoot, TYPES } from 'sprotty'; +import { AbstractUIExtension, Action, LabeledAction, SModelRoot, TYPES } from '~glsp-sprotty'; import '../../../css/autocomplete-palette.css'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { AutoCompleteWidget, CloseReason, toActionArray } from '../../base/auto-complete/auto-complete-widget'; diff --git a/packages/client/src/features/bounds/di.config.ts b/packages/client/src/features/bounds/di.config.ts index 14bfde7..4b3b16d 100644 --- a/packages/client/src/features/bounds/di.config.ts +++ b/packages/client/src/features/bounds/di.config.ts @@ -13,20 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { - configureCommand, - configureLayout, HBoxLayouter, HiddenBoundsUpdater, - Layouter, LayoutRegistry, + Layouter, RequestBoundsCommand, SetBoundsCommand, - VBoxLayouter -} from 'sprotty'; -import { TYPES } from '../../base/types'; + TYPES, + VBoxLayouter, + bindAsService, + configureCommand, + configureLayout +} from '~glsp-sprotty'; import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; diff --git a/packages/client/src/features/bounds/freeform-layout.spec.ts b/packages/client/src/features/bounds/freeform-layout.spec.ts index 163f97c..67e452b 100644 --- a/packages/client/src/features/bounds/freeform-layout.spec.ts +++ b/packages/client/src/features/bounds/freeform-layout.spec.ts @@ -16,8 +16,8 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, SModelElement } from 'sprotty'; -import { gModel, layout, sComp, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, SModelElement } from '~glsp-sprotty'; +import { gModel, layout, sComp, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; describe('FreeFormLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index ddaafd4..a4cf0ff 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -13,11 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { BoundsData, LayoutContainer, SChildElement, SParentElement, StatefulLayouter } from 'sprotty'; -import { AbstractLayout } from 'sprotty/lib/features/bounds/abstract-layout'; -import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; +import { + AbstractLayout, + AbstractLayoutOptions, + Bounds, + BoundsData, + Dimension, + LayoutContainer, + Point, + SChildElement, + SParentElement, + StatefulLayouter +} from '~glsp-sprotty'; /** * Layouts children of a container with explicit X/Y positions diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index b891dc4..52a42d2 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -14,10 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ComputedBoundsAction, Deferred, ElementAndRoutingPoints, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; -import { EdgeRouterRegistry, HiddenBoundsUpdater, IActionDispatcher, SModelElement, SRoutableElement } from 'sprotty'; +import { + Action, + ComputedBoundsAction, + Deferred, + EdgeRouterRegistry, + ElementAndRoutingPoints, + HiddenBoundsUpdater, + IActionDispatcher, + RequestAction, + ResponseAction, + SModelElement, + SRoutableElement +} from '~glsp-sprotty'; import { calcElementAndRoute, isRoutable } from '../../utils/smodel-util'; /** diff --git a/packages/client/src/features/bounds/hbox-layout.spec.ts b/packages/client/src/features/bounds/hbox-layout.spec.ts index 58d4dc2..dddca5e 100644 --- a/packages/client/src/features/bounds/hbox-layout.spec.ts +++ b/packages/client/src/features/bounds/hbox-layout.spec.ts @@ -14,12 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable deprecation/deprecation */ -import { Dimension } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; -import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, Dimension, SModelElement, SNode } from '~glsp-sprotty'; +import { layout, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; describe('HBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index e1f0760..e60c5da 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -13,20 +13,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { + Bounds, BoundsData, - isBoundsAware, - isLayoutableChild, + Dimension, + HBoxLayoutOptions, + HBoxLayouter, LayoutContainer, + Point, SChildElement, SModelElement, SParentElement, StatefulLayouter, - HBoxLayouter, - HBoxLayoutOptions -} from 'sprotty'; + isBoundsAware, + isLayoutableChild +} from '~glsp-sprotty'; export interface HBoxLayoutOptionsExt extends HBoxLayoutOptions { hGrab: boolean; diff --git a/packages/client/src/features/bounds/layouter-test-util.spec.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts index 7020f7c..47a6791 100644 --- a/packages/client/src/features/bounds/layouter-test-util.spec.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -14,22 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension, Point } from '@eclipse-glsp/protocol'; import { Container } from 'inversify'; import 'mocha'; import { BoundsData, ConsoleLogger, - createFeatureSet, - layoutableChildFeature, + Dimension, LayoutRegistry, + Point, SCompartment, SLabel, SModelElement, SNode, SParentElement, - TYPES -} from 'sprotty'; + TYPES, + createFeatureSet, + layoutableChildFeature +} from '~glsp-sprotty'; import { initializeDiagramContainer } from '../../base/container-modules'; import { GLSPGraph } from '../../lib/model'; import { StatefulLayouterExt } from './layouter'; diff --git a/packages/client/src/features/bounds/layouter.ts b/packages/client/src/features/bounds/layouter.ts index fd328fe..98f9d74 100644 --- a/packages/client/src/features/bounds/layouter.ts +++ b/packages/client/src/features/bounds/layouter.ts @@ -13,19 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; import { + Bounds, BoundsData, ILogger, + LayoutContainer, LayoutRegistry, - StatefulLayouter, - SModelElement, Layouter, + SModelElement, SParentElement, - LayoutContainer, + StatefulLayouter, isLayoutContainer -} from 'sprotty'; -import { injectable } from 'inversify'; -import { Bounds } from 'sprotty-protocol/lib/utils/geometry'; +} from '~glsp-sprotty'; @injectable() export class LayouterExt extends Layouter { diff --git a/packages/client/src/features/bounds/vbox-layout.spec.ts b/packages/client/src/features/bounds/vbox-layout.spec.ts index cf04e1f..17d5bec 100644 --- a/packages/client/src/features/bounds/vbox-layout.spec.ts +++ b/packages/client/src/features/bounds/vbox-layout.spec.ts @@ -14,12 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable deprecation/deprecation */ -import { Dimension } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, SModelElement, SNode } from 'sprotty'; -import { layout, setupLayoutRegistry, sLabel, sNode } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, Dimension, SModelElement, SNode } from '~glsp-sprotty'; +import { layout, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; describe('VBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 0467dc8..483dda0 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -13,20 +13,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { + Bounds, BoundsData, - isBoundsAware, - isLayoutableChild, + Dimension, LayoutContainer, + Point, SChildElement, SModelElement, SParentElement, StatefulLayouter, + VBoxLayoutOptions, VBoxLayouter, - VBoxLayoutOptions -} from 'sprotty'; + isBoundsAware, + isLayoutableChild +} from '~glsp-sprotty'; export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { hGrab: boolean; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index ac28fb6..cb0181a 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,7 +25,7 @@ import { Selectable, SModelElement, SParentElement -} from 'sprotty'; +} from '~glsp-sprotty'; export const resizeFeature = Symbol('resizeFeature'); diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index e4c16b6..0d3d3b0 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { BoundsAware, SModelElement, SNode, SParentElement } from 'sprotty'; +import { BoundsAware, Point, SModelElement, SNode, SParentElement } from '~glsp-sprotty'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { ModifyCSSFeedbackAction } from '../tool-feedback/css-feedback'; -import { isBoundsAwareMoveable, SResizeHandle } from './model'; +import { SResizeHandle, isBoundsAwareMoveable } from './model'; /** * A `MovementRestrictor` is an optional service that can be used by tools to validate diff --git a/packages/client/src/features/change-bounds/snap.spec.ts b/packages/client/src/features/change-bounds/snap.spec.ts index eccdf53..8ad6056 100644 --- a/packages/client/src/features/change-bounds/snap.spec.ts +++ b/packages/client/src/features/change-bounds/snap.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import { expect } from 'chai'; -import { SModelElement } from 'sprotty'; +import { SModelElement } from '~glsp-sprotty'; import { GridSnapper, PointPositionUpdater } from './snap'; describe('GridSnapper', () => { diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 8658c9e..74c259b 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point, Writable } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { findParentByFeature, ISnapper, isViewport, SModelElement } from 'sprotty'; +import { ISnapper, Point, SModelElement, Writable, findParentByFeature, isViewport } from '~glsp-sprotty'; /** * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index 6d9e107..c323d29 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, postConstruct } from 'inversify'; -import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from 'sprotty'; +import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from '~glsp-sprotty'; @injectable() export class CommandPaletteTool implements Tool { diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/di.config.ts index 6f3c8a0..52a1079 100644 --- a/packages/client/src/features/command-palette/di.config.ts +++ b/packages/client/src/features/command-palette/di.config.ts @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { CommandPalette, CommandPaletteActionProviderRegistry } from 'sprotty'; +import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES, bindAsService } from '~glsp-sprotty'; import '../../../css/command-palette.css'; -import { TYPES } from '../../base/types'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 7057b3d..71142a9 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, LabeledAction, Point, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ICommandPaletteActionProvider, SModelElement } from 'sprotty'; +import { + Action, + ICommandPaletteActionProvider, + LabeledAction, + Point, + RequestContextActions, + SModelElement, + SetContextActions, + TYPES +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; export namespace ServerCommandPalette { export const CONTEXT_ID = 'command-palette'; diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index c2f5953..6662ee3 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DeleteElementOperation, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IContextMenuItemProvider, MenuItem, SModelRoot } from 'sprotty'; +import { DeleteElementOperation, IContextMenuItemProvider, MenuItem, Point, SModelRoot, TYPES } from '~glsp-sprotty'; import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; @injectable() export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts index 684bcc0..7460c87 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/di.config.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { ContextMenuProviderRegistry, IContextMenuService } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { ContextMenuProviderRegistry, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 168d09a..14671a7 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement } from 'sprotty'; +import { Action, ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement, TYPES } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { TYPES } from '../../base/types'; import { SelectionService } from '../select/selection-service'; @injectable() diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 00f2113..4214112 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, LabeledAction, Point, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IContextMenuItemProvider, isSelected, SModelElement } from 'sprotty'; +import { + Action, + IContextMenuItemProvider, + LabeledAction, + Point, + RequestContextActions, + SModelElement, + SetContextActions, + TYPES, + isSelected +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; export namespace ServerContextMenu { export const CONTEXT_ID = 'context-menu'; diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index e855c4d..03d6eed 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,10 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable deprecation/deprecation */ -import { Action, hasStringProp, Point, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, IActionHandler, IContextMenuItemProvider, isSelected, MenuItem, SModelRoot } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { + Action, + IActionDispatcher, + IActionHandler, + IContextMenuItemProvider, + MenuItem, + Point, + SModelRoot, + ServerMessageAction, + ServerStatusAction, + TYPES, + hasStringProp, + isSelected +} from '~glsp-sprotty'; /** * An `InvokeCopyPasteAction` is dispatched by the client to initiate a cut, copy or paste operation. diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index c13cf25..62407b9 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -13,13 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction, SetClipboardDataAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ViewerOptions } from 'sprotty'; import { v4 as uuid } from 'uuid'; +import { + ClipboardData, + CutOperation, + PasteOperation, + RequestClipboardDataAction, + SetClipboardDataAction, + TYPES, + ViewerOptions +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; export interface ICopyPasteHandler { handleCopy(event: ClipboardEvent): void; diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/di.config.ts index 4249cf7..45488dc 100644 --- a/packages/client/src/features/copy-paste/di.config.ts +++ b/packages/client/src/features/copy-paste/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { configureActionHandler, TYPES } from '~glsp-sprotty'; import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 8363008..7eb12d0 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Decoration, DecorationPlacer, isSizeable, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; +import { Decoration, DecorationPlacer, isSizeable, Point, SChildElement, SModelElement, SRoutableElement } from '~glsp-sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts index 977002f..3a5f282 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/di.config.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; +import { TYPES, bindAsService } from '~glsp-sprotty'; import '../../../css/decoration.css'; -import { TYPES } from '../../base/types'; import { GlspDecorationPlacer } from './decoration-placer'; const glspDecorationModule = new ContainerModule(bind => { diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/edit-label/di.config.ts index df7860b..f6efb3d 100644 --- a/packages/client/src/features/edit-label/di.config.ts +++ b/packages/client/src/features/edit-label/di.config.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { ApplyLabelEditCommand, configureCommand } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { ApplyLabelEditCommand, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/edit-label/edit-label-tool.ts index e7a96df..0cc4b61 100644 --- a/packages/client/src/features/edit-label/edit-label-tool.ts +++ b/packages/client/src/features/edit-label/edit-label-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from 'sprotty'; +import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from '~glsp-sprotty'; import { BaseGLSPTool } from '../tools/base-glsp-tool'; @injectable() diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index 62c5aa9..8a9e490 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,17 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - EditableLabel, + Action, EditLabelValidationResult, + EditableLabel, IEditLabelValidationDecorator, IEditLabelValidator, - Severity, + RequestEditValidationAction, SModelElement, - TYPES -} from 'sprotty'; + SetEditValidationResultAction, + Severity, + TYPES, + ValidationStatus +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; export namespace LabelEditValidation { diff --git a/packages/client/src/features/export/di.config.ts b/packages/client/src/features/export/di.config.ts index 1f2498e..5fbc94b 100644 --- a/packages/client/src/features/export/di.config.ts +++ b/packages/client/src/features/export/di.config.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureCommand, ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, TYPES } from 'sprotty'; +import { bindAsService, configureCommand, ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, TYPES } from '~glsp-sprotty'; import { GLSPSvgExporter } from './glsp-svg-exporter'; const glspExportSvgModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 82e44a1..2060b3b 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { RequestAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { ExportSvgAction, SModelRoot, SvgExporter } from 'sprotty'; import { v4 as uuid } from 'uuid'; +import { ExportSvgAction, RequestAction, SModelRoot, SvgExporter } from '~glsp-sprotty'; @injectable() export class GLSPSvgExporter extends SvgExporter { diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/di.config.ts index 390f068..a1ce11d 100644 --- a/packages/client/src/features/hints/di.config.ts +++ b/packages/client/src/features/hints/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SetTypeHintsAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureCommand } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { SetTypeHintsAction, TYPES, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 168cb31..11a3fa5 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElementSchema } from '@eclipse-glsp/protocol'; -import { SModelElement, SModelExtension } from 'sprotty'; +import { SModelElement, SModelElementSchema, SModelExtension } from '~glsp-sprotty'; export const containerFeature = Symbol('containable'); diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 0123de9..ffd7f10 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,25 +13,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, EdgeTypeHint, SetTypeHintsAction, ShapeTypeHint, TypeHint } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, CommandExecutionContext, CommandReturn, Connectable, - connectableFeature, - deletableFeature, - editFeature, + EdgeTypeHint, FeatureSet, IActionHandler, ICommand, - moveFeature, SEdge, SModelElement, SModelRoot, - SShapeElement -} from 'sprotty'; -import { TYPES } from '../../base/types'; + SShapeElement, + SetTypeHintsAction, + ShapeTypeHint, + TYPES, + TypeHint, + connectableFeature, + deletableFeature, + editFeature, + moveFeature +} from '~glsp-sprotty'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index 0ec146f..7f498d4 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -13,13 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { CenterCommand, ClosePopupActionHandler, - configureActionHandler, - configureCommand, EnableDefaultToolsAction, EnableToolsAction, FitToScreenCommand, @@ -28,11 +25,14 @@ import { HoverState, MoveCommand, PopupHoverMouseListener, + PopupPositionUpdater, SetPopupModelCommand, SetViewportCommand, - TYPES -} from 'sprotty'; -import { PopupPositionUpdater } from 'sprotty/lib/features/hover/popup-position-updater'; + TYPES, + bindAsService, + configureActionHandler, + configureCommand +} from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { GlspHoverMouseListener } from './hover'; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index f100915..c84c4e6 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; import { Action, Bounds, + EMPTY_ROOT, + EnableDefaultToolsAction, + EnableToolsAction, + HoverFeedbackAction, + HoverMouseListener, + IActionHandler, + ICommand, PreRenderedElementSchema, RequestPopupModelAction, - SetPopupModelAction, + SModelElement, SModelElementSchema, - SModelRootSchema -} from '@eclipse-glsp/protocol'; -import { injectable } from 'inversify'; -import { EMPTY_ROOT, HoverMouseListener, IActionHandler, ICommand, SModelElement } from 'sprotty'; -import { HoverFeedbackAction } from 'sprotty-protocol/lib/actions'; + SModelRootSchema, + SetPopupModelAction +} from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool-actions'; import { EdgeCreationTool } from '../tools/edge-creation-tool'; -import { getSeverity, GIssueMarker } from '../validation/issue-marker'; +import { GIssueMarker, getSeverity } from '../validation/issue-marker'; @injectable() export class GlspHoverMouseListener extends HoverMouseListener implements IActionHandler { protected enableHover = true; diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 62ef6bb..212c7b2 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; +import { configureActionHandler } from '~glsp-sprotty'; import { AlignElementsAction, AlignElementsActionHandler, diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index e2c57f2..980de1c 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,35 +14,33 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable deprecation/deprecation */ -import { - Action, - Bounds, - ChangeBoundsOperation, - ElementAndBounds, - RequestAction, - ResponseAction, - SetBoundsAction -} from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; import 'mocha'; import 'reflect-metadata'; import { + Action, AnimationFrameSyncer, + Bounds, + ChangeBoundsOperation, CommandExecutionContext, ConsoleLogger, - defaultModule, + ElementAndBounds, ElementMove, FeatureSet, IActionDispatcher, MoveAction, MoveCommand, + RequestAction, + ResponseAction, SChildElement, - SetBoundsCommand, SGraphFactory, - SModelRoot -} from 'sprotty'; -import { TYPES } from '../../base/types'; + SModelRoot, + SetBoundsAction, + SetBoundsCommand, + TYPES, + defaultModule +} from '~glsp-sprotty'; import { resizeFeature } from '../change-bounds/model'; import { SelectionService } from '../select/selection-service'; import { FeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 0d8135d..40e7a06 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,20 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { inject, injectable, optional } from 'inversify'; import { Action, ChangeBoundsOperation, ElementAndBounds, + ElementMove, + IActionDispatcher, + IActionHandler, + ICommand, + MoveAction, + SModelElement, + SetBoundsAction, + TYPES, + Writable, hasArrayProp, hasNumberProp, - hasStringProp, - SetBoundsAction, - Writable -} from '@eclipse-glsp/protocol'; -import { inject, injectable, optional } from 'inversify'; -import { ElementMove, IActionDispatcher, IActionHandler, ICommand, SModelElement } from 'sprotty'; -import { MoveAction } from 'sprotty-protocol/lib/actions'; -import { TYPES } from '../../base/types'; + hasStringProp +} from '~glsp-sprotty'; import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement, getElements } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; @@ -36,7 +40,6 @@ import { SelectionService } from '../select/selection-service'; /** * Used to specify the desired resize dimension for a {@link ResizeElementsCommand}. */ -// eslint-disable-next-line no-shadow export enum ResizeDimension { Width, Height, @@ -272,7 +275,6 @@ export class ResizeElementsActionHandler extends LayoutElementsActionHandler { } } -// eslint-disable-next-line no-shadow export enum Alignment { Left, Center, diff --git a/packages/client/src/features/mouse-tool/di.config.ts b/packages/client/src/features/mouse-tool/di.config.ts index bee91f4..cc69ce6 100644 --- a/packages/client/src/features/mouse-tool/di.config.ts +++ b/packages/client/src/features/mouse-tool/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { MouseTool } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { MouseTool, TYPES } from '~glsp-sprotty'; import { RankingMouseTool } from './mouse-tool'; const glspMouseToolModule = new ContainerModule((bind, _unbind, _isBound, rebind) => { diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index 0b78733..c1d5465 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { injectable, multiInject, optional } from 'inversify'; -import { MouseListener, MouseTool, SModelElement, SModelRoot } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { Action, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; import { getRank } from '../rank/model'; export interface IMouseTool { diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/di.config.ts index 9f8f6c4..80d6097 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/di.config.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { NavigateToExternalTargetAction, NavigateToTargetAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; +import { NavigateToExternalTargetAction, NavigateToTargetAction, configureActionHandler } from '~glsp-sprotty'; import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index eaad0b4..9867a69 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -13,12 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { inject, injectable } from 'inversify'; import { Action, + ActionHandlerRegistry, Args, CenterAction, - hasObjectProp, - hasStringProp, + IActionDispatcher, + IActionHandler, + ICommand, + ILogger, NavigateToExternalTargetAction, NavigateToTargetAction, NavigationTarget, @@ -29,12 +33,12 @@ import { ServerSeverity, ServerStatusAction, SetNavigationTargetsAction, - SetResolvedNavigationTargetAction -} from '@eclipse-glsp/protocol'; -import { inject, injectable } from 'inversify'; -import { ActionHandlerRegistry, IActionDispatcher, IActionHandler, ICommand, ILogger } from 'sprotty'; + SetResolvedNavigationTargetAction, + TYPES, + hasObjectProp, + hasStringProp +} from '~glsp-sprotty'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; import { NavigationTargetResolver } from './navigation-target-resolver'; /** diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index ec4f3bb..207b718 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable no-unused-expressions */ -import { NavigationTarget } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; +import { NavigationTarget } from '~glsp-sprotty'; describe('NavigationTarget', () => { it('should be able to set and get element IDs', () => { diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 92d6652..12cbb63 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { NavigationTarget, ResolveNavigationTargetAction, ResponseAction, SetResolvedNavigationTargetAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, ILogger } from 'sprotty'; +import { + IActionDispatcher, + ILogger, + NavigationTarget, + ResolveNavigationTargetAction, + ResponseAction, + SetResolvedNavigationTargetAction, + TYPES +} from '~glsp-sprotty'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; /** * Resolves `NavigationTargets` to element ids. diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index a0eee52..1f728f1 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -22,7 +22,7 @@ import { SModelExtension, SRoutableElement, SRoutingHandle -} from 'sprotty'; +} from '~glsp-sprotty'; export const reconnectFeature = Symbol('reconnectFeature'); diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index e130bca..8c395cf 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { TYPES } from '../../base/types'; +import { TYPES, bindAsService } from '~glsp-sprotty'; import { SaveModelKeyboardListener } from './save-keylistener'; /** diff --git a/packages/client/src/features/save/model.ts b/packages/client/src/features/save/model.ts index c3a01e0..05a8c5c 100644 --- a/packages/client/src/features/save/model.ts +++ b/packages/client/src/features/save/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelExtension, SModelRoot } from 'sprotty'; +import { SModelElement, SModelExtension, SModelRoot } from '~glsp-sprotty'; export const saveFeature = Symbol.for('saveFeature'); diff --git a/packages/client/src/features/save/save-keylistener.ts b/packages/client/src/features/save/save-keylistener.ts index 7626f69..242ef2d 100644 --- a/packages/client/src/features/save/save-keylistener.ts +++ b/packages/client/src/features/save/save-keylistener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SaveModelAction } from '@eclipse-glsp/protocol'; -import { KeyListener, SModelRoot } from 'sprotty'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { Action, KeyListener, SModelRoot, SaveModelAction, matchesKeystroke } from '~glsp-sprotty'; export class SaveModelKeyboardListener extends KeyListener { override keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts index 9a52195..8862f62 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/di.config.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureCommand } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; import { SelectFeedbackCommand } from './select-feedback-action'; import { RankedSelectMouseListener } from './select-mouse-listener'; import { SelectAllCommand, SelectCommand, SelectionService } from './selection-service'; diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 787f674..033c99e 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasArrayProp, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { Command, CommandExecutionContext, SelectCommand as SprottySelectCommand, SModelRoot } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { + Action, + Command, + CommandExecutionContext, + SModelRoot, + SelectAction, + SprottySelectCommand, + TYPES, + hasArrayProp +} from '~glsp-sprotty'; export interface SelectFeedbackAction extends Omit, Action { kind: typeof SelectFeedbackAction.KIND; diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index a13c073..17b6ada 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,21 +13,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SelectAction } from '@eclipse-glsp/protocol'; import { - findParentByFeature, - isCtrlOrCmd, - isSelectable, + Action, + BringToFrontAction, SButton, - SelectMouseListener, SModelElement, SModelRoot, SRoutableElement, SRoutingHandle, - SwitchEditModeAction -} from 'sprotty'; -import { BringToFrontAction } from 'sprotty-protocol/lib/actions'; -import { toArray } from 'sprotty/lib/utils/iterable'; + SelectAction, + SelectMouseListener, + SwitchEditModeAction, + findParentByFeature, + isCtrlOrCmd, + isSelectable, + toArray +} from '~glsp-sprotty'; import { DEFAULT_RANK, Ranked } from '../rank/model'; /** diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index c7c1711..aa5b8b3 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -13,12 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable import/no-deprecated,no-unused-expressions */ -import { Action, initializeContainer, SModelElementSchema } from '@eclipse-glsp/protocol'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; -import { defaultModule, SGraphFactory, SModelRoot } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { Action, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, defaultModule, initializeContainer } from '~glsp-sprotty'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; import { SelectionListener, SelectionService } from './selection-service'; diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index 3e214de..458476b 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, distinctAdd, pluck, remove, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; import { + Action, Command, CommandExecutionContext, ILogger, - isSelectable, SChildElement, - Selectable, - SelectAllCommand as SprottySelectAllCommand, - SelectCommand as SprottySelectCommand, SModelElement, - SModelRoot -} from 'sprotty'; + SModelRoot, + SelectAction, + SelectAllAction, + Selectable, + SprottySelectAllCommand, + SprottySelectCommand, + TYPES, + distinctAdd, + isSelectable, + pluck, + remove +} from '~glsp-sprotty'; import { SModelRootListener } from '../../base/model/update-model-command'; -import { TYPES } from '../../base/types'; import { getElements, getMatchingElements } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; diff --git a/packages/client/src/features/source-model-watcher/di.config.ts b/packages/client/src/features/source-model-watcher/di.config.ts index 847fe5d..1a22b18 100644 --- a/packages/client/src/features/source-model-watcher/di.config.ts +++ b/packages/client/src/features/source-model-watcher/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SourceModelChangedAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler } from 'sprotty'; +import { SourceModelChangedAction, configureActionHandler } from '~glsp-sprotty'; import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; const sourceModelWatcherModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts index f9159a5..8577fd1 100644 --- a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts +++ b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ServerMessageAction, ServerStatusAction, SourceModelChangedAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { IActionDispatcher, IActionHandler, ViewerOptions } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { + Action, + IActionDispatcher, + IActionHandler, + ServerMessageAction, + ServerStatusAction, + SourceModelChangedAction, + TYPES, + ViewerOptions +} from '~glsp-sprotty'; /** * An external handler of the source model change event. diff --git a/packages/client/src/features/svg-metadata/di.config.ts b/packages/client/src/features/svg-metadata/di.config.ts index b9204b6..b939376 100644 --- a/packages/client/src/features/svg-metadata/di.config.ts +++ b/packages/client/src/features/svg-metadata/di.config.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { TYPES } from '../../base/types'; +import { TYPES } from '~glsp-sprotty'; import { MetadataPlacer } from './metadata-placer'; const svgMetadataModule = new ContainerModule(bind => { diff --git a/packages/client/src/features/svg-metadata/metadata-placer.ts b/packages/client/src/features/svg-metadata/metadata-placer.ts index 1ffd8e9..166a29d 100644 --- a/packages/client/src/features/svg-metadata/metadata-placer.ts +++ b/packages/client/src/features/svg-metadata/metadata-placer.ts @@ -14,11 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; +import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IVNodePostprocessor, SChildElement, SEdge, setAttr, SModelElement, SModelRoot } from 'sprotty'; -import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; -import { TYPES } from '../../base/types'; +import { DOMHelper, IVNodePostprocessor, SChildElement, SEdge, SModelElement, SModelRoot, TYPES, setAttr } from '~glsp-sprotty'; @injectable() export class MetadataPlacer implements IVNodePostprocessor { diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index e4e06cb..5ab953f 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -13,26 +13,28 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasStringProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { + Action, CommandExecutionContext, CommandReturn, ElementMove, - findParentByFeature, - isMoveable, - isSelectable, - isViewport, MouseListener, + MoveAction, + Point, SChildElement, SModelElement, SModelRoot, - TYPES -} from 'sprotty'; -import { MoveAction } from 'sprotty-protocol/lib/actions'; + TYPES, + findParentByFeature, + hasStringProp, + isMoveable, + isSelectable, + isViewport +} from '~glsp-sprotty'; import { forEachElement } from '../../utils/smodel-util'; -import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from '../change-bounds/model'; +import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { ChangeBoundsTool } from '../tools/change-bounds-tool'; diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index a88a6b6..3915921 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -13,27 +13,30 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Bounds, Point, SEdgeSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, AnchorComputerRegistry, + Bounds, CommandExecutionContext, CommandReturn, - findChildrenAtPosition, - findParentByFeature, - isBoundsAware, - isConnectable, MouseListener, + MoveAction, + Point, PolylineEdgeRouter, SChildElement, SConnectableElement, SDanglingAnchor, + SEdgeSchema, SModelElement, SModelRoot, SRoutableElement, - TYPES -} from 'sprotty'; -import { MoveAction } from 'sprotty-protocol/lib/actions'; + TYPES, + findChildrenAtPosition, + findParentByFeature, + isBoundsAware, + isConnectable +} from '~glsp-sprotty'; import { BoundsAwareModelElement, isRoutable } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackCommand } from './model'; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 16d1f97..3983e3a 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { CommandExecutionContext, SModelElement, SModelRoot } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES, hasArrayProp } from '~glsp-sprotty'; import { addCssClasses, getElements, removeCssClasses } from '../../utils/smodel-util'; import { FeedbackCommand } from './model'; @@ -71,7 +69,6 @@ export class ModifyCssFeedbackCommand extends FeedbackCommand { } } -// eslint-disable-next-line no-shadow export enum CursorCSS { DEFAULT = 'default-mode', OVERLAP_FORBIDDEN = 'overlap-forbidden-mode', diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index dfa292e..ae0d8ce 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureCommand, configureView, LocationPostprocessor, MoveCommand } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { bindAsService, configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from '~glsp-sprotty'; import { SResizeHandle } from '../change-bounds/model'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from './creation-tool-feedback'; diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index b66d9e3..ec91743 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -13,36 +13,39 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Bounds, hasStringProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { + Action, AnchorComputerRegistry, + Bounds, CommandExecutionContext, CommandReturn, EdgeRouterRegistry, ElementMove, - findChildrenAtPosition, - findParentByFeature, - isBoundsAware, - isConnectable, ISnapper, - isSelected, MouseListener, + MoveAction, + Point, PolylineEdgeRouter, SConnectableElement, SModelElement, SRoutingHandle, SwitchEditModeAction, SwitchEditModeCommand, - TYPES -} from 'sprotty'; -import { MoveAction } from 'sprotty-protocol/lib/actions'; + TYPES, + findChildrenAtPosition, + findParentByFeature, + hasStringProp, + isBoundsAware, + isConnectable, + isSelected +} from '~glsp-sprotty'; import { forEachElement, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { PointPositionUpdater } from '../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; -import { FeedbackEdgeEnd, feedbackEdgeEndId, FeedbackEdgeEndMovingMouseListener, feedbackEdgeId } from './creation-tool-feedback'; +import { FeedbackEdgeEnd, FeedbackEdgeEndMovingMouseListener, feedbackEdgeEndId, feedbackEdgeId } from './creation-tool-feedback'; import { FeedbackCommand } from './model'; /** diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index 92bd309..a338f43 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, ILogger } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { Action, IActionDispatcher, ILogger, TYPES } from '~glsp-sprotty'; export interface IFeedbackEmitter {} diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index a3b91b5..ebe2de7 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,18 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasObjectProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, AnchorComputerRegistry, CommandExecutionContext, CommandReturn, MouseListener, + Point, SChildElement, SModelElement, SModelRoot, - TYPES -} from 'sprotty'; + TYPES, + hasObjectProp +} from '~glsp-sprotty'; import { FeedbackCommand } from './model'; export interface DrawMarqueeAction extends Action { diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts index af834d8..d3b0a30 100644 --- a/packages/client/src/features/tool-feedback/model.ts +++ b/packages/client/src/features/tool-feedback/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Command, CommandExecutionContext, CommandReturn } from 'sprotty'; +import { Command, CommandExecutionContext, CommandReturn } from '~glsp-sprotty'; export abstract class FeedbackCommand extends Command { // used by the `FeedbackAwareUpdateModelCommand` diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index fbc52f4..c3504b6 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; +import { IView, Point, RenderingContext, setAttr, SModelElement, svg } from '~glsp-sprotty'; import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts index 6867962..dc01b63 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler, EnableDefaultToolsAction } from 'sprotty'; +import { bindAsService, configureActionHandler, EnableDefaultToolsAction, TYPES } from '~glsp-sprotty'; import '../../../css/tool-palette.css'; -import { TYPES } from '../../base/types'; import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 3bb4177..9926d77 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -13,21 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MarkersReason, PaletteItem, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, + Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, IToolManager, + MarkersReason, + PaletteItem, + RequestContextActions, + RequestMarkersAction, SModelRoot, + SetContextActions, SetUIExtensionVisibilityAction, - TYPES -} from 'sprotty'; -import { codiconCSSClasses } from 'sprotty/lib/utils/codicon'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; + TYPES, + codiconCSSClasses, + matchesKeystroke +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditModeListener, EditorContextService } from '../../base/editor-context-service'; import { MouseDeleteTool } from '../tools/delete-tool'; diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 328082a..0bda02d 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, KeyTool } from 'sprotty'; +import { Action, IActionDispatcher, KeyTool, TYPES } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 06031ca..f80f86e 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -13,35 +13,33 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { inject, injectable, optional } from 'inversify'; import { Action, Bounds, + BoundsAware, ChangeBoundsOperation, ChangeRoutingPointsOperation, CompoundOperation, Dimension, + EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, - Operation, - Point, - SetBoundsAction -} from '@eclipse-glsp/protocol'; -import { inject, injectable, optional } from 'inversify'; -import { - BoundsAware, - EdgeRouterRegistry, - findParentByFeature, ISnapper, - isSelected, MouseListener, + Operation, + Point, SChildElement, SConnectableElement, SModelElement, SModelRoot, - SParentElement -} from 'sprotty'; + SParentElement, + SetBoundsAction, + TYPES, + findParentByFeature, + isSelected +} from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { TYPES } from '../../base/types'; import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { calcElementAndRoutingPoints, @@ -49,10 +47,10 @@ import { isNonRoutableSelectedMovableBoundsAware, toElementAndBounds } from '../../utils/smodel-util'; -import { isBoundsAwareMoveable, isResizable, Resizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; +import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { - createMovementRestrictionFeedback, IMovementRestrictor, + createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { PointPositionUpdater } from '../change-bounds/snap'; @@ -62,7 +60,7 @@ import { HideChangeBoundsToolResizeFeedbackAction, ShowChangeBoundsToolResizeFeedbackAction } from '../tool-feedback/change-bounds-tool-feedback'; -import { applyCssClasses, CursorCSS, cursorFeedbackAction, deleteCssClasses } from '../tool-feedback/css-feedback'; +import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../tool-feedback/css-feedback'; import { BaseGLSPTool } from './base-glsp-tool'; /** diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index 3bcbb84..eb12cad 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, + DeleteElementOperation, EnableDefaultToolsAction, + KeyListener, + KeyTool, + MouseListener, + SModelElement, + TYPES, findParentByFeature, isCtrlOrCmd, isDeletable, isSelectable, - KeyListener, - KeyTool, - MouseListener, - SModelElement -} from 'sprotty'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; + matchesKeystroke +} from '~glsp-sprotty'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 4f7fe25..890476b 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -13,11 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext, bindOrRebind, TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureModelElement, ManhattanEdgeRouter } from 'sprotty'; +import { + bindAsService, + BindingContext, + bindOrRebind, + configureActionHandler, + configureModelElement, + ManhattanEdgeRouter, + TriggerEdgeCreationAction, + TriggerNodeCreationAction, + TYPES +} from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { TYPES } from '../../base/types'; import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; import { ChangeBoundsTool } from './change-bounds-tool'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 1627b2e..1dc4612 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,19 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CreateEdgeOperation, TriggerEdgeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, AnchorComputerRegistry, + CreateEdgeOperation, EnableDefaultToolsAction, EnableToolsAction, - findParentByFeature, IActionHandler, - isConnectable, - isCtrlOrCmd, SEdge, - SModelElement -} from 'sprotty'; + SModelElement, + TriggerEdgeCreationAction, + findParentByFeature, + isConnectable, + isCtrlOrCmd +} from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { DrawFeedbackEdgeAction, diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 209d2cc..0fb94fd 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,28 +13,30 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ChangeRoutingPointsOperation, ReconnectEdgeOperation } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { + Action, AnchorComputerRegistry, - canEditRouting, + ChangeRoutingPointsOperation, Connectable, EdgeRouterRegistry, - findParentByFeature, - isConnectable, ISnapper, - isSelected, + ReconnectEdgeOperation, SModelElement, SModelRoot, SRoutableElement, - SRoutingHandle -} from 'sprotty'; + SRoutingHandle, + TYPES, + canEditRouting, + findParentByFeature, + isConnectable, + isSelected +} from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { TYPES } from '../../base/types'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; -import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle } from '../reconnect/model'; +import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../reconnect/model'; import { SelectionListener, SelectionService } from '../select/selection-service'; -import { DrawFeedbackEdgeAction, feedbackEdgeId, RemoveFeedbackEdgeAction } from '../tool-feedback/creation-tool-feedback'; +import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../tool-feedback/creation-tool-feedback'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { DrawFeedbackEdgeSourceAction, diff --git a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts index b2a98d7..c540a41 100644 --- a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts +++ b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { EnableDefaultToolsAction, IActionHandler, ICommand } from 'sprotty'; +import { Action, EnableDefaultToolsAction, IActionHandler, ICommand } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; /** diff --git a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts index a562ed3..00e2d24 100644 --- a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts +++ b/packages/client/src/features/tools/glsp-manhattan-edge-router.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { almostEquals } from '@eclipse-glsp/protocol'; -import { ManhattanEdgeRouter, ResolvedHandleMove, SRoutableElement } from 'sprotty'; +import { ManhattanEdgeRouter, ResolvedHandleMove, SRoutableElement, almostEquals } from '~glsp-sprotty'; export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { protected override applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { diff --git a/packages/client/src/features/tools/marquee-behavior.ts b/packages/client/src/features/tools/marquee-behavior.ts index b898729..02f3537 100644 --- a/packages/client/src/features/tools/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Point } from '@eclipse-glsp/protocol'; -import { PointToPointLine } from 'sprotty'; +import { Bounds, Point, PointToPointLine } from '~glsp-sprotty'; import { DrawMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; export interface IMarqueeBehavior { diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index a580789..791f857 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { + Action, BoundsAware, + DOMHelper, EnableDefaultToolsAction, - isSelectable, - isSelected, KeyListener, SEdge, SModelElement, SModelRoot, - SNode -} from 'sprotty'; -import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; + SNode, + SelectAction, + TYPES, + isSelectable, + isSelected +} from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { TYPES } from '../../base/types'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { RemoveMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index 2228e5c..7c38552 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { EnableToolsAction, KeyListener, KeyTool, SModelElement } from 'sprotty'; +import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from '~glsp-sprotty'; import { BaseGLSPTool } from '../tools/base-glsp-tool'; import { MarqueeMouseTool } from './marquee-mouse-tool'; diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/model.ts index e95068c..1a61fde 100644 --- a/packages/client/src/features/tools/model.ts +++ b/packages/client/src/features/tools/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; -import { boundsFeature, RectangularNode } from 'sprotty'; +import { boundsFeature, Point, RectangularNode } from '~glsp-sprotty'; export class MarqueeNode extends RectangularNode { static override readonly DEFAULT_FEATURES = [boundsFeature]; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index e37b5fe..0f2840b 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,19 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CreateNodeOperation, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { + Action, + CreateNodeOperation, EnableDefaultToolsAction, EnableToolsAction, - findParentByFeature, IActionHandler, - isCtrlOrCmd, ISnapper, SModelElement, SNode, - TYPES -} from 'sprotty'; + TYPES, + TriggerNodeCreationAction, + findParentByFeature, + isCtrlOrCmd +} from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { Containable, isContainable } from '../hints/model'; diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx index 96c8034..4087c3e 100644 --- a/packages/client/src/features/tools/view.tsx +++ b/packages/client/src/features/tools/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { RectangularNodeView, RenderingContext, svg } from 'sprotty'; +import { RectangularNodeView, RenderingContext, svg } from '~glsp-sprotty'; import { MarqueeNode } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index ba733a6..aba687c 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, SetMarkersAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureActionHandler, configureCommand } from 'sprotty'; -import { TYPES } from '../../base/types'; +import { SetMarkersAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { LeftToRightTopToBottomComparator, MarkerNavigator, diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index a546b8a..b9f7f74 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Marker, MarkerKind } from '@eclipse-glsp/protocol'; -import { isBoundsAware, Projectable, SDecoration, SIssue, SIssueMarker, SIssueSeverity, SParentElement } from 'sprotty'; +import { + Bounds, + Marker, + MarkerKind, + Projectable, + SDecoration, + SIssue, + SIssueMarker, + SIssueSeverity, + SParentElement, + isBoundsAware +} from '~glsp-sprotty'; export class GIssueMarker extends SIssueMarker implements Projectable { constructor() { diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 07b580c..6c8a0a4 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,25 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable import/no-deprecated,no-unused-expressions */ -import { SNodeSchema } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; import 'mocha'; import 'reflect-metadata'; import { BoundsAware, - decorationModule, SChildElement, + SGraphFactory, SIssue, SIssueMarker, SModelElement, SModelRoot, + SNodeSchema, SParentElement, - TYPES -} from 'sprotty'; -import defaultModule from 'sprotty/lib/base/di.config'; -import { SGraphFactory } from 'sprotty/lib/graph/sgraph-factory'; + TYPES, + decorationModule, + defaultModule +} from '~glsp-sprotty'; import { markerNavigatorModule } from './di.config'; import { MarkerNavigator } from './marker-navigator'; diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 2a302c1..a39b95d 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,27 +13,32 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CenterAction, hasArrayProp, hasStringProp, Point, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + Action, ActionDispatcher, BoundsAware, - findParentByFeature, + CenterAction, IActionHandler, IContextMenuItemProvider, - isBoundsAware, - isSelectable, KeyListener, MenuItem, - Selectable, + Point, SIssueMarker, SIssueSeverity, SModelElement, - SModelRoot -} from 'sprotty'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { TYPES } from '../../base/types'; -import { collectIssueMarkers, MarkerPredicates } from '../../utils/marker'; + SModelRoot, + SelectAction, + Selectable, + TYPES, + findParentByFeature, + hasArrayProp, + hasStringProp, + isBoundsAware, + isSelectable, + matchesKeystroke +} from '~glsp-sprotty'; +import { MarkerPredicates, collectIssueMarkers } from '../../utils/marker'; import { getElements, isSelectableAndBoundsAware } from '../../utils/smodel-util'; import { SelectionService } from '../select/selection-service'; diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 2915645..b8d4b9f 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -13,15 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, DeleteMarkersAction, hasArrayProp, Marker, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { CommandExecutionContext, CommandReturn, IActionDispatcher, IActionHandler, ICommand, SIssueMarker, SParentElement } from 'sprotty'; +import { + Action, + CommandExecutionContext, + CommandReturn, + DeleteMarkersAction, + IActionDispatcher, + IActionHandler, + ICommand, + Marker, + SIssueMarker, + SParentElement, + SetMarkersAction, + TYPES, + hasArrayProp +} from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; -import { TYPES } from '../../base/types'; import { removeCssClasses } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../tool-feedback/model'; -import { createSIssue, getOrCreateSIssueMarker, getSeverity, getSIssueMarker, GIssueMarker } from './issue-marker'; +import { GIssueMarker, createSIssue, getOrCreateSIssueMarker, getSIssueMarker, getSeverity } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/di.config.ts index 7056ec7..7c05397 100644 --- a/packages/client/src/features/viewport/di.config.ts +++ b/packages/client/src/features/viewport/di.config.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { + bindAsService, CenterCommand, CenterKeyboardListener, configureActionHandler, @@ -27,7 +27,7 @@ import { SetViewportCommand, TYPES, ZoomMouseListener -} from 'sprotty'; +} from '~glsp-sprotty'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 2bf2032..7b073f0 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { EnableDefaultToolsAction, EnableToolsAction, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from 'sprotty'; +import { + Action, + EnableDefaultToolsAction, + EnableToolsAction, + IActionHandler, + ICommand, + ScrollMouseListener, + SModelElement +} from '~glsp-sprotty'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @injectable() diff --git a/packages/client/src/base/tool-manager/tool-actions.ts b/packages/client/src/glsp-sprotty/augmented-actions.ts similarity index 71% rename from packages/client/src/base/tool-manager/tool-actions.ts rename to packages/client/src/glsp-sprotty/augmented-actions.ts index 2a816eb..bb740ff 100644 --- a/packages/client/src/base/tool-manager/tool-actions.ts +++ b/packages/client/src/glsp-sprotty/augmented-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,13 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable-next-line no-restricted-imports*/ import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; +import { SetBoundsAction } from 'sprotty-protocol/lib/actions'; import { EnableDefaultToolsAction, EnableToolsAction } from 'sprotty/lib/base/tool-manager/tool'; -/* eslint-disable no-shadow */ /** - * Use module augmentation to add the `is` utility function to the tool action namespaces provided by sprotty + * Use module augmentation to add namespaces and the `is` utility function to sprotty actions that directly reused by GLSP */ +declare module 'sprotty-protocol/lib/actions' { + namespace SetBoundsAction { + export function is(object: any): object is SetBoundsAction; + } +} + +SetBoundsAction.is = (object: any): object is SetBoundsAction => + Action.hasKind(object, SetBoundsAction.KIND) && hasArrayProp(object, 'bounds'); + declare module 'sprotty/lib/base/tool-manager/tool' { export namespace EnableDefaultToolsAction { export function is(object: any): object is EnableDefaultToolsAction; diff --git a/packages/client/src/sprotty-reexport.ts b/packages/client/src/glsp-sprotty/index.ts similarity index 84% rename from packages/client/src/sprotty-reexport.ts rename to packages/client/src/glsp-sprotty/index.ts index f83c418..2a57d47 100644 --- a/packages/client/src/sprotty-reexport.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -19,19 +19,32 @@ * The deprecated definitions are planned to be removed with Sprotty 1.0.0. * Until then we have to manually reexport the sprotty main index and exclude the clashing types. */ +/* eslint-disable no-restricted-imports */ +/** + * @eclipse-glsp/protocol + */ export * from '@eclipse-glsp/protocol'; -// sprotty-protocol +/* + * sprotty-protocol + */ export { BringToFrontAction, CollapseExpandAction, CollapseExpandAllAction, ElementAndAlignment, + GetSelectionAction, + GetViewportAction, HoverFeedbackAction, MoveAction, - SetBoundsAction + SelectionResult, + SetBoundsAction, + ViewportResult } from 'sprotty-protocol/lib/actions'; export { Viewport } from 'sprotty-protocol/lib/model'; +/* + * sprotty + */ // ------------------ Base ------------------ export * from 'sprotty/lib/base/actions/action-dispatcher'; export * from 'sprotty/lib/base/actions/action-handler'; @@ -46,15 +59,15 @@ export * from 'sprotty/lib/base/commands/command-stack-options'; export * from 'sprotty/lib/base/features/initialize-canvas'; export { SetModelCommand } from 'sprotty/lib/base/features/set-model'; export { - createRandomId, FeatureSet, IModelIndex, - isParent, ModelIndexImpl, - SChildElement, - SModelElement, - SModelRoot, - SParentElement + SChildElementImpl as SChildElement, + SModelElementImpl as SModelElement, + SModelRootImpl as SModelRoot, + SParentElementImpl as SParentElement, + createRandomId, + isParent } from 'sprotty/lib/base/model/smodel'; export * from 'sprotty/lib/base/model/smodel-extension'; export * from 'sprotty/lib/base/model/smodel-factory'; @@ -70,6 +83,7 @@ export * from 'sprotty/lib/base/tool-manager/tool'; export * from 'sprotty/lib/base/tool-manager/tool-manager'; export * from 'sprotty/lib/base/ui-extensions/ui-extension'; export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; +export * from 'sprotty/lib/base/views/dom-helper'; export * from 'sprotty/lib/base/views/key-tool'; export * from 'sprotty/lib/base/views/mouse-tool'; export * from 'sprotty/lib/base/views/thunk-view'; @@ -79,6 +93,7 @@ export * from 'sprotty/lib/base/views/viewer-cache'; export * from 'sprotty/lib/base/views/viewer-options'; export * from 'sprotty/lib/base/views/vnode-postprocessor'; export * from 'sprotty/lib/base/views/vnode-utils'; +export * from 'sprotty/lib/features/bounds/abstract-layout'; // ------------------ Features ------------------ export { RequestBoundsCommand, @@ -89,34 +104,35 @@ export { export * from 'sprotty/lib/features/bounds/hbox-layout'; export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; export * from 'sprotty/lib/features/bounds/layout'; +export * from 'sprotty/lib/features/bounds/layout-options'; export { Alignable, - alignFeature, BoundsAware, + LayoutContainer, + LayoutableChild, + ModelLayoutOptions, + SShapeElementImpl as SShapeElement, + alignFeature, boundsFeature, findChildrenAtPosition, getAbsoluteBounds, getAbsoluteClientBounds, isAlignable, isBoundsAware, - isLayoutableChild, isLayoutContainer, + isLayoutableChild, isSizeable, - LayoutableChild, - layoutableChildFeature, - LayoutContainer, layoutContainerFeature, - ModelLayoutOptions, - SShapeElement + layoutableChildFeature } from 'sprotty/lib/features/bounds/model'; export * from 'sprotty/lib/features/bounds/stack-layout'; export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; export { ButtonHandlerRegistry, - configureButtonHandler, IButtonHandler, - IButtonHandlerRegistration + IButtonHandlerRegistration, + configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; export * from 'sprotty/lib/features/button/model'; export * from 'sprotty/lib/features/command-palette/action-providers'; @@ -146,7 +162,7 @@ export * from 'sprotty/lib/features/expand/model'; export * from 'sprotty/lib/features/expand/views'; export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, RequestExportSvgAction } from 'sprotty/lib/features/export/export'; export * from 'sprotty/lib/features/export/model'; -export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; +export { ExportSvgAction, SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; export * from 'sprotty/lib/features/fade/fade'; export * from 'sprotty/lib/features/fade/model'; export { @@ -160,6 +176,7 @@ export { SetPopupModelCommand } from 'sprotty/lib/features/hover/hover'; export * from 'sprotty/lib/features/hover/model'; +export { PopupPositionUpdater } from 'sprotty/lib/features/hover/popup-position-updater'; export * from 'sprotty/lib/features/move/model'; export { ElementMove, @@ -183,20 +200,31 @@ export * from 'sprotty/lib/features/routing/bezier-anchors'; export * from 'sprotty/lib/features/routing/bezier-edge-router'; export * from 'sprotty/lib/features/routing/manhattan-anchors'; export * from 'sprotty/lib/features/routing/manhattan-edge-router'; -export * from 'sprotty/lib/features/routing/model'; +export { + Connectable, + RoutingHandleKind, + SConnectableElementImpl as SConnectableElement, + SDanglingAnchorImpl as SDanglingAnchor, + SRoutableElementImpl as SRoutableElement, + SRoutingHandleImpl as SRoutingHandle, + connectableFeature, + edgeInProgressID, + edgeInProgressTargetHandleID, + getAbsoluteRouteBounds, + getRouteBounds, + isConnectable +} from 'sprotty/lib/features/routing/model'; export * from 'sprotty/lib/features/routing/polyline-anchors'; export * from 'sprotty/lib/features/routing/polyline-edge-router'; export * from 'sprotty/lib/features/routing/routing'; export * from 'sprotty/lib/features/routing/views'; export * from 'sprotty/lib/features/select/model'; export { - GetSelectionAction, GetSelectionCommand, - SelectAllCommand as SprottySelectAllCommand, - SelectCommand as SprottySelectCommand, - SelectionResult, SelectKeyboardListener, - SelectMouseListener + SelectMouseListener, + SelectAllCommand as SprottySelectAllCommand, + SelectCommand as SprottySelectCommand } from 'sprotty/lib/features/select/select'; export { UndoRedoKeyListener } from 'sprotty/lib/features/undo-redo/undo-redo'; export * from 'sprotty/lib/features/update/model-matching'; @@ -209,17 +237,20 @@ export { } from 'sprotty/lib/features/viewport/center-fit'; export * from 'sprotty/lib/features/viewport/model'; export * from 'sprotty/lib/features/viewport/scroll'; -export { - GetViewportAction, - GetViewportCommand, - SetViewportCommand, - ViewportAnimation, - ViewportResult -} from 'sprotty/lib/features/viewport/viewport'; +export { GetViewportCommand, SetViewportCommand, ViewportAnimation } from 'sprotty/lib/features/viewport/viewport'; export * from 'sprotty/lib/features/viewport/viewport-root'; export * from 'sprotty/lib/features/viewport/zoom'; export { BringToFrontCommand, ZOrderElement } from 'sprotty/lib/features/zorder/zorder'; -export { SCompartment, SEdge, SGraph, SGraphIndex, SLabel, SNode, SPort } from 'sprotty/lib/graph/sgraph'; +export { + SCompartmentImpl as SCompartment, + SEdgeImpl as SEdge, + SGraphImpl as SGraph, + SGraphIndex, + SLabelImpl as SLabel, + SNodeImpl as SNode, + SPortImpl as SPort +} from 'sprotty/lib/graph/sgraph'; +export * from 'sprotty/lib/lib/virtualize'; // ------------------ Graph ------------------ export * from 'sprotty/lib/graph/sgraph-factory'; export * from 'sprotty/lib/graph/views'; @@ -230,12 +261,12 @@ export { CircularNode, CircularPort, DiamondNode, - ForeignObjectElement, - HtmlRoot, - PreRenderedElement, + ForeignObjectElementImpl as ForeignObjectElement, + HtmlRootImpl as HtmlRoot, + PreRenderedElementImpl as PreRenderedElement, RectangularNode, RectangularPort, - ShapedPreRenderedElement + ShapedPreRenderedElementImpl as ShapedPreRenderedElement } from 'sprotty/lib/lib/model'; // ------------------ Library ------------------ export * from 'sprotty/lib/lib/modules'; @@ -251,24 +282,35 @@ export * from 'sprotty/lib/model-source/websocket'; export * from 'sprotty/lib/utils/browser'; export * from 'sprotty/lib/utils/codicon'; export * from 'sprotty/lib/utils/color'; -export { Diamond, Insets, intersection, Line, Orientation, PointToPointLine } from 'sprotty/lib/utils/geometry'; +export { Diamond, Insets, Line, Orientation, PointToPointLine, intersection } from 'sprotty/lib/utils/geometry'; export * from 'sprotty/lib/utils/inversify'; +export * from 'sprotty/lib/utils/iterable'; +export * from 'sprotty/lib/utils/keyboard'; export * from 'sprotty/lib/utils/logging'; export * from 'sprotty/lib/utils/registry'; -export { defaultModule }; +/** + * Misc glsp adaptions/augmentations + */ +export * from './augmented-actions'; +export * from './types'; +/** + * Modules + */ export { - graphModule, boundsModule, buttonModule, commandPaletteModule, contextMenuModule, decorationModule, + defaultModule, edgeIntersectionModule, edgeLayoutModule, expandModule, exportModule, fadeModule, + graphModule, hoverModule, + modelSourceModule, moveModule, openModule, routingModule, @@ -278,8 +320,6 @@ export { viewportModule, zorderModule }; -export { modelSourceModule }; - import defaultModule from 'sprotty/lib/base/di.config'; import boundsModule from 'sprotty/lib/features/bounds/di.config'; import buttonModule from 'sprotty/lib/features/button/di.config'; diff --git a/packages/client/src/base/types.ts b/packages/client/src/glsp-sprotty/types.ts similarity index 95% rename from packages/client/src/base/types.ts rename to packages/client/src/glsp-sprotty/types.ts index 0311caa..a35974c 100644 --- a/packages/client/src/base/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +// eslint-disable-next-line no-restricted-imports import { TYPES as SprottyTYPES } from 'sprotty'; /** * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 4d3c6a9..3b67d8b 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -58,8 +58,6 @@ export * from './base/model/update-model-command'; export * from './base/selection-clearing-mouse-listener'; export * from './base/source-uri-aware'; export * from './base/tool-manager/glsp-tool-manager'; -export * from './base/tool-manager/tool-actions'; -export * from './base/types'; export * from './base/view/view-registry'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; @@ -126,11 +124,12 @@ export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; +export * from './glsp-sprotty'; +export * from './glsp-sprotty/types'; // // ------------------ Misc ------------------ export * from './lib/model'; export * from './model-source/glsp-diagram-server'; -export * from './sprotty-reexport'; export * from './utils/argument-utils'; export * from './utils/html-utils'; export * from './utils/layout-utils'; diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index 934ed7c..a4ee16a 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -13,8 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, isBounds, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; -import { exportFeature, getRouteBounds, SEdge, SGraph, SModelElement, viewportFeature } from 'sprotty'; +import { + Bounds, + exportFeature, + getRouteBounds, + isBounds, + Point, + SEdge, + SGraph, + SModelElement, + SModelElementSchema, + viewportFeature +} from '~glsp-sprotty'; import { Containable, containerFeature } from '../features/hints/model'; import { Saveable, saveFeature } from '../features/save/model'; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 185a0b9..118789a 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,17 +13,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; import { Action, + ActionHandlerRegistry, ActionMessage, ComputedBoundsAction, + DiagramServerProxy, + ExportSvgAction, GLSPClient, + ICommand, RequestModelAction, ServerMessageAction, - SetEditModeAction -} from '@eclipse-glsp/protocol'; -import { injectable } from 'inversify'; -import { ActionHandlerRegistry, DiagramServerProxy, ExportSvgAction, ICommand, ServerStatusAction, SwitchEditModeCommand } from 'sprotty'; + ServerStatusAction, + SetEditModeAction, + SwitchEditModeCommand +} from '~glsp-sprotty'; import { SourceUriAware } from '../base/source-uri-aware'; const receivedFromServerProperty = '__receivedFromServer'; diff --git a/packages/client/src/utils/argument-utils.ts b/packages/client/src/utils/argument-utils.ts index 54428d0..74b96ec 100644 --- a/packages/client/src/utils/argument-utils.ts +++ b/packages/client/src/utils/argument-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement } from 'sprotty'; +import { SModelElement } from '~glsp-sprotty'; import { hasArguments } from '../base/argumentable'; export namespace GArgument { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 77615a8..84a7bd7 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension, ElementAndBounds, Point, Writable } from '@eclipse-glsp/protocol'; -import { BoundsAware, ElementMove, ModelLayoutOptions, SModelElement } from 'sprotty'; +import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement, Writable } from '~glsp-sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; export function minWidth(element: SModelElement & BoundsAware): number { diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts index 5a37bc8..1feb59f 100644 --- a/packages/client/src/utils/marker.ts +++ b/packages/client/src/utils/marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { MarkerKind } from '@eclipse-glsp/protocol'; -import { SIssueMarker, SParentElement } from 'sprotty'; +import { MarkerKind, SIssueMarker, SParentElement } from '~glsp-sprotty'; export namespace MarkerPredicates { export const ALL = (): boolean => true; diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index b7a1b27..c8db633 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -14,29 +14,27 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + BoundsAware, distinctAdd, + EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, - Point, - remove, - SModelElementSchema, - TypeGuard -} from '@eclipse-glsp/protocol'; -import { - BoundsAware, - EdgeRouterRegistry, + FluentIterable, isBoundsAware, isMoveable, isSelectable, isSelected, ModelIndexImpl, + Point, + remove, RoutedPoint, Selectable, SModelElement, + SModelElementSchema, SRoutableElement, - SRoutingHandle -} from 'sprotty'; -import { FluentIterable } from 'sprotty/lib/utils/iterable'; + SRoutingHandle, + TypeGuard +} from '~glsp-sprotty'; /** * Helper type to represent a filter predicate for {@link SModelElement}s. This is used to retrieve diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 75f16f5..3f71010 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Dimension, Point, Viewport } from '@eclipse-glsp/protocol'; -import { BoundsAware, findParentByFeature, isAlignable, isViewport, SModelElement, translateBounds } from 'sprotty'; +import { + Bounds, + BoundsAware, + Dimension, + Point, + SModelElement, + Viewport, + findParentByFeature, + isAlignable, + isViewport, + translateBounds +} from '~glsp-sprotty'; /** * Return the position corresponding to this mouse event (Browser coordinates) diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 2f6b900..5521f13 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, DefaultTypes } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { + BindingContext, CircularNode, CircularNodeView, - configureModelElement, + DefaultTypes, DiamondNode, DiamondNodeView, ExpandButtonView, @@ -26,7 +26,6 @@ import { ForeignObjectView, HtmlRoot, HtmlRootView, - moveFeature, PreRenderedElement, PreRenderedView, RectangularNode, @@ -35,9 +34,7 @@ import { SCompartment, SCompartmentView, SEdge, - selectFeature, SGraphView, - ShapedPreRenderedElement, SIssueMarker, SLabel, SLabelView, @@ -45,9 +42,13 @@ import { SPort, SRoutingHandle, SRoutingHandleView, + ShapedPreRenderedElement, SvgViewportView, - ViewportRootElement -} from 'sprotty'; + ViewportRootElement, + configureModelElement, + moveFeature, + selectFeature +} from '~glsp-sprotty'; import { GLSPGraph } from '../lib/model'; import { GEdgeView } from './glsp-edge-view'; import { GIssueMarkerView } from './issue-marker-view'; diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index ebd47dc..d2c3e31 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { BoundsAware, RenderingContext, SChildElement, SCompartment, ShapeView, svg } from 'sprotty/lib'; +import { BoundsAware, RenderingContext, SChildElement, SCompartment, ShapeView, svg } from '~glsp-sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index 506aab8..900c003 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { PolylineEdgeView, RenderingContext, SEdge, svg } from 'sprotty'; +import { Point, PolylineEdgeView, RenderingContext, SEdge, svg } from '~glsp-sprotty'; import { EdgePadding } from '../utils/argument-utils'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 037b5f3..ad566b4 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2022 TypeFox and others. + * Copyright (c) 2017-2023 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,12 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/** @jsx html */ -import { Bounds } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { h, VNode, VNodeStyle } from 'snabbdom'; import { - // eslint-disable-next-line @typescript-eslint/no-unused-vars + Bounds, html, IViewArgs, ProjectedViewportView, @@ -29,7 +27,10 @@ import { setClass, ViewportRootElement, ViewProjection -} from 'sprotty'; +} from '~glsp-sprotty'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: html }; /** * Special viewport root view that renders horizontal and vertical projection bars for quick navigation. diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index fca0df1..6225fe6 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ /* eslint-disable max-len */ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity, svg } from 'sprotty'; +import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity, svg } from '~glsp-sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 041fd69..2f03c8a 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { Hoverable, RectangularNodeView, RenderingContext, Selectable, SNode, SPort, SShapeElement, svg } from 'sprotty'; +import { Hoverable, RectangularNodeView, RenderingContext, SNode, SPort, SShapeElement, Selectable, svg } from '~glsp-sprotty'; import { CornerRadius } from '../utils/argument-utils'; import { RoundedCornerWrapper } from './rounded-corner'; diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index 4592461..370a5c4 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension } from '@eclipse-glsp/protocol'; -import { Hoverable, Selectable, SShapeElement } from 'sprotty'; +import { Dimension, Hoverable, Selectable, SShapeElement } from '~glsp-sprotty'; import { CornerRadius } from '../utils/argument-utils'; export interface RoundedCorner { diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 5752a13..4629484 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,10 +1,13 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", - "baseUrl": "." + "baseUrl": ".", + "paths": { + "~glsp-sprotty": ["src/glsp-sprotty/index"] + } }, "exclude": ["**/*.spec.ts"], "include": ["src"] diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 9cc16bc..6773f9c 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "0.13.0-next.f4445dd.342", + "sprotty-protocol": "0.14.0-next.02bbac0.26", "uuid": "7.0.3", "vscode-jsonrpc": "^8.0.2" }, diff --git a/packages/protocol/src/action-protocol/element-text-editing.ts b/packages/protocol/src/action-protocol/element-text-editing.ts index 1f05aea..45157ce 100644 --- a/packages/protocol/src/action-protocol/element-text-editing.ts +++ b/packages/protocol/src/action-protocol/element-text-editing.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -164,14 +164,12 @@ export namespace ValidationStatus { /** * The default {@link ValidationStatus} severity levels used in GLSP. */ - // eslint-disable-next-line no-shadow export enum Severity { FATAL, ERROR, WARNING, INFO, OK, - // eslint-disable-next-line no-shadow NONE } diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 192698a..86d062d 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -31,7 +31,6 @@ export class ApplicationIdProvider { export type ActionMessageHandler = (message: ActionMessage) => void; -// eslint-disable-next-line no-shadow export enum ClientState { /** * The client has been created. diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index f591976..e2e45d7 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -19,12 +19,12 @@ import { hasBooleanProp, hasObjectProp, hasStringProp } from './utils/type-util' // Add the is() function to the namespace declarations of sprotty-protocol actions declare module 'sprotty-protocol/lib/actions' { - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow namespace SetViewportAction { export function is(object: any): object is SetViewportAction; } - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow namespace SetBoundsAction { export function is(object: any): object is SetBoundsAction; } diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index 8dc48d5..e81a993 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", "outDir": "lib", From e35d8c2599bc10a80020afe1871ab82c21a6d87e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 20 Jun 2023 13:31:03 +0200 Subject: [PATCH 220/566] GLSP-1022: Update to TS5 and inversify 6 (#48) - Update to typescript 5 - Update to inversify 6. Inversify is now a peerDependency (^6.0.0). A matching version has to be provided by consuming projects. - Adapt codebase to new inversify API - Rework elk-layouit package to remove unnecessary dependency in `sprotty-elk` Part of https://github.com/eclipse-glsp/glsp/pull/1023 (uses locally published glsp packages. needs to be adapted once the offical packages are available) Requires https://github.com/eclipse-glsp/glsp-client/pull/253 --- examples/workflow-server/package.json | 3 ++- examples/workflow-server/tsconfig.json | 2 +- packages/graph/tsconfig.json | 10 +++++----- packages/layout-elk/package.json | 6 ++++-- packages/layout-elk/src/di.config.ts | 3 +-- .../layout-elk/src/glsp-elk-layout-engine.ts | 10 +++++++++- packages/layout-elk/src/index.ts | 4 ++-- packages/layout-elk/src/layout-configurator.ts | 3 +-- packages/layout-elk/src/sprotty-elk.ts | 18 ------------------ packages/layout-elk/tsconfig.json | 18 +++++++++++++----- packages/server/package.json | 4 +++- .../server/src/common/di/binding-target.ts | 1 + packages/server/src/common/utils/args-util.ts | 2 +- packages/server/src/common/utils/logger.ts | 3 ++- packages/server/tsconfig.json | 13 +++++++++---- 15 files changed, 54 insertions(+), 46 deletions(-) delete mode 100644 packages/layout-elk/src/sprotty-elk.ts diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index e7e23ab..1b88a91 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -57,7 +57,8 @@ }, "dependencies": { "@eclipse-glsp/layout-elk": "1.1.0-next", - "@eclipse-glsp/server": "1.1.0-next" + "@eclipse-glsp/server": "1.1.0-next", + "inversify": "^6.0.1" }, "devDependencies": { "source-map-loader": "^4.0.1", diff --git a/examples/workflow-server/tsconfig.json b/examples/workflow-server/tsconfig.json index 60d997d..22dce50 100644 --- a/examples/workflow-server/tsconfig.json +++ b/examples/workflow-server/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/", "compilerOptions": { "rootDir": "src", "outDir": "lib", diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json index 183726a..40952a0 100644 --- a/packages/graph/tsconfig.json +++ b/packages/graph/tsconfig.json @@ -1,12 +1,12 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", "baseUrl": "src", - "types": ["node", "mocha", "reflect-metadata"] }, - "exclude": ["**/*.spec.ts"], - "include": ["src"] -} + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 7ed81c2..1bd357a 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -51,8 +51,10 @@ }, "dependencies": { "@eclipse-glsp/server": "1.1.0-next", - "elkjs": "^0.7.1", - "sprotty-elk": "0.12.0" + "elkjs": "^0.7.1" + }, + "peerDependencies": { + "inversify": "^6.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index d5cd6f7..41954b4 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -16,9 +16,8 @@ import { LayoutEngine, ModelState } from '@eclipse-glsp/server'; import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled'; import { ContainerModule } from 'inversify'; -import { ElkFactory } from 'sprotty-elk'; import { DefaultElementFilter, ElementFilter } from './element-filter'; -import { GlspElkLayoutEngine } from './glsp-elk-layout-engine'; +import { ElkFactory, GlspElkLayoutEngine } from './glsp-elk-layout-engine'; import { FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator'; type Constructor = new (...args: any[]) => T; diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index ab28c11..cc39589 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -32,10 +32,18 @@ import { } from '@eclipse-glsp/server'; import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkPrimitiveEdge, ElkShape } from 'elkjs/lib/elk-api'; import { injectable } from 'inversify'; -import { ElkFactory } from 'sprotty-elk'; import { ElementFilter } from './element-filter'; import { LayoutConfigurator } from './layout-configurator'; +/** + * Factory for ELK instances. Follow the elkjs package documentation on how to configure ELK + * instances. For example, the bundled version can be used by importing the ELK constructor + * from `"elkjs/lib/elk.bundled"`. For the webworker version, import the constructor from + * `"elkjs/lib/elk-api"` and add the option `workerUrl: "elk/elk-worker.min.js"`. + */ +export type ElkFactory = () => ELK; +export const ElkFactory = Symbol('ElkFactory'); + /** * An implementation of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState}, * transforms this model into an ELK graph and then invokes the underlying ELK instance for layout computation. diff --git a/packages/layout-elk/src/index.ts b/packages/layout-elk/src/index.ts index be72fc2..59d1710 100644 --- a/packages/layout-elk/src/index.ts +++ b/packages/layout-elk/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export { LayoutOptions } from 'elkjs'; export * from './di.config'; export * from './element-filter'; export * from './glsp-elk-layout-engine'; export * from './layout-configurator'; -export * from './sprotty-elk'; diff --git a/packages/layout-elk/src/layout-configurator.ts b/packages/layout-elk/src/layout-configurator.ts index 33779b2..3eaeadd 100644 --- a/packages/layout-elk/src/layout-configurator.ts +++ b/packages/layout-elk/src/layout-configurator.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GEdge, GGraph, GLabel, GModelElement, GNode, GPort, ModelState } from '@eclipse-glsp/server'; +import { LayoutOptions } from 'elkjs'; import { inject, injectable } from 'inversify'; -import { LayoutOptions } from './sprotty-elk'; - export const LayoutConfigurator = Symbol('LayoutConfigurator'); /** * Configurator for ELK layout algorithms; provides mappings of layout options for each model element. diff --git a/packages/layout-elk/src/sprotty-elk.ts b/packages/layout-elk/src/sprotty-elk.ts deleted file mode 100644 index a6a129c..0000000 --- a/packages/layout-elk/src/sprotty-elk.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -// Reexport of the relevant sprotty-elk typings -export { LayoutOptions } from 'elkjs'; -export { ElkFactory } from 'sprotty-elk/lib/inversify'; diff --git a/packages/layout-elk/tsconfig.json b/packages/layout-elk/tsconfig.json index 183726a..280894d 100644 --- a/packages/layout-elk/tsconfig.json +++ b/packages/layout-elk/tsconfig.json @@ -1,12 +1,20 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", "baseUrl": "src", - "types": ["node", "mocha", "reflect-metadata"] + "types": [ + "node", + "mocha", + "reflect-metadata" + ] }, - "exclude": ["**/*.spec.ts"], - "include": ["src"] -} + "exclude": [ + "**/*.spec.ts" + ], + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 1fb75e2..32e80b7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -64,7 +64,6 @@ "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", - "inversify": "^5.1.1", "winston": "^3.3.3", "ws": "^8.12.1" }, @@ -72,6 +71,9 @@ "@types/fs-extra": "^9.0.13", "@types/ws": "^8.5.4" }, + "peerDependencies": { + "inversify": "^6.0.0" + }, "publishConfig": { "access": "public" } diff --git a/packages/server/src/common/di/binding-target.ts b/packages/server/src/common/di/binding-target.ts index a963e0a..5cdf1e3 100644 --- a/packages/server/src/common/di/binding-target.ts +++ b/packages/server/src/common/di/binding-target.ts @@ -152,6 +152,7 @@ namespace NoOPSyntax { }; const syntax = { onActivation: noOpReturn, + onDeactivation: noOpReturn, when: noOpReturn, whenAnyAncestorIs: noOpReturn, whenAnyAncestorMatches: noOpReturn, diff --git a/packages/server/src/common/utils/args-util.ts b/packages/server/src/common/utils/args-util.ts index 4ae82db..d05f70e 100644 --- a/packages/server/src/common/utils/args-util.ts +++ b/packages/server/src/common/utils/args-util.ts @@ -48,7 +48,7 @@ export namespace ArgsUtil { } return { - [KEY_RADIUS_TOP_RIGHT]: topLeft, + [KEY_RADIUS_TOP_LEFT]: topLeft, [KEY_RADIUS_BOTTOM_LEFT]: bottomLeft, [KEY_RADIUS_BOTTOM_RIGHT]: bottomRight, [KEY_RADIUS_TOP_RIGHT]: topRight diff --git a/packages/server/src/common/utils/logger.ts b/packages/server/src/common/utils/logger.ts index 460e47b..fea6732 100644 --- a/packages/server/src/common/utils/logger.ts +++ b/packages/server/src/common/utils/logger.ts @@ -80,7 +80,8 @@ export function getRequestParentName(context: interfaces.Context): string | unde if (context.currentRequest.parentRequest) { const bindings = context.currentRequest.parentRequest.bindings; if (bindings.length > 0) { - return bindings[0].implementationType?.name; + const binding = bindings[0] as interfaces.Binding<{ name?: string }>; + return binding.implementationType?.name; } } return undefined; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index e7ecba5..cafad90 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,10 +1,15 @@ { - "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", "outDir": "lib", "baseUrl": "src" }, - "exclude": ["**/*.spec.ts", "src/common/test/mock-util.ts"], - "include": ["src"] -} + "exclude": [ + "**/*.spec.ts", + "src/common/test/mock-util.ts" + ], + "include": [ + "src" + ] +} \ No newline at end of file From d5ef99b6703f4bb0364919f84d6b8f320d03cee9 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 3 Jul 2023 03:43:29 -0700 Subject: [PATCH 221/566] Update READMEs (#256) - Update outdated information sections in READMEs --- packages/client/README.md | 2 +- packages/protocol/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/README.md b/packages/client/README.md index 9853763..9e22533 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -9,6 +9,6 @@ This project is built with `yarn` and is available from npm via [@eclipse-glsp/c ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). -If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/protocol/README.md b/packages/protocol/README.md index 07c3f14..d568fb2 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -8,6 +8,6 @@ This project is built with `yarn` and is available from npm via [@eclipse-glsp/p ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). -If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). ![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) From f63d9dda4f4311d6baa5b183a9493e8dd3abd7ce Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 5 Jul 2023 10:42:23 +0200 Subject: [PATCH 222/566] GLSP-979: Introduce 'deselectAll' flag for SelectAction (#257) - Introduce optional flag that indicates deselection of all elements - Introduce convenience creation methods for Select action https://github.com/eclipse-glsp/glsp/issues/979 --- .../features/select/select-feedback-action.ts | 15 +++++- .../src/features/select/selection-service.ts | 4 +- .../src/features/tools/marquee-mouse-tool.ts | 6 +-- .../action-protocol/element-selection.spec.ts | 52 ++++++++++++++++++- .../src/action-protocol/element-selection.ts | 27 ++++++++-- 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 033c99e..027920e 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -36,10 +36,23 @@ export namespace SelectFeedbackAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); } - export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] }): SelectFeedbackAction { + export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean }): SelectFeedbackAction { return { ...SelectAction.create(options), kind: KIND }; } + + export function addSelection(selectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.addSelection(selectedElementsIDs), kind: KIND }; + } + + export function removeSelection(deselectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.removeSelection(deselectedElementsIDs), kind: KIND }; + } + + export function setSelection(selectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.setSelection(selectedElementsIDs), kind: KIND }; + } } + @injectable() export class SelectFeedbackCommand extends Command { static readonly KIND = SelectFeedbackAction.KIND; diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index 458476b..0d23cc7 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -179,7 +179,9 @@ export class SelectCommand extends Command { const model = context.root; const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element); const selectedElements = getElements(model.index, this.action.selectedElementsIDs, selectionGuard); - const deselectedElements = getElements(model.index, this.action.deselectedElementsIDs, selectionGuard); + const deselectedElements = this.action.deselectAll + ? this.selectionService.getSelectedElements() + : getElements(model.index, this.action.deselectedElementsIDs, selectionGuard); this.selectionService.updateSelection(model, pluck(selectedElements, 'id'), pluck(deselectedElements, 'id')); return model; diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index 791f857..99b9bcd 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -114,11 +114,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isNodeMarked(toAbsoluteBounds(e))).map(e => e.id); const edgeIdsSelected = this.edges.filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e)); const selected = nodeIdsSelected.concat(edgeIdsSelected); - return [ - SelectAction.create({ deselectedElementsIDs: Array.from(target.root.index.all().map(e => e.id)) }), - SelectAction.create({ selectedElementsIDs: selected.concat(this.previouslySelected) }), - this.marqueeUtil.drawMarqueeAction() - ]; + return [SelectAction.setSelection(selected.concat(this.previouslySelected)), this.marqueeUtil.drawMarqueeAction()]; } return []; } diff --git a/packages/protocol/src/action-protocol/element-selection.spec.ts b/packages/protocol/src/action-protocol/element-selection.spec.ts index 1e2e208..36ff7a6 100644 --- a/packages/protocol/src/action-protocol/element-selection.spec.ts +++ b/packages/protocol/src/action-protocol/element-selection.spec.ts @@ -45,7 +45,8 @@ describe('Element selection actions', () => { const expected: SelectAction = { kind: 'elementSelected', selectedElementsIDs: [], - deselectedElementsIDs: [] + deselectedElementsIDs: [], + deselectAll: false }; expect(SelectAction.create()).to.deep.equals(expected); }); @@ -53,11 +54,58 @@ describe('Element selection actions', () => { const expected: SelectAction = { kind: 'elementSelected', selectedElementsIDs: ['selected'], - deselectedElementsIDs: ['deselected'] + deselectedElementsIDs: ['deselected'], + deselectAll: false }; const { selectedElementsIDs, deselectedElementsIDs } = expected; expect(SelectAction.create({ deselectedElementsIDs, selectedElementsIDs })).to.deep.equals(expected); }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments: deselectAll', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: ['selected'], + deselectedElementsIDs: [], + deselectAll: true + }; + const { selectedElementsIDs } = expected; + expect(SelectAction.create({ deselectedElementsIDs: true, selectedElementsIDs })).to.deep.equals(expected); + }); + }); + describe('addSelection', () => { + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: ['selected'], + deselectedElementsIDs: [], + deselectAll: false + }; + const { selectedElementsIDs } = expected; + expect(SelectAction.addSelection(selectedElementsIDs)).to.deep.equals(expected); + }); + }); + describe('removeSelection', () => { + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: [], + deselectedElementsIDs: ['deselected'], + deselectAll: false + }; + const { deselectedElementsIDs } = expected; + expect(SelectAction.removeSelection(deselectedElementsIDs)).to.deep.equals(expected); + }); + }); + describe('setSelection', () => { + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: ['selected'], + deselectedElementsIDs: [], + deselectAll: true + }; + const { selectedElementsIDs } = expected; + expect(SelectAction.setSelection(selectedElementsIDs)).to.deep.equals(expected); + }); }); }); diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts index e64891b..8b5caf4 100644 --- a/packages/protocol/src/action-protocol/element-selection.ts +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; +import { isStringArray } from '../utils/array-util'; import { hasArrayProp, hasBooleanProp } from '../utils/type-util'; import { Action } from './base-protocol'; @@ -36,6 +37,11 @@ export interface SelectAction extends Action, sprotty.SelectAction { * The identifiers of the elements to mark as not selected. */ deselectedElementsIDs: string[]; + + /** + * Whether all currently selected elements should be deselected. + */ + deselectAll?: boolean; } export namespace SelectAction { @@ -45,14 +51,27 @@ export namespace SelectAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); } - export function create(options: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] } = {}): SelectAction { + export function create(options: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean } = {}): SelectAction { + const deselectedElementsIDs = options.deselectedElementsIDs ?? []; return { kind: KIND, - selectedElementsIDs: [], - deselectedElementsIDs: [], - ...options + selectedElementsIDs: options.selectedElementsIDs ?? [], + deselectedElementsIDs: isStringArray(deselectedElementsIDs, true) ? deselectedElementsIDs : [], + deselectAll: typeof deselectedElementsIDs === 'boolean' ? deselectedElementsIDs : false }; } + + export function addSelection(selectedElementsIDs: string[]): SelectAction { + return create({ selectedElementsIDs }); + } + + export function removeSelection(deselectedElementsIDs: string[]): SelectAction { + return create({ deselectedElementsIDs }); + } + + export function setSelection(selectedElementsIDs: string[]): SelectAction { + return create({ selectedElementsIDs, deselectedElementsIDs: true }); + } } /** From c0e42bb836b70be6be815f805be7660d8f3055d0 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 5 Jul 2023 11:11:28 +0200 Subject: [PATCH 223/566] Be more flexible when it comes to calculating the route of an edge (#255) - Allow tolerance when comparing coordinates to detect duplicates - Only apply duplicate detection on pure routing point kinds Fixes https://github.com/eclipse-glsp/glsp/issues/976 --- packages/client/src/utils/smodel-util.spec.ts | 222 ++++++++++++++++++ packages/client/src/utils/smodel-util.ts | 34 ++- 2 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 packages/client/src/utils/smodel-util.spec.ts diff --git a/packages/client/src/utils/smodel-util.spec.ts b/packages/client/src/utils/smodel-util.spec.ts new file mode 100644 index 0000000..58c862e --- /dev/null +++ b/packages/client/src/utils/smodel-util.spec.ts @@ -0,0 +1,222 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + AbstractEdgeRouter, + EdgeRouterRegistry, + LinearRouteOptions, + Point, + ResolvedHandleMove, + RoutedPoint, + SEdge, + SGraph, + SNode, + SRoutableElement, + SRoutingHandle, + routingModule +} from '@eclipse-glsp/client'; +import { expect } from 'chai'; +import { Container } from 'inversify'; +import { ALL_ROUTING_POINTS, ROUTE_KINDS, ROUTING_POINT_KINDS, calcRoute } from './smodel-util'; + +class TestRouter extends AbstractEdgeRouter { + kind = 'test-router'; + + route(edge: SRoutableElement): RoutedPoint[] { + const pureRoute = edge.routingPoints.map( + (point, idx) => + { + kind: 'linear', + ...point, + pointIndex: idx + } + ); + return [ + { kind: 'source', ...edge.source!.position }, + ...pureRoute, + { kind: 'bezier-control-after', x: 999, y: 999, pointIndex: pureRoute.length }, + { kind: 'target', ...edge.target!.position } + ]; + } + + createRoutingHandles(edge: SRoutableElement): void { + // do nothing + } + + protected getOptions(edge: SRoutableElement): LinearRouteOptions { + return { + minimalPointDistance: 0, + selfEdgeOffset: 0, + standardDistance: 0 + }; + } + + protected getInnerHandlePosition(edge: SRoutableElement, route: RoutedPoint[], handle: SRoutingHandle): Point | undefined { + return undefined; + } + + protected applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { + // do nothing + } +} + +describe('SModel Util', () => { + describe('calcRoute', () => { + const graph = new SGraph(); + + const source = new SNode(); + source.id = 'node0'; + source.position = { x: 0, y: 0 }; + source.size = { width: 0, height: 0 }; + graph.add(source); + + const target = new SNode(); + target.id = 'node1'; + target.position = { x: 100, y: 100 }; + target.size = { width: 0, height: 0 }; + graph.add(target); + + const edge = new SEdge(); + edge.id = 'edge0'; + edge.sourceId = 'node0'; + edge.targetId = 'node1'; + graph.add(edge); + + edge.routerKind = 'test-router'; + + const container = new Container(); + container.load(routingModule); + + const routerRegistry = container.get(EdgeRouterRegistry); + routerRegistry.register('test-router', new TestRouter()); + + it('should return complete route', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 100, y: 100 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + const route = calcRoute(edge, routerRegistry); + expect(route).to.deep.equal([ + { x: 0, y: 0, kind: 'source' }, + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 1 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 2 }, + { x: 999, y: 999, kind: 'bezier-control-after', pointIndex: 3 }, + { x: 100, y: 100, kind: 'target' } + ]); + }); + + it('should filter duplicates with same coordinates', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 100, y: 100 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 20, y: 20 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + + const route = calcRoute(edge, routerRegistry); + expect(route).to.deep.equal([ + { x: 0, y: 0, kind: 'source' }, + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 2 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 3 }, + { x: 999, y: 999, kind: 'bezier-control-after', pointIndex: 4 }, + { x: 100, y: 100, kind: 'target' } + ]); + }); + + it('should not filter source and target even if duplicate coordinates', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 0, y: 0 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + + const route = calcRoute(edge, routerRegistry); + expect(route).to.deep.equal([ + { x: 0, y: 0, kind: 'source' }, + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 1 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 2 }, + { x: 999, y: 999, kind: 'bezier-control-after', pointIndex: 3 }, + { x: 0, y: 0, kind: 'target' } + ]); + }); + + it('should filter duplicates with same coordinates but allow tolerance', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 100, y: 100 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 25, y: 25 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + + const route = calcRoute(edge, routerRegistry, ALL_ROUTING_POINTS, 10); + expect(route).to.deep.equal([ + { x: 0, y: 0, kind: 'source' }, + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 2 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 3 }, + { x: 999, y: 999, kind: 'bezier-control-after', pointIndex: 4 }, + { x: 100, y: 100, kind: 'target' } + ]); + }); + + it('should allow filtering based on point type: ROUTE_KINDS', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 100, y: 100 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + + const route = calcRoute(edge, routerRegistry, ROUTE_KINDS); + expect(route).to.deep.equal([ + { x: 0, y: 0, kind: 'source' }, + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 1 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 2 }, + { x: 100, y: 100, kind: 'target' } + ]); + }); + + it('should allow filtering based on point type: ROUTING_POINT_KINDS', () => { + edge.source!.position = { x: 0, y: 0 }; + edge.target!.position = { x: 100, y: 100 }; + edge.routingPoints = [ + { x: 20, y: 20 }, + { x: 30, y: 30 }, + { x: 40, y: 40 } + ]; + + const route = calcRoute(edge, routerRegistry, ROUTING_POINT_KINDS); + expect(route).to.deep.equal([ + { x: 20, y: 20, kind: 'linear', pointIndex: 0 }, + { x: 30, y: 30, kind: 'linear', pointIndex: 1 }, + { x: 40, y: 40, kind: 'linear', pointIndex: 2 } + ]); + }); + }); +}); diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index c8db633..5dbe29f 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -232,6 +232,8 @@ export function toElementAndRoutingPoints(element: SRoutableElement): ElementAnd newRoutingPoints: element.routingPoints }; } +/** All routing points. */ +export const ALL_ROUTING_POINTS = undefined; /** Pure routing point data kinds. */ export const ROUTING_POINT_KINDS = ['linear', 'bezier-junction']; @@ -272,18 +274,36 @@ export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: } /** - * Helper function to calculate the route for a given {@link SRoutableElement}. + * Helper function to calculate the route for a given {@link SRoutableElement} by filtering duplicate points. * @param element The element to translate. * @param routerRegistry the edge router registry. * @param pointKinds the routing point kinds that should be considered. + * @param tolerance the tolerance applied to a point's coordinates to determine duplicates. * @returns The corresponding route for the given element. */ -export function calcRoute(element: SRoutableElement, routerRegistry: EdgeRouterRegistry, pointKinds?: string[]): RoutedPoint[] | undefined { - return routerRegistry - ?.get(element.routerKind) - .route(element) - .filter((point, idx, fullRoute) => idx === fullRoute.findIndex(otherPoint => otherPoint.x === point.x && otherPoint.y === point.y)) - .filter(point => !pointKinds || pointKinds.includes(point.kind)); +export function calcRoute( + element: SRoutableElement, + routerRegistry: EdgeRouterRegistry, + pointKinds: string[] | undefined = ALL_ROUTING_POINTS, + tolerance = Number.EPSILON +): RoutedPoint[] | undefined { + const route = routerRegistry.get(element.routerKind).route(element); + const calculatedRoute: RoutedPoint[] = []; + for (const point of route) { + // only include points we are actually interested in + if (pointKinds && !pointKinds.includes(point.kind)) { + continue; + } + // check if we are a duplicate based on coordinates in the already calculated route + if ( + ROUTING_POINT_KINDS.includes(point.kind) && + calculatedRoute.find(calculatedPoint => Point.maxDistance(point, calculatedPoint) < tolerance) + ) { + continue; + } + calculatedRoute.push(point); + } + return calculatedRoute; } /** From a7342e5eaae27eeaf443dfce057bfb3e2c5df4e5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 6 Jul 2023 08:40:28 +0200 Subject: [PATCH 224/566] Fix release scripts (#1045) * Fix release scripts - Use correct output channel in isGithubCLIAuthenticated() helper function - Update client release script to use the correct workflow server package - Fix extraction of wrong version number for github releases * Minor fixes in title creation --------- Co-authored-by: Martin Fleck --- dev-packages/cli/src/commands/release/common.ts | 2 +- dev-packages/cli/src/commands/release/release-client.ts | 2 +- dev-packages/cli/src/commands/release/release.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index 08ee759..604befd 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -186,7 +186,7 @@ function doPublish(tag: string, preRelease: boolean, latestRelease: string, draf fatalExec(`git push origin HEAD:${tag}`, 'Could not push release branch to Github', getShellConfig({ silent: false })); fatalExec(`git push origin tag ${tag}`, 'Could not push tag to Github', getShellConfig({ silent: false })); const version = tagToVersion(tag); - const titleSuffix = preRelease ? ` Candiate ${version.substring(version.length - 1)}` : ''; + const titleSuffix = preRelease ? ` Candidate ${version.substring(version.lastIndexOf('-RC') + 3)}` : ''; const title = `${version.replace(/-.*/, '')} Release${titleSuffix} `; sh.exec( `gh release create ${tag} -t "${title}" --notes-start-tag ${latestRelease} --generate-notes ${draft ? '-d' : ''} ${ diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index c556b0c..5af918e 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -46,7 +46,7 @@ export async function releaseClient(options: ReleaseOptions): Promise { function updateExternalGLSPDependencies(version: string): void { LOGGER.info('Update external GLSP dependencies (workflow example server)'); sh.cd(REPO_ROOT); - updateVersion({ name: '@eclipse-glsp-examples/workflow-server', version }); + updateVersion({ name: '@eclipse-glsp-examples/workflow-server-bundled', version }); } function generateChangeLog(): void { diff --git a/dev-packages/cli/src/commands/release/release.ts b/dev-packages/cli/src/commands/release/release.ts index 192dab1..16df74a 100644 --- a/dev-packages/cli/src/commands/release/release.ts +++ b/dev-packages/cli/src/commands/release/release.ts @@ -134,7 +134,7 @@ function isGithubCLIAuthenticated(): boolean { } throw new Error(status.stderr); } - if (!status.stderr.trim().includes('Logged in to github.com')) { + if (!status.stdout.trim().includes('Logged in to github.com')) { LOGGER.debug("No user is logged in for host 'github.com'"); return false; } From 45da7f3b7fb19c69241877a9ed9a578ac77c8fd0 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 6 Jul 2023 08:52:04 +0200 Subject: [PATCH 225/566] GLSP-1048: Keep focus if it was taken deliberately through an action (#258) - Fix minor issue with default button not being present before init - Fix minor issue where accessing selection before root update --- packages/client/src/features/select/selection-service.ts | 2 +- .../client/src/features/tool-palette/tool-palette.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index 0d23cc7..1730a5f 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -132,7 +132,7 @@ export class SelectionService implements SModelRootListener { } getSelectedElements(): Readonly[] { - return getElements(this.root.index, Array.from(this.selectedElementIDs), isSelectable); + return !this.root ? [] : getElements(this.root.index, Array.from(this.selectedElementIDs), isSelectable); } /** diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 9926d77..f7f9a36 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -35,6 +35,7 @@ import { } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditModeListener, EditorContextService } from '../../base/editor-context-service'; +import { FocusTracker } from '../../base/focus-tracker'; import { MouseDeleteTool } from '../tools/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @@ -66,6 +67,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; @inject(TYPES.IToolManager) protected readonly toolManager: IToolManager; @inject(EditorContextService) protected readonly editorContext: EditorContextService; + @inject(FocusTracker) protected readonly focusTracker: FocusTracker; protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; @@ -311,7 +313,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, if (button) { button.classList.add(CLICKED_CSS_CLASS); this.lastActivebutton = button; - } else { + } else if (this.defaultToolsButton) { this.defaultToolsButton.classList.add(CLICKED_CSS_CLASS); this.lastActivebutton = this.defaultToolsButton; } @@ -335,7 +337,10 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, }); } else if (action.kind === EnableDefaultToolsAction.KIND) { this.changeActiveButton(); - this.restoreFocus(); + if (this.focusTracker.hasFocus) { + // if focus was deliberately taken do not restore focus to the palette + this.restoreFocus(); + } } } From 1442afe77aaf37a920bce1010a1f4b01420a5d5d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 6 Jul 2023 15:51:33 +0200 Subject: [PATCH 226/566] Refactor code base (#259) * Update to lerna 7 and fix test cases * Refactor base package - Move `FeedbackActionDispatcher` and related components into base package - Refactor `FeedbackAwareModelCommand`. Remove `ModelRootChangeListener` API from this command and add it to the command stack instead. This ensures that the model listeners are notified after the update has been processed succeessfully. It also makes it easier to rebind the `UpdateModelCommand` as you don't have to consider the listeners anymore. * Refactor SelectionService - Remove unnecessary Types.SelectionService binding and bind SelectionService directly instead - Move `SelectionService` into base package. A lot of feature modules are dependent on this service => they would no longer work if the selectModule is not bound or customized * Move container module out of base package The base package should have no direct dependencies to feature module to avoid circular dependency issues. * Update changelog * Remove unused import * Add listener registration methods to command stack * Refactor mouseTool The mouseTool is part of the default sprotty module and our customization should therefore be part of the `glspDefaultModule`. - Move mouse tool into base package - Rework `Rank` interface and utility function to our default interface+namespace pattern - Remove `TYPES.MouseTool` service identifert, and unecessary interface wrapper. Directly inject mousetool instead --- packages/client/src/base/command-stack.ts | 36 ++++++++++- packages/client/src/base/di.config.ts | 23 ++++--- .../client/src/base/editor-context-service.ts | 4 +- .../feedback/feeback-command.ts} | 0 .../feedback}/feedback-action-dispatcher.ts | 0 .../update-model-command.ts | 32 +--------- .../di.config.ts => base/ranked.ts} | 34 +++++++--- .../select => base}/selection-service.spec.ts | 8 +-- .../select => base}/selection-service.ts | 57 ++++++++++++----- .../mouse-tool => base/view}/mouse-tool.ts | 22 +++---- .../src/{base => }/container-modules.ts | 42 ++++++------ .../resize-key-tool/resize-key-tool.ts | 4 +- .../view-key-tools/movement-key-tool.ts | 4 +- .../view-key-tools/zoom-key-tool.ts | 6 +- .../bounds/layouter-test-util.spec.ts | 2 +- ...rvice-aware-context-menu-mouse-listener.ts | 4 +- .../client/src/features/hints/type-hints.ts | 4 +- .../layout/layout-elements-action.spec.ts | 4 +- .../features/layout/layout-elements-action.ts | 4 +- packages/client/src/features/rank/model.ts | 29 --------- .../client/src/features/select/di.config.ts | 6 +- ...k-action.ts => select-feedback-command.ts} | 40 +----------- .../features/select/select-mouse-listener.ts | 4 +- .../change-bounds-tool-feedback.ts | 2 +- .../tool-feedback/creation-tool-feedback.ts | 2 +- .../features/tool-feedback/css-feedback.ts | 2 +- .../src/features/tool-feedback/di.config.ts | 2 - .../tool-feedback/edge-edit-tool-feedback.ts | 2 +- .../tool-feedback/marquee-tool-feedback.ts | 2 +- .../src/features/tools/base-glsp-tool.ts | 7 +- .../src/features/tools/change-bounds-tool.ts | 4 +- .../client/src/features/tools/delete-tool.ts | 6 +- .../src/features/tools/edge-edit-tool.ts | 4 +- .../features/validation/marker-navigator.ts | 6 +- .../src/features/validation/validate.ts | 4 +- packages/client/src/glsp-sprotty/index.ts | 2 +- packages/client/src/glsp-sprotty/types.ts | 2 - packages/client/src/index.ts | 64 +++++++++---------- packages/client/src/utils/smodel-util.spec.ts | 6 +- packages/client/tsconfig.json | 1 - 40 files changed, 229 insertions(+), 258 deletions(-) rename packages/client/src/{features/tool-feedback/model.ts => base/feedback/feeback-command.ts} (100%) rename packages/client/src/{features/tool-feedback => base/feedback}/feedback-action-dispatcher.ts (100%) rename packages/client/src/base/{model => feedback}/update-model-command.ts (77%) rename packages/client/src/{features/mouse-tool/di.config.ts => base/ranked.ts} (51%) rename packages/client/src/{features/select => base}/selection-service.spec.ts (98%) rename packages/client/src/{features/select => base}/selection-service.ts (81%) rename packages/client/src/{features/mouse-tool => base/view}/mouse-tool.ts (86%) rename packages/client/src/{base => }/container-modules.ts (76%) delete mode 100644 packages/client/src/features/rank/model.ts rename packages/client/src/features/select/{select-feedback-action.ts => select-feedback-command.ts} (54%) diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index a6f5823..e068eea 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -13,11 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { CommandStack, SModelRoot } from '~glsp-sprotty'; +import { injectable, multiInject, optional } from 'inversify'; +import { CommandStack, ICommand, SModelRoot, SetModelCommand, TYPES, UpdateModelCommand, distinctAdd, remove } from '~glsp-sprotty'; + +/** + * A hook to listen for model root changes. Will be called after a server update + * has been processed + */ +export interface SModelRootListener { + modelRootChanged(root: Readonly): void; +} @injectable() export class GLSPCommandStack extends CommandStack { + @multiInject(TYPES.SModelRootListener) + @optional() + protected modelRootListeners: SModelRootListener[] = []; + override undo(): Promise { this.logger.warn( this, @@ -33,4 +45,24 @@ export class GLSPCommandStack extends CommandStack { ); return this.currentModel; } + + override execute(command: ICommand): Promise { + const result = super.execute(command); + if (command instanceof SetModelCommand || command instanceof UpdateModelCommand) { + result.then(root => this.notifyListeners(root)); + } + return result; + } + + protected notifyListeners(root: Readonly): void { + this.modelRootListeners.forEach(listener => listener.modelRootChanged(root)); + } + + register(listener: SModelRootListener): void { + distinctAdd(this.modelRootListeners, listener); + } + + deregister(listener: SModelRootListener): void { + remove(this.modelRootListeners, listener); + } } diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 695a1bf..dca6838 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -17,27 +17,30 @@ import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; import { ActionHandlerRegistry, - bindAsService, - bindOrRebind, - configureActionHandler, - configureCommand, InitializeResult, ModelSource, + MouseTool, SetEditModeAction, - SetModelCommand, - TYPES + TYPES, + bindAsService, + bindOrRebind, + configureActionHandler, + configureCommand } from '~glsp-sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; import { FocusStateChangedAction } from './actions/focus-change-action'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; +import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; +import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusTracker } from './focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; import { GLSPModelRegistry } from './model/model-registry'; -import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from './model/update-model-command'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; +import { SelectionService } from './selection-service'; import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; +import { RankingMouseTool } from './view/mouse-tool'; import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { @@ -60,8 +63,10 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); // Model update initialization ------------------------------------ + bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand(context, FeedbackAwareUpdateModelCommand); - configureActionHandler(context, SetModelCommand.KIND, SetModelActionHandler); + + rebind(MouseTool).to(RankingMouseTool).inSingletonScope(); bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); @@ -76,6 +81,8 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = // support re-registration of model elements and views bindOrRebind(context, TYPES.SModelRegistry).to(GLSPModelRegistry).inSingletonScope(); bindOrRebind(context, TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); + + bind(SelectionService).toSelf().inSingletonScope(); }); export default defaultGLSPModule; diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 284c462..0b0f6ec 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -29,7 +29,7 @@ import { distinctAdd, remove } from '~glsp-sprotty'; -import { SelectionService } from '../features/select/selection-service'; +import { SelectionService } from './selection-service'; import { isSourceUriAware } from './source-uri-aware'; export interface EditModeListener { @@ -50,7 +50,7 @@ export interface EditModeListener { */ @injectable() export class EditorContextService implements IActionHandler { - @inject(TYPES.SelectionService) + @inject(SelectionService) protected selectionService: SelectionService; @inject(MousePositionTracker) diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/base/feedback/feeback-command.ts similarity index 100% rename from packages/client/src/features/tool-feedback/model.ts rename to packages/client/src/base/feedback/feeback-command.ts diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts similarity index 100% rename from packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts rename to packages/client/src/base/feedback/feedback-action-dispatcher.ts diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts similarity index 77% rename from packages/client/src/base/model/update-model-command.ts rename to packages/client/src/base/feedback/update-model-command.ts index 473148d..7768fac 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -13,46 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from 'inversify'; +import { inject, injectable, optional } from 'inversify'; import { - Action, ActionHandlerRegistry, Animation, Command, CommandActionHandler, CommandExecutionContext, CommandReturn, - IActionHandler, ILogger, MorphEdgesAnimation, SModelRoot, - SetModelAction, TYPES, UpdateAnimationData, UpdateModelAction, UpdateModelCommand, toTypeGuard } from '~glsp-sprotty'; -import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; -import { FeedbackCommand } from '../../features/tool-feedback/model'; - -/** - * ActionHandler that transforms a {@link SetModelAction} into an {@link UpdateModelAction} that can be handled - * by the {@link FeedbackAwareUpdateModelCommand}. This can be done because in sprotty an {@link UpdateModelCommand} and - * a {@link SetModelCommand} have the same behavior of no model is present yet. - */ -@injectable() -export class SetModelActionHandler implements IActionHandler { - handle(action: Action): Action | void { - if (SetModelAction.is(action)) { - return UpdateModelAction.create(action.newRoot, { animate: false }); - } - } -} - -export interface SModelRootListener { - modelRootChanged(root: Readonly): void; -} +import { FeedbackCommand } from './feeback-command'; +import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; /** * A special {@link UpdateModelCommand} that retrieves all registered {@link Action}s from the {@link IFeedbackActionDispatcher} @@ -68,10 +47,6 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { @optional() protected feedbackActionDispatcher: IFeedbackActionDispatcher; - @multiInject(TYPES.SModelRootListener) - @optional() - protected modelRootListeners: SModelRootListener[] = []; - protected actionHandlerRegistry?: ActionHandlerRegistry; constructor( @@ -96,7 +71,6 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { feedbackCommands.forEach(command => command.execute(tempContext)); } - this.modelRootListeners.forEach(listener => listener.modelRootChanged(newRoot)); return super.performUpdate(oldRoot, newRoot, context); } diff --git a/packages/client/src/features/mouse-tool/di.config.ts b/packages/client/src/base/ranked.ts similarity index 51% rename from packages/client/src/features/mouse-tool/di.config.ts rename to packages/client/src/base/ranked.ts index cc69ce6..d70b3e9 100644 --- a/packages/client/src/features/mouse-tool/di.config.ts +++ b/packages/client/src/base/ranked.ts @@ -13,14 +13,30 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { MouseTool, TYPES } from '~glsp-sprotty'; -import { RankingMouseTool } from './mouse-tool'; -const glspMouseToolModule = new ContainerModule((bind, _unbind, _isBound, rebind) => { - bind(RankingMouseTool).toSelf().inSingletonScope(); - bind(TYPES.MouseTool).toService(RankingMouseTool); - rebind(MouseTool).toService(RankingMouseTool); -}); +import { AnyObject, hasNumberProp } from '~glsp-sprotty'; -export default glspMouseToolModule; +/** + * A common interface for services/listeners that should be + * orderable by a type or rank/priority. + */ +export interface Ranked { + rank: number; +} + +export namespace Ranked { + export const DEFAULT_RANK = 0; + export function is(object: unknown): object is Ranked { + return AnyObject.is(object) && hasNumberProp(object, 'rank'); + } + + /** + * Tries to retrieve the rank form the given object. If the object + * implements the {@link Ranked} interface the corresponding rank is returned + * otherwise the {@link DEFAULT_RANK} is returned. + * @param object + */ + export function getRank(object: unknown): number { + return is(object) ? object.rank : DEFAULT_RANK; + } +} diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts similarity index 98% rename from packages/client/src/features/select/selection-service.spec.ts rename to packages/client/src/base/selection-service.spec.ts index aa5b8b3..35f2fda 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -16,9 +16,8 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import { Action, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, defaultModule, initializeContainer } from '~glsp-sprotty'; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; -import { SelectFeedbackAction } from './select-feedback-action'; -import { SelectionListener, SelectionService } from './selection-service'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; +import { SelectFeedbackAction, SelectionListener, SelectionService } from './selection-service'; @injectable() class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { @@ -74,7 +73,6 @@ function createContainer(): Container { container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); container.bind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); container.bind(SelectionService).toSelf().inSingletonScope(); - container.bind(TYPES.SelectionService).toService(SelectionService); return container; } @@ -90,7 +88,7 @@ describe('SelectionService', () => { // eslint-disable-next-line deprecation/deprecation graphFactory = container.get(TYPES.IModelFactory); root = createRoot('node1', 'node2', 'node3', 'node4', 'node5'); - selectionService = container.get(TYPES.SelectionService); + selectionService = container.get(SelectionService); feedbackDispatcher = container.get(TYPES.IFeedbackActionDispatcher); }); diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/base/selection-service.ts similarity index 81% rename from packages/client/src/features/select/selection-service.ts rename to packages/client/src/base/selection-service.ts index 1730a5f..f83867a 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -29,14 +29,14 @@ import { SprottySelectCommand, TYPES, distinctAdd, + hasArrayProp, isSelectable, pluck, remove } from '~glsp-sprotty'; -import { SModelRootListener } from '../../base/model/update-model-command'; -import { getElements, getMatchingElements } from '../../utils/smodel-util'; -import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; -import { SelectFeedbackAction } from './select-feedback-action'; +import { getElements, getMatchingElements } from '../utils/smodel-util'; +import { SModelRootListener } from './command-stack'; +import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; export interface SelectionListener { selectionChanged(root: Readonly, selectedElements: string[]): void; @@ -44,11 +44,14 @@ export interface SelectionListener { @injectable() export class SelectionService implements SModelRootListener { - private root: Readonly; - private selectedElementIDs: Set = new Set(); + protected root: Readonly; + protected selectedElementIDs: Set = new Set(); - @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; - @inject(TYPES.ILogger) protected logger: ILogger; + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackDispatcher: IFeedbackActionDispatcher; + + @inject(TYPES.ILogger) + protected logger: ILogger; constructor(@multiInject(TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) {} @@ -168,10 +171,7 @@ export class SelectCommand extends Command { protected selected: SModelElement[] = []; protected deselected: SModelElement[] = []; - constructor( - @inject(TYPES.Action) public action: SelectAction, - @inject(TYPES.SelectionService) public selectionService: SelectionService - ) { + constructor(@inject(TYPES.Action) public action: SelectAction, @inject(SelectionService) public selectionService: SelectionService) { super(); } @@ -208,10 +208,7 @@ export class SelectAllCommand extends Command { static readonly KIND = SprottySelectAllCommand.KIND; protected previousSelection: Map = new Map(); - constructor( - @inject(TYPES.Action) public action: SelectAllAction, - @inject(TYPES.SelectionService) public selectionService: SelectionService - ) { + constructor(@inject(TYPES.Action) public action: SelectAllAction, @inject(SelectionService) public selectionService: SelectionService) { super(); } @@ -240,3 +237,31 @@ export class SelectAllCommand extends Command { return context.root; } } + +export interface SelectFeedbackAction extends Omit, Action { + kind: typeof SelectFeedbackAction.KIND; +} + +export namespace SelectFeedbackAction { + export const KIND = 'selectFeedback'; + + export function is(object: any): object is SelectFeedbackAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); + } + + export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean }): SelectFeedbackAction { + return { ...SelectAction.create(options), kind: KIND }; + } + + export function addSelection(selectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.addSelection(selectedElementsIDs), kind: KIND }; + } + + export function removeSelection(deselectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.removeSelection(deselectedElementsIDs), kind: KIND }; + } + + export function setSelection(selectedElementsIDs: string[]): SelectFeedbackAction { + return { ...SelectAction.setSelection(selectedElementsIDs), kind: KIND }; + } +} diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts similarity index 86% rename from packages/client/src/features/mouse-tool/mouse-tool.ts rename to packages/client/src/base/view/mouse-tool.ts index c1d5465..8f011cc 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -15,12 +15,7 @@ ********************************************************************************/ import { injectable, multiInject, optional } from 'inversify'; import { Action, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; -import { getRank } from '../rank/model'; - -export interface IMouseTool { - register(mouseListener: MouseListener): void; - deregister(mouseListener: MouseListener): void; -} +import { Ranked } from '../ranked'; /** * Custom helper type to declare the explicit mouse listener methods @@ -29,22 +24,22 @@ export interface IMouseTool { type MouseListenerMethods = keyof Omit; @injectable() -export class RankingMouseTool extends MouseTool implements IMouseTool { +export class RankingMouseTool extends MouseTool { protected rankedMouseListeners: Map; constructor(@multiInject(TYPES.MouseListener) @optional() protected override mouseListeners: MouseListener[] = []) { super(mouseListeners); - this.rankedMouseListeners = groupBy(mouseListeners, listener => getRank(listener)); + this.rankedMouseListeners = groupBy(mouseListeners, listener => Ranked.getRank(listener)); } override register(mouseListener: MouseListener): void { super.register(mouseListener); - this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); + this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener)); } override deregister(mouseListener: MouseListener): void { super.deregister(mouseListener); - this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); + this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener)); } protected override handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { @@ -53,13 +48,12 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { if (!element) { return; } - this.notifyListenersByRank(element, methodName, model, event); + this.notifyListenersByRank(element, methodName, event); } - async notifyListenersByRank( + protected async notifyListenersByRank( element: SModelElement, methodName: K, - model: SModelRoot, event: MouseEvent ): Promise { for (const rank of this.rankedMouseListeners) { @@ -67,7 +61,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { } } - async dispatchActions( + protected async dispatchActions( mouseListeners: MouseListener[], methodName: K, element: SModelElement, diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/container-modules.ts similarity index 76% rename from packages/client/src/base/container-modules.ts rename to packages/client/src/container-modules.ts index 43b4850..a79b15e 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/container-modules.ts @@ -30,27 +30,26 @@ import { routingModule, zorderModule } from '~glsp-sprotty'; -import glspBoundsModule from '../features/bounds/di.config'; -import glspCommandPaletteModule from '../features/command-palette/di.config'; -import glspContextMenuModule from '../features/context-menu/di.config'; -import { glspServerCopyPasteModule } from '../features/copy-paste/di.config'; -import glspDecorationModule from '../features/decoration/di.config'; -import glspEditLabelModule from '../features/edit-label/di.config'; -import glspExportModule from '../features/export/di.config'; -import modelHintsModule from '../features/hints/di.config'; -import glspHoverModule from '../features/hover/di.config'; -import layoutModule from '../features/layout/di.config'; -import glspMouseToolModule from '../features/mouse-tool/di.config'; -import navigationModule from '../features/navigation/di.config'; -import glspSelectModule from '../features/select/di.config'; -import sourceModelWatcherModule from '../features/source-model-watcher/di.config'; -import svgMetadataModule from '../features/svg-metadata/di.config'; -import toolFeedbackModule from '../features/tool-feedback/di.config'; -import toolPaletteModule from '../features/tool-palette/di.config'; -import { enableDefaultToolsOnFocusLossModule, toolsModule } from '../features/tools/di.config'; -import { markerNavigatorModule, validationModule } from '../features/validation/di.config'; -import glspViewportModule from '../features/viewport/di.config'; -import defaultGLSPModule from './di.config'; +import defaultGLSPModule from './base/di.config'; +import glspBoundsModule from './features/bounds/di.config'; +import glspCommandPaletteModule from './features/command-palette/di.config'; +import glspContextMenuModule from './features/context-menu/di.config'; +import { glspServerCopyPasteModule } from './features/copy-paste/di.config'; +import glspDecorationModule from './features/decoration/di.config'; +import glspEditLabelModule from './features/edit-label/di.config'; +import glspExportModule from './features/export/di.config'; +import modelHintsModule from './features/hints/di.config'; +import glspHoverModule from './features/hover/di.config'; +import layoutModule from './features/layout/di.config'; +import navigationModule from './features/navigation/di.config'; +import glspSelectModule from './features/select/di.config'; +import sourceModelWatcherModule from './features/source-model-watcher/di.config'; +import svgMetadataModule from './features/svg-metadata/di.config'; +import toolFeedbackModule from './features/tool-feedback/di.config'; +import toolPaletteModule from './features/tool-palette/di.config'; +import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; +import { markerNavigatorModule, validationModule } from './features/validation/di.config'; +import glspViewportModule from './features/viewport/di.config'; export const DEFAULT_MODULES = [ defaultModule, @@ -67,7 +66,6 @@ export const DEFAULT_MODULES = [ glspDecorationModule, glspEditLabelModule, glspHoverModule, - glspMouseToolModule, glspSelectModule, glspServerCopyPasteModule, glspViewportModule, diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index 66c5a1d..368383b 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -17,9 +17,9 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { SelectionService } from '../../../base/selection-service'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; -import { SelectionService } from '../../select/selection-service'; import { ResizeElementAction, ResizeType } from './resize-key-handler'; @injectable() @@ -31,7 +31,7 @@ export class ResizeKeyTool implements GLSPTool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(TYPES.SelectionService) readonly selectionService: SelectionService; + @inject(SelectionService) readonly selectionService: SelectionService; protected resizeKeyListener: ResizeKeyListener = new ResizeKeyListener(this); diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 0e1dc38..9326135 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -20,7 +20,7 @@ import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~g import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { GridSnapper } from '../../change-bounds/snap'; -import { SelectionService } from '../../select/selection-service'; +import { SelectionService } from '../../../base/selection-service'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; /** @@ -35,7 +35,7 @@ export class MovementKeyTool implements GLSPTool { protected readonly movementKeyListener = new MoveKeyListener(this); @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(TYPES.SelectionService) selectionService: SelectionService; + @inject(SelectionService) selectionService: SelectionService; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; get id(): string { diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index cf54e0d..d6a8ff5 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -16,9 +16,9 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, CenterAction, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { Action, CenterAction, KeyListener, KeyTool, SModelElement } from '~glsp-sprotty'; +import { SelectionService } from '../../../base/selection-service'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { SelectionService } from '../../select/selection-service'; import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; /** @@ -33,7 +33,7 @@ export class ZoomKeyTool implements GLSPTool { protected readonly zoomKeyListener = new ZoomKeyListener(this); @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(TYPES.SelectionService) selectionService: SelectionService; + @inject(SelectionService) selectionService: SelectionService; get id(): string { return ZoomKeyTool.ID; diff --git a/packages/client/src/features/bounds/layouter-test-util.spec.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts index 47a6791..87306b0 100644 --- a/packages/client/src/features/bounds/layouter-test-util.spec.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -31,7 +31,7 @@ import { createFeatureSet, layoutableChildFeature } from '~glsp-sprotty'; -import { initializeDiagramContainer } from '../../base/container-modules'; +import { initializeDiagramContainer } from '../../container-modules'; import { GLSPGraph } from '../../lib/model'; import { StatefulLayouterExt } from './layouter'; diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 14671a7..7abbe0a 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -16,7 +16,7 @@ import { inject, injectable, optional } from 'inversify'; import { Action, ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement, TYPES } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { SelectionService } from '../select/selection-service'; +import { SelectionService } from '../../base/selection-service'; @injectable() export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { @@ -28,7 +28,7 @@ export class SelectionServiceAwareContextMenuMouseListener extends MouseListener @optional() protected readonly menuProvider?: ContextMenuProviderRegistry; - @inject(TYPES.SelectionService) + @inject(SelectionService) protected selectionService: SelectionService; /** diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index ffd7f10..a4d523c 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -36,11 +36,11 @@ import { editFeature, moveFeature } from '~glsp-sprotty'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; -import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; -import { FeedbackCommand } from '../tool-feedback/model'; import { Containable, containerFeature, reparentFeature } from './model'; /** diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index 980de1c..b1b6d37 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -41,9 +41,9 @@ import { TYPES, defaultModule } from '~glsp-sprotty'; +import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { SelectionService } from '../../base/selection-service'; import { resizeFeature } from '../change-bounds/model'; -import { SelectionService } from '../select/selection-service'; -import { FeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { AlignElementsAction, AlignElementsActionHandler, diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 40e7a06..33841f2 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -31,11 +31,11 @@ import { hasNumberProp, hasStringProp } from '~glsp-sprotty'; +import { SelectionService } from '../../base/selection-service'; import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement, getElements } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; -import { SelectionService } from '../select/selection-service'; /** * Used to specify the desired resize dimension for a {@link ResizeElementsCommand}. @@ -161,7 +161,7 @@ export abstract class LayoutElementsActionHandler implements IActionHandler { @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - @inject(TYPES.SelectionService) + @inject(SelectionService) protected selectionService: SelectionService; @inject(TYPES.IMovementRestrictor) diff --git a/packages/client/src/features/rank/model.ts b/packages/client/src/features/rank/model.ts deleted file mode 100644 index 2718db3..0000000 --- a/packages/client/src/features/rank/model.ts +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -export const DEFAULT_RANK = 0; - -export interface Ranked { - rank: number; -} - -export function isRanked(arg: any): arg is Ranked { - return arg !== undefined && arg.rank !== undefined && typeof arg.rank === 'number'; -} - -export function getRank(arg: any): number { - return isRanked(arg) ? arg.rank : DEFAULT_RANK; -} diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts index 8862f62..068d16d 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/di.config.ts @@ -15,18 +15,16 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; -import { SelectFeedbackCommand } from './select-feedback-action'; +import { SelectAllCommand, SelectCommand } from '../../base/selection-service'; +import { SelectFeedbackCommand } from './select-feedback-command'; import { RankedSelectMouseListener } from './select-mouse-listener'; -import { SelectAllCommand, SelectCommand, SelectionService } from './selection-service'; const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; - bindAsService(context, TYPES.SelectionService, SelectionService); configureCommand(context, SelectCommand); configureCommand(context, SelectAllCommand); configureCommand(context, SelectFeedbackCommand); bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); - bind(TYPES.SModelRootListener).toService(SelectionService); }); export default glspSelectModule; diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-command.ts similarity index 54% rename from packages/client/src/features/select/select-feedback-action.ts rename to packages/client/src/features/select/select-feedback-command.ts index 027920e..64bd28c 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-command.ts @@ -14,44 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { - Action, - Command, - CommandExecutionContext, - SModelRoot, - SelectAction, - SprottySelectCommand, - TYPES, - hasArrayProp -} from '~glsp-sprotty'; - -export interface SelectFeedbackAction extends Omit, Action { - kind: typeof SelectFeedbackAction.KIND; -} - -export namespace SelectFeedbackAction { - export const KIND = 'selectFeedback'; - - export function is(object: any): object is SelectFeedbackAction { - return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); - } - - export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean }): SelectFeedbackAction { - return { ...SelectAction.create(options), kind: KIND }; - } - - export function addSelection(selectedElementsIDs: string[]): SelectFeedbackAction { - return { ...SelectAction.addSelection(selectedElementsIDs), kind: KIND }; - } - - export function removeSelection(deselectedElementsIDs: string[]): SelectFeedbackAction { - return { ...SelectAction.removeSelection(deselectedElementsIDs), kind: KIND }; - } - - export function setSelection(selectedElementsIDs: string[]): SelectFeedbackAction { - return { ...SelectAction.setSelection(selectedElementsIDs), kind: KIND }; - } -} +import { Command, CommandExecutionContext, SModelRoot, SelectAction, SprottySelectCommand, TYPES } from '~glsp-sprotty'; +import { SelectFeedbackAction } from '../../base/selection-service'; @injectable() export class SelectFeedbackCommand extends Command { diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 17b6ada..5b99814 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -29,14 +29,14 @@ import { isSelectable, toArray } from '~glsp-sprotty'; -import { DEFAULT_RANK, Ranked } from '../rank/model'; +import { Ranked } from '../../base/ranked'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. * This ensures that default mouse listeners are working on a model that has selection changes already applied. */ export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { - rank: number = DEFAULT_RANK - 1; /* we want to be executed before all default mouse listeners */ + rank: number = Ranked.DEFAULT_RANK - 1; /* we want to be executed before all default mouse listeners */ override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { const result: Action[] = []; diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 5ab953f..7d6181f 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -33,12 +33,12 @@ import { isSelectable, isViewport } from '~glsp-sprotty'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { forEachElement } from '../../utils/smodel-util'; import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { ChangeBoundsTool } from '../tools/change-bounds-tool'; -import { FeedbackCommand } from './model'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 3915921..12984d9 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -37,9 +37,9 @@ import { isBoundsAware, isConnectable } from '~glsp-sprotty'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { BoundsAwareModelElement, isRoutable } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; -import { FeedbackCommand } from './model'; export interface DrawFeedbackEdgeAction extends Action { kind: typeof DrawFeedbackEdgeAction.KIND; elementTypeId: string; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index 3983e3a..ae4f052 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES, hasArrayProp } from '~glsp-sprotty'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { addCssClasses, getElements, removeCssClasses } from '../../utils/smodel-util'; -import { FeedbackCommand } from './model'; export interface ModifyCSSFeedbackAction extends Action { kind: typeof ModifyCSSFeedbackAction.KIND; diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index ae0d8ce..3f36855 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -25,13 +25,11 @@ import { ShowEdgeReconnectHandlesFeedbackCommand, SwitchRoutingModeCommand } from './edge-edit-tool-feedback'; -import { FeedbackActionDispatcher } from './feedback-action-dispatcher'; import { DrawMarqueeCommand, RemoveMarqueeCommand } from './marquee-tool-feedback'; import { FeedbackEdgeEndView, SResizeHandleView } from './view'; const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; - bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand(context, ModifyCssFeedbackCommand); diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index ec91743..951ceac 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -41,12 +41,12 @@ import { isConnectable, isSelected } from '~glsp-sprotty'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { forEachElement, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { PointPositionUpdater } from '../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; import { FeedbackEdgeEnd, FeedbackEdgeEndMovingMouseListener, feedbackEdgeEndId, feedbackEdgeId } from './creation-tool-feedback'; -import { FeedbackCommand } from './model'; /** * RECONNECT HANDLES FEEDBACK diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index ebe2de7..62b944b 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -27,7 +27,7 @@ import { TYPES, hasObjectProp } from '~glsp-sprotty'; -import { FeedbackCommand } from './model'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; export interface DrawMarqueeAction extends Action { kind: typeof DrawMarqueeAction.KIND; diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 0bda02d..4b100d2 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -14,17 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, IActionDispatcher, KeyTool, TYPES } from '~glsp-sprotty'; +import { Action, IActionDispatcher, KeyTool, MouseTool, TYPES } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { IMouseTool } from '../mouse-tool/mouse-tool'; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; @injectable() export abstract class BaseGLSPTool implements GLSPTool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - @inject(TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(MouseTool) protected mouseTool: MouseTool; @inject(KeyTool) protected keyTool: KeyTool; @inject(EditorContextService) protected readonly editorContext: EditorContextService; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index f80f86e..94d59d1 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -40,6 +40,7 @@ import { isSelected } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { SelectionListener, SelectionService } from '../../base/selection-service'; import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { calcElementAndRoutingPoints, @@ -54,7 +55,6 @@ import { removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { PointPositionUpdater } from '../change-bounds/snap'; -import { SelectionListener, SelectionService } from '../select/selection-service'; import { FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, @@ -80,7 +80,7 @@ import { BaseGLSPTool } from './base-glsp-tool'; export class ChangeBoundsTool extends BaseGLSPTool { static ID = 'glsp.change-bounds-tool'; - @inject(TYPES.SelectionService) protected selectionService: SelectionService; + @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index eb12cad..82baed3 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -21,6 +21,7 @@ import { KeyListener, KeyTool, MouseListener, + MouseTool, SModelElement, TYPES, findParentByFeature, @@ -29,10 +30,9 @@ import { isSelectable, matchesKeystroke } from '~glsp-sprotty'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { IMouseTool } from '../mouse-tool/mouse-tool'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; -import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; /** * Deletes selected elements when hitting the `Del` key. @@ -89,7 +89,7 @@ export class MouseDeleteTool implements GLSPTool { protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); - @inject(TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(MouseTool) protected mouseTool: MouseTool; @inject(TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher; get id(): string { diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 0fb94fd..14959a7 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -33,9 +33,9 @@ import { isSelected } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { SelectionListener, SelectionService } from '../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../reconnect/model'; -import { SelectionListener, SelectionService } from '../select/selection-service'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../tool-feedback/creation-tool-feedback'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { @@ -53,7 +53,7 @@ import { BaseGLSPTool } from './base-glsp-tool'; export class EdgeEditTool extends BaseGLSPTool { static ID = 'glsp.edge-edit-tool'; - @inject(TYPES.SelectionService) protected selectionService: SelectionService; + @inject(SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index a39b95d..367fb90 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -38,9 +38,9 @@ import { isSelectable, matchesKeystroke } from '~glsp-sprotty'; +import { SelectionService } from '../../base/selection-service'; import { MarkerPredicates, collectIssueMarkers } from '../../utils/marker'; import { getElements, isSelectableAndBoundsAware } from '../../utils/smodel-util'; -import { SelectionService } from '../select/selection-service'; export interface NavigateToMarkerAction extends Action { kind: typeof NavigateToMarkerAction.KIND; @@ -164,7 +164,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { @inject(MarkerNavigator) protected markerNavigator: MarkerNavigator; - @inject(TYPES.SelectionService) + @inject(SelectionService) protected selectionService: SelectionService; @inject(TYPES.IActionDispatcher) @@ -206,7 +206,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { @injectable() export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemProvider { - @inject(TYPES.SelectionService) protected selectionService: SelectionService; + @inject(SelectionService) protected selectionService: SelectionService; getItems(root: Readonly, lastMousePosition?: Point): Promise { const selectedElementIds = Array.from(this.selectionService.getSelectedElementIDs()); diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index b8d4b9f..ac80a0a 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -30,9 +30,9 @@ import { hasArrayProp } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; +import { FeedbackCommand } from '../../base/feedback/feeback-command'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { removeCssClasses } from '../../utils/smodel-util'; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; -import { FeedbackCommand } from '../tool-feedback/model'; import { GIssueMarker, createSIssue, getOrCreateSIssueMarker, getSIssueMarker, getSeverity } from './issue-marker'; /** diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/client/src/glsp-sprotty/index.ts index 2a57d47..28602a5 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -134,7 +134,7 @@ export { IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; -export * from 'sprotty/lib/features/button/model'; +export { SButtonImpl as SButton, SButtonSchema } from 'sprotty/lib/features/button/model'; export * from 'sprotty/lib/features/command-palette/action-providers'; export * from 'sprotty/lib/features/command-palette/command-palette'; export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index a35974c..1593e1a 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -27,10 +27,8 @@ export const TYPES = { IToolFactory: Symbol('Factory'), ITypeHintProvider: Symbol('ITypeHintProvider'), IMovementRestrictor: Symbol('IMovementRestrictor'), - SelectionService: Symbol('SelectionService'), SelectionListener: Symbol('SelectionListener'), SModelRootListener: Symbol('SModelRootListener'), - MouseTool: Symbol('MouseTool'), IContextMenuProvider: Symbol('IContextMenuProvider'), ICopyPasteHandler: Symbol('ICopyPasteHandler'), ITool: Symbol('ITool'), diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3b67d8b..7773c8d 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -27,7 +27,6 @@ import glspEditLabelModule from './features/edit-label/di.config'; import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; import layoutModule from './features/layout/di.config'; -import glspMouseToolModule from './features/mouse-tool/di.config'; import navigationModule from './features/navigation/di.config'; import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; @@ -47,24 +46,31 @@ export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; export * from './base/command-stack'; -export * from './base/container-modules'; export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context-service'; +export * from './base/feedback/feeback-command'; +export * from './base/feedback/feedback-action-dispatcher'; +export * from './base/feedback/update-model-command'; export * from './base/focus-tracker'; export * from './base/model-initialization-constraint'; export * from './base/model/model-registry'; -export * from './base/model/update-model-command'; +export * from './base/ranked'; export * from './base/selection-clearing-mouse-listener'; +export * from './base/selection-service'; export * from './base/source-uri-aware'; export * from './base/tool-manager/glsp-tool-manager'; +export * from './base/view/mouse-tool'; export * from './base/view/view-registry'; +export * from './container-modules'; + +// +// ------------------ Features ------------------ +export * from './base/view/mouse-tool'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; -// -// ------------------ Features ------------------ export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; export * from './features/bounds/hbox-layout'; @@ -88,25 +94,20 @@ export * from './features/hints/model'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; export * from './features/layout/layout-elements-action'; -export * from './features/mouse-tool/mouse-tool'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; -export * from './features/rank/model'; export * from './features/reconnect/model'; export * from './features/save/model'; export * from './features/save/save-keylistener'; -export * from './features/select/select-feedback-action'; +export * from './features/select/select-feedback-command'; export * from './features/select/select-mouse-listener'; -export * from './features/select/selection-service'; export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/svg-metadata/metadata-placer'; export * from './features/tool-feedback/change-bounds-tool-feedback'; export * from './features/tool-feedback/creation-tool-feedback'; export * from './features/tool-feedback/css-feedback'; export * from './features/tool-feedback/edge-edit-tool-feedback'; -export * from './features/tool-feedback/feedback-action-dispatcher'; export * from './features/tool-feedback/marquee-tool-feedback'; -export * from './features/tool-feedback/model'; export * from './features/tool-feedback/view'; export * from './features/tool-palette/tool-palette'; export * from './features/tools/base-glsp-tool'; @@ -139,33 +140,32 @@ export * from './utils/viewpoint-util'; export * from './views'; // ------------------ DI Modules ------------------ export { - validationModule, - saveModule, - paletteModule, - toolFeedbackModule, + copyPasteContextMenuModule, defaultGLSPModule, - modelHintsModule, + enableDefaultToolsOnFocusLossModule, + glspAccessibilityModule, glspCommandPaletteModule, glspContextMenuModule, - glspServerCopyPasteModule, - copyPasteContextMenuModule, - glspSelectModule, - glspMouseToolModule, - layoutModule, + glspDecorationModule, glspEditLabelModule, glspHoverModule, - toolsModule, - enableDefaultToolsOnFocusLossModule, - navigationModule, - markerNavigatorModule, - glspDecorationModule, - sourceModelWatcherModule, - markerNavigatorContextMenuModule, - glspViewportModule, - svgMetadataModule, + glspMoveZoomModule, glspResizeKeyModule, glspSearchPaletteModule, - glspAccessibilityModule, + glspSelectModule, + glspServerCopyPasteModule, glspViewKeyToolsModule, - glspMoveZoomModule + glspViewportModule, + layoutModule, + markerNavigatorContextMenuModule, + markerNavigatorModule, + modelHintsModule, + navigationModule, + paletteModule, + saveModule, + sourceModelWatcherModule, + svgMetadataModule, + toolFeedbackModule, + toolsModule, + validationModule }; diff --git a/packages/client/src/utils/smodel-util.spec.ts b/packages/client/src/utils/smodel-util.spec.ts index 58c862e..8569c00 100644 --- a/packages/client/src/utils/smodel-util.spec.ts +++ b/packages/client/src/utils/smodel-util.spec.ts @@ -13,6 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; import { AbstractEdgeRouter, EdgeRouterRegistry, @@ -26,9 +28,7 @@ import { SRoutableElement, SRoutingHandle, routingModule -} from '@eclipse-glsp/client'; -import { expect } from 'chai'; -import { Container } from 'inversify'; +} from '~glsp-sprotty'; import { ALL_ROUTING_POINTS, ROUTE_KINDS, ROUTING_POINT_KINDS, calcRoute } from './smodel-util'; class TestRouter extends AbstractEdgeRouter { diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 4629484..dc8e6ac 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -9,6 +9,5 @@ "~glsp-sprotty": ["src/glsp-sprotty/index"] } }, - "exclude": ["**/*.spec.ts"], "include": ["src"] } From 34064e63c3fce8beffbebc1c5a27928a76ab5441 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 10 Jul 2023 13:39:16 +0200 Subject: [PATCH 227/566] GLSP-1050 Introduce Event API (#261) * GLSP-1050 Introduce Event API - Introduce/reuse Event API from `vscode-jsonrpc` - Rework notifier services to use new API - Remove manual registration methods from notifier services - Adapt test cases for `SelectionService` and remove test cases that were testing generic listener functionality that is already provided (and therefore assumed tested) by `vscode-jsonrpc` Also: - Align prefix of injectable interfaces to consistently use the `I` prefix Part of https://github.com/eclipse-glsp/glsp/issues/1050 * Address review feedback --- packages/client/src/base/command-stack.ts | 50 ++++-- packages/client/src/base/di.config.ts | 3 +- .../client/src/base/editor-context-service.ts | 37 +++-- .../client/src/base/selection-service.spec.ts | 151 +++++------------- packages/client/src/base/selection-service.ts | 58 +++++-- .../base/tool-manager/glsp-tool-manager.ts | 8 +- .../src/features/tool-palette/tool-palette.ts | 20 +-- .../src/features/tools/change-bounds-tool.ts | 18 ++- .../src/features/tools/edge-edit-tool.ts | 12 +- packages/client/src/glsp-sprotty/types.ts | 4 +- .../src/client-server-protocol/glsp-client.ts | 2 +- packages/protocol/src/index.ts | 3 +- .../{disposable => utils}/disposable.spec.ts | 0 .../src/{disposable => utils}/disposable.ts | 3 +- packages/protocol/src/utils/event.ts | 93 +++++++++++ packages/protocol/src/utils/type-util.ts | 6 + 16 files changed, 281 insertions(+), 187 deletions(-) rename packages/protocol/src/{disposable => utils}/disposable.spec.ts (100%) rename packages/protocol/src/{disposable => utils}/disposable.ts (96%) create mode 100644 packages/protocol/src/utils/event.ts diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index e068eea..d16c1b6 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -13,22 +13,50 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, multiInject, optional } from 'inversify'; -import { CommandStack, ICommand, SModelRoot, SetModelCommand, TYPES, UpdateModelCommand, distinctAdd, remove } from '~glsp-sprotty'; +import { injectable, multiInject, optional, preDestroy } from 'inversify'; +import { + CommandStack, + Disposable, + DisposableCollection, + Emitter, + Event, + ICommand, + SModelRoot, + SetModelCommand, + TYPES, + UpdateModelCommand +} from '~glsp-sprotty'; /** * A hook to listen for model root changes. Will be called after a server update * has been processed */ -export interface SModelRootListener { +export interface ISModelRootListener { modelRootChanged(root: Readonly): void; } @injectable() -export class GLSPCommandStack extends CommandStack { - @multiInject(TYPES.SModelRootListener) +export class GLSPCommandStack extends CommandStack implements Disposable { + @multiInject(TYPES.ISModelRootListener) @optional() - protected modelRootListeners: SModelRootListener[] = []; + protected modelRootListeners: ISModelRootListener[] = []; + protected toDispose = new DisposableCollection(); + + protected override initialize(): void { + super.initialize(); + this.toDispose.push(this.onModelRootChangedEmitter); + this.modelRootListeners.forEach(listener => this.onModelRootChanged(root => listener.modelRootChanged(root))); + } + + @preDestroy() + dispose(): void { + this.toDispose.dispose(); + } + + protected onModelRootChangedEmitter = new Emitter>(); + get onModelRootChanged(): Event> { + return this.onModelRootChangedEmitter.event; + } override undo(): Promise { this.logger.warn( @@ -55,14 +83,6 @@ export class GLSPCommandStack extends CommandStack { } protected notifyListeners(root: Readonly): void { - this.modelRootListeners.forEach(listener => listener.modelRootChanged(root)); - } - - register(listener: SModelRootListener): void { - distinctAdd(this.modelRootListeners, listener); - } - - deregister(listener: SModelRootListener): void { - remove(this.modelRootListeners, listener); + this.onModelRootChangedEmitter.fire(root); } } diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index dca6838..d6a1924 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -70,7 +70,7 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); - bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack); + bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPToolManager).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IToolManager).toService(GLSPToolManager); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); @@ -83,6 +83,7 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = bindOrRebind(context, TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); bind(SelectionService).toSelf().inSingletonScope(); + bind(TYPES.ISModelRootListener).toService(SelectionService); }); export default defaultGLSPModule; diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 0b0f6ec..f53626b 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -13,12 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from 'inversify'; +import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { Action, Args, + Disposable, + DisposableCollection, EditMode, EditorContext, + Emitter, + Event, IActionHandler, ModelSource, MousePositionTracker, @@ -26,13 +30,12 @@ import { SModelRoot, SetEditModeAction, TYPES, - distinctAdd, - remove + ValueChange } from '~glsp-sprotty'; import { SelectionService } from './selection-service'; import { isSourceUriAware } from './source-uri-aware'; -export interface EditModeListener { +export interface IEditModeListener { editModeChanged(newValue: string, oldValue: string): void; } @@ -49,7 +52,7 @@ export interface EditModeListener { * position, etc.). */ @injectable() -export class EditorContextService implements IActionHandler { +export class EditorContextService implements IActionHandler, Disposable { @inject(SelectionService) protected selectionService: SelectionService; @@ -58,19 +61,31 @@ export class EditorContextService implements IActionHandler { @multiInject(TYPES.IEditModeListener) @optional() - protected editModeListeners: EditModeListener[] = []; + protected editModeListeners: IEditModeListener[] = []; @inject(TYPES.ModelSourceProvider) protected modelSourceProvider: () => Promise; protected _editMode: string; - register(editModeListener: EditModeListener): void { - distinctAdd(this.editModeListeners, editModeListener); + protected onEditModeChangedEmitter = new Emitter>(); + get onEditModeChanged(): Event> { + return this.onEditModeChangedEmitter.event; } - deregister(editModeListener: EditModeListener): void { - remove(this.editModeListeners, editModeListener); + protected toDispose = new DisposableCollection(); + + @postConstruct() + protected initialize(): void { + this.toDispose.push(this.onEditModeChangedEmitter); + this.editModeListeners.forEach(listener => + this.onEditModeChanged(change => listener.editModeChanged(change.newValue, change.oldValue)) + ); + } + + @preDestroy() + dispose(): void { + this.toDispose.dispose(); } get(args?: Args): EditorContext { @@ -98,7 +113,7 @@ export class EditorContextService implements IActionHandler { } protected notifyEditModeListeners(oldValue: string): void { - this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); + this.onEditModeChangedEmitter.fire({ newValue: this.editMode, oldValue }); } async getSourceUri(): Promise { diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index 35f2fda..ba48f25 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -15,10 +15,10 @@ ********************************************************************************/ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; +import * as sinon from 'sinon'; import { Action, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, defaultModule, initializeContainer } from '~glsp-sprotty'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; -import { SelectFeedbackAction, SelectionListener, SelectionService } from './selection-service'; - +import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @injectable() class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { protected feedbackEmitters: Map = new Map(); @@ -43,27 +43,9 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { } } -class MockSelectionListener implements SelectionListener { - private called = 0; - private root: SModelRoot; - private selectedElements: string[] = []; - - selectionChanged(newRoot: Readonly, selectedElements: string[]): void { - this.called++; - this.root = newRoot; - this.selectedElements = selectedElements; - } - - getRoot(): SModelRoot { - return this.root; - } - - getSelectedElements(): string[] { - return this.selectedElements; - } - - getCalled(): number { - return this.called; +class MockSelectionListener implements ISelectionListener { + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements: string[] | undefined): void { + // no.op } } @@ -205,118 +187,56 @@ describe('SelectionService', () => { }); }); describe('Listeners', () => { + const sandbox = sinon.createSandbox(); + const listener = sandbox.createStubInstance(MockSelectionListener); + + beforeEach(() => { + selectionService.onSelectionChanged(change => + listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements) + ); + }); + + afterEach(() => { + selectionService.dispose(); + sandbox.reset(); + }); it('A registered listener should be notified of a single selection change.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, ['node1', 'node1'], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); }); it('A registered listener should be notified of a multi-selection change.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); - assertListener(listener, root, ['node1', 'node2', 'node3'], 1); + assertListener(listener, root, ['node1', 'node2', 'node3'], [], 1); }); it('A registered listener should be notified of series of selection changes.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); selectionService.updateSelection(root, ['node2'], ['node1']); - assertListener(listener, root, ['node2'], 2); + assertListener(listener, root, ['node2'], ['node1'], 2); selectionService.updateSelection(root, ['node3', 'node4'], []); - assertListener(listener, root, ['node2', 'node3', 'node4'], 3); + assertListener(listener, root, ['node2', 'node3', 'node4'], [], 3); selectionService.updateSelection(root, [], ['node4']); - assertListener(listener, root, ['node2', 'node3'], 4); - }); - it('A registered listener should receive the selected elements in the right order.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); - selectionService.updateSelection(root, ['node2', 'node1'], []); - expect(() => assertListener(listener, root, ['node1', 'node2'], 1)).to.throw(AssertionError, 'ordered members'); + assertListener(listener, root, ['node2', 'node3'], ['node4'], 4); }); it('A registered listener should be notified of root changes.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, [], []); - assertListener(listener, root, [], 1); + assertListener(listener, root, [], [], 1); const newRoot = createRoot('node1', 'newNode2', 'newNode3'); selectionService.updateSelection(newRoot, [], []); - assertListener(listener, newRoot, [], 2); - }); - it('Registering an already registered listener should have no effect.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - selectionService.register(listener); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); - selectionService.updateSelection(root, ['node1', 'node2', 'node3'], []); - assertListener(listener, root, ['node1', 'node2', 'node3'], 1); - selectionService.register(listener); - selectionService.updateSelection(root, [], ['node2']); - assertListener(listener, root, ['node1', 'node3'], 2); + assertListener(listener, newRoot, [], [], 2); }); it('Selecting the same elements consecutively should not trigger a listener update.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); }); it('Selecting a not-existing elements should not trigger a listener update.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); selectionService.updateSelection(root, ['not-existing'], []); - assertListener(listener, root, ['node1'], 1); - }); - it('All registered listeners should be called on selection changes.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - const listener2: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - selectionService.register(listener2); - assertListener(listener, undefined, [], 0); - assertListener(listener2, undefined, [], 0); - selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); - assertListener(listener2, root, ['node1'], 1); - selectionService.updateSelection(root, ['node2'], ['node1']); - assertListener(listener, root, ['node2'], 2); - assertListener(listener2, root, ['node2'], 2); - selectionService.updateSelection(root, ['node3', 'node4'], []); - assertListener(listener, root, ['node2', 'node3', 'node4'], 3); - assertListener(listener2, root, ['node2', 'node3', 'node4'], 3); - selectionService.updateSelection(root, [], ['node4']); - assertListener(listener, root, ['node2', 'node3'], 4); - assertListener(listener2, root, ['node2', 'node3'], 4); - }); - it('A deregistered listener should not receive further updates.', () => { - const listener: MockSelectionListener = new MockSelectionListener(); - selectionService.register(listener); - assertListener(listener, undefined, [], 0); - selectionService.updateSelection(root, ['node1'], []); - assertListener(listener, root, ['node1'], 1); - selectionService.deregister(listener); - selectionService.updateSelection(root, ['node2'], ['node1']); - assertListener(listener, root, ['node1'], 1); - selectionService.updateSelection(root, ['node3', 'node4'], []); - assertListener(listener, root, ['node1'], 1); - selectionService.updateSelection(root, [], ['node4']); - assertListener(listener, root, ['node1'], 1); - - const newRoot = createRoot('node1', 'newNode2', 'newNode3'); - selectionService.updateSelection(newRoot, [], []); - assertListener(listener, root, ['node1'], 1); + assertListener(listener, root, ['node1'], [], 1); }); }); @@ -361,14 +281,15 @@ describe('SelectionService', () => { } function assertListener( - listener: MockSelectionListener, + listener: sinon.SinonStubbedInstance, expectedRoot: SModelRoot | undefined, expectedSelection: string[], + expectedDeselection: string[], expectedCalled: number ): void { - expect(listener.getRoot()).to.equal(expectedRoot); - expect(listener.getSelectedElements()).to.have.lengthOf(expectedSelection.length); - expect(listener.getSelectedElements()).to.have.ordered.members(expectedSelection); - expect(listener.getCalled()).to.equal(expectedCalled); + expect(listener.selectionChanged.callCount).to.be.equal(expectedCalled); + expect(listener.selectionChanged.lastCall.args[0]).to.be.deep.equals(expectedRoot); + expect(listener.selectionChanged.lastCall.args[1]).to.be.deep.equals(expectedSelection); + expect(listener.selectionChanged.lastCall.args[2]).to.be.deep.equals(expectedDeselection); } }); diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index f83867a..fdfe06b 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -13,11 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from 'inversify'; +import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { Action, Command, CommandExecutionContext, + Disposable, + DisposableCollection, + Emitter, + Event, ILogger, SChildElement, SModelElement, @@ -28,22 +32,26 @@ import { SprottySelectAllCommand, SprottySelectCommand, TYPES, - distinctAdd, hasArrayProp, isSelectable, - pluck, - remove + pluck } from '~glsp-sprotty'; import { getElements, getMatchingElements } from '../utils/smodel-util'; -import { SModelRootListener } from './command-stack'; +import { ISModelRootListener } from './command-stack'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; -export interface SelectionListener { - selectionChanged(root: Readonly, selectedElements: string[]): void; +export interface ISelectionListener { + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; +} + +export interface SelectionChange { + root: Readonly; + selectedElements: string[]; + deselectedElements: string[]; } @injectable() -export class SelectionService implements SModelRootListener { +export class SelectionService implements ISModelRootListener, Disposable { protected root: Readonly; protected selectedElementIDs: Set = new Set(); @@ -53,14 +61,28 @@ export class SelectionService implements SModelRootListener { @inject(TYPES.ILogger) protected logger: ILogger; - constructor(@multiInject(TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) {} + @multiInject(TYPES.ISelectionListener) + @optional() + protected selectionListeners: ISelectionListener[] = []; + + protected toDispose = new DisposableCollection(); + + @postConstruct() + protected initialize(): void { + this.toDispose.push(this.onSelectionChangedEmitter); + this.selectionListeners.forEach(listener => + this.onSelectionChanged(change => listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements)) + ); + } - register(selectionListener: SelectionListener): void { - distinctAdd(this.selectionListeners, selectionListener); + @preDestroy() + dispose(): void { + this.toDispose.dispose(); } - deregister(selectionListener: SelectionListener): void { - remove(this.selectionListeners, selectionListener); + protected onSelectionChangedEmitter = new Emitter(); + get onSelectionChanged(): Event { + return this.onSelectionChangedEmitter.event; } modelRootChanged(root: Readonly): void { @@ -118,7 +140,7 @@ export class SelectionService implements SModelRootListener { const rootChanged = prevRoot !== root; if (rootChanged || selectionChanged) { // notify listeners after the feedback action - this.notifyListeners(this.root, this.selectedElementIDs); + this.notifyListeners(this.root, this.selectedElementIDs, deselectedElementIDs); } } @@ -126,8 +148,12 @@ export class SelectionService implements SModelRootListener { this.feedbackDispatcher.registerFeedback(this, actions); } - notifyListeners(root: SModelRoot, selectedElementIDs: Set): void { - this.selectionListeners.forEach(listener => listener.selectionChanged(root, Array.from(selectedElementIDs))); + notifyListeners(root: SModelRoot, selectedElementIDs: Set, deselectedElementIDs: Set): void { + this.onSelectionChangedEmitter.fire({ + root, + selectedElements: Array.from(selectedElementIDs), + deselectedElements: Array.from(deselectedElementIDs) + }); } getModelRoot(): Readonly { diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index bd79d7c..a44736b 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -15,10 +15,10 @@ ********************************************************************************/ import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { AnyObject, EditMode, TYPES, Tool, ToolManager, distinctAdd, hasBooleanProp, hasFunctionProp, hasStringProp } from '~glsp-sprotty'; -import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; +import { EditorContextService, EditorContextServiceProvider, IEditModeListener } from '../editor-context-service'; @injectable() -export class GLSPToolManager extends ToolManager implements EditModeListener { +export class GLSPToolManager extends ToolManager implements IEditModeListener { protected editorContext?: EditorContextService; @multiInject(TYPES.ITool) @optional() override tools: Tool[]; @@ -31,7 +31,7 @@ export class GLSPToolManager extends ToolManager implements EditModeListener { this.registerDefaultTools(...this.defaultTools); this.enableDefaultTools(); this.contextServiceProvider().then(editorContext => { - editorContext.register(this); + editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); this.editorContext = editorContext; }); } @@ -67,7 +67,7 @@ export class GLSPToolManager extends ToolManager implements EditModeListener { this.enable(this.defaultTools.filter(tool => !GLSPTool.is(tool) || tool.isEditTool === false).map(tool => tool.id)); } - editModeChanged(oldValue: string, newValue: string): void { + editModeChanged(newValue: string, oldValue: string): void { if (oldValue === newValue) { return; } diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index f7f9a36..d43d012 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -34,7 +34,7 @@ import { matchesKeystroke } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { EditModeListener, EditorContextService } from '../../base/editor-context-service'; +import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus-tracker'; import { MouseDeleteTool } from '../tools/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; @@ -61,7 +61,7 @@ export namespace EnableToolPaletteAction { } } @injectable() -export class ToolPalette extends AbstractUIExtension implements IActionHandler, EditModeListener { +export class ToolPalette extends AbstractUIExtension implements IActionHandler, IEditModeListener { static readonly ID = 'tool-palette'; @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; @@ -72,7 +72,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; protected bodyDiv?: HTMLElement; - protected lastActivebutton?: HTMLElement; + protected lastActiveButton?: HTMLElement; protected defaultToolsButton: HTMLElement; protected searchField: HTMLInputElement; modelRootId: string; @@ -86,7 +86,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, @postConstruct() postConstruct(): void { - this.editorContext.register(this); + this.editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); } override initialize(): boolean { @@ -99,7 +99,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected initializeContents(_containerElement: HTMLElement): void { this.createHeader(); this.createBody(); - this.lastActivebutton = this.defaultToolsButton; + this.lastActiveButton = this.defaultToolsButton; } protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { @@ -307,15 +307,15 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } changeActiveButton(button?: HTMLElement): void { - if (this.lastActivebutton) { - this.lastActivebutton.classList.remove(CLICKED_CSS_CLASS); + if (this.lastActiveButton) { + this.lastActiveButton.classList.remove(CLICKED_CSS_CLASS); } if (button) { button.classList.add(CLICKED_CSS_CLASS); - this.lastActivebutton = button; + this.lastActiveButton = button; } else if (this.defaultToolsButton) { this.defaultToolsButton.classList.add(CLICKED_CSS_CLASS); - this.lastActivebutton = this.defaultToolsButton; + this.lastActiveButton = this.defaultToolsButton; } } @@ -344,7 +344,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - editModeChanged(_oldValue: string, _newValue: string): void { + editModeChanged(_newValue: string, _oldValue: string): void { this.actionDispatcher.dispatch( SetUIExtensionVisibilityAction.create({ extensionId: ToolPalette.ID, visible: !this.editorContext.isReadonly }) ); diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 94d59d1..cd6e07d 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -22,6 +22,7 @@ import { ChangeRoutingPointsOperation, CompoundOperation, Dimension, + DisposableCollection, EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, @@ -40,7 +41,7 @@ import { isSelected } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { SelectionListener, SelectionService } from '../../base/selection-service'; +import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { calcElementAndRoutingPoints, @@ -85,7 +86,8 @@ export class ChangeBoundsTool extends BaseGLSPTool { @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; protected feedbackMoveMouseListener: MouseListener; - protected changeBoundsListener: MouseListener & SelectionListener; + protected changeBoundsListener: MouseListener & ISelectionListener; + protected toDispose = new DisposableCollection(); get id(): string { return ChangeBoundsTool.ID; @@ -99,27 +101,31 @@ export class ChangeBoundsTool extends BaseGLSPTool { // install change bounds listener for client-side resize updates and server-side updates this.changeBoundsListener = this.createChangeBoundsListener(); this.mouseTool.register(this.changeBoundsListener); - this.selectionService.register(this.changeBoundsListener); + this.toDispose.push( + this.selectionService.onSelectionChanged(change => + this.changeBoundsListener.selectionChanged(change.root, change.selectedElements) + ) + ); } protected createMoveMouseListener(): MouseListener { return new FeedbackMoveMouseListener(this); } - protected createChangeBoundsListener(): MouseListener & SelectionListener { + protected createChangeBoundsListener(): MouseListener & ISelectionListener { return new ChangeBoundsListener(this); } disable(): void { this.mouseTool.deregister(this.changeBoundsListener); - this.selectionService.deregister(this.changeBoundsListener); this.mouseTool.deregister(this.feedbackMoveMouseListener); this.deregisterFeedback([], this.feedbackMoveMouseListener); this.deregisterFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this.changeBoundsListener); + this.toDispose.dispose(); } } -export class ChangeBoundsListener extends DragAwareMouseListener implements SelectionListener { +export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener { static readonly CSS_CLASS_ACTIVE = 'active'; // members for calculating the correct position change diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 14959a7..f66e67e 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -19,6 +19,7 @@ import { AnchorComputerRegistry, ChangeRoutingPointsOperation, Connectable, + DisposableCollection, EdgeRouterRegistry, ISnapper, ReconnectEdgeOperation, @@ -33,7 +34,7 @@ import { isSelected } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { SelectionListener, SelectionService } from '../../base/selection-service'; +import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../reconnect/model'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../tool-feedback/creation-tool-feedback'; @@ -62,6 +63,7 @@ export class EdgeEditTool extends BaseGLSPTool { protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; protected feedbackMovingListener: FeedbackEdgeRouteMovingMouseListener; protected edgeEditListener: EdgeEditListener; + protected toDispose = new DisposableCollection(); get id(): string { return EdgeEditTool.ID; @@ -70,7 +72,9 @@ export class EdgeEditTool extends BaseGLSPTool { enable(): void { this.edgeEditListener = new EdgeEditListener(this); this.mouseTool.register(this.edgeEditListener); - this.selectionService.register(this.edgeEditListener); + this.toDispose.push( + this.selectionService.onSelectionChanged(change => this.edgeEditListener.selectionChanged(change.root, change.selectedElements)) + ); // install feedback move mouse listener for client-side move updates this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry); @@ -92,13 +96,13 @@ export class EdgeEditTool extends BaseGLSPTool { disable(): void { this.edgeEditListener.reset(); - this.selectionService.deregister(this.edgeEditListener); this.deregisterFeedbackListeners(); this.mouseTool.deregister(this.edgeEditListener); + this.toDispose.dispose(); } } -class EdgeEditListener extends DragAwareMouseListener implements SelectionListener { +class EdgeEditListener extends DragAwareMouseListener implements ISelectionListener { // active selection data protected edge?: SRoutableElement; protected routingHandle?: SRoutingHandle; diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index 1593e1a..4de45f4 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -27,8 +27,8 @@ export const TYPES = { IToolFactory: Symbol('Factory'), ITypeHintProvider: Symbol('ITypeHintProvider'), IMovementRestrictor: Symbol('IMovementRestrictor'), - SelectionListener: Symbol('SelectionListener'), - SModelRootListener: Symbol('SModelRootListener'), + ISelectionListener: Symbol('ISelectionListener'), + ISModelRootListener: Symbol('ISModelRootListener'), IContextMenuProvider: Symbol('IContextMenuProvider'), ICopyPasteHandler: Symbol('ICopyPasteHandler'), ITool: Symbol('ITool'), diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 86d062d..f4a7ee2 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -16,7 +16,7 @@ import * as uuid from 'uuid'; import { ActionMessage } from '../action-protocol'; -import { Disposable } from '../disposable/disposable'; +import { Disposable } from '../utils/disposable'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export class ApplicationIdProvider { diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e2e45d7..09b4b12 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -55,10 +55,11 @@ export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; export * from './client-server-protocol/jsonrpc/websocket-connection'; export * from './client-server-protocol/types'; -export * from './disposable/disposable'; export * from './model/default-types'; export * from './model/model-schema'; export * from './utils/array-util'; export * from './utils/di-util'; +export * from './utils/disposable'; +export * from './utils/event'; export * from './utils/type-util'; export { SetBoundsAction, SetViewportAction }; diff --git a/packages/protocol/src/disposable/disposable.spec.ts b/packages/protocol/src/utils/disposable.spec.ts similarity index 100% rename from packages/protocol/src/disposable/disposable.spec.ts rename to packages/protocol/src/utils/disposable.spec.ts diff --git a/packages/protocol/src/disposable/disposable.ts b/packages/protocol/src/utils/disposable.ts similarity index 96% rename from packages/protocol/src/disposable/disposable.ts rename to packages/protocol/src/utils/disposable.ts index 077fcf3..07b8dc8 100644 --- a/packages/protocol/src/disposable/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -13,13 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as jsonrpc from 'vscode-jsonrpc'; import { remove } from '../utils/array-util'; import { AnyObject, hasFunctionProp } from '../utils/type-util'; /** * Interface for objects that can or need to be disposed properly. */ -export interface Disposable { +export interface Disposable extends jsonrpc.Disposable { /** * Dispose this object. */ diff --git a/packages/protocol/src/utils/event.ts b/packages/protocol/src/utils/event.ts new file mode 100644 index 0000000..03dd201 --- /dev/null +++ b/packages/protocol/src/utils/event.ts @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as jsonrpc from 'vscode-jsonrpc'; +import { Disposable } from './disposable'; +import { SafeFunction } from './type-util'; + +/** + * GLSP event/emitter API. Reuses the API provided by + * `vscode-jsonrpc` and overrides certain type definitions to match the type + * definitions provided by GLSP (Event, Disposable) + */ + +/** + * Represents a typed event. Listener functions can subscribe for a event + * and will be called when the event is fired. Typically events are provided by services via + * public property. For example assume we have a service `myService` that exposes a + * `onStartupComplete` event. Then a listener could be registered like this + * ```ts + * const disposable= this.myService.onStartupComplete(event=> doSomething(event)); + * + * // If the listener should be unregistered again, simply call + * disposable.dispose() + * ``` + */ +export interface Event extends jsonrpc.Event { + /** + * + * @param listener The listener function will be called when the event happens. + * @param thisArgs The 'this' which will be used when calling the event listener. + * @param disposables An array to which the {@link Disposable} for removing the listener will be added. + * @returns a {@link Disposable} to remove the listener again. + */ + (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable; +} + +/** + * Optional options that can be passed to the constructor + * of an {@link Emitter}. + */ +export interface EmitterOptions { + /** + * Callback function that will be invoked after the first + * listener has been registered for the emitter + */ + onFirstListenerAdd?: SafeFunction; + /** + * Callback function that will be invoked after the last + * listener has been removed from the emitter + */ + onLastListenerRemove?: SafeFunction; +} + +export class Emitter extends jsonrpc.Emitter implements Disposable { + constructor(options: EmitterOptions = {}) { + super(options); + } + + /** + * The even that is managed by this emitter. + * Intended for the public to allow to subscribe to the emitter`s events. + */ + override get event(): Event { + return super.event; + } + + /** + * Fires and event and notifies all registered listeners + */ + override fire(event: T): void { + super.fire(event); + } +} + +/** + * Common interface for events that are fired on a single value change + */ +export interface ValueChange { + oldValue: T; + newValue: T; +} diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 8febd82..ca24947 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -68,6 +68,12 @@ export function toTypeGuard(constructor: Constructor): TypeGuard { return (element: unknown): element is G => element instanceof constructor; } +/** + * Utility type that represents an arbitrary function. Should be used instead + * of the default `Function` type which is considered to be unsafe. + */ +export type SafeFunction = (...args: any[]) => T; + /** * Validates whether the given object has a property of type `string` with the given key. * @param object The object that should be validated From 78bcf112d8cc9e8f4ac1444bd97383898f73c7aa Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 12 Jul 2023 09:04:40 +0200 Subject: [PATCH 228/566] Use feedback dispatcher for change bounds feedback (#262) Fixes https://github.com/eclipse-glsp/glsp/issues/941 --- .../change-bounds-tool-feedback.ts | 53 ++++++++++++++----- .../src/features/tools/change-bounds-tool.ts | 27 ++++++++-- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index 7d6181f..b7e4798 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -19,6 +19,7 @@ import { Action, CommandExecutionContext, CommandReturn, + Disposable, ElementMove, MouseListener, MoveAction, @@ -118,9 +119,10 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { * the visual feedback but also the basis for sending the change to the server * (see also `tools/MoveTool`). */ -export class FeedbackMoveMouseListener extends MouseListener { +export class FeedbackMoveMouseListener extends MouseListener implements Disposable { protected hasDragged = false; - protected startDragPosition: Point | undefined; + protected rootElement?: SModelRoot; + protected startDragPosition?: Point; protected elementId2startPos = new Map(); constructor(protected tool: ChangeBoundsTool) { @@ -155,10 +157,12 @@ export class FeedbackMoveMouseListener extends MouseListener { result.push(cursorFeedbackAction(CursorCSS.MOVE)); } } - return result; + this.tool.dispatchFeedback(result, this); + return []; } protected collectStartPositions(root: SModelRoot): void { + this.rootElement = root; const selectedElements = root.index.all().filter(element => isSelectable(element) && element.selected); const elementsSet = new Set(selectedElements); selectedElements @@ -184,13 +188,29 @@ export class FeedbackMoveMouseListener extends MouseListener { if (!this.startDragPosition) { return undefined; } - const elementMoves: ElementMove[] = []; + const viewport = findParentByFeature(target, isViewport); const zoom = viewport ? viewport.zoom : 1; const delta = { x: (event.pageX - this.startDragPosition.x) / zoom, y: (event.pageY - this.startDragPosition.y) / zoom }; + + const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, !event.shiftKey, finished); + if (elementMoves.length > 0) { + return MoveAction.create(elementMoves, { animate: false, finished }); + } else { + return undefined; + } + } + + protected getElementMovesForDelta( + target: SModelElement, + delta: { x: number; y: number }, + isSnap: boolean, + finished: boolean + ): ElementMove[] { + const elementMoves: ElementMove[] = []; this.elementId2startPos.forEach((startPosition, elementId) => { const element = target.root.index.getById(elementId); if (element) { @@ -200,7 +220,7 @@ export class FeedbackMoveMouseListener extends MouseListener { y: startPosition.y + delta.y }, element, - !event.shiftKey + isSnap ); if (isMoveable(element)) { @@ -216,11 +236,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } } }); - if (elementMoves.length > 0) { - return MoveAction.create(elementMoves, { animate: false, finished }); - } else { - return undefined; - } + return elementMoves; } protected validateMove(startPosition: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { @@ -237,7 +253,6 @@ export class FeedbackMoveMouseListener extends MouseListener { } else { action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); } - this.tool.dispatchFeedback([action], this); } return newPosition; @@ -270,13 +285,27 @@ export class FeedbackMoveMouseListener extends MouseListener { } result.push(cursorFeedbackAction(CursorCSS.DEFAULT)); } + this.reset(); + return result; + } + + protected reset(resetFeedback = false): void { + if (this.rootElement && resetFeedback) { + const elementMoves: ElementMove[] = this.getElementMovesForDelta(this.rootElement, { x: 0, y: 0 }, true, true); + const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); + this.tool.deregisterFeedback([moveAction], this); + } this.hasDragged = false; this.startDragPosition = undefined; + this.rootElement = undefined; this.elementId2startPos.clear(); - return result; } override decorate(vnode: VNode, _element: SModelElement): VNode { return vnode; } + + dispose(): void { + this.reset(true); + } } diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index cd6e07d..6601138 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -22,6 +22,7 @@ import { ChangeRoutingPointsOperation, CompoundOperation, Dimension, + Disposable, DisposableCollection, EdgeRouterRegistry, ElementAndBounds, @@ -117,6 +118,12 @@ export class ChangeBoundsTool extends BaseGLSPTool { } disable(): void { + if (Disposable.is(this.feedbackMoveMouseListener)) { + this.feedbackMoveMouseListener.dispose(); + } + if (Disposable.is(this.changeBoundsListener)) { + this.changeBoundsListener.dispose(); + } this.mouseTool.deregister(this.changeBoundsListener); this.mouseTool.deregister(this.feedbackMoveMouseListener); this.deregisterFeedback([], this.feedbackMoveMouseListener); @@ -125,7 +132,7 @@ export class ChangeBoundsTool extends BaseGLSPTool { } } -export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener { +export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener, Disposable { static readonly CSS_CLASS_ACTIVE = 'active'; // members for calculating the correct position change @@ -176,7 +183,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel const resizeActions = this.handleResizeOnClient(positionUpdate); actions.push(...resizeActions); } - return actions; + this.tool.dispatchFeedback(actions, this); } return []; } @@ -319,9 +326,23 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } } + dispose(): void { + this.reset(); + } + protected reset(): void { if (this.activeResizeElement && isResizable(this.activeResizeElement)) { - this.tool.dispatchFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this); + if (this.initialBounds) { + const resetResizeAction = SetBoundsAction.create([ + { + elementId: this.activeResizeElement.id, + newPosition: this.initialBounds, + newSize: this.initialBounds + } + ]); + this.tool.deregisterFeedback([resetResizeAction], this); + } + this.tool.deregisterFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this); } this.tool.dispatchActions([cursorFeedbackAction(CursorCSS.DEFAULT)]); this.resetPosition(); From 0082546083f621f0ed3ba59320043b4522532a44 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 13 Jul 2023 14:21:18 +0200 Subject: [PATCH 229/566] GLSP-102: Move tools and their feedback into dedicated modules (#264) - Restructure code to separate tools module into dedicated modules - Fully replace sprotty routingModule - Add custom methods to mouse and key tool to automate de-registration - Return disposable from feedback dispatcher to return de-registration - Extract parent class for creation tools - Use disposable collections for cleanup in parent classes where useful - Add change log entries --- packages/client/src/base/action-dispatcher.ts | 4 +- packages/client/src/base/di.config.ts | 21 +++- .../src/base/drag-aware-mouse-listener.ts | 4 +- .../feedback}/css-feedback.ts | 2 +- .../feedback/feedback-action-dispatcher.ts | 54 +++++---- ...feeback-command.ts => feedback-command.ts} | 0 .../src/base/feedback/update-model-command.ts | 2 +- .../client/src/base/selection-service.spec.ts | 17 ++- packages/client/src/base/view/key-tool.ts | 25 ++++ packages/client/src/base/view/mouse-tool.ts | 9 +- packages/client/src/container-modules.ts | 21 +++- .../accessibility/search/search-palette.ts | 2 +- .../change-bounds/movement-restrictor.ts | 2 +- .../features/edit-label/edit-label-tool.ts | 16 +-- .../client/src/features/hints/type-hints.ts | 2 +- packages/client/src/features/hover/hover.ts | 3 +- .../client/src/features/layout/di.config.ts | 2 +- .../layout/layout-elements-action.spec.ts | 2 - .../client/src/features/routing/di.config.ts | 82 +++++++++++++ .../glsp-manhattan-edge-router.ts | 0 .../src/features/tool-feedback/di.config.ts | 63 ---------- .../src/features/tool-palette/tool-palette.ts | 4 +- .../features/tools/base-glsp-creation-tool.ts | 40 +++++++ .../src/features/tools/base-glsp-tool.ts | 39 +++++-- .../change-bounds-tool-feedback.ts | 24 ++-- .../{ => change-bounds}/change-bounds-tool.ts | 108 +++++++++--------- .../features/tools/change-bounds/di.config.ts | 31 +++++ .../change-bounds}/view.tsx | 16 +-- .../tools/{ => deletion}/delete-tool.ts | 26 ++--- .../src/features/tools/deletion/di.config.ts | 26 +++++ .../client/src/features/tools/di.config.ts | 77 ------------- .../edge-creation/dangling-edge-feedback.ts} | 106 +++-------------- .../features/tools/edge-creation/di.config.ts | 28 +++++ .../edge-creation-tool-feedback.ts | 89 +++++++++++++++ .../{ => edge-creation}/edge-creation-tool.ts | 69 +++++------ .../src/features/tools/edge-creation/view.tsx | 33 ++++++ .../src/features/tools/edge-edit/di.config.ts | 41 +++++++ .../edge-edit}/edge-edit-tool-feedback.ts | 42 ++++--- .../tools/{ => edge-edit}/edge-edit-tool.ts | 58 +++++----- .../enable-default-tools-on-focus-loss.ts | 13 ++- .../tools/marquee-selection/di.config.ts | 35 ++++++ .../marquee-behavior.spec.ts | 0 .../marquee-behavior.ts | 18 +-- .../marquee-mouse-tool.ts | 28 ++--- .../marquee-tool-feedback.ts | 2 +- .../{ => marquee-selection}/marquee-tool.ts | 16 +-- .../tools/{ => marquee-selection}/model.ts | 0 .../tools/{ => marquee-selection}/view.tsx | 0 .../features/tools/node-creation/di.config.ts | 26 +++++ .../{ => node-creation}/node-creation-tool.ts | 45 +++----- .../features/validation/marker-navigator.ts | 4 +- .../src/features/validation/validate.ts | 2 +- .../viewport/glsp-scroll-mouse-listener.ts | 2 +- packages/client/src/index.ts | 54 +++++---- packages/client/src/utils/viewpoint-util.ts | 32 ++++++ 55 files changed, 890 insertions(+), 577 deletions(-) rename packages/client/src/{features/tool-feedback => base/feedback}/css-feedback.ts (98%) rename packages/client/src/base/feedback/{feeback-command.ts => feedback-command.ts} (100%) create mode 100644 packages/client/src/base/view/key-tool.ts create mode 100644 packages/client/src/features/routing/di.config.ts rename packages/client/src/features/{tools => routing}/glsp-manhattan-edge-router.ts (100%) delete mode 100644 packages/client/src/features/tool-feedback/di.config.ts create mode 100644 packages/client/src/features/tools/base-glsp-creation-tool.ts rename packages/client/src/features/{tool-feedback => tools/change-bounds}/change-bounds-tool-feedback.ts (92%) rename packages/client/src/features/tools/{ => change-bounds}/change-bounds-tool.ts (85%) create mode 100644 packages/client/src/features/tools/change-bounds/di.config.ts rename packages/client/src/features/{tool-feedback => tools/change-bounds}/view.tsx (80%) rename packages/client/src/features/tools/{ => deletion}/delete-tool.ts (79%) create mode 100644 packages/client/src/features/tools/deletion/di.config.ts delete mode 100644 packages/client/src/features/tools/di.config.ts rename packages/client/src/features/{tool-feedback/creation-tool-feedback.ts => tools/edge-creation/dangling-edge-feedback.ts} (55%) create mode 100644 packages/client/src/features/tools/edge-creation/di.config.ts create mode 100644 packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts rename packages/client/src/features/tools/{ => edge-creation}/edge-creation-tool.ts (73%) create mode 100644 packages/client/src/features/tools/edge-creation/view.tsx create mode 100644 packages/client/src/features/tools/edge-edit/di.config.ts rename packages/client/src/features/{tool-feedback => tools/edge-edit}/edge-edit-tool-feedback.ts (89%) rename packages/client/src/features/tools/{ => edge-edit}/edge-edit-tool.ts (89%) create mode 100644 packages/client/src/features/tools/marquee-selection/di.config.ts rename packages/client/src/features/tools/{ => marquee-selection}/marquee-behavior.spec.ts (100%) rename packages/client/src/features/tools/{ => marquee-selection}/marquee-behavior.ts (89%) rename packages/client/src/features/tools/{ => marquee-selection}/marquee-mouse-tool.ts (82%) rename packages/client/src/features/{tool-feedback => tools/marquee-selection}/marquee-tool-feedback.ts (98%) rename packages/client/src/features/tools/{ => marquee-selection}/marquee-tool.ts (74%) rename packages/client/src/features/tools/{ => marquee-selection}/model.ts (100%) rename packages/client/src/features/tools/{ => marquee-selection}/view.tsx (100%) create mode 100644 packages/client/src/features/tools/node-creation/di.config.ts rename packages/client/src/features/tools/{ => node-creation}/node-creation-tool.ts (69%) diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 0417eaf..35ebee9 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,13 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject } from 'inversify'; +import { inject, injectable } from 'inversify'; import { Action, ActionDispatcher, RequestAction, ResponseAction } from '~glsp-sprotty'; import { ModelInitializationConstraint } from './model-initialization-constraint'; +@injectable() export class GLSPActionDispatcher extends ActionDispatcher { protected readonly timeouts: Map = new Map(); protected initializedConstraint = false; + @inject(ModelInitializationConstraint) protected initializationConstraint: ModelInitializationConstraint; override initialize(): Promise { diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index d6a1924..2f6859c 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -18,8 +18,11 @@ import { Container, ContainerModule } from 'inversify'; import { ActionHandlerRegistry, InitializeResult, + KeyTool, + LocationPostprocessor, ModelSource, MouseTool, + MoveCommand, SetEditModeAction, TYPES, bindAsService, @@ -32,6 +35,7 @@ import { GLSPActionDispatcher } from './action-dispatcher'; import { FocusStateChangedAction } from './actions/focus-change-action'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; +import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusTracker } from './focus-tracker'; @@ -40,7 +44,8 @@ import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; -import { RankingMouseTool } from './view/mouse-tool'; +import { GLSPKeyTool } from './view/key-tool'; +import { GLSPMouseTool } from './view/mouse-tool'; import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { @@ -66,7 +71,10 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand(context, FeedbackAwareUpdateModelCommand); - rebind(MouseTool).to(RankingMouseTool).inSingletonScope(); + bind(GLSPMouseTool).toSelf().inSingletonScope(); + bindOrRebind(context, MouseTool).toService(GLSPMouseTool); + bind(GLSPKeyTool).toSelf().inSingletonScope(); + bindOrRebind(context, KeyTool).toService(GLSPKeyTool); bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); @@ -84,6 +92,15 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = bind(SelectionService).toSelf().inSingletonScope(); bind(TYPES.ISModelRootListener).toService(SelectionService); + + // Feedback Support ------------------------------------ + // Generic re-usable feedback modifying css classes + configureCommand(context, ModifyCssFeedbackCommand); + // We support using sprotty's MoveCommand as client-side visual feedback + configureCommand(context, MoveCommand); + + bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); + bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); }); export default defaultGLSPModule; diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index 6284a40..a5d85fb 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -24,8 +24,8 @@ import { Action, MouseListener, SModelElement } from '~glsp-sprotty'; * of mouse up events. */ export class DragAwareMouseListener extends MouseListener { - private _isMouseDown = false; - private _isMouseDrag = false; + protected _isMouseDown = false; + protected _isMouseDrag = false; override mouseDown(target: SModelElement, event: MouseEvent): Action[] { this._isMouseDown = true; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts similarity index 98% rename from packages/client/src/features/tool-feedback/css-feedback.ts rename to packages/client/src/base/feedback/css-feedback.ts index ae4f052..935195b 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES, hasArrayProp } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { addCssClasses, getElements, removeCssClasses } from '../../utils/smodel-util'; +import { FeedbackCommand } from './feedback-command'; export interface ModifyCSSFeedbackAction extends Action { kind: typeof ModifyCSSFeedbackAction.KIND; diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index a338f43..5762605 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, IActionDispatcher, ILogger, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, IActionDispatcher, ILogger, TYPES } from '~glsp-sprotty'; export interface IFeedbackEmitter {} @@ -35,21 +35,23 @@ export interface IFeedbackActionDispatcher { /** * Registers `actions` to be sent out by a `feedbackEmitter`. * @param feedbackEmitter the emitter sending out feedback actions. - * @param actions the actions to be sent out. + * @param feedbackActions the actions to be sent out. + * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. + * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. */ - registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void; + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[]): Disposable; /** * Deregisters a `feedbackEmitter` from this dispatcher and thereafter * dispatches the provided `actions`. * @param feedbackEmitter the emitter to be deregistered. - * @param actions the actions to be dispatched right after the deregistration. + * @param cleanupActions the actions to be dispatched right after the deregistration. * These actions do not have to be related to the actions sent out by the * deregistered `feedbackEmitter`. The purpose of these actions typically is * to reset the normal state of the diagram without the feedback (e.g., reset a * CSS class that was set by a feedbackEmitter). */ - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void; + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[]): void; /** * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. @@ -59,41 +61,47 @@ export interface IFeedbackActionDispatcher { @injectable() export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { - protected feedbackEmitters: Map = new Map(); + protected registeredFeedback: Map = new Map(); @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; @inject(TYPES.ILogger) protected logger: ILogger; - registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { - this.feedbackEmitters.set(feedbackEmitter, actions); - this.dispatch(actions, feedbackEmitter); + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable { + this.registeredFeedback.set(feedbackEmitter, feedbackActions); + this.dispatchFeedback(feedbackActions, feedbackEmitter); + return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); } - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { - this.feedbackEmitters.delete(feedbackEmitter); - this.dispatch(actions, feedbackEmitter); - } - - private dispatch(actions: Action[], feedbackEmitter: IFeedbackEmitter): void { - this.actionDispatcher() - .then(dispatcher => dispatcher.dispatchAll(actions)) - .then(() => this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`)) - .catch(reason => this.logger.error(this, 'Failed to dispatch feedback actions', reason)); + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[] | undefined): void { + this.registeredFeedback.delete(feedbackEmitter); + if (cleanupActions) { + this.dispatchFeedback(cleanupActions, feedbackEmitter); + } } getRegisteredFeedback(): Action[] { const result: Action[] = []; - this.feedbackEmitters.forEach((value, key) => result.push(...value)); + this.registeredFeedback.forEach(actions => result.push(...actions)); return result; } getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { const result: IFeedbackEmitter[] = []; - this.feedbackEmitters.forEach((value, key) => { - if (value.find(a => a === action)) { - result.push(key); + this.registeredFeedback.forEach((actions, emitter) => { + if (actions.includes(action)) { + result.push(emitter); } }); return result; } + + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { + try { + const actionDispatcher = await this.actionDispatcher(); + await actionDispatcher.dispatchAll(actions); + this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); + } catch (reason) { + this.logger.error(this, 'Failed to dispatch feedback actions', reason); + } + } } diff --git a/packages/client/src/base/feedback/feeback-command.ts b/packages/client/src/base/feedback/feedback-command.ts similarity index 100% rename from packages/client/src/base/feedback/feeback-command.ts rename to packages/client/src/base/feedback/feedback-command.ts diff --git a/packages/client/src/base/feedback/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts index 7768fac..98743e5 100644 --- a/packages/client/src/base/feedback/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -30,8 +30,8 @@ import { UpdateModelCommand, toTypeGuard } from '~glsp-sprotty'; -import { FeedbackCommand } from './feeback-command'; import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; +import { FeedbackCommand } from './feedback-command'; /** * A special {@link UpdateModelCommand} that retrieves all registered {@link Action}s from the {@link IFeedbackActionDispatcher} diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index ba48f25..7c203e4 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -16,18 +16,29 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { Action, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, defaultModule, initializeContainer } from '~glsp-sprotty'; +import { + Action, + Disposable, + SGraphFactory, + SModelElementSchema, + SModelRoot, + TYPES, + defaultModule, + initializeContainer +} from '~glsp-sprotty'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; + @injectable() class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { protected feedbackEmitters: Map = new Map(); - registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): Disposable { this.feedbackEmitters.set(feedbackEmitter, actions); + return Disposable.create(() => this.deregisterFeedback(feedbackEmitter)); } - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): void { + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, _actions?: Action[]): void { this.feedbackEmitters.delete(feedbackEmitter); } diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts new file mode 100644 index 0000000..2255e55 --- /dev/null +++ b/packages/client/src/base/view/key-tool.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { Disposable, KeyListener, KeyTool } from '~glsp-sprotty'; + +@injectable() +export class GLSPKeyTool extends KeyTool { + registerListener(keyListener: KeyListener): Disposable { + super.register(keyListener); + return Disposable.create(() => this.deregister(keyListener)); + } +} diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index 8f011cc..ed498d4 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, multiInject, optional } from 'inversify'; -import { Action, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; import { Ranked } from '../ranked'; /** @@ -24,7 +24,7 @@ import { Ranked } from '../ranked'; type MouseListenerMethods = keyof Omit; @injectable() -export class RankingMouseTool extends MouseTool { +export class GLSPMouseTool extends MouseTool { protected rankedMouseListeners: Map; constructor(@multiInject(TYPES.MouseListener) @optional() protected override mouseListeners: MouseListener[] = []) { @@ -37,6 +37,11 @@ export class RankingMouseTool extends MouseTool { this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener)); } + registerListener(mouseListener: MouseListener): Disposable { + this.register(mouseListener); + return Disposable.create(() => this.deregister(mouseListener)); + } + override deregister(mouseListener: MouseListener): void { super.deregister(mouseListener); this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener)); diff --git a/packages/client/src/container-modules.ts b/packages/client/src/container-modules.ts index a79b15e..af0929f 100644 --- a/packages/client/src/container-modules.ts +++ b/packages/client/src/container-modules.ts @@ -27,7 +27,6 @@ import { labelEditUiModule, modelSourceModule, openModule, - routingModule, zorderModule } from '~glsp-sprotty'; import defaultGLSPModule from './base/di.config'; @@ -42,12 +41,18 @@ import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; import layoutModule from './features/layout/di.config'; import navigationModule from './features/navigation/di.config'; +import glspRoutingModule from './features/routing/di.config'; import glspSelectModule from './features/select/di.config'; import sourceModelWatcherModule from './features/source-model-watcher/di.config'; import svgMetadataModule from './features/svg-metadata/di.config'; -import toolFeedbackModule from './features/tool-feedback/di.config'; import toolPaletteModule from './features/tool-palette/di.config'; -import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; +import changeBoundsToolModule from './features/tools/change-bounds/di.config'; +import deletionToolModule from './features/tools/deletion/di.config'; +import edgeCreationToolModule from './features/tools/edge-creation/di.config'; +import edgeEditToolModule from './features/tools/edge-edit/di.config'; +import enableDefaultToolsOnFocusLossModule from './features/tools/enable-default-tools-on-focus-loss'; +import marqueeSelectionToolModule from './features/tools/marquee-selection/di.config'; +import nodeCreationToolModule from './features/tools/node-creation/di.config'; import { markerNavigatorModule, validationModule } from './features/validation/di.config'; import glspViewportModule from './features/viewport/di.config'; @@ -77,10 +82,14 @@ export const DEFAULT_MODULES = [ sourceModelWatcherModule, navigationModule, openModule, + glspRoutingModule, toolPaletteModule, - routingModule, - toolFeedbackModule, - toolsModule, + edgeCreationToolModule, + edgeEditToolModule, + deletionToolModule, + nodeCreationToolModule, + changeBoundsToolModule, + marqueeSelectionToolModule, enableDefaultToolsOnFocusLossModule, validationModule, zorderModule, diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index 56f6097..f2e8060 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -34,7 +34,7 @@ import { import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; import { BaseAutocompletePalette } from '../../autocomplete-palette/base-autocomplete-palette'; -import { applyCssClasses, deleteCssClasses } from '../../tool-feedback/css-feedback'; +import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; const CSS_SEARCH_HIDDEN = 'search-hidden'; const CSS_SEARCH_HIGHLIGHTED = 'search-highlighted'; diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 0d3d3b0..8a1a599 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { BoundsAware, Point, SModelElement, SNode, SParentElement } from '~glsp-sprotty'; +import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { ModifyCSSFeedbackAction } from '../tool-feedback/css-feedback'; import { SResizeHandle, isBoundsAwareMoveable } from './model'; /** diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/edit-label/edit-label-tool.ts index 0cc4b61..2fb66ff 100644 --- a/packages/client/src/features/edit-label/edit-label-tool.ts +++ b/packages/client/src/features/edit-label/edit-label-tool.ts @@ -21,9 +21,6 @@ import { BaseGLSPTool } from '../tools/base-glsp-tool'; export class DirectLabelEditTool extends BaseGLSPTool { static readonly ID = 'glsp.direct-label-edit-tool'; - protected editLabelMouseListener: MouseListener; - protected editLabelKeyListener: KeyListener; - get id(): string { return DirectLabelEditTool.ID; } @@ -37,14 +34,9 @@ export class DirectLabelEditTool extends BaseGLSPTool { } enable(): void { - this.editLabelKeyListener = this.createEditLabelKeyListener(); - this.editLabelMouseListener = this.createEditLabelMouseListener(); - this.mouseTool.register(this.editLabelMouseListener); - this.keyTool.register(this.editLabelKeyListener); - } - - disable(): void { - this.keyTool.deregister(this.editLabelKeyListener); - this.mouseTool.deregister(this.editLabelMouseListener); + this.toDisposeOnDisable.push( + this.mouseTool.registerListener(this.createEditLabelMouseListener()), + this.keyTool.registerListener(this.createEditLabelKeyListener()) + ); } } diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index a4d523c..292caa1 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -36,8 +36,8 @@ import { editFeature, moveFeature } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index c84c4e6..59a06e4 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -32,8 +32,9 @@ import { SetPopupModelAction } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { EdgeCreationTool } from '../tools/edge-creation-tool'; +import { EdgeCreationTool } from '../tools/edge-creation/edge-creation-tool'; import { GIssueMarker, getSeverity } from '../validation/issue-marker'; + @injectable() export class GlspHoverMouseListener extends HoverMouseListener implements IActionHandler { protected enableHover = true; diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 212c7b2..88b1f55 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -22,7 +22,7 @@ import { ResizeElementsActionHandler } from './layout-elements-action'; -const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +const layoutModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index b1b6d37..cb21dad 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -41,7 +41,6 @@ import { TYPES, defaultModule } from '~glsp-sprotty'; -import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { SelectionService } from '../../base/selection-service'; import { resizeFeature } from '../change-bounds/model'; import { @@ -80,7 +79,6 @@ class MockSelectionService extends SelectionService { // Generic Test setup const container = new Container(); container.load(defaultModule); -container.bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); const graphFactory = container.get(TYPES.IModelFactory); diff --git a/packages/client/src/features/routing/di.config.ts b/packages/client/src/features/routing/di.config.ts new file mode 100644 index 0000000..fcae1ec --- /dev/null +++ b/packages/client/src/features/routing/di.config.ts @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { + AddRemoveBezierSegmentCommand, + AnchorComputerRegistry, + BezierDiamondAnchor, + BezierEdgeRouter, + BezierEllipseAnchor, + BezierRectangleAnchor, + DiamondAnchor, + EdgeRouterRegistry, + EllipseAnchor, + ManhattanDiamondAnchor, + ManhattanEdgeRouter, + ManhattanEllipticAnchor, + ManhattanRectangularAnchor, + PolylineEdgeRouter, + RectangleAnchor, + TYPES, + bindOrRebind, + configureCommand +} from '~glsp-sprotty'; +import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; + +/** + * Customization of the routing module for GLSP. + */ +const glspRoutingModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + + bind(EdgeRouterRegistry).toSelf().inSingletonScope(); + + bind(AnchorComputerRegistry).toSelf().inSingletonScope(); + + bind(GLSPManhattanEdgeRouter).toSelf().inSingletonScope(); + bind(ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); + bind(TYPES.IEdgeRouter).toService(ManhattanEdgeRouter); + bind(ManhattanEllipticAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(ManhattanEllipticAnchor); + bind(ManhattanRectangularAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(ManhattanRectangularAnchor); + bind(ManhattanDiamondAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(ManhattanDiamondAnchor); + + bind(PolylineEdgeRouter).toSelf().inSingletonScope(); + bind(TYPES.IEdgeRouter).toService(PolylineEdgeRouter); + bind(EllipseAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(EllipseAnchor); + bind(RectangleAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(RectangleAnchor); + bind(DiamondAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(DiamondAnchor); + + bind(BezierEdgeRouter).toSelf().inSingletonScope(); + bind(TYPES.IEdgeRouter).toService(BezierEdgeRouter); + bind(BezierEllipseAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(BezierEllipseAnchor); + bind(BezierRectangleAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(BezierRectangleAnchor); + bind(BezierDiamondAnchor).toSelf().inSingletonScope(); + bind(TYPES.IAnchorComputer).toService(BezierDiamondAnchor); + + configureCommand({ bind, isBound }, AddRemoveBezierSegmentCommand); + + bindOrRebind(context, ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); +}); + +export default glspRoutingModule; diff --git a/packages/client/src/features/tools/glsp-manhattan-edge-router.ts b/packages/client/src/features/routing/glsp-manhattan-edge-router.ts similarity index 100% rename from packages/client/src/features/tools/glsp-manhattan-edge-router.ts rename to packages/client/src/features/routing/glsp-manhattan-edge-router.ts diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts deleted file mode 100644 index 3f36855..0000000 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from '~glsp-sprotty'; -import { SResizeHandle } from '../change-bounds/model'; -import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; -import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from './creation-tool-feedback'; -import { ModifyCssFeedbackCommand } from './css-feedback'; -import { - DrawFeedbackEdgeSourceCommand, - HideEdgeReconnectHandlesFeedbackCommand, - ShowEdgeReconnectHandlesFeedbackCommand, - SwitchRoutingModeCommand -} from './edge-edit-tool-feedback'; -import { DrawMarqueeCommand, RemoveMarqueeCommand } from './marquee-tool-feedback'; -import { FeedbackEdgeEndView, SResizeHandleView } from './view'; - -const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - - configureCommand(context, ModifyCssFeedbackCommand); - - // create node and edge tool feedback - configureCommand(context, DrawFeedbackEdgeCommand); - configureCommand(context, RemoveFeedbackEdgeCommand); - - configureCommand(context, DrawMarqueeCommand); - configureCommand(context, RemoveMarqueeCommand); - - configureView(context, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); - // move tool feedback: we use sprotty's MoveCommand as client-side visual feedback - configureCommand(context, MoveCommand); - - // resize tool feedback - configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); - configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); - configureView(context, SResizeHandle.TYPE, SResizeHandleView); - - // reconnect edge tool feedback - configureCommand(context, ShowEdgeReconnectHandlesFeedbackCommand); - configureCommand(context, HideEdgeReconnectHandlesFeedbackCommand); - configureCommand(context, DrawFeedbackEdgeSourceCommand); - - configureCommand(context, SwitchRoutingModeCommand); - - bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); - bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); -}); - -export default toolFeedbackModule; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index d43d012..257820c 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -36,8 +36,8 @@ import { import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus-tracker'; -import { MouseDeleteTool } from '../tools/delete-tool'; -import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; +import { MouseDeleteTool } from '../tools/deletion/delete-tool'; +import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; const CLICKED_CSS_CLASS = 'clicked'; const SEARCH_ICON_ID = 'search'; diff --git a/packages/client/src/features/tools/base-glsp-creation-tool.ts b/packages/client/src/features/tools/base-glsp-creation-tool.ts new file mode 100644 index 0000000..52f9580 --- /dev/null +++ b/packages/client/src/features/tools/base-glsp-creation-tool.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { Action, EnableToolsAction, IActionHandler } from '~glsp-sprotty'; +import { BaseGLSPTool } from './base-glsp-tool'; + +@injectable() +export abstract class BaseGLSPCreationTool extends BaseGLSPTool implements IActionHandler { + protected abstract isTriggerAction: (obj: any) => obj is T; + protected triggerAction: T; + + handle(action: Action): Action | void { + if (this.isTriggerAction(action)) { + this.triggerAction = action; + return EnableToolsAction.create([this.id]); + } + } + + override enable(): void { + if (this.triggerAction === undefined) { + throw new TypeError(`Could not enable tool ${this.id}. The triggerAction cannot be undefined.`); + } + this.doEnable(); + } + + protected abstract doEnable(): void; +} diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 4b100d2..bbb2efc 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -14,21 +14,28 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, IActionDispatcher, KeyTool, MouseTool, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, DisposableCollection, IActionDispatcher, TYPES } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; +import { GLSPKeyTool } from '../../base/view/key-tool'; +import { GLSPMouseTool } from '../../base/view/mouse-tool'; @injectable() export abstract class BaseGLSPTool implements GLSPTool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - @inject(MouseTool) protected mouseTool: MouseTool; - @inject(KeyTool) protected keyTool: KeyTool; + @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; + @inject(GLSPKeyTool) protected keyTool: GLSPKeyTool; @inject(EditorContextService) protected readonly editorContext: EditorContextService; + protected readonly toDisposeOnDisable = new DisposableCollection(); + abstract enable(): void; - abstract disable(): void; + + disable(): void { + this.toDisposeOnDisable.dispose(); + } abstract id: string; @@ -40,11 +47,27 @@ export abstract class BaseGLSPTool implements GLSPTool { this.actionDispatcher.dispatchAll(actions); } - dispatchFeedback(actions: Action[], feedbackEmitter?: IFeedbackEmitter): void { - this.feedbackDispatcher.registerFeedback(feedbackEmitter ?? this, actions); + /** + * Registers `actions` to be sent out as feedback, i.e., changes that are re-established whenever the `SModelRoot` + * has been set or updated. + * + * @param feedbackActions the actions to be sent out. + * @param feedbackEmitter the emitter sending out feedback actions (this tool by default). + * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. + * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. + */ + registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable { + return this.feedbackDispatcher.registerFeedback(feedbackEmitter ?? this, feedbackActions, cleanupActions); } - deregisterFeedback(actions: Action[], feedbackEmitter?: IFeedbackEmitter): void { - this.feedbackDispatcher.deregisterFeedback(feedbackEmitter ?? this, actions); + /** + * De-registers all feedback from the given `feedbackEmitter` (this tool by default) and cleans up any pending feedback with the + * given `cleanupActions`. + * + * @param feedbackEmitter the emitter to be deregistered (this tool by default). + * @param cleanupActions the actions to be dispatched right after the deregistration to clean up any pending feedback. + */ + deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): void { + this.feedbackDispatcher.deregisterFeedback(feedbackEmitter ?? this, cleanupActions); } } diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts similarity index 92% rename from packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts rename to packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index b7e4798..975a1e9 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -34,12 +34,12 @@ import { isSelectable, isViewport } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; -import { forEachElement } from '../../utils/smodel-util'; -import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../change-bounds/model'; -import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; -import { ChangeBoundsTool } from '../tools/change-bounds-tool'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackCommand } from '../../../base/feedback/feedback-command'; +import { forEachElement } from '../../../utils/smodel-util'; +import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; +import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; +import { ChangeBoundsTool } from './change-bounds-tool'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; @@ -157,7 +157,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab result.push(cursorFeedbackAction(CursorCSS.MOVE)); } } - this.tool.dispatchFeedback(result, this); + this.tool.registerFeedback(result, this); return []; } @@ -253,7 +253,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab } else { action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); } - this.tool.dispatchFeedback([action], this); + this.tool.registerFeedback([action], this); } return newPosition; } @@ -280,10 +280,12 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab if (moveAction) { result.push(moveAction); } + const resetFeedback: Action[] = []; if (this.tool.movementRestrictor) { - this.tool.deregisterFeedback([removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)], this); + resetFeedback.push(removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)); } - result.push(cursorFeedbackAction(CursorCSS.DEFAULT)); + resetFeedback.push(cursorFeedbackAction(CursorCSS.DEFAULT)); + this.tool.deregisterFeedback(this, resetFeedback); } this.reset(); return result; @@ -293,7 +295,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab if (this.rootElement && resetFeedback) { const elementMoves: ElementMove[] = this.getElementMovesForDelta(this.rootElement, { x: 0, y: 0 }, true, true); const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); - this.tool.deregisterFeedback([moveAction], this); + this.tool.deregisterFeedback(this, [moveAction]); } this.hasDragged = false; this.startDragPosition = undefined; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts similarity index 85% rename from packages/client/src/features/tools/change-bounds-tool.ts rename to packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 6601138..8123775 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, optional } from 'inversify'; + import { Action, Bounds, @@ -23,7 +24,6 @@ import { CompoundOperation, Dimension, Disposable, - DisposableCollection, EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, @@ -41,29 +41,29 @@ import { findParentByFeature, isSelected } from '~glsp-sprotty'; -import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { ISelectionListener, SelectionService } from '../../base/selection-service'; -import { isValidMove, isValidSize } from '../../utils/layout-utils'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { ISelectionListener, SelectionService } from '../../../base/selection-service'; +import { PointPositionUpdater } from '../../../features/change-bounds/snap'; +import { isValidMove, isValidSize } from '../../../utils/layout-utils'; import { calcElementAndRoutingPoints, forEachElement, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds -} from '../../utils/smodel-util'; -import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; +} from '../../../utils/smodel-util'; +import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor, createMovementRestrictionFeedback, removeMovementRestrictionFeedback -} from '../change-bounds/movement-restrictor'; -import { PointPositionUpdater } from '../change-bounds/snap'; +} from '../../change-bounds/movement-restrictor'; +import { BaseGLSPTool } from '../base-glsp-tool'; import { FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, ShowChangeBoundsToolResizeFeedbackAction -} from '../tool-feedback/change-bounds-tool-feedback'; -import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../tool-feedback/css-feedback'; -import { BaseGLSPTool } from './base-glsp-tool'; +} from './change-bounds-tool-feedback'; /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. @@ -86,9 +86,6 @@ export class ChangeBoundsTool extends BaseGLSPTool { @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; - protected feedbackMoveMouseListener: MouseListener; - protected changeBoundsListener: MouseListener & ISelectionListener; - protected toDispose = new DisposableCollection(); get id(): string { return ChangeBoundsTool.ID; @@ -96,16 +93,23 @@ export class ChangeBoundsTool extends BaseGLSPTool { enable(): void { // install feedback move mouse listener for client-side move updates - this.feedbackMoveMouseListener = this.createMoveMouseListener(); - this.mouseTool.register(this.feedbackMoveMouseListener); + const feedbackMoveMouseListener = this.createMoveMouseListener(); + if (Disposable.is(feedbackMoveMouseListener)) { + this.toDisposeOnDisable.push(feedbackMoveMouseListener); + } // install change bounds listener for client-side resize updates and server-side updates - this.changeBoundsListener = this.createChangeBoundsListener(); - this.mouseTool.register(this.changeBoundsListener); - this.toDispose.push( - this.selectionService.onSelectionChanged(change => - this.changeBoundsListener.selectionChanged(change.root, change.selectedElements) - ) + const changeBoundsListener = this.createChangeBoundsListener(); + if (Disposable.is(changeBoundsListener)) { + this.toDisposeOnDisable.push(changeBoundsListener); + } + + this.toDisposeOnDisable.push( + this.mouseTool.registerListener(feedbackMoveMouseListener), + this.mouseTool.registerListener(changeBoundsListener), + Disposable.create(() => this.deregisterFeedback(feedbackMoveMouseListener)), + Disposable.create(() => this.deregisterFeedback(changeBoundsListener, [HideChangeBoundsToolResizeFeedbackAction.create()])), + this.selectionService.onSelectionChanged(change => changeBoundsListener.selectionChanged(change.root, change.selectedElements)) ); } @@ -116,20 +120,6 @@ export class ChangeBoundsTool extends BaseGLSPTool { protected createChangeBoundsListener(): MouseListener & ISelectionListener { return new ChangeBoundsListener(this); } - - disable(): void { - if (Disposable.is(this.feedbackMoveMouseListener)) { - this.feedbackMoveMouseListener.dispose(); - } - if (Disposable.is(this.changeBoundsListener)) { - this.changeBoundsListener.dispose(); - } - this.mouseTool.deregister(this.changeBoundsListener); - this.mouseTool.deregister(this.feedbackMoveMouseListener); - this.deregisterFeedback([], this.feedbackMoveMouseListener); - this.deregisterFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this.changeBoundsListener); - this.toDispose.dispose(); - } } export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener, Disposable { @@ -183,12 +173,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel const resizeActions = this.handleResizeOnClient(positionUpdate); actions.push(...resizeActions); } - this.tool.dispatchFeedback(actions, this); + this.tool.registerFeedback(actions, this); } return []; } - override draggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { + override draggingMouseUp(target: SModelElement, _event: MouseEvent): Action[] { if (this.pointPositionUpdater.isLastDragPositionUndefined()) { this.resetPosition(); return []; @@ -203,6 +193,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel actions.push(...this.handleMoveOnServer(target)); } this.resetPosition(); + this.tool.deregisterFeedback(this, [cursorFeedbackAction(CursorCSS.DEFAULT)]); return actions; } @@ -302,7 +293,10 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel // only allow one element to have the element resize handles this.activeResizeElement = moveableElement; if (isResizable(this.activeResizeElement)) { - this.tool.dispatchFeedback([ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id)], this); + this.tool.registerFeedback( + [ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id)], + this.activeResizeElement + ); } return true; } @@ -327,25 +321,33 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } dispose(): void { - this.reset(); + this.reset(true); + } + + protected reset(resetBounds = false): void { + this.resetFeedback(resetBounds); + this.resetPosition(); } - protected reset(): void { + protected resetFeedback(resetBounds = false): void { + const resetFeedback: Action[] = []; if (this.activeResizeElement && isResizable(this.activeResizeElement)) { - if (this.initialBounds) { - const resetResizeAction = SetBoundsAction.create([ - { - elementId: this.activeResizeElement.id, - newPosition: this.initialBounds, - newSize: this.initialBounds - } - ]); - this.tool.deregisterFeedback([resetResizeAction], this); + if (this.initialBounds && this.activeResizeHandle && resetBounds) { + // we only reset the bounds if an active resize operation was cancelled due to the tool being disabled + resetFeedback.push( + SetBoundsAction.create([ + { + elementId: this.activeResizeElement.id, + newPosition: this.initialBounds, + newSize: this.initialBounds + } + ]) + ); } - this.tool.deregisterFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this); + this.tool.deregisterFeedback(this.activeResizeElement, [HideChangeBoundsToolResizeFeedbackAction.create()]); } - this.tool.dispatchActions([cursorFeedbackAction(CursorCSS.DEFAULT)]); - this.resetPosition(); + resetFeedback.push(cursorFeedbackAction(CursorCSS.DEFAULT)); + this.tool.deregisterFeedback(this, resetFeedback); } protected resetPosition(): void { diff --git a/packages/client/src/features/tools/change-bounds/di.config.ts b/packages/client/src/features/tools/change-bounds/di.config.ts new file mode 100644 index 0000000..cba8ac4 --- /dev/null +++ b/packages/client/src/features/tools/change-bounds/di.config.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, bindAsService, configureCommand, configureView } from '~glsp-sprotty'; +import { SResizeHandle } from '../../../features/change-bounds/model'; +import { ChangeBoundsTool } from './change-bounds-tool'; +import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; +import { SResizeHandleView } from './view'; + +const changeBoundsToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); + configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); + configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); + configureView(context, SResizeHandle.TYPE, SResizeHandleView); +}); + +export default changeBoundsToolModule; diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx similarity index 80% rename from packages/client/src/features/tool-feedback/view.tsx rename to packages/client/src/features/tools/change-bounds/view.tsx index c3504b6..ae87655 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -15,24 +15,12 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, Point, RenderingContext, setAttr, SModelElement, svg } from '~glsp-sprotty'; -import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; +import { IView, Point, RenderingContext, setAttr, svg } from '~glsp-sprotty'; +import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; -/** - * This view is used for the invisible end of the feedback edge. - * A feedback edge is shown as a visual feedback when creating edges. - */ -@injectable() -export class FeedbackEdgeEndView implements IView { - render(model: Readonly, context: RenderingContext): VNode { - const position: Point = (model as any).position ?? Point.ORIGIN; - return ; - } -} - @injectable() export class SResizeHandleView implements IView { render(handle: SResizeHandle, context: RenderingContext): VNode { diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts similarity index 79% rename from packages/client/src/features/tools/delete-tool.ts rename to packages/client/src/features/tools/deletion/delete-tool.ts index 82baed3..78c2847 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -21,18 +21,16 @@ import { KeyListener, KeyTool, MouseListener, - MouseTool, SModelElement, - TYPES, findParentByFeature, isCtrlOrCmd, isDeletable, isSelectable, matchesKeystroke } from '~glsp-sprotty'; -import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; -import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { BaseGLSPTool } from '../base-glsp-tool'; /** * Deletes selected elements when hitting the `Del` key. @@ -82,28 +80,20 @@ export class DeleteKeyListener extends KeyListener { * Deletes selected elements when clicking on them. */ @injectable() -export class MouseDeleteTool implements GLSPTool { +export class MouseDeleteTool extends BaseGLSPTool { static ID = 'glsp.delete-mouse'; - isEditTool = true; - protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); - @inject(MouseTool) protected mouseTool: MouseTool; - @inject(TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher; - get id(): string { return MouseDeleteTool.ID; } enable(): void { - this.mouseTool.register(this.deleteToolMouseListener); - this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction(CursorCSS.ELEMENT_DELETION)]); - } - - disable(): void { - this.mouseTool.deregister(this.deleteToolMouseListener); - this.feedbackDispatcher.registerFeedback(this, [cursorFeedbackAction()]); + this.toDisposeOnDisable.push( + this.mouseTool.registerListener(this.deleteToolMouseListener), + this.registerFeedback([cursorFeedbackAction(CursorCSS.ELEMENT_DELETION)], this, [cursorFeedbackAction()]) + ); } } diff --git a/packages/client/src/features/tools/deletion/di.config.ts b/packages/client/src/features/tools/deletion/di.config.ts new file mode 100644 index 0000000..185ab24 --- /dev/null +++ b/packages/client/src/features/tools/deletion/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, bindAsService } from '~glsp-sprotty'; +import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; + +const deletionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); + bindAsService(context, TYPES.ITool, MouseDeleteTool); +}); + +export default deletionToolModule; diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts deleted file mode 100644 index 890476b..0000000 --- a/packages/client/src/features/tools/di.config.ts +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { - bindAsService, - BindingContext, - bindOrRebind, - configureActionHandler, - configureModelElement, - ManhattanEdgeRouter, - TriggerEdgeCreationAction, - TriggerNodeCreationAction, - TYPES -} from '~glsp-sprotty'; -import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; -import { ChangeBoundsTool } from './change-bounds-tool'; -import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; -import { EdgeCreationTool } from './edge-creation-tool'; -import { EdgeEditTool } from './edge-edit-tool'; -import { EnableDefaultToolsOnFocusLossHandler } from './enable-default-tools-on-focus-loss'; -import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; -import { MarqueeMouseTool } from './marquee-mouse-tool'; -import { MarqueeTool } from './marquee-tool'; -import { MarqueeNode } from './model'; -import { NodeCreationTool } from './node-creation-tool'; -import { MarqueeView } from './view'; - -/** - * Registers the default tools of GLSP (node and edge creation, changing bounds, edge editing, deletion) - * and adds the marquee selection tool. - */ -export const toolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - const context = { bind, isBound, rebind }; - // Register default tools - bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); - bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); - bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); - - // Register tools - bindAsService(context, TYPES.ITool, MouseDeleteTool); - bindAsService(context, TYPES.ITool, NodeCreationTool); - bindAsService(context, TYPES.ITool, EdgeCreationTool); - - configureMarqueeTool(context); - configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); - configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); - - bind(GLSPManhattanEdgeRouter).toSelf().inSingletonScope(); - bindOrRebind(context, ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); -}); - -export function configureMarqueeTool(context: Pick): void { - configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); - bindAsService(context, TYPES.IDefaultTool, MarqueeTool); - bindAsService(context, TYPES.ITool, MarqueeMouseTool); -} - -/** - * Enables the default tools in the tool manager if the diagram looses focus. - */ -export const enableDefaultToolsOnFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { - configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); -}); diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts similarity index 55% rename from packages/client/src/features/tool-feedback/creation-tool-feedback.ts rename to packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts index 12984d9..867967c 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts @@ -16,30 +16,26 @@ import { inject, injectable } from 'inversify'; import { Action, - AnchorComputerRegistry, - Bounds, + BindingContext, CommandExecutionContext, CommandReturn, - MouseListener, - MoveAction, - Point, - PolylineEdgeRouter, SChildElement, - SConnectableElement, SDanglingAnchor, SEdgeSchema, - SModelElement, SModelRoot, SRoutableElement, TYPES, - findChildrenAtPosition, + configureCommand, + configureView, findParentByFeature, isBoundsAware, isConnectable } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; -import { BoundsAwareModelElement, isRoutable } from '../../utils/smodel-util'; -import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; +import { FeedbackCommand } from '../../../base/feedback/feedback-command'; +import { isRoutable } from '../../../utils/smodel-util'; +import { toAbsolutePosition } from '../../../utils/viewpoint-util'; +import { FeedbackEdgeEndView } from './view'; + export interface DrawFeedbackEdgeAction extends Action { kind: typeof DrawFeedbackEdgeAction.KIND; elementTypeId: string; @@ -101,7 +97,7 @@ export class RemoveFeedbackEdgeCommand extends FeedbackCommand { static readonly KIND = RemoveFeedbackEdgeAction.KIND; execute(context: CommandExecutionContext): CommandReturn { - removeFeedbackEdge(context.root); + removeDanglingFeedbackEdge(context.root); return context.root; } } @@ -118,80 +114,6 @@ export class FeedbackEdgeEnd extends SDanglingAnchor { } } -export class FeedbackEdgeEndMovingMouseListener extends MouseListener { - constructor(protected anchorRegistry: AnchorComputerRegistry) { - super(); - } - - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { - const root = target.root; - const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); - if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { - return []; - } - - const edge = edgeEnd.feedbackEdge; - const position = getAbsolutePosition(edgeEnd, event); - const endAtMousePosition = findChildrenAtPosition(target.root, position) - .reverse() - .find(element => isConnectable(element) && element.canConnect(edge, 'target')); - - if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { - const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(toAbsoluteBounds(edge.source))); - if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { - return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })]; - } - } else { - return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })]; - } - - return []; - } - - protected computeAbsoluteAnchor(element: SConnectableElement, absoluteReferencePoint: Point, offset?: number): Point { - const referencePointInParent = absoluteToParent(element, absoluteReferencePoint); - const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); - let anchor = anchorComputer.getAnchor(element, referencePointInParent, offset); - // The anchor is computed in the local coordinate system of the element. - // If the element is a nested child element we have to add the absolute position of its parent to the anchor. - if (element.parent !== element.root) { - const parent = findParentByFeature(element.parent, isBoundsAware); - if (parent) { - const absoluteParentPosition = toAbsoluteBounds(parent); - anchor = Point.add(absoluteParentPosition, anchor); - } - } - return anchor; - } -} - -/** - * Convert a point, specified in absolute coordinates, to a point relative - * to the parent of the specified child element. - * @param element the child element - * @param absolutePoint a point in absolute coordinates - * @returns the equivalent point, relative to the element's parent coordinates - */ -function absoluteToParent(element: BoundsAwareModelElement & SChildElement, absolutePoint: Point): Point { - if (isBoundsAware(element.parent)) { - return absoluteToLocal(element.parent, absolutePoint); - } - // If the parent is not bounds-aware, assume it's at 0; 0 and proceed - return absoluteToLocal(element, absolutePoint); -} - -/** - * Convert a point, specified in absolute coordinates, to a point relative - * to the specified element. - * @param element the element - * @param absolutePoint a point in absolute coordinates - * @returns the equivalent point, relative to the element's coordinates - */ -function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: Point): Point { - const absoluteElementBounds = toAbsoluteBounds(element); - return { x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y }; -} - export function feedbackEdgeId(root: SModelRoot): string { return root.id + '_feedback_edge'; } @@ -243,7 +165,7 @@ export function drawFeedbackEdge( } } -export function removeFeedbackEdge(root: SModelRoot): void { +export function removeDanglingFeedbackEdge(root: SModelRoot): void { const feedbackEdge = root.index.getById(feedbackEdgeId(root)); const feedbackEdgeEnd = root.index.getById(feedbackEdgeEndId(root)); if (feedbackEdge instanceof SChildElement) { @@ -253,3 +175,11 @@ export function removeFeedbackEdge(root: SModelRoot): void { root.remove(feedbackEdgeEnd); } } + +export function configureDanglingFeedbackEdge(context: BindingContext): void { + if (!context.isBound(DrawFeedbackEdgeCommand) && !context.isBound(RemoveFeedbackEdgeCommand) && !context.isBound(FeedbackEdgeEndView)) { + configureCommand(context, DrawFeedbackEdgeCommand); + configureCommand(context, RemoveFeedbackEdgeCommand); + configureView(context, FeedbackEdgeEnd.TYPE, FeedbackEdgeEndView); + } +} diff --git a/packages/client/src/features/tools/edge-creation/di.config.ts b/packages/client/src/features/tools/edge-creation/di.config.ts new file mode 100644 index 0000000..3d42717 --- /dev/null +++ b/packages/client/src/features/tools/edge-creation/di.config.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { configureDanglingFeedbackEdge } from './dangling-edge-feedback'; +import { EdgeCreationTool } from './edge-creation-tool'; + +const edgeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.ITool, EdgeCreationTool); + configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); + configureDanglingFeedbackEdge(context); +}); + +export default edgeCreationToolModule; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts new file mode 100644 index 0000000..05cc634 --- /dev/null +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + AnchorComputerRegistry, + Bounds, + Disposable, + MouseListener, + MoveAction, + Point, + PolylineEdgeRouter, + SConnectableElement, + SModelElement, + findChildrenAtPosition, + findParentByFeature, + isBoundsAware, + isConnectable +} from '~glsp-sprotty'; +import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; +import { absoluteToParent, getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; +import { FeedbackEdgeEnd, feedbackEdgeEndId } from './dangling-edge-feedback'; + +export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements Disposable { + constructor(protected anchorRegistry: AnchorComputerRegistry, protected feedbackDispatcher: IFeedbackActionDispatcher) { + super(); + } + + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + const root = target.root; + const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); + if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { + return []; + } + + const edge = edgeEnd.feedbackEdge; + const position = getAbsolutePosition(edgeEnd, event); + const endAtMousePosition = findChildrenAtPosition(target.root, position) + .reverse() + .find(element => isConnectable(element) && element.canConnect(edge, 'target')); + + if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { + const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(toAbsoluteBounds(edge.source))); + if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { + this.feedbackDispatcher.registerFeedback(this, [ + MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false }) + ]); + } + } else { + this.feedbackDispatcher.registerFeedback(this, [ + MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false }) + ]); + } + + return []; + } + + protected computeAbsoluteAnchor(element: SConnectableElement, absoluteReferencePoint: Point, offset?: number): Point { + const referencePointInParent = absoluteToParent(element, absoluteReferencePoint); + const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); + let anchor = anchorComputer.getAnchor(element, referencePointInParent, offset); + // The anchor is computed in the local coordinate system of the element. + // If the element is a nested child element we have to add the absolute position of its parent to the anchor. + if (element.parent !== element.root) { + const parent = findParentByFeature(element.parent, isBoundsAware); + if (parent) { + const absoluteParentPosition = toAbsoluteBounds(parent); + anchor = Point.add(absoluteParentPosition, anchor); + } + } + return anchor; + } + + dispose(): void { + this.feedbackDispatcher.deregisterFeedback(this); + } +} diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts similarity index 73% rename from packages/client/src/features/tools/edge-creation-tool.ts rename to packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 1dc4612..40d26f8 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -19,8 +19,6 @@ import { AnchorComputerRegistry, CreateEdgeOperation, EnableDefaultToolsAction, - EnableToolsAction, - IActionHandler, SEdge, SModelElement, TriggerEdgeCreationAction, @@ -28,54 +26,39 @@ import { isConnectable, isCtrlOrCmd } from '~glsp-sprotty'; -import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { - DrawFeedbackEdgeAction, - FeedbackEdgeEndMovingMouseListener, - RemoveFeedbackEdgeAction -} from '../tool-feedback/creation-tool-feedback'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; -import { BaseGLSPTool } from './base-glsp-tool'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; + +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { BaseGLSPCreationTool } from '../base-glsp-creation-tool'; +import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; +import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedback'; /** * Tool to create connections in a Diagram, by selecting a source and target node. */ @injectable() -export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { +export class EdgeCreationTool extends BaseGLSPCreationTool { static ID = 'tool_create_edge'; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; - protected triggerAction: TriggerEdgeCreationAction; - protected creationToolMouseListener: EdgeCreationToolMouseListener; - protected feedbackEndMovingMouseListener: FeedbackEdgeEndMovingMouseListener; + protected isTriggerAction = TriggerEdgeCreationAction.is; get id(): string { return EdgeCreationTool.ID; } - enable(): void { - if (this.triggerAction === undefined) { - throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); - } - this.creationToolMouseListener = new EdgeCreationToolMouseListener(this.triggerAction, this); - this.mouseTool.register(this.creationToolMouseListener); - this.feedbackEndMovingMouseListener = new FeedbackEdgeEndMovingMouseListener(this.anchorRegistry); - this.mouseTool.register(this.feedbackEndMovingMouseListener); - this.dispatchFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); - } - - disable(): void { - this.mouseTool.deregister(this.creationToolMouseListener); - this.mouseTool.deregister(this.feedbackEndMovingMouseListener); - this.deregisterFeedback([RemoveFeedbackEdgeAction.create(), cursorFeedbackAction()]); - } - - handle(action: Action): Action | void { - if (TriggerEdgeCreationAction.is(action)) { - this.triggerAction = action; - return EnableToolsAction.create([this.id]); - } + doEnable(): void { + const mouseMovingFeedback = new FeedbackEdgeEndMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); + this.toDisposeOnDisable.push( + mouseMovingFeedback, + this.mouseTool.registerListener(new EdgeCreationToolMouseListener(this.triggerAction, this)), + this.mouseTool.registerListener(mouseMovingFeedback), + this.registerFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)], this, [ + RemoveFeedbackEdgeAction.create(), + cursorFeedbackAction() + ]) + ); } } @@ -98,7 +81,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.target = undefined; this.currentTarget = undefined; this.allowedTarget = false; - this.tool.dispatchFeedback([RemoveFeedbackEdgeAction.create()]); + this.tool.registerFeedback([RemoveFeedbackEdgeAction.create()]); } override nonDraggingMouseUp(_element: SModelElement, event: MouseEvent): Action[] { @@ -107,7 +90,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { if (!this.isSourceSelected()) { if (this.currentTarget && this.allowedTarget) { this.source = this.currentTarget.id; - this.tool.dispatchFeedback([ + this.tool.registerFeedback([ DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }) ]); } @@ -117,6 +100,11 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { } } if (this.source && this.target) { + if (!isCtrlOrCmd(event)) { + result.push(EnableDefaultToolsAction.create()); + } else { + this.reinitialize(); + } result.push( CreateEdgeOperation.create({ elementTypeId: this.triggerAction.elementTypeId, @@ -125,11 +113,6 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { args: this.triggerAction.args }) ); - if (!isCtrlOrCmd(event)) { - result.push(EnableDefaultToolsAction.create()); - } else { - this.reinitialize(); - } } } else if (event.button === 2) { result.push(EnableDefaultToolsAction.create()); diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx new file mode 100644 index 0000000..e8d2375 --- /dev/null +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { IView, Point, RenderingContext, SModelElement, svg } from '~glsp-sprotty'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: svg }; + +/** + * This view is used for the invisible end of the feedback edge. + * A feedback edge is shown as a visual feedback when creating edges. + */ +@injectable() +export class FeedbackEdgeEndView implements IView { + render(model: Readonly, context: RenderingContext): VNode { + const position: Point = (model as any).position ?? Point.ORIGIN; + return ; + } +} diff --git a/packages/client/src/features/tools/edge-edit/di.config.ts b/packages/client/src/features/tools/edge-edit/di.config.ts new file mode 100644 index 0000000..c90bd71 --- /dev/null +++ b/packages/client/src/features/tools/edge-edit/di.config.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { EdgeEditTool } from './edge-edit-tool'; +import { + DrawFeedbackEdgeSourceCommand, + HideEdgeReconnectHandlesFeedbackCommand, + ShowEdgeReconnectHandlesFeedbackCommand, + SwitchRoutingModeCommand +} from './edge-edit-tool-feedback'; +import { configureDanglingFeedbackEdge } from '../edge-creation/dangling-edge-feedback'; + +const edgeEditToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); + + // reconnect edge tool feedback + configureCommand(context, ShowEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, HideEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, DrawFeedbackEdgeSourceCommand); + configureCommand(context, SwitchRoutingModeCommand); + + // dangling edge feedback + configureDanglingFeedbackEdge(context); +}); + +export default edgeEditToolModule; diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts similarity index 89% rename from packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts rename to packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index 951ceac..657fa3b 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -21,6 +21,7 @@ import { Bounds, CommandExecutionContext, CommandReturn, + Disposable, EdgeRouterRegistry, ElementMove, ISnapper, @@ -41,12 +42,14 @@ import { isConnectable, isSelected } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; -import { forEachElement, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; -import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { PointPositionUpdater } from '../change-bounds/snap'; -import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; -import { FeedbackEdgeEnd, FeedbackEdgeEndMovingMouseListener, feedbackEdgeEndId, feedbackEdgeId } from './creation-tool-feedback'; +import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../../../base/feedback/feedback-command'; +import { forEachElement, isRoutable, isRoutingHandle } from '../../../utils/smodel-util'; +import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; +import { PointPositionUpdater } from '../../change-bounds/snap'; +import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; +import { FeedbackEdgeEnd, feedbackEdgeEndId, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; +import { FeedbackEdgeEndMovingMouseListener } from '../edge-creation/edge-creation-tool-feedback'; /** * RECONNECT HANDLES FEEDBACK @@ -67,6 +70,7 @@ export namespace ShowEdgeReconnectHandlesFeedbackAction { return { kind: KIND, elementId }; } } + export interface HideEdgeReconnectHandlesFeedbackAction extends Action { kind: typeof HideEdgeReconnectHandlesFeedbackAction.KIND; } @@ -135,6 +139,7 @@ export namespace SwitchRoutingModeAction { }; } } + @injectable() export class SwitchRoutingModeCommand extends SwitchEditModeCommand { static override KIND = SwitchRoutingModeAction.KIND; @@ -187,13 +192,13 @@ export class DrawFeedbackEdgeSourceCommand extends FeedbackCommand { */ export class FeedbackEdgeTargetMovingMouseListener extends FeedbackEdgeEndMovingMouseListener { - constructor(protected override anchorRegistry: AnchorComputerRegistry) { - super(anchorRegistry); + constructor(anchorRegistry: AnchorComputerRegistry, feedbackDispatcher: IFeedbackActionDispatcher) { + super(anchorRegistry, feedbackDispatcher); } } -export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { - constructor(protected anchorRegistry: AnchorComputerRegistry) { +export class FeedbackEdgeSourceMovingMouseListener extends MouseListener implements Disposable { + constructor(protected anchorRegistry: AnchorComputerRegistry, protected feedbackDispatcher: IFeedbackActionDispatcher) { super(); } @@ -207,16 +212,20 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { const edge = edgeEnd.feedbackEdge; const position = getAbsolutePosition(edgeEnd, event); const endAtMousePosition = findChildrenAtPosition(target.root, position).find( - e => isConnectable(e) && e.canConnect(edge, 'source') + element => isConnectable(element) && element.canConnect(edge, 'source') ); if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(edge.target.bounds)); if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { - return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })]; + this.feedbackDispatcher.registerFeedback(this, [ + MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false }) + ]); } } else { - return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })]; + this.feedbackDispatcher.registerFeedback(this, [ + MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false }) + ]); } return []; @@ -236,6 +245,10 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { } return anchor; } + + dispose(): void { + this.feedbackDispatcher.deregisterFeedback(this); + } } export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { @@ -341,8 +354,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { /** * UTILITY FUNCTIONS */ - -function drawFeedbackEdgeSource(context: CommandExecutionContext, targetId: string, elementTypeId: string): void { +export function drawFeedbackEdgeSource(context: CommandExecutionContext, targetId: string, elementTypeId: string): void { const root = context.root; const targetChild = root.index.getById(targetId); if (!targetChild) { diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts similarity index 89% rename from packages/client/src/features/tools/edge-edit-tool.ts rename to packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index f66e67e..9de9a93 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -19,7 +19,7 @@ import { AnchorComputerRegistry, ChangeRoutingPointsOperation, Connectable, - DisposableCollection, + Disposable, EdgeRouterRegistry, ISnapper, ReconnectEdgeOperation, @@ -33,12 +33,13 @@ import { isConnectable, isSelected } from '~glsp-sprotty'; -import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { ISelectionListener, SelectionService } from '../../base/selection-service'; -import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; -import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../reconnect/model'; -import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../tool-feedback/creation-tool-feedback'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { ISelectionListener, SelectionService } from '../../../base/selection-service'; +import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/smodel-util'; +import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; +import { BaseGLSPTool } from '../base-glsp-tool'; +import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { DrawFeedbackEdgeSourceAction, FeedbackEdgeRouteMovingMouseListener, @@ -47,8 +48,7 @@ import { HideEdgeReconnectHandlesFeedbackAction, ShowEdgeReconnectHandlesFeedbackAction, SwitchRoutingModeAction -} from '../tool-feedback/edge-edit-tool-feedback'; -import { BaseGLSPTool } from './base-glsp-tool'; +} from './edge-edit-tool-feedback'; @injectable() export class EdgeEditTool extends BaseGLSPTool { @@ -63,7 +63,6 @@ export class EdgeEditTool extends BaseGLSPTool { protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; protected feedbackMovingListener: FeedbackEdgeRouteMovingMouseListener; protected edgeEditListener: EdgeEditListener; - protected toDispose = new DisposableCollection(); get id(): string { return EdgeEditTool.ID; @@ -71,15 +70,19 @@ export class EdgeEditTool extends BaseGLSPTool { enable(): void { this.edgeEditListener = new EdgeEditListener(this); - this.mouseTool.register(this.edgeEditListener); - this.toDispose.push( - this.selectionService.onSelectionChanged(change => this.edgeEditListener.selectionChanged(change.root, change.selectedElements)) - ); // install feedback move mouse listener for client-side move updates - this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry); - this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry); + this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); + this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.edgeRouterRegistry, this.snapper); + + this.toDisposeOnDisable.push( + Disposable.create(() => this.edgeEditListener.reset()), + this.mouseTool.registerListener(this.edgeEditListener), + this.feedbackEdgeSourceMovingListener, + this.feedbackEdgeTargetMovingListener, + this.selectionService.onSelectionChanged(change => this.edgeEditListener.selectionChanged(change.root, change.selectedElements)) + ); } registerFeedbackListeners(): void { @@ -89,20 +92,15 @@ export class EdgeEditTool extends BaseGLSPTool { } deregisterFeedbackListeners(): void { + this.feedbackEdgeSourceMovingListener.dispose(); + this.feedbackEdgeTargetMovingListener.dispose(); this.mouseTool.deregister(this.feedbackEdgeSourceMovingListener); this.mouseTool.deregister(this.feedbackEdgeTargetMovingListener); this.mouseTool.deregister(this.feedbackMovingListener); } - - disable(): void { - this.edgeEditListener.reset(); - this.deregisterFeedbackListeners(); - this.mouseTool.deregister(this.edgeEditListener); - this.toDispose.dispose(); - } } -class EdgeEditListener extends DragAwareMouseListener implements ISelectionListener { +export class EdgeEditListener extends DragAwareMouseListener implements ISelectionListener { // active selection data protected edge?: SRoutableElement; protected routingHandle?: SRoutingHandle; @@ -136,7 +134,7 @@ class EdgeEditListener extends DragAwareMouseListener implements ISelectionListe if (isReconnectable(edge)) { feedbackActions.push(ShowEdgeReconnectHandlesFeedbackAction.create(this.edge.id)); } - this.tool.dispatchFeedback(feedbackActions); + this.tool.registerFeedback(feedbackActions); } protected isEdgeSelected(): boolean { @@ -146,14 +144,14 @@ class EdgeEditListener extends DragAwareMouseListener implements ISelectionListe protected setReconnectHandleSelected(edge: SRoutableElement, reconnectHandle: SReconnectHandle): void { if (this.edge && this.edge.target && this.edge.source) { if (isSourceRoutingHandle(edge, reconnectHandle)) { - this.tool.dispatchFeedback([ + this.tool.registerFeedback([ HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), DrawFeedbackEdgeSourceAction.create({ elementTypeId: this.edge.type, targetId: this.edge.targetId }) ]); this.reconnectMode = 'NEW_SOURCE'; } else if (isTargetRoutingHandle(edge, reconnectHandle)) { - this.tool.dispatchFeedback([ + this.tool.registerFeedback([ HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), DrawFeedbackEdgeAction.create({ elementTypeId: this.edge.type, sourceId: this.edge.sourceId }) @@ -261,11 +259,11 @@ class EdgeEditListener extends DragAwareMouseListener implements ISelectionListe (this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target')) ) { - this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); + this.tool.registerFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); return []; } } - this.tool.dispatchFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); + this.tool.registerFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); } } return []; @@ -318,7 +316,7 @@ class EdgeEditListener extends DragAwareMouseListener implements ISelectionListe result.push(SwitchRoutingModeAction.create({ elementsToDeactivate: [this.edge.id] })); } result.push(...[HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(), RemoveFeedbackEdgeAction.create()]); - this.tool.deregisterFeedback(result); + this.tool.deregisterFeedback(undefined, result); this.tool.deregisterFeedbackListeners(); } } diff --git a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts index c540a41..9a83e9c 100644 --- a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts +++ b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { Action, EnableDefaultToolsAction, IActionHandler, ICommand } from '~glsp-sprotty'; +import { ContainerModule, injectable } from 'inversify'; +import { Action, EnableDefaultToolsAction, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; /** @@ -30,3 +30,12 @@ export class EnableDefaultToolsOnFocusLossHandler implements IActionHandler { } } } + +/** + * Enables the default tools in the tool manager if the diagram looses focus. + */ +const enableDefaultToolsOnFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); +}); + +export default enableDefaultToolsOnFocusLossModule; diff --git a/packages/client/src/features/tools/marquee-selection/di.config.ts b/packages/client/src/features/tools/marquee-selection/di.config.ts new file mode 100644 index 0000000..d338536 --- /dev/null +++ b/packages/client/src/features/tools/marquee-selection/di.config.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, bindAsService, configureCommand, configureModelElement } from '~glsp-sprotty'; +import { MarqueeMouseTool } from './marquee-mouse-tool'; +import { MarqueeTool } from './marquee-tool'; +import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-tool-feedback'; +import { MarqueeNode } from './model'; +import { MarqueeView } from './view'; + +const marqueeSelectionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, MarqueeTool); + bindAsService(context, TYPES.ITool, MarqueeMouseTool); + + configureCommand(context, DrawMarqueeCommand); + configureCommand(context, RemoveMarqueeCommand); + + configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); +}); + +export default marqueeSelectionToolModule; diff --git a/packages/client/src/features/tools/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts similarity index 100% rename from packages/client/src/features/tools/marquee-behavior.spec.ts rename to packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts diff --git a/packages/client/src/features/tools/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts similarity index 89% rename from packages/client/src/features/tools/marquee-behavior.ts rename to packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index 02f3537..c96178b 100644 --- a/packages/client/src/features/tools/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { Bounds, Point, PointToPointLine } from '~glsp-sprotty'; -import { DrawMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; +import { DrawMarqueeAction } from './marquee-tool-feedback'; export interface IMarqueeBehavior { readonly entireElement: boolean; @@ -77,7 +77,7 @@ export class MarqueeUtil { return horizontallyIn && verticallyIn; } - private isEntireEdgeMarked(points: Point[]): boolean { + protected isEntireEdgeMarked(points: Point[]): boolean { for (let i = 0; i < points.length; i++) { if (!this.pointInRect(points[i])) { return false; @@ -86,7 +86,7 @@ export class MarqueeUtil { return true; } - private isPartOfEdgeMarked(points: Point[]): boolean { + protected isPartOfEdgeMarked(points: Point[]): boolean { for (let i = 0; i < points.length - 1; i++) { if (this.isLineMarked(points[i], points[i + 1])) { return true; @@ -95,7 +95,7 @@ export class MarqueeUtil { return false; } - private isLineMarked(point1: Point, point2: Point): boolean { + protected isLineMarked(point1: Point, point2: Point): boolean { const line = new PointToPointLine(point1, point2); return ( this.pointInRect(point1) || @@ -107,11 +107,11 @@ export class MarqueeUtil { ); } - private lineIntersect(line: PointToPointLine, p1: Point, p2: Point): boolean { + protected lineIntersect(line: PointToPointLine, p1: Point, p2: Point): boolean { return line.intersection(new PointToPointLine(p1, p2)) !== undefined; } - private pointInRect(point: Point): boolean { + protected pointInRect(point: Point): boolean { const boolX = this.startPoint.x <= this.currentPoint.x ? this.isBetween(point.x, this.startPoint.x, this.currentPoint.x) @@ -123,7 +123,7 @@ export class MarqueeUtil { return boolX && boolY; } - private isElementBetweenXAxis(elementBounds: Bounds, marqueeLeft: number, marqueeRight: number): boolean { + protected isElementBetweenXAxis(elementBounds: Bounds, marqueeLeft: number, marqueeRight: number): boolean { const leftEdge = this.isBetween(elementBounds.x, marqueeLeft, marqueeRight); const rightEdge = this.isBetween(elementBounds.x + elementBounds.width, marqueeLeft, marqueeRight); if (this.marqueeBehavior.entireElement) { @@ -137,7 +137,7 @@ export class MarqueeUtil { ); } - private isElementBetweenYAxis(elementBounds: Bounds, marqueeTop: number, marqueeBottom: number): boolean { + protected isElementBetweenYAxis(elementBounds: Bounds, marqueeTop: number, marqueeBottom: number): boolean { const topEdge = this.isBetween(elementBounds.y, marqueeTop, marqueeBottom); const bottomEdge = this.isBetween(elementBounds.y + elementBounds.height, marqueeTop, marqueeBottom); if (this.marqueeBehavior.entireElement) { @@ -151,7 +151,7 @@ export class MarqueeUtil { ); } - private isBetween(x: number, lower: number, upper: number): boolean { + protected isBetween(x: number, lower: number, upper: number): boolean { return lower <= x && x <= upper; } } diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts similarity index 82% rename from packages/client/src/features/tools/marquee-mouse-tool.ts rename to packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 99b9bcd..b5d70bc 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -29,12 +29,12 @@ import { isSelectable, isSelected } from '~glsp-sprotty'; -import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; -import { RemoveMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; -import { BaseGLSPTool } from './base-glsp-tool'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; +import { BaseGLSPTool } from '../base-glsp-tool'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; +import { RemoveMarqueeAction } from './marquee-tool-feedback'; @injectable() export class MarqueeMouseTool extends BaseGLSPTool { @@ -43,7 +43,6 @@ export class MarqueeMouseTool extends BaseGLSPTool { @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; @inject(TYPES.IMarqueeBehavior) @optional() protected marqueeBehavior: IMarqueeBehavior; - protected marqueeMouseListener: MarqueeMouseListener; protected shiftKeyListener: ShiftKeyListener = new ShiftKeyListener(); get id(): string { @@ -51,16 +50,11 @@ export class MarqueeMouseTool extends BaseGLSPTool { } enable(): void { - this.marqueeMouseListener = new MarqueeMouseListener(this.domHelper, this.editorContext.modelRoot, this.marqueeBehavior); - this.mouseTool.register(this.marqueeMouseListener); - this.keyTool.register(this.shiftKeyListener); - this.dispatchFeedback([cursorFeedbackAction(CursorCSS.MARQUEE)]); - } - - disable(): void { - this.mouseTool.deregister(this.marqueeMouseListener); - this.keyTool.deregister(this.shiftKeyListener); - this.deregisterFeedback([cursorFeedbackAction()]); + this.toDisposeOnDisable.push( + this.mouseTool.registerListener(new MarqueeMouseListener(this.domHelper, this.editorContext.modelRoot, this.marqueeBehavior)), + this.keyTool.registerListener(this.shiftKeyListener), + this.registerFeedback([cursorFeedbackAction(CursorCSS.MARQUEE)], this, [cursorFeedbackAction()]) + ); } } @@ -119,7 +113,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { return []; } - override mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(_target: SModelElement, event: MouseEvent): Action[] { this.isActive = false; if (event.shiftKey) { return [RemoveMarqueeAction.create()]; diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts similarity index 98% rename from packages/client/src/features/tool-feedback/marquee-tool-feedback.ts rename to packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts index 62b944b..b1848bf 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts @@ -27,7 +27,7 @@ import { TYPES, hasObjectProp } from '~glsp-sprotty'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; +import { FeedbackCommand } from '../../../base/feedback/feedback-command'; export interface DrawMarqueeAction extends Action { kind: typeof DrawMarqueeAction.KIND; diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts similarity index 74% rename from packages/client/src/features/tools/marquee-tool.ts rename to packages/client/src/features/tools/marquee-selection/marquee-tool.ts index 7c38552..cc23c1d 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { Action, EnableToolsAction, KeyListener, KeyTool, SModelElement } from '~glsp-sprotty'; -import { BaseGLSPTool } from '../tools/base-glsp-tool'; +import { injectable } from 'inversify'; +import { Action, EnableToolsAction, KeyListener, SModelElement } from '~glsp-sprotty'; +import { BaseGLSPTool } from '../base-glsp-tool'; import { MarqueeMouseTool } from './marquee-mouse-tool'; @injectable() @@ -24,24 +24,18 @@ export class MarqueeTool extends BaseGLSPTool { protected marqueeKeyListener: MarqueeKeyListener = new MarqueeKeyListener(); - @inject(KeyTool) protected readonly keytool: KeyTool; - get id(): string { return MarqueeTool.ID; } enable(): void { - this.keyTool.register(this.marqueeKeyListener); - } - - disable(): void { - this.keyTool.deregister(this.marqueeKeyListener); + this.toDisposeOnDisable.push(this.keyTool.registerListener(this.marqueeKeyListener)); } } @injectable() export class MarqueeKeyListener extends KeyListener { - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return [EnableToolsAction.create([MarqueeMouseTool.ID])]; } diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/marquee-selection/model.ts similarity index 100% rename from packages/client/src/features/tools/model.ts rename to packages/client/src/features/tools/marquee-selection/model.ts diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx similarity index 100% rename from packages/client/src/features/tools/view.tsx rename to packages/client/src/features/tools/marquee-selection/view.tsx diff --git a/packages/client/src/features/tools/node-creation/di.config.ts b/packages/client/src/features/tools/node-creation/di.config.ts new file mode 100644 index 0000000..62da4a3 --- /dev/null +++ b/packages/client/src/features/tools/node-creation/di.config.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { NodeCreationTool } from './node-creation-tool'; + +const nodeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.ITool, NodeCreationTool); + configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); +}); + +export default nodeCreationToolModule; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts similarity index 69% rename from packages/client/src/features/tools/node-creation-tool.ts rename to packages/client/src/features/tools/node-creation/node-creation-tool.ts index 0f2840b..cd91dc0 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -18,8 +18,6 @@ import { Action, CreateNodeOperation, EnableDefaultToolsAction, - EnableToolsAction, - IActionHandler, ISnapper, SModelElement, SNode, @@ -28,44 +26,29 @@ import { findParentByFeature, isCtrlOrCmd } from '~glsp-sprotty'; -import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { getAbsolutePosition } from '../../utils/viewpoint-util'; -import { Containable, isContainable } from '../hints/model'; -import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; -import { BaseGLSPTool } from './base-glsp-tool'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { getAbsolutePosition } from '../../../utils/viewpoint-util'; +import { Containable, isContainable } from '../../hints/model'; +import { BaseGLSPCreationTool } from '../base-glsp-creation-tool'; @injectable() -export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { +export class NodeCreationTool extends BaseGLSPCreationTool { static ID = 'tool_create_node'; - @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + protected isTriggerAction = TriggerNodeCreationAction.is; - protected creationToolMouseListener: NodeCreationToolMouseListener; - protected triggerAction: TriggerNodeCreationAction; + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; get id(): string { return NodeCreationTool.ID; } - enable(): void { - if (this.triggerAction === undefined) { - throw new TypeError(`Could not enable tool ${this.id}.The triggerAction cannot be undefined.`); - } - this.creationToolMouseListener = new NodeCreationToolMouseListener(this.triggerAction, this); - this.mouseTool.register(this.creationToolMouseListener); - this.dispatchFeedback([cursorFeedbackAction(CursorCSS.NODE_CREATION)]); - } - - disable(): void { - this.mouseTool.deregister(this.creationToolMouseListener); - this.deregisterFeedback([cursorFeedbackAction()]); - } - - handle(action: Action): Action | void { - if (TriggerNodeCreationAction.is(action)) { - this.triggerAction = action; - return EnableToolsAction.create([this.id]); - } + doEnable(): void { + this.toDisposeOnDisable.push( + this.mouseTool.registerListener(new NodeCreationToolMouseListener(this.triggerAction, this)), + this.registerFeedback([cursorFeedbackAction(CursorCSS.NODE_CREATION)], this, [cursorFeedbackAction()]) + ); } } @@ -111,7 +94,7 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { const feedback = this.creationAllowed(this.elementTypeId) ? cursorFeedbackAction(CursorCSS.NODE_CREATION) : cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED); - this.tool.dispatchFeedback([feedback]); + this.tool.registerFeedback([feedback]); } return []; } diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 367fb90..b1780dc 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -16,7 +16,6 @@ import { inject, injectable } from 'inversify'; import { Action, - ActionDispatcher, BoundsAware, CenterAction, IActionHandler, @@ -38,6 +37,7 @@ import { isSelectable, matchesKeystroke } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { SelectionService } from '../../base/selection-service'; import { MarkerPredicates, collectIssueMarkers } from '../../utils/marker'; import { getElements, isSelectableAndBoundsAware } from '../../utils/smodel-util'; @@ -168,7 +168,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { protected selectionService: SelectionService; @inject(TYPES.IActionDispatcher) - protected actionDispatcher: ActionDispatcher; + protected actionDispatcher: GLSPActionDispatcher; handle(action: NavigateToMarkerAction): void { const selected = this.getSelectedElements(action); diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index ac80a0a..b5e6dcc 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -30,8 +30,8 @@ import { hasArrayProp } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; -import { FeedbackCommand } from '../../base/feedback/feeback-command'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { removeCssClasses } from '../../utils/smodel-util'; import { GIssueMarker, createSIssue, getOrCreateSIssueMarker, getSIssueMarker, getSeverity } from './issue-marker'; diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 7b073f0..4cdd4fd 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -23,7 +23,7 @@ import { ScrollMouseListener, SModelElement } from '~glsp-sprotty'; -import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; +import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; @injectable() export class GLSPScrollMouseListener extends ScrollMouseListener implements IActionHandler { diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7773c8d..8307eb5 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -28,13 +28,19 @@ import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; import layoutModule from './features/layout/di.config'; import navigationModule from './features/navigation/di.config'; +import glspRoutingModule from './features/routing/di.config'; import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; import sourceModelWatcherModule from './features/source-model-watcher/di.config'; import svgMetadataModule from './features/svg-metadata/di.config'; -import toolFeedbackModule from './features/tool-feedback/di.config'; import paletteModule from './features/tool-palette/di.config'; -import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; +import changeBoundsToolModule from './features/tools/change-bounds/di.config'; +import deletionToolModule from './features/tools/deletion/di.config'; +import edgeCreationToolModule from './features/tools/edge-creation/di.config'; +import edgeEditToolModule from './features/tools/edge-edit/di.config'; +import enableDefaultToolsOnFocusLossModule from './features/tools/enable-default-tools-on-focus-loss'; +import marqueeSelectionToolModule from './features/tools/marquee-selection/di.config'; +import nodeCreationToolModule from './features/tools/node-creation/di.config'; import { markerNavigatorContextMenuModule, markerNavigatorModule, validationModule } from './features/validation/di.config'; import glspViewportModule from './features/viewport/di.config'; @@ -49,8 +55,8 @@ export * from './base/command-stack'; export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context-service'; -export * from './base/feedback/feeback-command'; export * from './base/feedback/feedback-action-dispatcher'; +export * from './base/feedback/feedback-command'; export * from './base/feedback/update-model-command'; export * from './base/focus-tracker'; export * from './base/model-initialization-constraint'; @@ -66,6 +72,7 @@ export * from './container-modules'; // // ------------------ Features ------------------ +export * from './base/feedback/css-feedback'; export * from './base/view/mouse-tool'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; @@ -97,30 +104,32 @@ export * from './features/layout/layout-elements-action'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; export * from './features/reconnect/model'; +export * from './features/routing/glsp-manhattan-edge-router'; export * from './features/save/model'; export * from './features/save/save-keylistener'; export * from './features/select/select-feedback-command'; export * from './features/select/select-mouse-listener'; export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/svg-metadata/metadata-placer'; -export * from './features/tool-feedback/change-bounds-tool-feedback'; -export * from './features/tool-feedback/creation-tool-feedback'; -export * from './features/tool-feedback/css-feedback'; -export * from './features/tool-feedback/edge-edit-tool-feedback'; -export * from './features/tool-feedback/marquee-tool-feedback'; -export * from './features/tool-feedback/view'; export * from './features/tool-palette/tool-palette'; export * from './features/tools/base-glsp-tool'; -export * from './features/tools/change-bounds-tool'; -export * from './features/tools/delete-tool'; -export { configureMarqueeTool } from './features/tools/di.config'; -export * from './features/tools/edge-creation-tool'; -export * from './features/tools/edge-edit-tool'; +export * from './features/tools/change-bounds/change-bounds-tool'; +export * from './features/tools/change-bounds/change-bounds-tool-feedback'; +export * from './features/tools/change-bounds/view'; +export * from './features/tools/deletion/delete-tool'; +export * from './features/tools/edge-creation/edge-creation-tool'; +export * from './features/tools/edge-creation/edge-creation-tool-feedback'; +export * from './features/tools/edge-creation/view'; +export * from './features/tools/edge-edit/edge-edit-tool'; +export * from './features/tools/edge-edit/edge-edit-tool-feedback'; export * from './features/tools/enable-default-tools-on-focus-loss'; -export * from './features/tools/glsp-manhattan-edge-router'; -export * from './features/tools/marquee-behavior'; -export * from './features/tools/model'; -export * from './features/tools/node-creation-tool'; +export * from './features/tools/marquee-selection/marquee-behavior'; +export * from './features/tools/marquee-selection/marquee-mouse-tool'; +export * from './features/tools/marquee-selection/marquee-tool'; +export * from './features/tools/marquee-selection/marquee-tool-feedback'; +export * from './features/tools/marquee-selection/model'; +export * from './features/tools/marquee-selection/view'; +export * from './features/tools/node-creation/node-creation-tool'; export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; @@ -140,8 +149,12 @@ export * from './utils/viewpoint-util'; export * from './views'; // ------------------ DI Modules ------------------ export { + changeBoundsToolModule, copyPasteContextMenuModule, defaultGLSPModule, + deletionToolModule, + edgeCreationToolModule, + edgeEditToolModule, enableDefaultToolsOnFocusLossModule, glspAccessibilityModule, glspCommandPaletteModule, @@ -151,6 +164,7 @@ export { glspHoverModule, glspMoveZoomModule, glspResizeKeyModule, + glspRoutingModule, glspSearchPaletteModule, glspSelectModule, glspServerCopyPasteModule, @@ -159,13 +173,13 @@ export { layoutModule, markerNavigatorContextMenuModule, markerNavigatorModule, + marqueeSelectionToolModule, modelHintsModule, navigationModule, + nodeCreationToolModule, paletteModule, saveModule, sourceModelWatcherModule, svgMetadataModule, - toolFeedbackModule, - toolsModule, validationModule }; diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 3f71010..5bbc3ef 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -19,13 +19,16 @@ import { BoundsAware, Dimension, Point, + SChildElement, SModelElement, Viewport, findParentByFeature, isAlignable, + isBoundsAware, isViewport, translateBounds } from '~glsp-sprotty'; +import { BoundsAwareModelElement } from './smodel-util'; /** * Return the position corresponding to this mouse event (Browser coordinates) @@ -98,3 +101,32 @@ export function toAbsolutePosition(target: SModelElement & BoundsAware): Point { export function toAbsoluteSize(target: SModelElement & BoundsAware): Dimension { return toAbsoluteBounds(target); } + +/** + * Convert a point, specified in absolute coordinates, to a point relative + * to the parent of the specified child element. + * + * @param element the child element + * @param absolutePoint a point in absolute coordinates + * @returns the equivalent point, relative to the element's parent coordinates + */ +export function absoluteToParent(element: BoundsAwareModelElement & SChildElement, absolutePoint: Point): Point { + if (isBoundsAware(element.parent)) { + return absoluteToLocal(element.parent, absolutePoint); + } + // If the parent is not bounds-aware, assume it's at 0; 0 and proceed + return absoluteToLocal(element, absolutePoint); +} + +/** + * Convert a point, specified in absolute coordinates, to a point relative + * to the specified element. + * + * @param element the element + * @param absolutePoint a point in absolute coordinates + * @returns the equivalent point, relative to the element's coordinates + */ +export function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: Point): Point { + const absoluteElementBounds = toAbsoluteBounds(element); + return { x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y }; +} From 277e42aba323f254267870c5b539b7feaf4d076c Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 13 Jul 2023 15:09:42 +0200 Subject: [PATCH 230/566] Add real progress reporting in favor of timeout (#265) * Add real progress reporting in favor of timeout * Remove `ServerMessageAction.timeout` * Add actions for reporting progress https://github.com/eclipse-glsp/glsp/issues/1019 --- .../copy-paste/copy-paste-context-menu.ts | 2 +- .../navigation/navigation-action-handler.ts | 2 +- .../source-model-changed-action-handler.ts | 2 +- .../src/model-source/glsp-diagram-server.ts | 8 +- .../client-notification.spec.ts | 117 ++++++++++++++++- .../action-protocol/client-notification.ts | 121 +++++++++++++++++- 6 files changed, 235 insertions(+), 17 deletions(-) diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 03d6eed..9069715 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -85,7 +85,7 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { const severity = 'WARNING'; this.dispatcher.dispatchAll([ ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity, timeout }) + ServerMessageAction.create(message, { severity }) ]); } } diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 9867a69..11e0c3b 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -268,7 +268,7 @@ export class NavigationActionHandler implements IActionHandler { const timeout = this.notificationTimeout; this.dispatcher.dispatchAll([ ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity, timeout }) + ServerMessageAction.create(message, { severity }) ]); } } diff --git a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts index 8577fd1..8cd79a8 100644 --- a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts +++ b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts @@ -70,7 +70,7 @@ export class SourceModelChangedActionHandler implements IActionHandler { const severity = 'WARNING'; this.dispatcher.dispatchAll([ ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity, timeout }) + ServerMessageAction.create(message, { severity }) ]); } } diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 118789a..07a81f8 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -20,6 +20,7 @@ import { ActionMessage, ComputedBoundsAction, DiagramServerProxy, + EndProgressAction, ExportSvgAction, GLSPClient, ICommand, @@ -27,7 +28,9 @@ import { ServerMessageAction, ServerStatusAction, SetEditModeAction, - SwitchEditModeCommand + StartProgressAction, + SwitchEditModeCommand, + UpdateProgressAction } from '~glsp-sprotty'; import { SourceUriAware } from '../base/source-uri-aware'; @@ -108,6 +111,9 @@ export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry registry.register(ServerMessageAction.KIND, diagramServer); registry.register(ServerStatusAction.KIND, diagramServer); registry.register(ExportSvgAction.KIND, diagramServer); + registry.register(StartProgressAction.KIND, diagramServer); + registry.register(UpdateProgressAction.KIND, diagramServer); + registry.register(EndProgressAction.KIND, diagramServer); // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. // We don't support SwitchEditMode, but Sprotty still sends those actions, so ignore them. diff --git a/packages/protocol/src/action-protocol/client-notification.spec.ts b/packages/protocol/src/action-protocol/client-notification.spec.ts index 4ad2652..bfd0bbf 100644 --- a/packages/protocol/src/action-protocol/client-notification.spec.ts +++ b/packages/protocol/src/action-protocol/client-notification.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,13 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { ServerMessageAction, ServerStatusAction } from './client-notification'; +import { + EndProgressAction, + ServerMessageAction, + ServerStatusAction, + StartProgressAction, + UpdateProgressAction +} from './client-notification'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. @@ -79,11 +85,110 @@ describe('Client notification actions', () => { kind: ServerMessageAction.KIND, message: 'someMessage', details: 'details', - severity: 'ERROR', - timeout: 5 + severity: 'ERROR' + }; + const { message, severity, details } = expected; + expect(ServerMessageAction.create(message, { severity, details })).to.deep.equals(expected); + }); + }); + }); + + describe('StartProgressAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const messageAction: StartProgressAction = { kind: 'startProgress', progressId: '1', title: 'Progress title' }; + expect(StartProgressAction.is(messageAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(StartProgressAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(StartProgressAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const progressId = '1'; + const title = 'Progress title'; + const expected: StartProgressAction = { kind: StartProgressAction.KIND, progressId, title }; + expect(StartProgressAction.create({ progressId, title })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: StartProgressAction = { + kind: StartProgressAction.KIND, + progressId: '1', + title: 'Progress title', + message: 'Some message', + percentage: 10 + }; + const { progressId, title, message, percentage } = expected; + expect(StartProgressAction.create({ progressId, title, message, percentage })).to.deep.equals(expected); + }); + }); + }); + + describe('UpdateProgressAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const messageAction: UpdateProgressAction = { kind: 'updateProgress', progressId: '1' }; + expect(UpdateProgressAction.is(messageAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(UpdateProgressAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(UpdateProgressAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const progressId = '1'; + const expected: UpdateProgressAction = { kind: UpdateProgressAction.KIND, progressId }; + expect(UpdateProgressAction.create(progressId)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: UpdateProgressAction = { + kind: UpdateProgressAction.KIND, + progressId: '1', + message: 'Some message', + percentage: 10 + }; + const { progressId, message, percentage } = expected; + expect(UpdateProgressAction.create(progressId, { message, percentage })).to.deep.equals(expected); + }); + }); + }); + + describe('EndProgressAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const messageAction: EndProgressAction = { kind: 'endProgress', progressId: '1' }; + expect(EndProgressAction.is(messageAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(EndProgressAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(EndProgressAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const progressId = '1'; + const expected: EndProgressAction = { kind: EndProgressAction.KIND, progressId, message: undefined }; + expect(EndProgressAction.create(progressId)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: EndProgressAction = { + kind: EndProgressAction.KIND, + progressId: '1', + message: 'Some message' }; - const { message, severity, timeout, details } = expected; - expect(ServerMessageAction.create(message, { severity, timeout, details })).to.deep.equals(expected); + const { progressId, message } = expected; + expect(EndProgressAction.create(progressId, message)).to.deep.equals(expected); }); }); }); diff --git a/packages/protocol/src/action-protocol/client-notification.ts b/packages/protocol/src/action-protocol/client-notification.ts index ce2c3b1..13efc41 100644 --- a/packages/protocol/src/action-protocol/client-notification.ts +++ b/packages/protocol/src/action-protocol/client-notification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -84,11 +84,6 @@ export interface ServerMessageAction extends Action { * Further details on the message. */ details?: string; - - /** - * Timeout after which a displayed message disappears. - */ - timeout?: number; } export namespace ServerMessageAction { @@ -103,7 +98,6 @@ export namespace ServerMessageAction { options: { severity?: ServerSeverity; details?: string; - timeout?: number; } = {} ): ServerMessageAction { return { @@ -114,3 +108,116 @@ export namespace ServerMessageAction { }; } } + +/** + * Sent by the server to the client to request presenting the progress of a long running process in the UI. + */ +export interface StartProgressAction extends Action { + kind: typeof StartProgressAction.KIND; + + /** + * An ID that can be used in subsequent `updateProgress` and `endProgress` events to make them refer to the same progress reporting. + */ + progressId: string; + /** + * Short title of the progress reporting. Shown in the UI to describe the long running process. + */ + title: string; + /** + * Optional additional progress message. Shown in the UI to describe the long running process. + */ + message?: string; + /** + * Progress percentage to display (value range: 0 to 100). If omitted no percentage is shown. + */ + percentage?: number; +} + +export namespace StartProgressAction { + export const KIND = 'startProgress'; + + export function is(object: any): object is StartProgressAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId') && hasStringProp(object, 'title'); + } + + export function create(options: { progressId: string; title: string; message?: string; percentage?: number }): StartProgressAction { + return { + kind: KIND, + ...options + }; + } +} + +/** + * Sent by the server to the client to presenting an update of the progress of a long running process in the UI. + */ +export interface UpdateProgressAction extends Action { + kind: typeof UpdateProgressAction.KIND; + + /** + * The ID of the progress reporting to update. + */ + progressId: string; + /** + * The message to show in the progress reporting. + */ + message?: string; + /** + * The percentage (value range: 0 to 100) to show in the progress reporting. + */ + percentage?: number; +} + +export namespace UpdateProgressAction { + export const KIND = 'updateProgress'; + + export function is(object: any): object is UpdateProgressAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId'); + } + + export function create( + progressId: string, + options: { + message?: string; + percentage?: number; + } = {} + ): UpdateProgressAction { + return { + kind: KIND, + progressId, + ...options + }; + } +} + +/** + * Sent by the server to the client to end the reporting of a progress. + */ +export interface EndProgressAction extends Action { + kind: typeof EndProgressAction.KIND; + + /** + * The ID of the progress reporting to update. + */ + progressId: string; + /** + * The message to show in the progress reporting. + */ + message?: string; +} + +export namespace EndProgressAction { + export const KIND = 'endProgress'; + + export function is(object: any): object is EndProgressAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId'); + } + + export function create(progressId: string, message?: string): EndProgressAction { + return { + kind: KIND, + progressId, + message + }; + } +} From 7845290de98799b27faaa874545995346e1fe3aa Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 13 Jul 2023 20:51:26 +0200 Subject: [PATCH 231/566] Add support for server progress reporting (#52) https://github.com/eclipse-glsp/glsp/issues/1019 --- .../server/src/common/di/diagram-module.ts | 27 +++++-- .../model/request-model-action-handler.ts | 31 ++++---- .../features/progress/progress-service.ts | 74 +++++++++++++++++++ 3 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 packages/server/src/common/features/progress/progress-service.ts diff --git a/packages/server/src/common/di/diagram-module.ts b/packages/server/src/common/di/diagram-module.ts index fdc1f61..0beb575 100644 --- a/packages/server/src/common/di/diagram-module.ts +++ b/packages/server/src/common/di/diagram-module.ts @@ -16,6 +16,7 @@ import { CenterAction, DeleteMarkersAction, + EndProgressAction, ExportSvgAction, FitToScreenAction, NavigateToExternalTargetAction, @@ -39,9 +40,11 @@ import { SetTypeHintsAction, SetViewportAction, SourceModelChangedAction, + StartProgressAction, TriggerEdgeCreationAction, TriggerNodeCreationAction, - UpdateModelAction + UpdateModelAction, + UpdateProgressAction } from '@eclipse-glsp/protocol'; import { interfaces } from 'inversify'; import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dispatcher'; @@ -86,6 +89,7 @@ import { RequestNavigationTargetsActionHandler } from '../features/navigation/re import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler'; import { PopupModelFactory } from '../features/popup/popup-model-factory'; import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; +import { DefaultProgressService, ProgressService } from '../features/progress/progress-service'; import { ModelValidator } from '../features/validation/model-validator'; import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; import { CompoundOperationHandler } from '../operations/compound-operation-handler'; @@ -93,7 +97,7 @@ import { OperationActionHandler } from '../operations/operation-action-handler'; import { OperationHandlerConstructor, OperationHandlerFactory } from '../operations/operation-handler'; import { OperationHandlerRegistry, OperationHandlerRegistryInitializer } from '../operations/operation-handler-registry'; import { ClientSessionInitializer } from '../session/client-session-initializer'; -import { applyBindingTarget, applyOptionalBindingTarget, BindingTarget } from './binding-target'; +import { BindingTarget, applyBindingTarget, applyOptionalBindingTarget } from './binding-target'; import { GLSPModule } from './glsp-module'; import { InstanceMultiBinding, MultiBinding } from './multi-binding'; import { @@ -133,6 +137,7 @@ import { * - {@link ContextActionsProviders} as {@link ClassMultiBinding} (empty) * - {@link ContextActionsProviderRegistry} * - {@link ActionDispatcher} + * - {@link ProgressService} * - {@link ClientActionKinds} as {@link InstanceMultiBinding} * - {@link ActionHandler} as {@link InstanceMultiBinding} * - {@link ActionHandlerFactory} @@ -217,6 +222,7 @@ export abstract class DiagramModule extends GLSPModule { this.configureMultiBinding(new MultiBinding(ClientSessionInitializer), binding => this.configureClientSessionInitializers(binding) ); + applyBindingTarget(context, ProgressService, this.bindProgressService()).inSingletonScope(); applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory()); applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine?.()); } @@ -308,6 +314,10 @@ export abstract class DiagramModule extends GLSPModule { binding.add(LayoutOperationHandler); } + protected bindProgressService(): BindingTarget { + return DefaultProgressService; + } + protected configureContextActionProviders(binding: MultiBinding): void { // empty as default } @@ -322,16 +332,17 @@ export abstract class DiagramModule extends GLSPModule { protected configureClientActions(binding: InstanceMultiBinding): void { binding.add(CenterAction.KIND); - binding.add(ExportSvgAction.KIND); binding.add(DeleteMarkersAction.KIND); + binding.add(EndProgressAction.KIND); + binding.add(ExportSvgAction.KIND); binding.add(FitToScreenAction.KIND); - binding.add(SourceModelChangedAction.KIND); - binding.add(NavigateToTargetAction.KIND); binding.add(NavigateToExternalTargetAction.KIND); + binding.add(NavigateToTargetAction.KIND); binding.add(RequestBoundsAction.KIND); binding.add(SelectAction.KIND); binding.add(SelectAllAction.KIND); binding.add(ServerMessageAction.KIND); + binding.add(ServerStatusAction.KIND); binding.add(SetBoundsAction.KIND); binding.add(SetClipboardDataAction.KIND); binding.add(SetContextActions.KIND); @@ -345,10 +356,12 @@ export abstract class DiagramModule extends GLSPModule { binding.add(SetResolvedNavigationTargetAction.KIND); binding.add(SetTypeHintsAction.KIND); binding.add(SetViewportAction.KIND); - binding.add(ServerStatusAction.KIND); - binding.add(TriggerNodeCreationAction.KIND); + binding.add(SourceModelChangedAction.KIND); + binding.add(StartProgressAction.KIND); binding.add(TriggerEdgeCreationAction.KIND); + binding.add(TriggerNodeCreationAction.KIND); binding.add(UpdateModelAction.KIND); + binding.add(UpdateProgressAction.KIND); } protected bindContextActionsProviderRegistry(): BindingTarget { diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 507e0bb..a08aa0a 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, RequestModelAction, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; +import { Action, RequestModelAction, ServerStatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../../actions/action-dispatcher'; import { ActionHandler } from '../../actions/action-handler'; import { Logger } from '../../utils/logger'; +import { ProgressMonitor, ProgressService } from '../progress/progress-service'; import { ModelState } from './model-state'; import { ModelSubmissionHandler } from './model-submission-handler'; import { SourceModelStorage } from './source-model-storage'; @@ -41,27 +42,27 @@ export class RequestModelActionHandler implements ActionHandler { @inject(ModelSubmissionHandler) protected submissionHandler: ModelSubmissionHandler; + @inject(ProgressService) + protected progressService: ProgressService; + async execute(action: RequestModelAction): Promise { this.logger.debug('Execute RequestModelAction:', action); this.modelState.setAll(action.options ?? {}); - this.notifyClient('Model loading in progress'); + const progress = this.reportModelLoading('Model loading in progress'); await this.sourceModelStorage.loadSourceModel(action); - // Clear the previous notification. - this.notifyClient(); + this.reportModelLoadingFinished(progress); + return this.submissionHandler.submitModel(); } - /** - * Send a message and status notification with the given message to the client. - * An empty message is an indication for the client to clear previously received notifications. - * @param message The message that should be sent to the client - */ - protected notifyClient(message = ''): void { - const severity = message.length > 0 ? 'INFO' : 'NONE'; - this.actionDispatcher.dispatchAll( - ServerMessageAction.create(message, { severity }), - ServerStatusAction.create(message, { severity }) - ); + protected reportModelLoading(message: string): ProgressMonitor { + this.actionDispatcher.dispatch(ServerStatusAction.create(message, { severity: 'INFO' })); + return this.progressService.start(message); + } + + protected reportModelLoadingFinished(monitor: ProgressMonitor): void { + this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); + monitor.end(); } } diff --git a/packages/server/src/common/features/progress/progress-service.ts b/packages/server/src/common/features/progress/progress-service.ts new file mode 100644 index 0000000..b14f2bc --- /dev/null +++ b/packages/server/src/common/features/progress/progress-service.ts @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { EndProgressAction, StartProgressAction, UpdateProgressAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import * as uuid from 'uuid'; +import { ActionDispatcher } from '../../actions/action-dispatcher'; + +export const ProgressService = Symbol('ProgressService'); + +/** + * Service for starting and monitoring progress reporting to the client. + */ +export interface ProgressService { + /** + * Start a progress reporting. + * @param title The title shown in the UI for the progress reporting. + * @param options Additional optional options for the progress reporting. + * @returns a monitor to update and end the progress reporting later. + */ + start(title: string, options?: ProgressOptions): ProgressMonitor; +} + +/** + * Optional progress reporting options. + */ +export interface ProgressOptions { + /** A message shown in the UI. */ + message?: string; + /* The percentage (value range: 0 to 100) to show in the progress reporting. */ + percentage?: number; +} + +/** + * The monitor of a progress reporting, which can be used to update and end the reporting. + */ +export interface ProgressMonitor { + /** + * Updates an ongoing progress reporting. + * @param options Updated message and/or percentage (value range: 0 to 100). + */ + update(options: { message?: string; percentage?: number }): void; + /** + * Ends an ongoing progress reporting. + */ + end(): void; +} + +@injectable() +export class DefaultProgressService implements ProgressService { + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + + start(title: string, options?: ProgressOptions): ProgressMonitor { + const progressId = uuid.v4(); + this.actionDispatcher.dispatch(StartProgressAction.create({ progressId, title, ...options })); + return { + update: updateOptions => this.actionDispatcher.dispatch(UpdateProgressAction.create(progressId, updateOptions)), + end: () => this.actionDispatcher.dispatch(EndProgressAction.create(progressId)) + }; + } +} From abbaf63e053f15ceb3b58bc39a79e0f59beaa53a Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 14 Jul 2023 18:42:39 +0200 Subject: [PATCH 232/566] Distinguish between live and batch validation (#53) https://github.com/eclipse-glsp/glsp/issues/980 Co-authored-by: Tobias Ortmayr --- .../common/marker/workflow-model-validator.ts | 31 ++++---- .../model/model-submission-handler.ts | 12 +++ .../features/validation/model-validator.ts | 78 ++++++++++++++++++- .../validation/request-markers-handler.ts | 14 +--- 4 files changed, 107 insertions(+), 28 deletions(-) diff --git a/examples/workflow-server/src/common/marker/workflow-model-validator.ts b/examples/workflow-server/src/common/marker/workflow-model-validator.ts index dccf1d4..05b9949 100644 --- a/examples/workflow-server/src/common/marker/workflow-model-validator.ts +++ b/examples/workflow-server/src/common/marker/workflow-model-validator.ts @@ -13,34 +13,35 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GCompartment, GLabel, GModelElement, Marker, MarkerKind, ModelState, ModelValidator } from '@eclipse-glsp/server'; +import { AbstractModelValidator, GCompartment, GLabel, GModelElement, Marker, MarkerKind, ModelState } from '@eclipse-glsp/server'; import { inject, injectable } from 'inversify'; import { ActivityNode, TaskNode } from '../graph-extension'; @injectable() -export class WorkflowModelValidator implements ModelValidator { +export class WorkflowModelValidator extends AbstractModelValidator { @inject(ModelState) protected readonly modelState: ModelState; - validate(elements: GModelElement[]): Marker[] { + override doLiveValidation(element: GModelElement): Marker[] { const markers: Marker[] = []; - for (const element of elements) { - if (element instanceof TaskNode) { - markers.push(...this.validateTaskNode(element)); - } else if (element instanceof ActivityNode) { - if (element.nodeType === 'decisionNode') { - markers.push(...this.validateDecisionNode(element)); - } else if (element.nodeType === 'mergeNode') { - markers.push(...this.validateMergeNode(element)); - } - } - if (element.children) { - markers.push(...this.validate(element.children)); + if (element instanceof ActivityNode) { + if (element.nodeType === 'decisionNode') { + markers.push(...this.validateDecisionNode(element)); + } else if (element.nodeType === 'mergeNode') { + markers.push(...this.validateMergeNode(element)); } } return markers; } + override doBatchValidation(element: GModelElement): Marker[] { + const markers: Marker[] = []; + if (element instanceof TaskNode) { + markers.push(...this.validateTaskNode(element)); + } + return markers; + } + protected validateTaskNode(taskNode: TaskNode): Marker[] { const markers: Marker[] = []; const automated = this.validateTaskNode_isAutomated(taskNode); diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index b345fdb..b8cb290 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -17,16 +17,20 @@ import { GModelRootSchema } from '@eclipse-glsp/graph'; import { Action, DirtyStateChangeReason, + MarkersReason, MaybePromise, RequestBoundsAction, SetDirtyStateAction, + SetMarkersAction, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; + import { inject, injectable, optional } from 'inversify'; import { CommandStack } from '../../command/command-stack'; import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; import { LayoutEngine } from '../layout/layout-engine'; +import { ModelValidator } from '../validation/model-validator'; import { GModelFactory } from './gmodel-factory'; import { GModelSerializer } from './gmodel-serializer'; import { ModelState } from './model-state'; @@ -52,6 +56,10 @@ export class ModelSubmissionHandler { @inject(CommandStack) protected commandStack: CommandStack; + @inject(ModelValidator) + @optional() + protected validator?: ModelValidator; + /** * Returns a list of actions to update the client-side model, based on the specified modelState. * @@ -101,6 +109,10 @@ export class ModelSubmissionHandler { if (!this.diagramConfiguration.needsClientLayout) { result.push(SetDirtyStateAction.create(this.commandStack.isDirty, { reason })); } + if (this.validator) { + const markers = await this.validator.validate([this.modelState.root], MarkersReason.LIVE); + result.push(SetMarkersAction.create(markers, { reason: MarkersReason.LIVE })); + } return result; } diff --git a/packages/server/src/common/features/validation/model-validator.ts b/packages/server/src/common/features/validation/model-validator.ts index 707d2cc..5c7daa9 100644 --- a/packages/server/src/common/features/validation/model-validator.ts +++ b/packages/server/src/common/features/validation/model-validator.ts @@ -14,7 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GModelElement } from '@eclipse-glsp/graph'; -import { Marker, MaybePromise } from '@eclipse-glsp/protocol'; +import { Marker, MarkersReason, MaybePromise } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; export const ModelValidator = Symbol('ModelValidator'); @@ -28,7 +29,78 @@ export interface ModelValidator { * Validates the given list of {@link GModelElement}s and returns a list of {@link Marker}s. * * @param elements The list of {@link GModelElement} to validate. - * @returns A list of {@link Marker}s for the validated {@link GModelElement}s. + * @param reason The reason for a validation request, such as "batch" or "live" validation. + * @return A list of {@link Marker}s for the validated {@link GModelElement}s. */ - validate(elements: GModelElement[]): MaybePromise; + validate(elements: GModelElement[], reason?: string): MaybePromise; +} + +@injectable() +export abstract class AbstractModelValidator implements ModelValidator { + validate(elements: GModelElement[], reason: string): Marker[] { + const markers: Marker[] = []; + for (const element of elements) { + if (MarkersReason.LIVE === reason) { + markers.push(...this.doLiveValidation(element)); + } else if (MarkersReason.BATCH === reason) { + markers.push(...this.doBatchValidation(element)); + } else { + markers.push(...this.doValidationForCustomReason(element)); + } + if (element.children) { + markers.push(...this.validate(element.children, reason)); + } + } + return markers; + } + + /** + * Perform the live validation rules for the given element. + * + * This will be invoked on start and after each operation for all elements. + * Thus, the validation should be rather inexpensive. + * There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke + * this method for all children anyway. + * + * @param element The element to validate. + * @return A list of {@link Marker}s for the validated {@link GModelElement}. + */ + doLiveValidation(element: GModelElement): Marker[] { + return []; + } + + /** + * Perform the batch validation rules for the given element. + * + *

+ * This will be invoked on demand by the client. + * Thus, the validation can include more expensive validation rules. + * There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke + * this method for all children anyway. + *

+ * + * @param element The element to validate. + * @return A list of {@link Marker}s for the validated {@link GModelElement}. + */ + doBatchValidation(element: GModelElement): Marker[] { + return []; + } + + /** + * Perform a validation for a custom reason with the given element. + * + *

+ * GLSP editors may add custom reasons for triggering a validation, other than live and + * batch. + * Validation requests that are not live or batch validations will be handled by this method. + * There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke + * this method for all children anyway. + *

+ * + * @param element The element to validate. + * @return A list of {@link Marker}s for the validated {@link GModelElement}. + */ + doValidationForCustomReason(element: GModelElement): Marker[] { + return []; + } } diff --git a/packages/server/src/common/features/validation/request-markers-handler.ts b/packages/server/src/common/features/validation/request-markers-handler.ts index afa9ebb..83f4222 100644 --- a/packages/server/src/common/features/validation/request-markers-handler.ts +++ b/packages/server/src/common/features/validation/request-markers-handler.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GGraph } from '@eclipse-glsp/graph'; -import { Action, Marker, RequestMarkersAction, SetMarkersAction } from '@eclipse-glsp/protocol'; +import { Action, MarkersReason, RequestMarkersAction, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; import { GLSPServerError } from '../../utils/glsp-server-error'; @@ -38,14 +37,9 @@ export class RequestMarkersHandler implements ActionHandler { elementIDs = [this.modelState.root.id]; } - let markers: Marker[] = []; - const currentModelIndex = this.modelState.index; - for (const elementID of elementIDs) { - const modelElement = currentModelIndex.findByClass(elementID, GGraph); - if (modelElement) { - markers = markers.concat(await this.validator.validate([modelElement])); - } - } + const modelElements = this.modelState.index.getAll(elementIDs); + const markers = await this.validator.validate(modelElements, action.reason ?? MarkersReason.BATCH); + return [SetMarkersAction.create(markers)]; } } From 2c317000be64b5e4e4e1be57334afa9160e07129 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sat, 15 Jul 2023 01:24:58 +0200 Subject: [PATCH 233/566] GLSP-1054: Rename di.config modules (#266) * GLSP-1054: Rename di.config modules - Rename `di.config.ts` files to `xyz-module.ts` - Remove default export from di.config files - Remove inconsistent prefixing of modules with `glsp`. All glsp modules are now unprefixed, if a module name conflicts with an existing sprotty module, the sprotty module is reexported with a `sprotty` prefix - Aligned/renamed some module names Also: - Move `focus-change-action` and `focus-tracker` to `focus` subdirectory Fixes https://github.com/eclipse-glsp/glsp/issues/1054 --- examples/workflow-glsp/src/index.ts | 2 +- ...i.config.ts => workflow-diagram-module.ts} | 4 +- .../src/base/{di.config.ts => base-module.ts} | 8 +- .../focus-state-change-action.ts} | 0 .../src/base/{ => focus}/focus-tracker.ts | 2 +- packages/client/src/container-modules.ts | 82 +++++++------- .../src/features/accessibility/README.md | 14 +-- .../{di.config.ts => accessibility-module.ts} | 10 +- .../{di.config.ts => move-zoom-module.ts} | 2 +- .../{di.config.ts => resize-key-module.ts} | 2 +- ...{di.config.ts => search-palette-module.ts} | 2 +- ...{di.config.ts => view-key-tools-module.ts} | 4 +- .../bounds/{di.config.ts => bounds-module.ts} | 4 +- ...di.config.ts => command-palette-module.ts} | 4 +- .../{di.config.ts => context-menu-module.ts} | 4 +- ...rvice-aware-context-menu-mouse-listener.ts | 2 +- .../{di.config.ts => copy-paste-modules.ts} | 4 +- .../{di.config.ts => decoration-module.ts} | 4 +- .../export/{di.config.ts => export-module.ts} | 4 +- .../{di.config.ts => type-hints-module.ts} | 4 +- .../hover/{di.config.ts => hover-module.ts} | 6 +- packages/client/src/features/hover/hover.ts | 2 +- .../edit-label-tool.ts | 0 .../edit-label-validator.ts | 0 .../label-edit-module.ts} | 4 +- .../layout/{di.config.ts => layout-module.ts} | 4 +- .../{di.config.ts => navigation-module.ts} | 4 +- .../client/src/features/routing/di.config.ts | 82 -------------- .../src/features/routing/routing-module.ts | 59 ++++++++++ .../save/{di.config.ts => save-module.ts} | 4 +- .../select/{di.config.ts => select-module.ts} | 4 +- ...onfig.ts => source-model-wacher-module.ts} | 4 +- .../{di.config.ts => svg-metadata-module.ts} | 4 +- .../{di.config.ts => tool-palette-module.ts} | 4 +- .../src/features/tool-palette/tool-palette.ts | 2 +- ...onfig.ts => change-boounds-tool-module.ts} | 6 +- .../{di.config.ts => deletion-tool-module.ts} | 4 +- ...{di.config.ts => edege-creation-module.ts} | 4 +- .../{di.config.ts => edge-edit-module.ts} | 6 +- ....config.ts => marquee-selection-module.ts} | 4 +- .../{di.config.ts => node-creation-module.ts} | 4 +- ...ocus-loss.ts => tool-focus-loss-module.ts} | 7 +- .../validation/marker-navigator.spec.ts | 4 +- .../{di.config.ts => validation-module.ts} | 0 .../{di.config.ts => viewport-module.ts} | 4 +- packages/client/src/glsp-sprotty/index.ts | 55 ++++----- packages/client/src/index.ts | 105 ++++++------------ packages/client/src/utils/smodel-util.spec.ts | 4 +- packages/client/src/views/base-view-module.ts | 4 +- 49 files changed, 223 insertions(+), 333 deletions(-) rename examples/workflow-glsp/src/{di.config.ts => workflow-diagram-module.ts} (97%) rename packages/client/src/base/{di.config.ts => base-module.ts} (96%) rename packages/client/src/base/{actions/focus-change-action.ts => focus/focus-state-change-action.ts} (100%) rename packages/client/src/base/{ => focus}/focus-tracker.ts (96%) rename packages/client/src/features/accessibility/{di.config.ts => accessibility-module.ts} (74%) rename packages/client/src/features/accessibility/move-zoom/{di.config.ts => move-zoom-module.ts} (95%) rename packages/client/src/features/accessibility/resize-key-tool/{di.config.ts => resize-key-module.ts} (94%) rename packages/client/src/features/accessibility/search/{di.config.ts => search-palette-module.ts} (93%) rename packages/client/src/features/accessibility/view-key-tools/{di.config.ts => view-key-tools-module.ts} (89%) rename packages/client/src/features/bounds/{di.config.ts => bounds-module.ts} (94%) rename packages/client/src/features/command-palette/{di.config.ts => command-palette-module.ts} (93%) rename packages/client/src/features/context-menu/{di.config.ts => context-menu-module.ts} (94%) rename packages/client/src/features/copy-paste/{di.config.ts => copy-paste-modules.ts} (90%) rename packages/client/src/features/decoration/{di.config.ts => decoration-module.ts} (91%) rename packages/client/src/features/export/{di.config.ts => export-module.ts} (93%) rename packages/client/src/features/hints/{di.config.ts => type-hints-module.ts} (92%) rename packages/client/src/features/hover/{di.config.ts => hover-module.ts} (93%) rename packages/client/src/features/{edit-label => label-edit}/edit-label-tool.ts (100%) rename packages/client/src/features/{edit-label => label-edit}/edit-label-validator.ts (100%) rename packages/client/src/features/{edit-label/di.config.ts => label-edit/label-edit-module.ts} (91%) rename packages/client/src/features/layout/{di.config.ts => layout-module.ts} (92%) rename packages/client/src/features/navigation/{di.config.ts => navigation-module.ts} (94%) delete mode 100644 packages/client/src/features/routing/di.config.ts create mode 100644 packages/client/src/features/routing/routing-module.ts rename packages/client/src/features/save/{di.config.ts => save-module.ts} (94%) rename packages/client/src/features/select/{di.config.ts => select-module.ts} (92%) rename packages/client/src/features/source-model-watcher/{di.config.ts => source-model-wacher-module.ts} (90%) rename packages/client/src/features/svg-metadata/{di.config.ts => svg-metadata-module.ts} (92%) rename packages/client/src/features/tool-palette/{di.config.ts => tool-palette-module.ts} (91%) rename packages/client/src/features/tools/change-bounds/{di.config.ts => change-boounds-tool-module.ts} (88%) rename packages/client/src/features/tools/deletion/{di.config.ts => deletion-tool-module.ts} (90%) rename packages/client/src/features/tools/edge-creation/{di.config.ts => edege-creation-module.ts} (91%) rename packages/client/src/features/tools/edge-edit/{di.config.ts => edge-edit-module.ts} (93%) rename packages/client/src/features/tools/marquee-selection/{di.config.ts => marquee-selection-module.ts} (92%) rename packages/client/src/features/tools/node-creation/{di.config.ts => node-creation-module.ts} (90%) rename packages/client/src/features/tools/{enable-default-tools-on-focus-loss.ts => tool-focus-loss-module.ts} (86%) rename packages/client/src/features/validation/{di.config.ts => validation-module.ts} (100%) rename packages/client/src/features/viewport/{di.config.ts => viewport-module.ts} (94%) diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index 194be94..60e33f8 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './di.config'; export * from './direct-task-editing/direct-task-editor'; export * from './model'; +export * from './workflow-diagram-module'; export * from './workflow-views'; diff --git a/examples/workflow-glsp/src/di.config.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts similarity index 97% rename from examples/workflow-glsp/src/di.config.ts rename to examples/workflow-glsp/src/workflow-diagram-module.ts index 4e0b11e..2f37df0 100644 --- a/examples/workflow-glsp/src/di.config.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -20,7 +20,6 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, GEdge, - glspAccessibilityModule, GLSPGraph, GLSPProjectionView, GridSnapper, @@ -34,6 +33,7 @@ import { SLabelView, StructureCompartmentView, TYPES, + accessibilityModule, bindAsService, bindOrRebind, configureDefaultModelElements, @@ -87,7 +87,7 @@ export function initializeWorkflowDiagramContainer( widgetId: string, ...containerConfiguration: ContainerConfiguration ): Container { - initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, glspAccessibilityModule, ...containerConfiguration); + initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, accessibilityModule, ...containerConfiguration); overrideViewerOptions(container, { baseDiv: widgetId, hiddenDiv: widgetId + '_hidden' diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/base-module.ts similarity index 96% rename from packages/client/src/base/di.config.ts rename to packages/client/src/base/base-module.ts index 2f6859c..0020e77 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/base-module.ts @@ -32,13 +32,13 @@ import { } from '~glsp-sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; -import { FocusStateChangedAction } from './actions/focus-change-action'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; -import { FocusTracker } from './focus-tracker'; +import { FocusStateChangedAction } from './focus/focus-state-change-action'; +import { FocusTracker } from './focus/focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; @@ -48,7 +48,7 @@ import { GLSPKeyTool } from './view/key-tool'; import { GLSPMouseTool } from './view/mouse-tool'; import { GLSPViewRegistry } from './view/view-registry'; -const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +export const baseModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, _unbind, isBound, rebind }; bind(EditorContextService).toSelf().inSingletonScope(); bind(TYPES.IEditorContextServiceProvider).toProvider( @@ -103,8 +103,6 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); }); -export default defaultGLSPModule; - /** * Utility function to configure the {@link ModelSource}, i.e. the `DiagramServer`, as action handler for all server actions for the given * diagramType. diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/focus/focus-state-change-action.ts similarity index 100% rename from packages/client/src/base/actions/focus-change-action.ts rename to packages/client/src/base/focus/focus-state-change-action.ts diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts similarity index 96% rename from packages/client/src/base/focus-tracker.ts rename to packages/client/src/base/focus/focus-tracker.ts index 2558c9f..d7fd898 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from '~glsp-sprotty'; -import { FocusStateChangedAction } from './actions/focus-change-action'; +import { FocusStateChangedAction } from './focus-state-change-action'; @injectable() export class FocusTracker implements IActionHandler { diff --git a/packages/client/src/container-modules.ts b/packages/client/src/container-modules.ts index af0929f..8d23091 100644 --- a/packages/client/src/container-modules.ts +++ b/packages/client/src/container-modules.ts @@ -26,63 +26,61 @@ import { initializeContainer, labelEditUiModule, modelSourceModule, - openModule, zorderModule } from '~glsp-sprotty'; -import defaultGLSPModule from './base/di.config'; -import glspBoundsModule from './features/bounds/di.config'; -import glspCommandPaletteModule from './features/command-palette/di.config'; -import glspContextMenuModule from './features/context-menu/di.config'; -import { glspServerCopyPasteModule } from './features/copy-paste/di.config'; -import glspDecorationModule from './features/decoration/di.config'; -import glspEditLabelModule from './features/edit-label/di.config'; -import glspExportModule from './features/export/di.config'; -import modelHintsModule from './features/hints/di.config'; -import glspHoverModule from './features/hover/di.config'; -import layoutModule from './features/layout/di.config'; -import navigationModule from './features/navigation/di.config'; -import glspRoutingModule from './features/routing/di.config'; -import glspSelectModule from './features/select/di.config'; -import sourceModelWatcherModule from './features/source-model-watcher/di.config'; -import svgMetadataModule from './features/svg-metadata/di.config'; -import toolPaletteModule from './features/tool-palette/di.config'; -import changeBoundsToolModule from './features/tools/change-bounds/di.config'; -import deletionToolModule from './features/tools/deletion/di.config'; -import edgeCreationToolModule from './features/tools/edge-creation/di.config'; -import edgeEditToolModule from './features/tools/edge-edit/di.config'; -import enableDefaultToolsOnFocusLossModule from './features/tools/enable-default-tools-on-focus-loss'; -import marqueeSelectionToolModule from './features/tools/marquee-selection/di.config'; -import nodeCreationToolModule from './features/tools/node-creation/di.config'; -import { markerNavigatorModule, validationModule } from './features/validation/di.config'; -import glspViewportModule from './features/viewport/di.config'; +import { baseModule } from './base/base-module'; +import { boundsModule } from './features/bounds/bounds-module'; +import { commandPaletteModule } from './features/command-palette/command-palette-module'; +import { contextMenuModule } from './features/context-menu/context-menu-module'; +import { serverCopyPasteModule } from './features/copy-paste/copy-paste-modules'; +import { decorationModule } from './features/decoration/decoration-module'; +import { exportModule } from './features/export/export-module'; +import { typeHintsModule } from './features/hints/type-hints-module'; +import { hoverModule } from './features/hover/hover-module'; +import { labelEditModule } from './features/label-edit/label-edit-module'; +import { layoutModule } from './features/layout/layout-module'; +import { navigationModule } from './features/navigation/navigation-module'; +import { routingModule } from './features/routing/routing-module'; +import { selectModule } from './features/select/select-module'; +import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-wacher-module'; +import { svgMetadataModule } from './features/svg-metadata/svg-metadata-module'; +import { toolPaletteModule } from './features/tool-palette/tool-palette-module'; +import { changeBoundsToolModule } from './features/tools/change-bounds/change-boounds-tool-module'; +import { deletionToolModule } from './features/tools/deletion/deletion-tool-module'; +import { edgeCreationToolModule } from './features/tools/edge-creation/edege-creation-module'; +import { edgeEditToolModule } from './features/tools/edge-edit/edge-edit-module'; +import { marqueeSelectionToolModule } from './features/tools/marquee-selection/marquee-selection-module'; +import { nodeCreationToolModule } from './features/tools/node-creation/node-creation-module'; +import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; +import { markerNavigatorModule, validationModule } from './features/validation/validation-module'; +import { viewportModule } from './features/viewport/viewport-module'; export const DEFAULT_MODULES = [ defaultModule, - defaultGLSPModule, + baseModule, buttonModule, edgeIntersectionModule, edgeLayoutModule, expandModule, - glspExportModule, + exportModule, fadeModule, - glspBoundsModule, - glspCommandPaletteModule, - glspContextMenuModule, - glspDecorationModule, - glspEditLabelModule, - glspHoverModule, - glspSelectModule, - glspServerCopyPasteModule, - glspViewportModule, + boundsModule, + commandPaletteModule, + contextMenuModule, + decorationModule, + labelEditModule, + hoverModule, + selectModule, + serverCopyPasteModule, + viewportModule, labelEditUiModule, layoutModule, markerNavigatorModule, - modelHintsModule, + typeHintsModule, modelSourceModule, sourceModelWatcherModule, navigationModule, - openModule, - glspRoutingModule, + routingModule, toolPaletteModule, edgeCreationToolModule, edgeEditToolModule, @@ -90,7 +88,7 @@ export const DEFAULT_MODULES = [ nodeCreationToolModule, changeBoundsToolModule, marqueeSelectionToolModule, - enableDefaultToolsOnFocusLossModule, + toolFocusLossModule, validationModule, zorderModule, svgMetadataModule diff --git a/packages/client/src/features/accessibility/README.md b/packages/client/src/features/accessibility/README.md index a9257e8..9ec02b2 100644 --- a/packages/client/src/features/accessibility/README.md +++ b/packages/client/src/features/accessibility/README.md @@ -4,19 +4,19 @@ The features presented in the following are introduced for disability-aware conc ## Getting started -In order to activate and use all of the currently provided keyboard only accessibility features, the module `glspAccessibilityModule` needs to be in use. +In order to activate and use all of the currently provided keyboard only accessibility features, the module `accessibilityModule` needs to be in use. To activate and use only specific accessibility features, the respective module (see list below) needs to be in use. ## Features Below is a list of the keyboard only accessibility features:
-| Feature | Description | Shortcut | Module | -| ------- | :----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | :-----------------------: | -| Search | Search for elements or edges using labels, types, etc.
with respective diagram highlighting | CTRL+F | `glspSearchPaletteModule` | -| Move | Move elements, edges or viewport into the directions | \|\| \|\| \|\| | `glspMoveZoomModule` | -| Zoom | Zoom in or out gradually to elements or viewport or reset to default zoom level. | + \|\| - \|\| CTRL+0 | `glspMoveZoomModule` | -| Resize | Increase or decrease element sizes gradually or reset to default element size. | ALT+A then + \|\| - \|\| CTRL+0 | `glspResizeKeyModule` | +| Feature | Description | Shortcut | Module | +| ------- | :----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | :-------------------: | +| Search | Search for elements or edges using labels, types, etc.
with respective diagram highlighting | CTRL+F | `searchPaletteModule` | +| Move | Move elements, edges or viewport into the directions | \|\| \|\| \|\| | `moveZoomModule` | +| Zoom | Zoom in or out gradually to elements or viewport or reset to default zoom level. | + \|\| - \|\| CTRL+0 | `moveZoomModule` | +| Resize | Increase or decrease element sizes gradually or reset to default element size. | ALT+A then + \|\| - \|\| CTRL+0 | `resizeKeyModule` | ## Usage diff --git a/packages/client/src/features/accessibility/di.config.ts b/packages/client/src/features/accessibility/accessibility-module.ts similarity index 74% rename from packages/client/src/features/accessibility/di.config.ts rename to packages/client/src/features/accessibility/accessibility-module.ts index 948d735..c7ea3c5 100644 --- a/packages/client/src/features/accessibility/di.config.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -15,15 +15,15 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureMoveZoom } from './move-zoom/di.config'; -import { configureResizeTools } from './resize-key-tool/di.config'; -import { configureSearchPaletteModule } from './search/di.config'; -import { configureViewKeyTools } from './view-key-tools/di.config'; +import { configureMoveZoom } from './move-zoom/move-zoom-module'; +import { configureResizeTools } from './resize-key-tool/resize-key-module'; +import { configureSearchPaletteModule } from './search/search-palette-module'; +import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; /** * Enables the accessibility tools for a keyboard-only-usage */ -export const glspAccessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const accessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureResizeTools(context); configureViewKeyTools(context); diff --git a/packages/client/src/features/accessibility/move-zoom/di.config.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts similarity index 95% rename from packages/client/src/features/accessibility/move-zoom/di.config.ts rename to packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index 599f170..f8257a7 100644 --- a/packages/client/src/features/accessibility/move-zoom/di.config.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -22,7 +22,7 @@ import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewport /** * Handles move and zoom actions. */ -export const glspMoveZoomModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const moveZoomModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureMoveZoom(context); }); diff --git a/packages/client/src/features/accessibility/resize-key-tool/di.config.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts similarity index 94% rename from packages/client/src/features/accessibility/resize-key-tool/di.config.ts rename to packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index b0d6ece..dde6779 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/di.config.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -22,7 +22,7 @@ import { ResizeKeyTool } from './resize-key-tool'; /** * Handles resize actions. */ -export const glspResizeKeyModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const resizeKeyModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureResizeTools(context); }); diff --git a/packages/client/src/features/accessibility/search/di.config.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts similarity index 93% rename from packages/client/src/features/accessibility/search/di.config.ts rename to packages/client/src/features/accessibility/search/search-palette-module.ts index 75052dd..f003365 100644 --- a/packages/client/src/features/accessibility/search/di.config.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -19,7 +19,7 @@ import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; -export const glspSearchPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +export const searchPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound, rebind }; configureSearchPaletteModule(context); }); diff --git a/packages/client/src/features/accessibility/view-key-tools/di.config.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts similarity index 89% rename from packages/client/src/features/accessibility/view-key-tools/di.config.ts rename to packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 86ef8eb..68afac1 100644 --- a/packages/client/src/features/accessibility/view-key-tools/di.config.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -16,11 +16,11 @@ import { ContainerModule } from 'inversify'; import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; -import { DeselectKeyTool } from '../view-key-tools/deselect-key-tool'; +import { DeselectKeyTool } from './deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; -export const glspViewKeyToolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +export const viewKeyToolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound, rebind }; configureViewKeyTools(context); }); diff --git a/packages/client/src/features/bounds/di.config.ts b/packages/client/src/features/bounds/bounds-module.ts similarity index 94% rename from packages/client/src/features/bounds/di.config.ts rename to packages/client/src/features/bounds/bounds-module.ts index 4b3b16d..8ae4f3e 100644 --- a/packages/client/src/features/bounds/di.config.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -32,7 +32,7 @@ import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; import { VBoxLayouterExt } from './vbox-layout'; -const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const boundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { const context = { bind, isBound }; configureCommand(context, SetBoundsCommand); configureCommand(context, RequestBoundsCommand); @@ -45,5 +45,3 @@ const glspBoundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) = configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); }); - -export default glspBoundsModule; diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/command-palette-module.ts similarity index 93% rename from packages/client/src/features/command-palette/di.config.ts rename to packages/client/src/features/command-palette/command-palette-module.ts index 52a1079..2c984c7 100644 --- a/packages/client/src/features/command-palette/di.config.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -19,11 +19,9 @@ import '../../../css/command-palette.css'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; -const glspCommandPaletteModule = new ContainerModule(bind => { +export const commandPaletteModule = new ContainerModule(bind => { bindAsService(bind, TYPES.IUIExtension, CommandPalette); bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); bindAsService(bind, TYPES.IDefaultTool, CommandPaletteTool); }); - -export default glspCommandPaletteModule; diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/context-menu-module.ts similarity index 94% rename from packages/client/src/features/context-menu/di.config.ts rename to packages/client/src/features/context-menu/context-menu-module.ts index 7460c87..6394d39 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -18,7 +18,7 @@ import { ContextMenuProviderRegistry, IContextMenuService, TYPES, bindAsService import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; -const glspContextMenuModule = new ContainerModule(bind => { +export const contextMenuModule = new ContainerModule(bind => { bind(TYPES.IContextMenuServiceProvider).toProvider( ctx => () => new Promise((resolve, reject) => { @@ -33,5 +33,3 @@ const glspContextMenuModule = new ContainerModule(bind => { bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); }); - -export default glspContextMenuModule; diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 7abbe0a..33e7acd 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { inject, injectable, optional } from 'inversify'; import { Action, ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement, TYPES } from '~glsp-sprotty'; -import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { SelectionService } from '../../base/selection-service'; @injectable() diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts similarity index 90% rename from packages/client/src/features/copy-paste/di.config.ts rename to packages/client/src/features/copy-paste/copy-paste-modules.ts index 45488dc..9fbf81d 100644 --- a/packages/client/src/features/copy-paste/di.config.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -18,7 +18,7 @@ import { configureActionHandler, TYPES } from '~glsp-sprotty'; import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; -export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { +export const serverCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); bind(TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); }); @@ -28,7 +28,7 @@ export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isB * in a standalone deployment of GLSP. If the GLSP diagram in Theia use the Theia-native * `CopyPasteMenuContribution` in `glsp-theia-integration` instead. */ -export const copyPasteContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { +export const pasteContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { bind(TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, InvokeCopyPasteAction.KIND, InvokeCopyPasteActionHandler); diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/decoration-module.ts similarity index 91% rename from packages/client/src/features/decoration/di.config.ts rename to packages/client/src/features/decoration/decoration-module.ts index 3a5f282..7ed43e6 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -18,8 +18,6 @@ import { TYPES, bindAsService } from '~glsp-sprotty'; import '../../../css/decoration.css'; import { GlspDecorationPlacer } from './decoration-placer'; -const glspDecorationModule = new ContainerModule(bind => { +export const decorationModule = new ContainerModule(bind => { bindAsService(bind, TYPES.IVNodePostprocessor, GlspDecorationPlacer); }); - -export default glspDecorationModule; diff --git a/packages/client/src/features/export/di.config.ts b/packages/client/src/features/export/export-module.ts similarity index 93% rename from packages/client/src/features/export/di.config.ts rename to packages/client/src/features/export/export-module.ts index 5fbc94b..3bb9878 100644 --- a/packages/client/src/features/export/di.config.ts +++ b/packages/client/src/features/export/export-module.ts @@ -17,12 +17,10 @@ import { ContainerModule } from 'inversify'; import { bindAsService, configureCommand, ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, TYPES } from '~glsp-sprotty'; import { GLSPSvgExporter } from './glsp-svg-exporter'; -const glspExportSvgModule = new ContainerModule((bind, _unbind, isBound) => { +export const exportModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; bindAsService(context, TYPES.KeyListener, ExportSvgKeyListener); bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); configureCommand(context, ExportSvgCommand); bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); }); - -export default glspExportSvgModule; diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/type-hints-module.ts similarity index 92% rename from packages/client/src/features/hints/di.config.ts rename to packages/client/src/features/hints/type-hints-module.ts index a1ce11d..d828338 100644 --- a/packages/client/src/features/hints/di.config.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -17,11 +17,9 @@ import { ContainerModule } from 'inversify'; import { SetTypeHintsAction, TYPES, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; -const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { +export const typeHintsModule = new ContainerModule((bind, _unbind, isBound) => { bind(TypeHintProvider).toSelf().inSingletonScope(); bind(TYPES.ITypeHintProvider).toService(TypeHintProvider); configureActionHandler({ bind, isBound }, SetTypeHintsAction.KIND, TypeHintProvider); configureCommand({ bind, isBound }, ApplyTypeHintsCommand); }); - -export default modelHintsModule; diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/hover-module.ts similarity index 93% rename from packages/client/src/features/hover/di.config.ts rename to packages/client/src/features/hover/hover-module.ts index 7f498d4..2e3860f 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -33,10 +33,10 @@ import { configureActionHandler, configureCommand } from '~glsp-sprotty'; -import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { GlspHoverMouseListener } from './hover'; -const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { +export const hoverModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; bindAsService(context, TYPES.PopupVNodePostprocessor, PopupPositionUpdater); bindAsService(context, TYPES.MouseListener, GlspHoverMouseListener); @@ -63,5 +63,3 @@ const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler(context, EnableDefaultToolsAction.KIND, GlspHoverMouseListener); configureActionHandler(context, FocusStateChangedAction.KIND, GlspHoverMouseListener); }); - -export default glspHoverModule; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index 59a06e4..56826b4 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -31,7 +31,7 @@ import { SModelRootSchema, SetPopupModelAction } from '~glsp-sprotty'; -import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { EdgeCreationTool } from '../tools/edge-creation/edge-creation-tool'; import { GIssueMarker, getSeverity } from '../validation/issue-marker'; diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/label-edit/edit-label-tool.ts similarity index 100% rename from packages/client/src/features/edit-label/edit-label-tool.ts rename to packages/client/src/features/label-edit/edit-label-tool.ts diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts similarity index 100% rename from packages/client/src/features/edit-label/edit-label-validator.ts rename to packages/client/src/features/label-edit/edit-label-validator.ts diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/label-edit/label-edit-module.ts similarity index 91% rename from packages/client/src/features/edit-label/di.config.ts rename to packages/client/src/features/label-edit/label-edit-module.ts index f6efb3d..6bda2fb 100644 --- a/packages/client/src/features/edit-label/di.config.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -18,11 +18,9 @@ import { ApplyLabelEditCommand, TYPES, bindAsService, configureCommand } from '~ import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; -const glspEditLabelModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const labelEditModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); bindAsService(bind, TYPES.IDefaultTool, DirectLabelEditTool); configureCommand({ bind, isBound }, ApplyLabelEditCommand); }); - -export default glspEditLabelModule; diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/layout-module.ts similarity index 92% rename from packages/client/src/features/layout/di.config.ts rename to packages/client/src/features/layout/layout-module.ts index 88b1f55..5c4d7e6 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -22,10 +22,8 @@ import { ResizeElementsActionHandler } from './layout-elements-action'; -const layoutModule = new ContainerModule((bind, _unbind, isBound) => { +export const layoutModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); }); - -export default layoutModule; diff --git a/packages/client/src/features/navigation/di.config.ts b/packages/client/src/features/navigation/navigation-module.ts similarity index 94% rename from packages/client/src/features/navigation/di.config.ts rename to packages/client/src/features/navigation/navigation-module.ts index 80d6097..69d22df 100644 --- a/packages/client/src/features/navigation/di.config.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -18,7 +18,7 @@ import { NavigateToExternalTargetAction, NavigateToTargetAction, configureAction import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; -const navigationModule = new ContainerModule((bind, _unbind, isBound) => { +export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { bind(NavigationTargetResolver).toSelf().inSingletonScope(); bind(NavigationActionHandler).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); @@ -26,5 +26,3 @@ const navigationModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); configureActionHandler({ bind, isBound }, NavigateToExternalTargetAction.KIND, NavigationActionHandler); }); - -export default navigationModule; diff --git a/packages/client/src/features/routing/di.config.ts b/packages/client/src/features/routing/di.config.ts deleted file mode 100644 index fcae1ec..0000000 --- a/packages/client/src/features/routing/di.config.ts +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { - AddRemoveBezierSegmentCommand, - AnchorComputerRegistry, - BezierDiamondAnchor, - BezierEdgeRouter, - BezierEllipseAnchor, - BezierRectangleAnchor, - DiamondAnchor, - EdgeRouterRegistry, - EllipseAnchor, - ManhattanDiamondAnchor, - ManhattanEdgeRouter, - ManhattanEllipticAnchor, - ManhattanRectangularAnchor, - PolylineEdgeRouter, - RectangleAnchor, - TYPES, - bindOrRebind, - configureCommand -} from '~glsp-sprotty'; -import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; - -/** - * Customization of the routing module for GLSP. - */ -const glspRoutingModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - const context = { bind, isBound, rebind }; - - bind(EdgeRouterRegistry).toSelf().inSingletonScope(); - - bind(AnchorComputerRegistry).toSelf().inSingletonScope(); - - bind(GLSPManhattanEdgeRouter).toSelf().inSingletonScope(); - bind(ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); - bind(TYPES.IEdgeRouter).toService(ManhattanEdgeRouter); - bind(ManhattanEllipticAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(ManhattanEllipticAnchor); - bind(ManhattanRectangularAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(ManhattanRectangularAnchor); - bind(ManhattanDiamondAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(ManhattanDiamondAnchor); - - bind(PolylineEdgeRouter).toSelf().inSingletonScope(); - bind(TYPES.IEdgeRouter).toService(PolylineEdgeRouter); - bind(EllipseAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(EllipseAnchor); - bind(RectangleAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(RectangleAnchor); - bind(DiamondAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(DiamondAnchor); - - bind(BezierEdgeRouter).toSelf().inSingletonScope(); - bind(TYPES.IEdgeRouter).toService(BezierEdgeRouter); - bind(BezierEllipseAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(BezierEllipseAnchor); - bind(BezierRectangleAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(BezierRectangleAnchor); - bind(BezierDiamondAnchor).toSelf().inSingletonScope(); - bind(TYPES.IAnchorComputer).toService(BezierDiamondAnchor); - - configureCommand({ bind, isBound }, AddRemoveBezierSegmentCommand); - - bindOrRebind(context, ManhattanEdgeRouter).toService(GLSPManhattanEdgeRouter); -}); - -export default glspRoutingModule; diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts new file mode 100644 index 0000000..59132fc --- /dev/null +++ b/packages/client/src/features/routing/routing-module.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { + AddRemoveBezierSegmentCommand, + AnchorComputerRegistry, + BezierDiamondAnchor, + BezierEdgeRouter, + BezierEllipseAnchor, + BezierRectangleAnchor, + DiamondAnchor, + EdgeRouterRegistry, + EllipseAnchor, + ManhattanDiamondAnchor, + ManhattanEllipticAnchor, + ManhattanRectangularAnchor, + PolylineEdgeRouter, + RectangleAnchor, + TYPES, + bindAsService, + configureCommand +} from '~glsp-sprotty'; +import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; + +export const routingModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(EdgeRouterRegistry).toSelf().inSingletonScope(); + bind(AnchorComputerRegistry).toSelf().inSingletonScope(); + + bindAsService(context, TYPES.IEdgeRouter, GLSPManhattanEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, ManhattanEllipticAnchor); + bindAsService(context, TYPES.IAnchorComputer, ManhattanRectangularAnchor); + bindAsService(context, TYPES.IAnchorComputer, ManhattanDiamondAnchor); + + bindAsService(context, TYPES.IEdgeRouter, PolylineEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, EllipseAnchor); + bindAsService(context, TYPES.IAnchorComputer, RectangleAnchor); + bindAsService(context, TYPES.IAnchorComputer, DiamondAnchor); + + bindAsService(context, TYPES.IEdgeRouter, BezierEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, BezierEllipseAnchor); + bindAsService(context, TYPES.IAnchorComputer, BezierRectangleAnchor); + bindAsService(context, TYPES.IAnchorComputer, BezierDiamondAnchor); + + configureCommand({ bind, isBound }, AddRemoveBezierSegmentCommand); +}); diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/save-module.ts similarity index 94% rename from packages/client/src/features/save/di.config.ts rename to packages/client/src/features/save/save-module.ts index 8c395cf..0a0a873 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/save-module.ts @@ -21,8 +21,6 @@ import { SaveModelKeyboardListener } from './save-keylistener'; * This module is not required the diagram is deployed in Theia with the `GLSPDiagramWidget` * but only intended to be used in a standalone deployment of GLSP. */ -const saveModule = new ContainerModule(bind => { +export const saveModule = new ContainerModule(bind => { bindAsService(bind, TYPES.KeyListener, SaveModelKeyboardListener); }); - -export default saveModule; diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/select-module.ts similarity index 92% rename from packages/client/src/features/select/di.config.ts rename to packages/client/src/features/select/select-module.ts index 068d16d..cc72f75 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/select-module.ts @@ -19,12 +19,10 @@ import { SelectAllCommand, SelectCommand } from '../../base/selection-service'; import { SelectFeedbackCommand } from './select-feedback-command'; import { RankedSelectMouseListener } from './select-mouse-listener'; -const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { +export const selectModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureCommand(context, SelectCommand); configureCommand(context, SelectAllCommand); configureCommand(context, SelectFeedbackCommand); bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); }); - -export default glspSelectModule; diff --git a/packages/client/src/features/source-model-watcher/di.config.ts b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts similarity index 90% rename from packages/client/src/features/source-model-watcher/di.config.ts rename to packages/client/src/features/source-model-watcher/source-model-wacher-module.ts index 1a22b18..4dcb595 100644 --- a/packages/client/src/features/source-model-watcher/di.config.ts +++ b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts @@ -17,8 +17,6 @@ import { ContainerModule } from 'inversify'; import { SourceModelChangedAction, configureActionHandler } from '~glsp-sprotty'; import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; -const sourceModelWatcherModule = new ContainerModule((bind, _unbind, isBound) => { +export const sourceModelWatcherModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, SourceModelChangedAction.KIND, SourceModelChangedActionHandler); }); - -export default sourceModelWatcherModule; diff --git a/packages/client/src/features/svg-metadata/di.config.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts similarity index 92% rename from packages/client/src/features/svg-metadata/di.config.ts rename to packages/client/src/features/svg-metadata/svg-metadata-module.ts index b939376..c169414 100644 --- a/packages/client/src/features/svg-metadata/di.config.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -18,9 +18,7 @@ import { ContainerModule } from 'inversify'; import { TYPES } from '~glsp-sprotty'; import { MetadataPlacer } from './metadata-placer'; -const svgMetadataModule = new ContainerModule(bind => { +export const svgMetadataModule = new ContainerModule(bind => { bind(MetadataPlacer).toSelf().inSingletonScope(); bind(TYPES.IVNodePostprocessor).toService(MetadataPlacer); }); - -export default svgMetadataModule; diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts similarity index 91% rename from packages/client/src/features/tool-palette/di.config.ts rename to packages/client/src/features/tool-palette/tool-palette-module.ts index dc01b63..7df0c7e 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -18,10 +18,8 @@ import { bindAsService, configureActionHandler, EnableDefaultToolsAction, TYPES import '../../../css/tool-palette.css'; import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; -const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { bindAsService(bind, TYPES.IUIExtension, ToolPalette); configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); }); - -export default toolPaletteModule; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 257820c..8b93a1b 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -35,7 +35,7 @@ import { } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; -import { FocusTracker } from '../../base/focus-tracker'; +import { FocusTracker } from '../../base/focus/focus-tracker'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; diff --git a/packages/client/src/features/tools/change-bounds/di.config.ts b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts similarity index 88% rename from packages/client/src/features/tools/change-bounds/di.config.ts rename to packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts index cba8ac4..547d6b3 100644 --- a/packages/client/src/features/tools/change-bounds/di.config.ts +++ b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts @@ -15,17 +15,15 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { TYPES, bindAsService, configureCommand, configureView } from '~glsp-sprotty'; -import { SResizeHandle } from '../../../features/change-bounds/model'; +import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { SResizeHandleView } from './view'; -const changeBoundsToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const changeBoundsToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); configureView(context, SResizeHandle.TYPE, SResizeHandleView); }); - -export default changeBoundsToolModule; diff --git a/packages/client/src/features/tools/deletion/di.config.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts similarity index 90% rename from packages/client/src/features/tools/deletion/di.config.ts rename to packages/client/src/features/tools/deletion/deletion-tool-module.ts index 185ab24..2c58162 100644 --- a/packages/client/src/features/tools/deletion/di.config.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -17,10 +17,8 @@ import { ContainerModule } from 'inversify'; import { TYPES, bindAsService } from '~glsp-sprotty'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; -const deletionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const deletionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); bindAsService(context, TYPES.ITool, MouseDeleteTool); }); - -export default deletionToolModule; diff --git a/packages/client/src/features/tools/edge-creation/di.config.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts similarity index 91% rename from packages/client/src/features/tools/edge-creation/di.config.ts rename to packages/client/src/features/tools/edge-creation/edege-creation-module.ts index 3d42717..b1f7ad1 100644 --- a/packages/client/src/features/tools/edge-creation/di.config.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -18,11 +18,9 @@ import { TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler import { configureDanglingFeedbackEdge } from './dangling-edge-feedback'; import { EdgeCreationTool } from './edge-creation-tool'; -const edgeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const edgeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.ITool, EdgeCreationTool); configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); configureDanglingFeedbackEdge(context); }); - -export default edgeCreationToolModule; diff --git a/packages/client/src/features/tools/edge-edit/di.config.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts similarity index 93% rename from packages/client/src/features/tools/edge-edit/di.config.ts rename to packages/client/src/features/tools/edge-edit/edge-edit-module.ts index c90bd71..688f242 100644 --- a/packages/client/src/features/tools/edge-edit/di.config.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { configureDanglingFeedbackEdge } from '../edge-creation/dangling-edge-feedback'; import { EdgeEditTool } from './edge-edit-tool'; import { DrawFeedbackEdgeSourceCommand, @@ -22,9 +23,8 @@ import { ShowEdgeReconnectHandlesFeedbackCommand, SwitchRoutingModeCommand } from './edge-edit-tool-feedback'; -import { configureDanglingFeedbackEdge } from '../edge-creation/dangling-edge-feedback'; -const edgeEditToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const edgeEditToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); @@ -37,5 +37,3 @@ const edgeEditToolModule = new ContainerModule((bind, unbind, isBound, rebind) = // dangling edge feedback configureDanglingFeedbackEdge(context); }); - -export default edgeEditToolModule; diff --git a/packages/client/src/features/tools/marquee-selection/di.config.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts similarity index 92% rename from packages/client/src/features/tools/marquee-selection/di.config.ts rename to packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index d338536..be8353c 100644 --- a/packages/client/src/features/tools/marquee-selection/di.config.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -21,7 +21,7 @@ import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-too import { MarqueeNode } from './model'; import { MarqueeView } from './view'; -const marqueeSelectionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const marqueeSelectionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, MarqueeTool); bindAsService(context, TYPES.ITool, MarqueeMouseTool); @@ -31,5 +31,3 @@ const marqueeSelectionToolModule = new ContainerModule((bind, unbind, isBound, r configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); }); - -export default marqueeSelectionToolModule; diff --git a/packages/client/src/features/tools/node-creation/di.config.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts similarity index 90% rename from packages/client/src/features/tools/node-creation/di.config.ts rename to packages/client/src/features/tools/node-creation/node-creation-module.ts index 62da4a3..294137c 100644 --- a/packages/client/src/features/tools/node-creation/di.config.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -17,10 +17,8 @@ import { ContainerModule } from 'inversify'; import { TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; import { NodeCreationTool } from './node-creation-tool'; -const nodeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const nodeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.ITool, NodeCreationTool); configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); }); - -export default nodeCreationToolModule; diff --git a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts similarity index 86% rename from packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts rename to packages/client/src/features/tools/tool-focus-loss-module.ts index 9a83e9c..936e830 100644 --- a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -15,11 +15,10 @@ ********************************************************************************/ import { ContainerModule, injectable } from 'inversify'; import { Action, EnableDefaultToolsAction, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; -import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; /** * Action handler that enables the default tools when the diagram lost focus. - * @see FocusTracker * @see FocusStateChangedAction */ @injectable() @@ -34,8 +33,6 @@ export class EnableDefaultToolsOnFocusLossHandler implements IActionHandler { /** * Enables the default tools in the tool manager if the diagram looses focus. */ -const enableDefaultToolsOnFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { +export const toolFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); }); - -export default enableDefaultToolsOnFocusLossModule; diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 6c8a0a4..927aa88 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -28,11 +28,11 @@ import { SNodeSchema, SParentElement, TYPES, - decorationModule, defaultModule } from '~glsp-sprotty'; -import { markerNavigatorModule } from './di.config'; +import { decorationModule } from '../decoration/decoration-module'; import { MarkerNavigator } from './marker-navigator'; +import { markerNavigatorModule } from './validation-module'; describe('MarkerNavigator', () => { const container = new Container(); diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/validation-module.ts similarity index 100% rename from packages/client/src/features/validation/di.config.ts rename to packages/client/src/features/validation/validation-module.ts diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/viewport-module.ts similarity index 94% rename from packages/client/src/features/viewport/di.config.ts rename to packages/client/src/features/viewport/viewport-module.ts index 7c05397..971d51b 100644 --- a/packages/client/src/features/viewport/di.config.ts +++ b/packages/client/src/features/viewport/viewport-module.ts @@ -30,7 +30,7 @@ import { } from '~glsp-sprotty'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; -const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { +export const viewportModule = new ContainerModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureCommand(context, CenterCommand); configureCommand(context, FitToScreenCommand); @@ -44,5 +44,3 @@ const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); }); - -export default glspViewportModule; diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/client/src/glsp-sprotty/index.ts index 28602a5..7fa8a2d 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -145,13 +145,11 @@ export * from 'sprotty/lib/features/decoration/model'; export * from 'sprotty/lib/features/decoration/views'; export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; export * from 'sprotty/lib/features/edge-intersection/sweepline'; -export * from 'sprotty/lib/features/edge-layout/di.config'; export * from 'sprotty/lib/features/edge-layout/edge-layout'; export * from 'sprotty/lib/features/edge-layout/model'; export * from 'sprotty/lib/features/edit/create'; export * from 'sprotty/lib/features/edit/create-on-drag'; export * from 'sprotty/lib/features/edit/delete'; -export * from 'sprotty/lib/features/edit/di.config'; export * from 'sprotty/lib/features/edit/edit-label'; export * from 'sprotty/lib/features/edit/edit-label-ui'; export * from 'sprotty/lib/features/edit/edit-routing'; @@ -296,30 +294,6 @@ export * from './types'; /** * Modules */ -export { - boundsModule, - buttonModule, - commandPaletteModule, - contextMenuModule, - decorationModule, - defaultModule, - edgeIntersectionModule, - edgeLayoutModule, - expandModule, - exportModule, - fadeModule, - graphModule, - hoverModule, - modelSourceModule, - moveModule, - openModule, - routingModule, - selectModule, - undoRedoModule, - updateModule, - viewportModule, - zorderModule -}; import defaultModule from 'sprotty/lib/base/di.config'; import boundsModule from 'sprotty/lib/features/bounds/di.config'; import buttonModule from 'sprotty/lib/features/button/di.config'; @@ -328,6 +302,7 @@ import contextMenuModule from 'sprotty/lib/features/context-menu/di.config'; import decorationModule from 'sprotty/lib/features/decoration/di.config'; import edgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; import edgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; +import { edgeEditModule, labelEditModule, labelEditUiModule } from 'sprotty/lib/features/edit/di.config'; import expandModule from 'sprotty/lib/features/expand/di.config'; import exportModule from 'sprotty/lib/features/export/di.config'; import fadeModule from 'sprotty/lib/features/fade/di.config'; @@ -342,3 +317,31 @@ import viewportModule from 'sprotty/lib/features/viewport/di.config'; import zorderModule from 'sprotty/lib/features/zorder/di.config'; import graphModule from 'sprotty/lib/graph/di.config'; import modelSourceModule from 'sprotty/lib/model-source/di.config'; + +export { + buttonModule, + defaultModule, + edgeEditModule, + edgeIntersectionModule, + edgeLayoutModule, + expandModule, + fadeModule, + graphModule, + labelEditUiModule, + modelSourceModule, + moveModule, + openModule, + boundsModule as sprottyBoundsModule, + commandPaletteModule as sprottyCommandModule, + contextMenuModule as sprottyContextMenuModule, + decorationModule as sprottyDecorationModule, + exportModule as sprottyExportModule, + hoverModule as sprottyHoverModule, + labelEditModule as sprottyLabelEditModule, + routingModule as sprottyRoutingModule, + selectModule as sprottySelectModule, + viewportModule as sprottyViewportModule, + undoRedoModule, + updateModule, + zorderModule +}; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 8307eb5..11c4a08 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -13,52 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import defaultGLSPModule from './base/di.config'; -import { glspAccessibilityModule } from './features/accessibility/di.config'; -import { glspMoveZoomModule } from './features/accessibility/move-zoom/di.config'; -import { glspResizeKeyModule } from './features/accessibility/resize-key-tool/di.config'; -import { glspSearchPaletteModule } from './features/accessibility/search/di.config'; -import { glspViewKeyToolsModule } from './features/accessibility/view-key-tools/di.config'; -import glspCommandPaletteModule from './features/command-palette/di.config'; -import glspContextMenuModule from './features/context-menu/di.config'; -import { copyPasteContextMenuModule, glspServerCopyPasteModule } from './features/copy-paste/di.config'; -import glspDecorationModule from './features/decoration/di.config'; -import glspEditLabelModule from './features/edit-label/di.config'; -import modelHintsModule from './features/hints/di.config'; -import glspHoverModule from './features/hover/di.config'; -import layoutModule from './features/layout/di.config'; -import navigationModule from './features/navigation/di.config'; -import glspRoutingModule from './features/routing/di.config'; -import saveModule from './features/save/di.config'; -import glspSelectModule from './features/select/di.config'; -import sourceModelWatcherModule from './features/source-model-watcher/di.config'; -import svgMetadataModule from './features/svg-metadata/di.config'; -import paletteModule from './features/tool-palette/di.config'; -import changeBoundsToolModule from './features/tools/change-bounds/di.config'; -import deletionToolModule from './features/tools/deletion/di.config'; -import edgeCreationToolModule from './features/tools/edge-creation/di.config'; -import edgeEditToolModule from './features/tools/edge-edit/di.config'; -import enableDefaultToolsOnFocusLossModule from './features/tools/enable-default-tools-on-focus-loss'; -import marqueeSelectionToolModule from './features/tools/marquee-selection/di.config'; -import nodeCreationToolModule from './features/tools/node-creation/di.config'; -import { markerNavigatorContextMenuModule, markerNavigatorModule, validationModule } from './features/validation/di.config'; -import glspViewportModule from './features/viewport/di.config'; // ------------------ Base ------------------ export * from './base/action-dispatcher'; -export * from './base/actions/focus-change-action'; export * from './base/argumentable'; export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; export * from './base/command-stack'; -export { configureServerActions } from './base/di.config'; export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context-service'; export * from './base/feedback/feedback-action-dispatcher'; export * from './base/feedback/feedback-command'; export * from './base/feedback/update-model-command'; -export * from './base/focus-tracker'; +export * from './base/focus/focus-state-change-action'; +export * from './base/focus/focus-tracker'; export * from './base/model-initialization-constraint'; export * from './base/model/model-registry'; export * from './base/ranked'; @@ -94,12 +63,12 @@ export * from './features/context-menu/server-context-menu-provider'; export * from './features/copy-paste/copy-paste-context-menu'; export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; -export * from './features/edit-label/edit-label-tool'; -export * from './features/edit-label/edit-label-validator'; export * from './features/export/glsp-svg-exporter'; export * from './features/hints/model'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; +export * from './features/label-edit/edit-label-tool'; +export * from './features/label-edit/edit-label-validator'; export * from './features/layout/layout-elements-action'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; @@ -122,7 +91,6 @@ export * from './features/tools/edge-creation/edge-creation-tool-feedback'; export * from './features/tools/edge-creation/view'; export * from './features/tools/edge-edit/edge-edit-tool'; export * from './features/tools/edge-edit/edge-edit-tool-feedback'; -export * from './features/tools/enable-default-tools-on-focus-loss'; export * from './features/tools/marquee-selection/marquee-behavior'; export * from './features/tools/marquee-selection/marquee-mouse-tool'; export * from './features/tools/marquee-selection/marquee-tool'; @@ -148,38 +116,33 @@ export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; // ------------------ DI Modules ------------------ -export { - changeBoundsToolModule, - copyPasteContextMenuModule, - defaultGLSPModule, - deletionToolModule, - edgeCreationToolModule, - edgeEditToolModule, - enableDefaultToolsOnFocusLossModule, - glspAccessibilityModule, - glspCommandPaletteModule, - glspContextMenuModule, - glspDecorationModule, - glspEditLabelModule, - glspHoverModule, - glspMoveZoomModule, - glspResizeKeyModule, - glspRoutingModule, - glspSearchPaletteModule, - glspSelectModule, - glspServerCopyPasteModule, - glspViewKeyToolsModule, - glspViewportModule, - layoutModule, - markerNavigatorContextMenuModule, - markerNavigatorModule, - marqueeSelectionToolModule, - modelHintsModule, - navigationModule, - nodeCreationToolModule, - paletteModule, - saveModule, - sourceModelWatcherModule, - svgMetadataModule, - validationModule -}; +export * from './base/base-module'; +export * from './features/accessibility/accessibility-module'; +export * from './features/accessibility/move-zoom/move-zoom-module'; +export * from './features/accessibility/resize-key-tool/resize-key-module'; +export * from './features/accessibility/search/search-palette-module'; +export * from './features/accessibility/view-key-tools/view-key-tools-module'; +export * from './features/command-palette/command-palette-module'; +export * from './features/context-menu/context-menu-module'; +export * from './features/copy-paste/copy-paste-modules'; +export * from './features/decoration/decoration-module'; +export * from './features/hints/type-hints-module'; +export * from './features/hover/hover-module'; +export * from './features/label-edit/label-edit-module'; +export * from './features/layout/layout-module'; +export * from './features/navigation/navigation-module'; +export * from './features/routing/routing-module'; +export * from './features/save/save-module'; +export * from './features/select/select-module'; +export * from './features/source-model-watcher/source-model-wacher-module'; +export * from './features/svg-metadata/svg-metadata-module'; +export * from './features/tool-palette/tool-palette-module'; +export * from './features/tools/change-bounds/change-boounds-tool-module'; +export * from './features/tools/deletion/deletion-tool-module'; +export * from './features/tools/edge-creation/edege-creation-module'; +export * from './features/tools/edge-edit/edge-edit-module'; +export * from './features/tools/marquee-selection/marquee-selection-module'; +export * from './features/tools/node-creation/node-creation-module'; +export * from './features/tools/tool-focus-loss-module'; +export * from './features/validation/validation-module'; +export * from './features/viewport/viewport-module'; diff --git a/packages/client/src/utils/smodel-util.spec.ts b/packages/client/src/utils/smodel-util.spec.ts index 8569c00..7b3f241 100644 --- a/packages/client/src/utils/smodel-util.spec.ts +++ b/packages/client/src/utils/smodel-util.spec.ts @@ -26,9 +26,9 @@ import { SGraph, SNode, SRoutableElement, - SRoutingHandle, - routingModule + SRoutingHandle } from '~glsp-sprotty'; +import { routingModule } from '../features/routing/routing-module'; import { ALL_ROUTING_POINTS, ROUTE_KINDS, ROUTING_POINT_KINDS, calcRoute } from './smodel-util'; class TestRouter extends AbstractEdgeRouter { diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 5521f13..5b2d31b 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -54,7 +54,7 @@ import { GEdgeView } from './glsp-edge-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; -const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); }); @@ -93,5 +93,3 @@ export function configureDefaultModelElements(context: Pick Date: Thu, 20 Jul 2023 01:07:00 +0200 Subject: [PATCH 234/566] GLSP-1063: Introduce feature modules (#267) Introduce custom container modules that allow to specify required modules and enable conditional loading i.e. only load if the all required modules have been loaded before. Each feature module bind a `featureId`(symbol) when loaded which can be used by other feature modules to identify whether a required feature module is bound. Prime usecase for this is to facilitate `extensionModules` i.e. modules that build on top of the functionality provided by a base module (and optionally customize,rebind certain services) for certain integration scenarios (standalone, theia, vscode). This also reduces the customization overhead e.g. application specific integrations as it's no longer necessary to manually customize the behavior if certain default modules should not be included. For instance if an adopter doesn't want to provide the `exportFunctionality` he can simply remove the `exportModule` and any application specific customization (`standaloneExportModule`, `theiaExportModule`) will not be loaded in the container as well. In addition, this change is also essential if we decide to switch to a `capability` based approach similar to LSP at some point. (i.e. the server defines it`s capabilities and the client dynamically loads the modules required for this capabilities). Fixes https://github.com/eclipse-glsp/glsp/issues/1063 --- .../src/direct-task-editing/di.config.ts | 5 +- examples/workflow-standalone/src/di.config.ts | 12 +- packages/client/package.json | 1 + packages/client/src/base/action-dispatcher.ts | 4 + .../{base-module.ts => default.module.ts} | 17 ++- .../client/src/base/selection-service.spec.ts | 15 +-- ...ontainer-modules.ts => default-modules.ts} | 36 ++++-- .../accessibility/accessibility-module.ts | 4 +- .../move-zoom/move-zoom-module.ts | 5 +- .../resize-key-tool/resize-key-module.ts | 5 +- .../search/search-palette-module.ts | 5 +- .../view-key-tools/view-key-tools-module.ts | 5 +- .../src/features/bounds/bounds-module.ts | 4 +- .../bounds/layouter-test-util.spec.ts | 2 +- .../command-palette/command-palette-module.ts | 5 +- .../context-menu/context-menu-module.ts | 5 +- .../features/copy-paste/copy-paste-modules.ts | 24 ++-- .../features/decoration/decoration-module.ts | 5 +- .../src/features/export/export-modules.ts | 50 ++++++++ .../export/export-svg-action-handler.ts | 32 +++++ .../src/features/hints/type-hints-module.ts | 5 +- .../client/src/features/hover/hover-module.ts | 4 +- .../features/label-edit/label-edit-module.ts | 5 +- .../layout/layout-elements-action.spec.ts | 4 +- .../src/features/layout/layout-module.ts | 5 +- .../features/navigation/navigation-module.ts | 5 +- .../src/features/routing/routing-module.ts | 4 +- .../client/src/features/save/save-module.ts | 10 +- .../src/features/select/select-module.ts | 17 ++- .../source-model-wacher-module.ts | 5 +- .../svg-metadata/svg-metadata-module.ts | 5 +- .../tool-palette/tool-palette-module.ts | 5 +- .../change-boounds-tool-module.ts | 5 +- .../tools/deletion/deletion-tool-module.ts | 5 +- .../edge-creation/edege-creation-module.ts | 5 +- .../tools/edge-edit/edge-edit-module.ts | 5 +- .../marquee-selection-module.ts | 5 +- .../node-creation/node-creation-module.ts | 5 +- .../features/tools/tool-focus-loss-module.ts | 6 +- .../undo-redo-key-listener.ts} | 24 ++-- .../undo-redo-module.ts} | 20 +-- .../validation/marker-navigator.spec.ts | 6 +- ...dation-module.ts => validation-modules.ts} | 24 ++-- ...viewport-module.ts => viewport-modules.ts} | 18 ++- packages/client/src/glsp-sprotty/index.ts | 4 +- packages/client/src/index.ts | 13 +- packages/client/src/lib/model.ts | 5 +- .../src/model-source/glsp-diagram-server.ts | 4 + packages/client/src/standalone-modules.ts | 48 ++++++++ packages/client/src/views/base-view-module.ts | 4 +- packages/protocol/src/utils/di-util.spec.ts | 114 ++++++++++++++++++ packages/protocol/src/utils/di-util.ts | 88 +++++++++++++- 52 files changed, 543 insertions(+), 180 deletions(-) rename packages/client/src/base/{base-module.ts => default.module.ts} (90%) rename packages/client/src/{container-modules.ts => default-modules.ts} (82%) create mode 100644 packages/client/src/features/export/export-modules.ts create mode 100644 packages/client/src/features/export/export-svg-action-handler.ts rename packages/client/src/features/{save/model.ts => undo-redo/undo-redo-key-listener.ts} (51%) rename packages/client/src/features/{export/export-module.ts => undo-redo/undo-redo-module.ts} (57%) rename packages/client/src/features/validation/{validation-module.ts => validation-modules.ts} (68%) rename packages/client/src/features/viewport/{viewport-module.ts => viewport-modules.ts} (74%) create mode 100644 packages/client/src/standalone-modules.ts create mode 100644 packages/protocol/src/utils/di-util.spec.ts diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts index 96a1367..855b48f 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/di.config.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, TYPES } from '@eclipse-glsp/client'; -import { ContainerModule } from 'inversify'; +import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/client'; import { TaskEditor } from './direct-task-editor'; -export const directTaskEditor = new ContainerModule((bind, _unbind, _isBound) => { +export const directTaskEditor = new FeatureModule((bind, _unbind, _isBound) => { bindAsService(bind, TYPES.IUIExtension, TaskEditor); }); diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index b11840f..698f9e6 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -14,12 +14,20 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; -import { bindAsService, bindOrRebind, ConsoleLogger, GLSPDiagramServer, LogLevel, TYPES } from '@eclipse-glsp/client'; +import { + bindAsService, + bindOrRebind, + ConsoleLogger, + GLSPDiagramServer, + LogLevel, + STANDALONE_MODULE_CONFIG, + TYPES +} from '@eclipse-glsp/client'; import { Container } from 'inversify'; import '../css/diagram.css'; export default function createContainer(): Container { - const container = createWorkflowDiagramContainer('sprotty'); + const container = createWorkflowDiagramContainer('sprotty', STANDALONE_MODULE_CONFIG); bindAsService(container, TYPES.ModelSource, GLSPDiagramServer); bindOrRebind(container, TYPES.ModelSource).toService(GLSPDiagramServer); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); diff --git a/packages/client/package.json b/packages/client/package.json index c8ef5f1..43cf281 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -53,6 +53,7 @@ "sprotty": "0.14.0-next.02bbac0.26" }, "devDependencies": { + "@types/file-saver": "^2.0.3", "@types/lodash": "4.14.191", "@vscode/codicons": "^0.0.25", "tsc-alias": "^1.8.2" diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 35ebee9..6af92dc 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -40,6 +40,10 @@ export class GLSPActionDispatcher extends ActionDispatcher { return this.initializationConstraint.onInitialized(); } + hasHandler(action: Action): boolean { + return this.actionHandlerRegistry.get(action.kind).length > 0; + } + override dispatch(action: Action): Promise { const result = super.dispatch(action); this.initializationConstraint.notifyDispatched(action); diff --git a/packages/client/src/base/base-module.ts b/packages/client/src/base/default.module.ts similarity index 90% rename from packages/client/src/base/base-module.ts rename to packages/client/src/base/default.module.ts index 0020e77..bf9d61b 100644 --- a/packages/client/src/base/base-module.ts +++ b/packages/client/src/base/default.module.ts @@ -14,9 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import '@vscode/codicons/dist/codicon.css'; -import { Container, ContainerModule } from 'inversify'; +import { Container } from 'inversify'; import { ActionHandlerRegistry, + FeatureModule, InitializeResult, KeyTool, LocationPostprocessor, @@ -28,7 +29,8 @@ import { bindAsService, bindOrRebind, configureActionHandler, - configureCommand + configureCommand, + sprottyDefaultModule } from '~glsp-sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; @@ -48,8 +50,15 @@ import { GLSPKeyTool } from './view/key-tool'; import { GLSPMouseTool } from './view/mouse-tool'; import { GLSPViewRegistry } from './view/view-registry'; -export const baseModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - const context = { bind, _unbind, isBound, rebind }; +/** + * The default module provides all of GLSP's base functionality and services. + * It builds on top of sprotty's default module {@link `sprottyDefaultModule`}. + */ +export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, ...rest) => { + // load bindings from sprotty's default module to avoid code duplication + sprottyDefaultModule.registry(bind, unbind, isBound, rebind, ...rest); + const context = { bind, unbind, isBound, rebind }; + bind(EditorContextService).toSelf().inSingletonScope(); bind(TYPES.IEditorContextServiceProvider).toProvider( ctx => () => diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index 7c203e4..7a56567 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -16,16 +16,8 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { - Action, - Disposable, - SGraphFactory, - SModelElementSchema, - SModelRoot, - TYPES, - defaultModule, - initializeContainer -} from '~glsp-sprotty'; +import { Action, Disposable, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, initializeContainer } from '~glsp-sprotty'; +import { defaultModule } from './default.module'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @@ -64,8 +56,7 @@ function createContainer(): Container { const container = initializeContainer(new Container(), defaultModule); // eslint-disable-next-line deprecation/deprecation container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); - container.bind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); - container.bind(SelectionService).toSelf().inSingletonScope(); + container.rebind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); return container; } diff --git a/packages/client/src/container-modules.ts b/packages/client/src/default-modules.ts similarity index 82% rename from packages/client/src/container-modules.ts rename to packages/client/src/default-modules.ts index 8d23091..3643975 100644 --- a/packages/client/src/container-modules.ts +++ b/packages/client/src/default-modules.ts @@ -17,24 +17,24 @@ import { Container, ContainerModule } from 'inversify'; import { ContainerConfiguration, + FeatureModule, buttonModule, - defaultModule, edgeIntersectionModule, edgeLayoutModule, expandModule, fadeModule, - initializeContainer, labelEditUiModule, modelSourceModule, + resolveContainerConfiguration, zorderModule } from '~glsp-sprotty'; -import { baseModule } from './base/base-module'; +import { defaultModule } from './base/default.module'; import { boundsModule } from './features/bounds/bounds-module'; import { commandPaletteModule } from './features/command-palette/command-palette-module'; import { contextMenuModule } from './features/context-menu/context-menu-module'; -import { serverCopyPasteModule } from './features/copy-paste/copy-paste-modules'; +import { copyPasteModule } from './features/copy-paste/copy-paste-modules'; import { decorationModule } from './features/decoration/decoration-module'; -import { exportModule } from './features/export/export-module'; +import { exportModule } from './features/export/export-modules'; import { typeHintsModule } from './features/hints/type-hints-module'; import { hoverModule } from './features/hover/hover-module'; import { labelEditModule } from './features/label-edit/label-edit-module'; @@ -52,12 +52,11 @@ import { edgeEditToolModule } from './features/tools/edge-edit/edge-edit-module' import { marqueeSelectionToolModule } from './features/tools/marquee-selection/marquee-selection-module'; import { nodeCreationToolModule } from './features/tools/node-creation/node-creation-module'; import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; -import { markerNavigatorModule, validationModule } from './features/validation/validation-module'; -import { viewportModule } from './features/viewport/viewport-module'; +import { markerNavigatorModule, validationModule } from './features/validation/validation-modules'; +import { viewportModule } from './features/viewport/viewport-modules'; export const DEFAULT_MODULES = [ defaultModule, - baseModule, buttonModule, edgeIntersectionModule, edgeLayoutModule, @@ -71,7 +70,7 @@ export const DEFAULT_MODULES = [ labelEditModule, hoverModule, selectModule, - serverCopyPasteModule, + copyPasteModule, viewportModule, labelEditUiModule, layoutModule, @@ -116,13 +115,24 @@ export const DEFAULT_MODULES = [ * ```typescript * rebind(NavigationTargetResolver).to(MyNavigationTargetResolver); * ``` - * - * @param containerConfiguration + * @param container The container that should be initialized + * @param containerConfigurations * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. + * @throws An error if the first module to load is not the `defaultModule` (or an equivalent custom replacement module) * @returns The initialized container. */ -export function initializeDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { - return initializeContainer(container, ...DEFAULT_MODULES, ...containerConfiguration); +export function initializeDiagramContainer(container: Container, ...containerConfigurations: ContainerConfiguration): Container { + const modules = resolveContainerConfiguration(...DEFAULT_MODULES, ...containerConfigurations); + // The `defaultModule` (or a custom replacement module with the same `featureId`) should be the first module that is + // loaded into the container + const firstModule = modules[0]; + if (!firstModule || !(firstModule instanceof FeatureModule && firstModule.featureId === defaultModule.featureId)) { + throw new Error( + 'Invalid module configuration. The first module to load should be the `defaultModule` (or an equivalent replacement module)' + ); + } + container.load(...modules); + return container; } /** diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index c7ea3c5..98077d0 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; +import { FeatureModule } from '~glsp-sprotty'; import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; @@ -23,7 +23,7 @@ import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; /** * Enables the accessibility tools for a keyboard-only-usage */ -export const accessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const accessibilityModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureResizeTools(context); configureViewKeyTools(context); diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index f8257a7..7e989b0 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -14,15 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { BindingContext, configureActionHandler } from '~glsp-sprotty'; +import { BindingContext, FeatureModule, configureActionHandler } from '~glsp-sprotty'; import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler'; import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler'; /** * Handles move and zoom actions. */ -export const moveZoomModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const moveZoomModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureMoveZoom(context); }); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index dde6779..c0c4878 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -14,15 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; +import { BindingContext, FeatureModule, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; import { ResizeElementAction, ResizeElementHandler } from './resize-key-handler'; import { ResizeKeyTool } from './resize-key-tool'; /** * Handles resize actions. */ -export const resizeKeyModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const resizeKeyModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureResizeTools(context); }); diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index f003365..98eb429 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -14,12 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, FeatureModule, TYPES } from '~glsp-sprotty'; import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; -export const searchPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +export const searchPaletteModule = new FeatureModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound, rebind }; configureSearchPaletteModule(context); }); diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 68afac1..28f3364 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -14,13 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, FeatureModule, TYPES } from '~glsp-sprotty'; import { DeselectKeyTool } from './deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; -export const viewKeyToolsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { +export const viewKeyToolsModule = new FeatureModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound, rebind }; configureViewKeyTools(context); }); diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 8ae4f3e..3d99565 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; import { + FeatureModule, HBoxLayouter, HiddenBoundsUpdater, LayoutRegistry, @@ -32,7 +32,7 @@ import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; import { VBoxLayouterExt } from './vbox-layout'; -export const boundsModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { const context = { bind, isBound }; configureCommand(context, SetBoundsCommand); configureCommand(context, RequestBoundsCommand); diff --git a/packages/client/src/features/bounds/layouter-test-util.spec.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts index 87306b0..899987a 100644 --- a/packages/client/src/features/bounds/layouter-test-util.spec.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -31,7 +31,7 @@ import { createFeatureSet, layoutableChildFeature } from '~glsp-sprotty'; -import { initializeDiagramContainer } from '../../container-modules'; +import { initializeDiagramContainer } from '../../default-modules'; import { GLSPGraph } from '../../lib/model'; import { StatefulLayouterExt } from './layouter'; diff --git a/packages/client/src/features/command-palette/command-palette-module.ts b/packages/client/src/features/command-palette/command-palette-module.ts index 2c984c7..64d0b19 100644 --- a/packages/client/src/features/command-palette/command-palette-module.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -13,13 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES, bindAsService } from '~glsp-sprotty'; +import { CommandPalette, CommandPaletteActionProviderRegistry, FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; import '../../../css/command-palette.css'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; -export const commandPaletteModule = new ContainerModule(bind => { +export const commandPaletteModule = new FeatureModule(bind => { bindAsService(bind, TYPES.IUIExtension, CommandPalette); bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 6394d39..2af5802 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { ContextMenuProviderRegistry, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; +import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; -export const contextMenuModule = new ContainerModule(bind => { +export const contextMenuModule = new FeatureModule(bind => { bind(TYPES.IContextMenuServiceProvider).toProvider( ctx => () => new Promise((resolve, reject) => { diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 9fbf81d..5fe0234 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -13,23 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { configureActionHandler, TYPES } from '~glsp-sprotty'; +import { configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; -export const serverCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { +export const copyPasteModule = new FeatureModule((bind, _unbind, isBound) => { bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); bind(TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); }); /** - * This module is not required if the diagram is deployed in Theia but only intended to be used - * in a standalone deployment of GLSP. If the GLSP diagram in Theia use the Theia-native - * `CopyPasteMenuContribution` in `glsp-theia-integration` instead. + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. */ -export const pasteContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { - bind(TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); - bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); - configureActionHandler({ bind, isBound }, InvokeCopyPasteAction.KIND, InvokeCopyPasteActionHandler); -}); +export const standaloneCopyPasteModule = new FeatureModule( + (bind, _unbind, isBound) => { + bind(TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); + bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); + configureActionHandler({ bind, isBound }, InvokeCopyPasteAction.KIND, InvokeCopyPasteActionHandler); + }, + { requires: copyPasteModule } +); diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index 7ed43e6..6f247cc 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; import '../../../css/decoration.css'; import { GlspDecorationPlacer } from './decoration-placer'; -export const decorationModule = new ContainerModule(bind => { +export const decorationModule = new FeatureModule(bind => { bindAsService(bind, TYPES.IVNodePostprocessor, GlspDecorationPlacer); }); diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts new file mode 100644 index 0000000..cc89ed2 --- /dev/null +++ b/packages/client/src/features/export/export-modules.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + bindAsService, + configureActionHandler, + configureCommand, + ExportSvgAction, + ExportSvgCommand, + ExportSvgKeyListener, + ExportSvgPostprocessor, + FeatureModule, + TYPES +} from '~glsp-sprotty'; +import { ExportSvgActionHandler } from './export-svg-action-handler'; +import { GLSPSvgExporter } from './glsp-svg-exporter'; + +export const exportModule = new FeatureModule((bind, _unbind, isBound) => { + const context = { bind, isBound }; + bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); + configureCommand(context, ExportSvgCommand); + bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); +}); + +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const standaloneExportModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + bindAsService(context, TYPES.KeyListener, ExportSvgKeyListener); + bind(ExportSvgActionHandler).toSelf().inSingletonScope(); + configureActionHandler(context, ExportSvgAction.KIND, ExportSvgActionHandler); + }, + { requires: exportModule } +); diff --git a/packages/client/src/features/export/export-svg-action-handler.ts b/packages/client/src/features/export/export-svg-action-handler.ts new file mode 100644 index 0000000..c0cbd2a --- /dev/null +++ b/packages/client/src/features/export/export-svg-action-handler.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { saveAs } from 'file-saver'; +import { injectable } from 'inversify'; +import { ExportSvgAction, IActionHandler } from '~glsp-sprotty'; + +/** + * The default handler for {@link ExportSvgAction}s. This generic handler can be used in + * any GLSP project independent of the target platform. However, platform integration modules typically + * * this handler is rebound to an application specific handler in platform integration modules + * (e.g. the Theia integration) + */ +@injectable() +export class ExportSvgActionHandler implements IActionHandler { + handle(action: ExportSvgAction): void { + const blob = new Blob([action.svg], { type: 'text/plain;charset=utf-8' }); + saveAs(blob, 'diagram.svg'); + } +} diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index d828338..f91d5a7 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { SetTypeHintsAction, TYPES, configureActionHandler, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SetTypeHintsAction, TYPES, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; -export const typeHintsModule = new ContainerModule((bind, _unbind, isBound) => { +export const typeHintsModule = new FeatureModule((bind, _unbind, isBound) => { bind(TypeHintProvider).toSelf().inSingletonScope(); bind(TYPES.ITypeHintProvider).toService(TypeHintProvider); configureActionHandler({ bind, isBound }, SetTypeHintsAction.KIND, TypeHintProvider); diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 2e3860f..ab12fd6 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; import { CenterCommand, ClosePopupActionHandler, EnableDefaultToolsAction, EnableToolsAction, + FeatureModule, FitToScreenCommand, HoverFeedbackCommand, HoverKeyListener, @@ -36,7 +36,7 @@ import { import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { GlspHoverMouseListener } from './hover'; -export const hoverModule = new ContainerModule((bind, _unbind, isBound) => { +export const hoverModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; bindAsService(context, TYPES.PopupVNodePostprocessor, PopupPositionUpdater); bindAsService(context, TYPES.MouseListener, GlspHoverMouseListener); diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 6bda2fb..45d52ca 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { ApplyLabelEditCommand, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { ApplyLabelEditCommand, FeatureModule, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; -export const labelEditModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const labelEditModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); bindAsService(bind, TYPES.IDefaultTool, DirectLabelEditTool); diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index cb21dad..24f5d94 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -38,9 +38,9 @@ import { SModelRoot, SetBoundsAction, SetBoundsCommand, - TYPES, - defaultModule + TYPES } from '~glsp-sprotty'; +import { defaultModule } from '../../base/default.module'; import { SelectionService } from '../../base/selection-service'; import { resizeFeature } from '../change-bounds/model'; import { diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 5c4d7e6..d66e1d5 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, configureActionHandler } from '~glsp-sprotty'; import { AlignElementsAction, AlignElementsActionHandler, @@ -22,7 +21,7 @@ import { ResizeElementsActionHandler } from './layout-elements-action'; -export const layoutModule = new ContainerModule((bind, _unbind, isBound) => { +export const layoutModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 69d22df..fff4628 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { NavigateToExternalTargetAction, NavigateToTargetAction, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, NavigateToExternalTargetAction, NavigateToTargetAction, configureActionHandler } from '~glsp-sprotty'; import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; -export const navigationModule = new ContainerModule((bind, _unbind, isBound) => { +export const navigationModule = new FeatureModule((bind, _unbind, isBound) => { bind(NavigationTargetResolver).toSelf().inSingletonScope(); bind(NavigationActionHandler).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 59132fc..525a9ff 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; import { AddRemoveBezierSegmentCommand, AnchorComputerRegistry, @@ -24,6 +23,7 @@ import { DiamondAnchor, EdgeRouterRegistry, EllipseAnchor, + FeatureModule, ManhattanDiamondAnchor, ManhattanEllipticAnchor, ManhattanRectangularAnchor, @@ -35,7 +35,7 @@ import { } from '~glsp-sprotty'; import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; -export const routingModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const routingModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bind(EdgeRouterRegistry).toSelf().inSingletonScope(); bind(AnchorComputerRegistry).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 0a0a873..1a52f94 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; import { SaveModelKeyboardListener } from './save-keylistener'; /** - * This module is not required the diagram is deployed in Theia with the `GLSPDiagramWidget` - * but only intended to be used in a standalone deployment of GLSP. + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. */ -export const saveModule = new ContainerModule(bind => { +export const saveModule = new FeatureModule(bind => { bindAsService(bind, TYPES.KeyListener, SaveModelKeyboardListener); }); diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index cc72f75..a9207a3 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -13,16 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SelectKeyboardListener, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; import { SelectAllCommand, SelectCommand } from '../../base/selection-service'; import { SelectFeedbackCommand } from './select-feedback-command'; import { RankedSelectMouseListener } from './select-mouse-listener'; -export const selectModule = new ContainerModule((bind, _unbind, isBound) => { +export const selectModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureCommand(context, SelectCommand); configureCommand(context, SelectAllCommand); configureCommand(context, SelectFeedbackCommand); bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); }); + +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const standaloneSelectModule = new FeatureModule( + bind => { + bindAsService(bind, TYPES.KeyListener, SelectKeyboardListener); + }, + { requires: selectModule } +); diff --git a/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts index 4dcb595..4eadf9e 100644 --- a/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { SourceModelChangedAction, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, SourceModelChangedAction, configureActionHandler } from '~glsp-sprotty'; import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; -export const sourceModelWatcherModule = new ContainerModule((bind, _unbind, isBound) => { +export const sourceModelWatcherModule = new FeatureModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, SourceModelChangedAction.KIND, SourceModelChangedActionHandler); }); diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index c169414..f5fd50d 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -14,11 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES } from '~glsp-sprotty'; +import { FeatureModule, TYPES } from '~glsp-sprotty'; import { MetadataPlacer } from './metadata-placer'; -export const svgMetadataModule = new ContainerModule(bind => { +export const svgMetadataModule = new FeatureModule(bind => { bind(MetadataPlacer).toSelf().inSingletonScope(); bind(TYPES.IVNodePostprocessor).toService(MetadataPlacer); }); diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index 7df0c7e..dd1dd56 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, configureActionHandler, EnableDefaultToolsAction, TYPES } from '~glsp-sprotty'; +import { bindAsService, configureActionHandler, EnableDefaultToolsAction, FeatureModule, TYPES } from '~glsp-sprotty'; import '../../../css/tool-palette.css'; import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; -export const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { +export const toolPaletteModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { bindAsService(bind, TYPES.IUIExtension, ToolPalette); configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); diff --git a/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts index 547d6b3..2ad90f1 100644 --- a/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts @@ -13,14 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService, configureCommand, configureView } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '~glsp-sprotty'; import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { SResizeHandleView } from './view'; -export const changeBoundsToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const changeBoundsToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 2c58162..8f41819 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; -export const deletionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const deletionToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); bindAsService(context, TYPES.ITool, MouseDeleteTool); diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index b1f7ad1..933b437 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -13,12 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; import { configureDanglingFeedbackEdge } from './dangling-edge-feedback'; import { EdgeCreationTool } from './edge-creation-tool'; -export const edgeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const edgeCreationToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.ITool, EdgeCreationTool); configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index 688f242..b2c2901 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; import { configureDanglingFeedbackEdge } from '../edge-creation/dangling-edge-feedback'; import { EdgeEditTool } from './edge-edit-tool'; import { @@ -24,7 +23,7 @@ import { SwitchRoutingModeCommand } from './edge-edit-tool-feedback'; -export const edgeEditToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const edgeEditToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index be8353c..f5fa387 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -13,15 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, bindAsService, configureCommand, configureModelElement } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand, configureModelElement } from '~glsp-sprotty'; import { MarqueeMouseTool } from './marquee-mouse-tool'; import { MarqueeTool } from './marquee-tool'; import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-tool-feedback'; import { MarqueeNode } from './model'; import { MarqueeView } from './view'; -export const marqueeSelectionToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const marqueeSelectionToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IDefaultTool, MarqueeTool); bindAsService(context, TYPES.ITool, MarqueeMouseTool); diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index 294137c..430160a 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; import { NodeCreationTool } from './node-creation-tool'; -export const nodeCreationToolModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const nodeCreationToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.ITool, NodeCreationTool); configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index 936e830..c58b3fb 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule, injectable } from 'inversify'; -import { Action, EnableDefaultToolsAction, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; +import { injectable } from 'inversify'; +import { Action, EnableDefaultToolsAction, FeatureModule, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; /** @@ -33,6 +33,6 @@ export class EnableDefaultToolsOnFocusLossHandler implements IActionHandler { /** * Enables the default tools in the tool manager if the diagram looses focus. */ -export const toolFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { +export const toolFocusLossModule = new FeatureModule((bind, _unbind, isBound) => { configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); }); diff --git a/packages/client/src/features/save/model.ts b/packages/client/src/features/undo-redo/undo-redo-key-listener.ts similarity index 51% rename from packages/client/src/features/save/model.ts rename to packages/client/src/features/undo-redo/undo-redo-key-listener.ts index 05a8c5c..d7ab735 100644 --- a/packages/client/src/features/save/model.ts +++ b/packages/client/src/features/undo-redo/undo-redo-key-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelExtension, SModelRoot } from '~glsp-sprotty'; -export const saveFeature = Symbol.for('saveFeature'); +import { Action, KeyListener, RedoAction, SModelElement, UndoAction, isMac, matchesKeystroke } from '~glsp-sprotty'; -export interface Saveable extends SModelExtension { - dirty: boolean; -} - -export function isSaveable(element: SModelElement): element is SModelRoot & Saveable { - return element.hasFeature(saveFeature); +/** + * Key listener that listens to the typical keyboard shortcuts for undo/redo and dispatches the corresponding actions. + */ +export class GLSPUndoRedoKeyListener extends KeyListener { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'KeyZ', 'ctrlCmd')) { + return [UndoAction.create()]; + } + if (matchesKeystroke(event, 'KeyZ', 'ctrlCmd', 'shift') || (!isMac() && matchesKeystroke(event, 'KeyY', 'ctrlCmd'))) { + return [RedoAction.create()]; + } + return []; + } } diff --git a/packages/client/src/features/export/export-module.ts b/packages/client/src/features/undo-redo/undo-redo-module.ts similarity index 57% rename from packages/client/src/features/export/export-module.ts rename to packages/client/src/features/undo-redo/undo-redo-module.ts index 3bb9878..9022ce2 100644 --- a/packages/client/src/features/export/export-module.ts +++ b/packages/client/src/features/undo-redo/undo-redo-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ + import { ContainerModule } from 'inversify'; -import { bindAsService, configureCommand, ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, TYPES } from '~glsp-sprotty'; -import { GLSPSvgExporter } from './glsp-svg-exporter'; +import { bindAsService, TYPES } from '~glsp-sprotty'; +import { GLSPUndoRedoKeyListener } from './undo-redo-key-listener'; -export const exportModule = new ContainerModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - bindAsService(context, TYPES.KeyListener, ExportSvgKeyListener); - bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); - configureCommand(context, ExportSvgCommand); - bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const undoRedoModule = new ContainerModule((bind, unbind, isBound, rebind) => { + bindAsService(bind, TYPES.KeyListener, GLSPUndoRedoKeyListener); }); diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 927aa88..0c3fb98 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -27,12 +27,12 @@ import { SModelRoot, SNodeSchema, SParentElement, - TYPES, - defaultModule + TYPES } from '~glsp-sprotty'; +import { defaultModule } from '../../base/default.module'; import { decorationModule } from '../decoration/decoration-module'; import { MarkerNavigator } from './marker-navigator'; -import { markerNavigatorModule } from './validation-module'; +import { markerNavigatorModule } from './validation-modules'; describe('MarkerNavigator', () => { const container = new Container(); diff --git a/packages/client/src/features/validation/validation-module.ts b/packages/client/src/features/validation/validation-modules.ts similarity index 68% rename from packages/client/src/features/validation/validation-module.ts rename to packages/client/src/features/validation/validation-modules.ts index aba687c..7077390 100644 --- a/packages/client/src/features/validation/validation-module.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { SetMarkersAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SetMarkersAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { LeftToRightTopToBottomComparator, MarkerNavigator, @@ -26,7 +25,7 @@ import { } from './marker-navigator'; import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersActionHandler, ValidationFeedbackEmitter } from './validate'; -export const validationModule = new ContainerModule((bind, _unbind, isBound) => { +export const validationModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureActionHandler(context, SetMarkersAction.KIND, SetMarkersActionHandler); configureCommand(context, ApplyMarkersCommand); @@ -34,18 +33,21 @@ export const validationModule = new ContainerModule((bind, _unbind, isBound) => bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); }); -export const markerNavigatorModule = new ContainerModule((bind, _unbind, isBound) => { +export const markerNavigatorModule = new FeatureModule((bind, _unbind, isBound) => { bind(SModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); bind(MarkerNavigator).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, NavigateToMarkerAction.KIND, NavigateToMarkerActionHandler); }); /** - * This module is not required if the diagram is deployed in Theia but only intended to be used - * in a standalone deployment of GLSP. If the GLSP diagram is in Theia use the Theia-native - * `registerMarkerNavigationCommands()` in `glsp-theia-integration` instead. + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. */ -export const markerNavigatorContextMenuModule = new ContainerModule(bind => { - bindAsService(bind, TYPES.IContextMenuProvider, MarkerNavigatorContextMenuItemProvider); - bindAsService(bind, TYPES.KeyListener, MarkerNavigatorKeyListener); -}); +export const standaloneMarkerNavigatorModule = new FeatureModule( + bind => { + bindAsService(bind, TYPES.IContextMenuProvider, MarkerNavigatorContextMenuItemProvider); + bindAsService(bind, TYPES.KeyListener, MarkerNavigatorKeyListener); + }, + { requires: markerNavigatorModule } +); diff --git a/packages/client/src/features/viewport/viewport-module.ts b/packages/client/src/features/viewport/viewport-modules.ts similarity index 74% rename from packages/client/src/features/viewport/viewport-module.ts rename to packages/client/src/features/viewport/viewport-modules.ts index 971d51b..18398a0 100644 --- a/packages/client/src/features/viewport/viewport-module.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; import { bindAsService, CenterCommand, @@ -22,6 +21,7 @@ import { configureCommand, EnableDefaultToolsAction, EnableToolsAction, + FeatureModule, FitToScreenCommand, GetViewportCommand, SetViewportCommand, @@ -30,17 +30,29 @@ import { } from '~glsp-sprotty'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; -export const viewportModule = new ContainerModule((bind, _unbind, isBound) => { +export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; configureCommand(context, CenterCommand); configureCommand(context, FitToScreenCommand); configureCommand(context, GetViewportCommand); configureCommand(context, SetViewportCommand); - bindAsService(context, TYPES.KeyListener, CenterKeyboardListener); bindAsService(context, TYPES.MouseListener, ZoomMouseListener); bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); }); + +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const standaloneViewportModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + bindAsService(context, TYPES.KeyListener, CenterKeyboardListener); + }, + { requires: viewportModule } +); diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/client/src/glsp-sprotty/index.ts index 7fa8a2d..e0011f2 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -320,7 +320,6 @@ import modelSourceModule from 'sprotty/lib/model-source/di.config'; export { buttonModule, - defaultModule, edgeEditModule, edgeIntersectionModule, edgeLayoutModule, @@ -335,13 +334,14 @@ export { commandPaletteModule as sprottyCommandModule, contextMenuModule as sprottyContextMenuModule, decorationModule as sprottyDecorationModule, + defaultModule as sprottyDefaultModule, exportModule as sprottyExportModule, hoverModule as sprottyHoverModule, labelEditModule as sprottyLabelEditModule, routingModule as sprottyRoutingModule, selectModule as sprottySelectModule, + undoRedoModule as sprottyUndoRedoModule, viewportModule as sprottyViewportModule, - undoRedoModule, updateModule, zorderModule }; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 11c4a08..b57a75c 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -37,7 +37,6 @@ export * from './base/source-uri-aware'; export * from './base/tool-manager/glsp-tool-manager'; export * from './base/view/mouse-tool'; export * from './base/view/view-registry'; -export * from './container-modules'; // // ------------------ Features ------------------ @@ -74,7 +73,6 @@ export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-target-resolver'; export * from './features/reconnect/model'; export * from './features/routing/glsp-manhattan-edge-router'; -export * from './features/save/model'; export * from './features/save/save-keylistener'; export * from './features/select/select-feedback-command'; export * from './features/select/select-mouse-listener'; @@ -98,6 +96,7 @@ export * from './features/tools/marquee-selection/marquee-tool-feedback'; export * from './features/tools/marquee-selection/model'; export * from './features/tools/marquee-selection/view'; export * from './features/tools/node-creation/node-creation-tool'; +export * from './features/undo-redo/undo-redo-key-listener'; export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; @@ -106,8 +105,10 @@ export * from './glsp-sprotty'; export * from './glsp-sprotty/types'; // // ------------------ Misc ------------------ +export * from './default-modules'; export * from './lib/model'; export * from './model-source/glsp-diagram-server'; +export * from './standalone-modules'; export * from './utils/argument-utils'; export * from './utils/html-utils'; export * from './utils/layout-utils'; @@ -116,7 +117,7 @@ export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; // ------------------ DI Modules ------------------ -export * from './base/base-module'; +export * from './base/default.module'; export * from './features/accessibility/accessibility-module'; export * from './features/accessibility/move-zoom/move-zoom-module'; export * from './features/accessibility/resize-key-tool/resize-key-module'; @@ -126,6 +127,7 @@ export * from './features/command-palette/command-palette-module'; export * from './features/context-menu/context-menu-module'; export * from './features/copy-paste/copy-paste-modules'; export * from './features/decoration/decoration-module'; +export * from './features/export/export-modules'; export * from './features/hints/type-hints-module'; export * from './features/hover/hover-module'; export * from './features/label-edit/label-edit-module'; @@ -144,5 +146,6 @@ export * from './features/tools/edge-edit/edge-edit-module'; export * from './features/tools/marquee-selection/marquee-selection-module'; export * from './features/tools/node-creation/node-creation-module'; export * from './features/tools/tool-focus-loss-module'; -export * from './features/validation/validation-module'; -export * from './features/viewport/viewport-module'; +export * from './features/undo-redo/undo-redo-module'; +export * from './features/validation/validation-modules'; +export * from './features/viewport/viewport-modules'; diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index a4ee16a..ce64c6d 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -26,10 +26,9 @@ import { viewportFeature } from '~glsp-sprotty'; import { Containable, containerFeature } from '../features/hints/model'; -import { Saveable, saveFeature } from '../features/save/model'; -export class GLSPGraph extends SGraph implements Saveable, Containable { - static override readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, saveFeature, containerFeature]; +export class GLSPGraph extends SGraph implements Containable { + static override readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, containerFeature]; dirty = false; isContainableElement(input: string | SModelElement | SModelElementSchema): boolean { return true; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 07a81f8..a74decf 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -85,6 +85,10 @@ export class GLSPDiagramServer extends DiagramServerProxy implements SourceUriAw return super.handleLocally(action); } + protected override handleExportSvgAction(action: ExportSvgAction): boolean { + return false; + } + protected handleServerMessageAction(action: ServerMessageAction): boolean { this.logger.log('GLSPDiagramServer', `[${action.severity}] -${action.message}`); return false; diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts new file mode 100644 index 0000000..f1bbb1d --- /dev/null +++ b/packages/client/src/standalone-modules.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ModuleConfiguration } from '~glsp-sprotty'; +import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; +import { standaloneExportModule } from './features/export/export-modules'; +import { saveModule } from './features/save/save-module'; +import { standaloneSelectModule } from './features/select/select-module'; +import { undoRedoModule } from './features/undo-redo/undo-redo-module'; +import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; +import { standaloneViewportModule } from './features/viewport/viewport-modules'; +/** + * Configuration of all `standalone` modules. + * + * Standalone modules compose additional features/services that are intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + * + * Typically standalone modules built on top of (i.e require ) a default module and are therefore conditionally loaded if the + * required default modules have been loaded beforehand. + */ + +export const STANDALONE_MODULES = [ + standaloneViewportModule, + standaloneCopyPasteModule, + standaloneMarkerNavigatorModule, + standaloneSelectModule, + standaloneExportModule, + saveModule, + undoRedoModule +] as const; + +export const STANDALONE_MODULE_CONFIG: ModuleConfiguration = { + add: [...STANDALONE_MODULES] +}; diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 5b2d31b..bd05350 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; import { BindingContext, CircularNode, @@ -22,6 +21,7 @@ import { DiamondNode, DiamondNodeView, ExpandButtonView, + FeatureModule, ForeignObjectElement, ForeignObjectView, HtmlRoot, @@ -54,7 +54,7 @@ import { GEdgeView } from './glsp-edge-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; -export const baseViewModule = new ContainerModule((bind, unbind, isBound, rebind) => { +export const baseViewModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; configureDefaultModelElements(context); }); diff --git a/packages/protocol/src/utils/di-util.spec.ts b/packages/protocol/src/utils/di-util.spec.ts new file mode 100644 index 0000000..8781a7a --- /dev/null +++ b/packages/protocol/src/utils/di-util.spec.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import { FeatureModule } from './di-util'; +describe('DI Utils', () => { + describe('FeatureModule', () => { + const container = new Container(); + + const moduleA = new FeatureModule(() => {}); + const moduleB = new FeatureModule(() => {}); + + afterEach(() => { + container.unbindAll(); + }); + + describe('No or single required module', () => { + it('Should load a feature module with no required module', () => { + const moduleWithNoRequirements = new FeatureModule(bind => { + bind('Foo').toConstantValue('Foo'); + }); + container.load(moduleWithNoRequirements); + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; + expect(container.isBound('Foo')).to.be.true; + }); + it('Should load a feature module with met required module', () => { + const moduleWithNoRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleA, moduleWithNoRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; + expect(container.isBound('Foo')).to.be.true; + }); + it('Should not load a feature module if required module is loaded afterwards', () => { + const moduleWithNoRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleWithNoRequirements, moduleA); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + it('Should not load a feature module with missing required module', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleWithUnmetRequirements); + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + }); + describe('Multiple required modules', () => { + it('Should load feature module with loaded required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleA, moduleWithUnmetRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + }); + it('Should not load a feature module with missing required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleWithUnmetRequirements); + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + it('Should not load a feature module with partially loaded required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleA, moduleWithUnmetRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + }); +}); diff --git a/packages/protocol/src/utils/di-util.ts b/packages/protocol/src/utils/di-util.ts index 7bc0f29..81f9e2f 100644 --- a/packages/protocol/src/utils/di-util.ts +++ b/packages/protocol/src/utils/di-util.ts @@ -33,13 +33,24 @@ export interface BindingContext { * consists of the set of {@link ContainerModule}s that should be loaded in the container. * In addition, for more fine-grained control {@link ModuleConfiguration}s can be passed as part fo the container configuration * Module loading is distinct,this means each module will only get loaded once even if it is configured multiple times. - @param containerConfiguration + @param containerConfigurations * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. @returns The initialized container. */ -export function initializeContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { +export function initializeContainer(container: Container, ...containerConfigurations: ContainerConfiguration): Container { + const modules = resolveContainerConfiguration(...containerConfigurations); + container.load(...modules); + return container; +} + +/** + * Processes the given container configurations and returns the corresponding set of {@link ContainerModule}s. + * @param containerConfigurations The container configurations to resolves + * @returns an Array of resolved container modules + */ +export function resolveContainerConfiguration(...containerConfigurations: ContainerConfiguration): ContainerModule[] { const modules: ContainerModule[] = []; - containerConfiguration.forEach(config => { + containerConfigurations.forEach(config => { if (isContainerModule(config)) { distinctAdd(modules, config); } else { @@ -51,10 +62,8 @@ export function initializeContainer(container: Container, ...containerConfigurat } } }); - container.load(...modules); - return container; + return modules; } - /** * The container modules might originate form different inversify contexts (e.g. `inversify` vs. `@theia/core/shared/inversify`). * If this is the case an instanceof check can return false negative. @@ -81,6 +90,73 @@ export interface ModuleConfiguration { remove?: MaybeArray; } +/** + * Optional constructor options for {@link FeatureModule}s. + */ +export interface FeatureModuleOptions { + /** + * The set of feature modules that is required in order for this module to load. + */ + requires?: MaybeArray; + /** + * Optional `featureId` that should be used. If omitted an id will be autogenerated + */ + featureId?: symbol; +} +/** + * A `FeatureModule` is a specialized {@link ContainerModule} that can declare dependencies to other {@link FeatureModule}. + * A feature module will only be loaded into a container if all of its required modules haven been loaded before. T + * Each feature module binds its `featureId` be default. This enables querying of existing container to check wether a + * feature module has been loaded into this container. + */ + +export class FeatureModule extends ContainerModule { + readonly featureId: symbol; + + readonly requires?: MaybeArray; + + constructor(registry: interfaces.ContainerModuleCallBack, options: FeatureModuleOptions = {}) { + super((bind, unbind, isBound, ...rest) => { + if (this.configure(bind, isBound)) { + registry(bind, unbind, isBound, ...rest); + } + }); + this.featureId = options.featureId ?? this.createFeatureId(); + this.requires = options.requires; + } + + protected createFeatureId(): symbol { + return Symbol(this.id); + } + + /** + * Configures the feature module i.e. checks if the requirements are met. + * If this is the case the {@link FeatureModule.featureId} will be bound and the module will be loaded + * @param bind container bind function + * @param isBound container isBound function + * @returns `true` if all requirements are met and the module is loaded. `false` otherwise + */ + configure(bind: interfaces.Bind, isBound: interfaces.IsBound): boolean { + if (this.checkRequirements(isBound)) { + bind(this.featureId).toConstantValue(this.featureId); + return true; + } + return false; + } + + /** + * Checks if all required {@link FeatureModule}s are already loaded/bound in the container. + * @param isBound The `isBound` property of the module callback. Used to check the required modules. + * @returns `true` if all requirements are met, `false` otherwise + */ + protected checkRequirements(isBound: interfaces.IsBound): boolean { + return this.requires ? asArray(this.requires).every(module => isBound(module.featureId)) : true; + } + + isLoaded(context: Pick): boolean { + return context.isBound(this.featureId); + } +} /** * Checks wether the given service identifier is already bound in the given context * then either calls the `bind` or `rebind` function respectively. From c685dc0a69c27c38fce5dd919c921279e88fe890 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 24 Jul 2023 01:57:09 -0700 Subject: [PATCH 235/566] GLSP-77: Allow WebSocket connections to reconnect after interrupt (#54) * GLSP-77: Allow WebSocket connections to reconnect after interrupt If Websocket connections reconnect after an interrupt, ensure that the last modelState is restored on RequestModelAction if available Part of https://github.com/eclipse-glsp/glsp/issues/77 --- .../model/request-model-action-handler.ts | 21 ++++++++++++++++++- .../src/common/utils/client-options-util.ts | 8 +++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index a08aa0a..2b1b633 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -17,6 +17,7 @@ import { Action, RequestModelAction, ServerStatusAction } from '@eclipse-glsp/pr import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../../actions/action-dispatcher'; import { ActionHandler } from '../../actions/action-handler'; +import { ClientOptionsUtil } from '../../utils/client-options-util'; import { Logger } from '../../utils/logger'; import { ProgressMonitor, ProgressService } from '../progress/progress-service'; import { ModelState } from './model-state'; @@ -49,13 +50,31 @@ export class RequestModelActionHandler implements ActionHandler { this.logger.debug('Execute RequestModelAction:', action); this.modelState.setAll(action.options ?? {}); + const isReconnecting = ClientOptionsUtil.isReconnecting(action.options); + const progress = this.reportModelLoading('Model loading in progress'); - await this.sourceModelStorage.loadSourceModel(action); + + if (isReconnecting) { + await this.handleReconnect(action); + } else { + await this.sourceModelStorage.loadSourceModel(action); + } this.reportModelLoadingFinished(progress); return this.submissionHandler.submitModel(); } + protected async handleReconnect(action: RequestModelAction): Promise { + const oldModelRoot = this.modelState.root; + if (oldModelRoot) { + // decrease revision by one, as each submit will increase it by one; + // the next save would produce warning that source model was changed otherwise + this.modelState.root.revision = (this.modelState.root.revision ?? 0) - 1; + } else { + await this.sourceModelStorage.loadSourceModel(action); + } + } + protected reportModelLoading(message: string): ProgressMonitor { this.actionDispatcher.dispatch(ServerStatusAction.create(message, { severity: 'INFO' })); return this.progressService.start(message); diff --git a/packages/server/src/common/utils/client-options-util.ts b/packages/server/src/common/utils/client-options-util.ts index e02a84e..5ed6387 100644 --- a/packages/server/src/common/utils/client-options-util.ts +++ b/packages/server/src/common/utils/client-options-util.ts @@ -13,10 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; +import { ArgsUtil } from './args-util'; + export class ClientOptionsUtil { private static FILE_PREFIX = 'file://'; + public static IS_RECONNECTING = 'isReconnecting'; public static adaptUri(uri: string): string { return uri.replace(this.FILE_PREFIX, ''); } + + public static isReconnecting(options?: Args): boolean { + return ArgsUtil.getBoolean(options, ClientOptionsUtil.IS_RECONNECTING); + } } From 7481fc6d19b3cb5145420e8e9d8ee6ae77b73135 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 24 Jul 2023 02:08:54 -0700 Subject: [PATCH 236/566] GLSP-77: Allow WebSocket connections to reconnect after interrupt (#269) * GLSP-77: Allow WebSocket connections to reconnect after interrupt - Introduce GLSPWebSocketProvider to allow websocket connections to reconnect after interrupt - Make use of ws provider in standalone example Part of https://github.com/eclipse-glsp/glsp/issues/77 --- examples/workflow-standalone/src/app.ts | 45 +++++-- .../jsonrpc/ws-connection-provider.ts | 112 ++++++++++++++++++ packages/protocol/src/index.ts | 1 + 3 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index b72a307..c65b4e4 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -18,15 +18,17 @@ import 'reflect-metadata'; import { ApplicationIdProvider, BaseJsonrpcGLSPClient, - configureServerActions, EnableToolPaletteAction, GLSPActionDispatcher, GLSPClient, GLSPDiagramServer, - listen, + GLSPWebSocketProvider, RequestModelAction, RequestTypeHintsAction, - TYPES + ServerMessageAction, + ServerStatusAction, + TYPES, + configureServerActions } from '@eclipse-glsp/client'; import { join, resolve } from 'path'; import { MessageConnection } from 'vscode-jsonrpc'; @@ -34,20 +36,22 @@ import createContainer from './di.config'; const port = 8081; const id = 'workflow'; const diagramType = 'workflow-diagram'; -const websocket = new WebSocket(`ws://localhost:${port}/${id}`); const loc = window.location.pathname; const currentDir = loc.substring(0, loc.lastIndexOf('/')); const examplePath = resolve(join(currentDir, '../app/example1.wf')); const clientId = ApplicationIdProvider.get() + '_' + examplePath; -const container = createContainer(); -const diagramServer = container.get(TYPES.ModelSource); +const webSocketUrl = `ws://localhost:${port}/${id}`; + +let container = createContainer(); +let diagramServer = container.get(TYPES.ModelSource); diagramServer.clientId = clientId; -listen(websocket, connection => initialize(connection)); +const wsProvider = new GLSPWebSocketProvider(webSocketUrl); +wsProvider.listen({ onConnection: initialize, onReconnect: reconnect, logger: console }); -async function initialize(connectionProvider: MessageConnection): Promise { +async function initialize(connectionProvider: MessageConnection, isReconnecting = false): Promise { const client = new BaseJsonrpcGLSPClient({ id, connectionProvider }); await diagramServer.connect(client); @@ -57,20 +61,39 @@ async function initialize(connectionProvider: MessageConnection): Promise }); await configureServerActions(result, diagramType, container); - const actionDispatcher = container.get(GLSPActionDispatcher); + const actionDispatcher: GLSPActionDispatcher = container.get(GLSPActionDispatcher); await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); + actionDispatcher.dispatch( RequestModelAction.create({ options: { sourceUri: `file://${examplePath}`, - diagramType + diagramType, + isReconnecting } }) ); actionDispatcher.dispatch(RequestTypeHintsAction.create()); await actionDispatcher.onceModelInitialized(); actionDispatcher.dispatch(EnableToolPaletteAction.create()); + + if (isReconnecting) { + const message = `Connection to the ${id} glsp server got closed. Connection was successfully re-established.`; + const timeout = 5000; + const severity = 'WARNING'; + actionDispatcher.dispatchAll([ + ServerStatusAction.create(message, { severity, timeout }), + ServerMessageAction.create(message, { severity }) + ]); + return; + } } -websocket.onerror = ev => alert('Connection to server errored. Please make sure that the server is running'); +async function reconnect(connectionProvider: MessageConnection): Promise { + container = createContainer(); + diagramServer = container.get(TYPES.ModelSource); + diagramServer.clientId = clientId; + + initialize(connectionProvider, true /* isReconnecting */); +} diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts new file mode 100644 index 0000000..55ad960 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts @@ -0,0 +1,112 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Logger, MessageConnection } from 'vscode-jsonrpc'; +import { MaybePromise } from '../../utils/type-util'; +import { createWebSocketConnection, wrap } from './websocket-connection'; + +export interface GLSPWebSocketOptions { + /** + * Allow automatic reconnect of WebSocket connections + * @default true + */ + reconnecting?: boolean; + /** + * Max attempts of reconnects + * @default Infinity + */ + reconnectAttempts?: number; + /** + * The time delay in milliseconds between reconnect attempts + * @default 1000 + */ + reconnectDelay?: number; +} + +export const GLSPConnectionHandler = Symbol('GLSPConnectionHandler'); +export interface GLSPConnectionHandler { + onConnection?(connection: MessageConnection): MaybePromise; + onReconnect?(connection: MessageConnection): MaybePromise; + logger?: Logger; +} + +export class GLSPWebSocketProvider { + protected webSocket: WebSocket; + protected reconnectTimer: NodeJS.Timer; + protected reconnectAttempts = 0; + + protected options: GLSPWebSocketOptions = { + // default values + reconnecting: true, + reconnectAttempts: Infinity, + reconnectDelay: 1000 + }; + + constructor(protected url: string, options?: GLSPWebSocketOptions) { + this.options = Object.assign(this.options, options); + } + + protected createWebSocket(url: string): WebSocket { + return new WebSocket(url); + } + + listen(handler: GLSPConnectionHandler, isReconnecting = false): Promise { + this.webSocket = this.createWebSocket(this.url); + + this.webSocket.onerror = (): void => { + handler.logger?.error('GLSPWebSocketProvider Connection to server errored. Please make sure that the server is running!'); + clearInterval(this.reconnectTimer); + this.webSocket.close(); + }; + + return new Promise(resolve => { + this.webSocket.onopen = (): void => { + clearInterval(this.reconnectTimer); + const wrappedSocket = wrap(this.webSocket); + const wsConnection = createWebSocketConnection(wrappedSocket, handler.logger); + + this.webSocket.onclose = (): void => { + const { reconnecting, reconnectAttempts, reconnectDelay } = this.options; + if (reconnecting) { + if (this.reconnectAttempts >= reconnectAttempts!) { + handler.logger?.error( + 'GLSPWebSocketProvider WebSocket reconnect failed - maximum number reconnect attempts ' + + `(${reconnectAttempts}) was exceeded!` + ); + } else { + this.reconnectTimer = setInterval(() => { + handler.logger?.warn('GLSPWebSocketProvider reconnecting...'); + this.listen(handler, true); + this.reconnectAttempts++; + }, reconnectDelay!); + } + } else { + handler.logger?.error('GLSPWebSocketProvider WebSocket will not reconnect - closing the connection now!'); + } + }; + + if (isReconnecting) { + handler.logger?.warn('GLSPWebSocketProvider Reconnecting!'); + handler.onReconnect?.(wsConnection); + } else { + handler.logger?.warn('GLSPWebSocketProvider Initializing!'); + handler.onConnection?.(wsConnection); + } + resolve(wsConnection); + }; + }); + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 09b4b12..0d7e65a 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -54,6 +54,7 @@ export * from './client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; export * from './client-server-protocol/jsonrpc/websocket-connection'; +export * from './client-server-protocol/jsonrpc/ws-connection-provider'; export * from './client-server-protocol/types'; export * from './model/default-types'; export * from './model/model-schema'; From fb1ce326bd0cfc6e880502d531ab1aa453031a87 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 26 Jul 2023 10:05:54 +0200 Subject: [PATCH 237/566] GLSP-1026 Resolve circular dependency between client and node-server (#270) Remove direct dependency in `@eclipse-glsp/client` and add dedicated download & start script instead. This resolves the circular dependency issues between glsp-client and server-node and unblocks us for releases. Fixes https://github.com/eclipse-glsp/glsp/issues/1026 --- examples/workflow-standalone/package.json | 4 +- .../workflow-standalone/scripts/config.json | 4 + .../scripts/start-example-server.ts | 74 +++++++++++++++++++ examples/workflow-standalone/server/.gitkeep | 0 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 examples/workflow-standalone/scripts/config.json create mode 100644 examples/workflow-standalone/scripts/start-example-server.ts create mode 100644 examples/workflow-standalone/server/.gitkeep diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 449e1ae..bf00384 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -27,6 +27,7 @@ "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", "prepare": "yarn clean && yarn build", + "start:exampleServer": "yarn ts-node ./scripts/start-example-server.ts", "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { @@ -34,7 +35,8 @@ "@eclipse-glsp/client": "1.1.0-next" }, "devDependencies": { - "@eclipse-glsp-examples/workflow-server-bundled": "next", + "@types/shelljs": "0.8.12", + "@types/tar": "6.1.5", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", "inversify": "^6.0.1", diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json new file mode 100644 index 0000000..eae82b0 --- /dev/null +++ b/examples/workflow-standalone/scripts/config.json @@ -0,0 +1,4 @@ +{ + "fileName": "workflow-server", + "version": "next" +} diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts new file mode 100644 index 0000000..9f6a320 --- /dev/null +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as fs from 'fs'; +import * as path from 'path'; +import * as sh from 'shelljs'; +import { extract } from 'tar'; +import * as config from './config.json'; +const serverDirPath = path.resolve(__dirname, '..', 'server'); + +async function run() { + const serverFile = await downloadIfNecessary(); + console.log(); + sh.cd(serverDirPath); + sh.exec(`node ${serverFile} -w -p 8081`); +} + +async function downloadIfNecessary(): Promise { + console.log(`Check if server executable with version ${config.version} is present.`); + + const existingServer = fs.readdirSync(serverDirPath).find(file => file.startsWith(config.fileName)); + if (existingServer) { + const existingVersion = existingServer.replace(config.fileName + '-', '').replace('.js', ''); + const latestVersion = sh + .exec(`npm show @eclipse-glsp-examples/workflow-server-bundled@${config.version} version`, { silent: true }) + .stdout.trim(); + if (existingVersion === latestVersion) { + console.log('Server executable already present. Skip download"'); + return existingServer; + } + } + + console.log('Server executable with correct version not found. Download from npm.'); + if (existingServer) { + fs.rmSync(existingServer); + } + sh.cd(serverDirPath); + const packResultJson = sh + .exec(`npm pack @eclipse-glsp-examples/workflow-server-bundled@${config.version} --json`, { silent: true }) + .stdout.trim(); + const version = JSON.parse(packResultJson)[0].version; + const tarBall = fs.readdirSync(serverDirPath).find(file => file.endsWith('.tgz' || '.tar.gz'))!; + console.log('Extract downloaded server tarball'); + await extract({ + file: tarBall, + cwd: serverDirPath + }); + + const tempDir = path.resolve(serverDirPath, 'package'); + fs.copyFileSync(path.resolve(tempDir, 'wf-glsp-server-node.js'), path.resolve(serverDirPath, `${config.fileName}-${version}.js`)); + fs.copyFileSync( + path.resolve(tempDir, 'wf-glsp-server-node.js.map'), + path.resolve(serverDirPath, `${config.fileName}-${version}.js.map`) + ); + + console.log('Remove temporary files'); + fs.rmSync(tempDir, { force: true, recursive: true }); + fs.rmSync(path.resolve(serverDirPath, tarBall), { force: true }); + return `${config.fileName}-${version}.js`; +} + +run(); diff --git a/examples/workflow-standalone/server/.gitkeep b/examples/workflow-standalone/server/.gitkeep new file mode 100644 index 0000000..e69de29 From c2b9c61fb6a9ff545a9b3113423ea254fd4a52c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:48:51 +0200 Subject: [PATCH 238/566] Introduce list of keyboard shortcuts (#260) - Introduce tool and handler for adding new key shortcuts to the list - Add style to display the list of key shortcuts --- packages/client/css/key-shortcut.css | 107 ++++++++++ .../accessibility/accessibility-module.ts | 2 + .../accessible-key-shortcut-tool.ts | 55 ++++++ .../key-shortcut/accessible-key-shortcut.ts | 182 ++++++++++++++++++ .../accessibility/key-shortcut/di.config.ts | 35 ++++ .../resize-key-tool/resize-key-tool.ts | 23 ++- .../accessibility/search/search-tool.ts | 29 ++- .../view-key-tools/movement-key-tool.ts | 22 ++- .../view-key-tools/zoom-key-tool.ts | 23 ++- packages/client/src/index.ts | 1 + 10 files changed, 469 insertions(+), 10 deletions(-) create mode 100644 packages/client/css/key-shortcut.css create mode 100644 packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts create mode 100644 packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts create mode 100644 packages/client/src/features/accessibility/key-shortcut/di.config.ts diff --git a/packages/client/css/key-shortcut.css b/packages/client/css/key-shortcut.css new file mode 100644 index 0000000..91d8386 --- /dev/null +++ b/packages/client/css/key-shortcut.css @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.keyboard-shortcuts-menu { + border: 1px solid #ccc; + border-radius: 5px; + background-color: #ededee; + margin-bottom: 20px; + position: absolute; + display: flex; + flex-direction: column; + bottom: 0; + right: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + line-height: 1.5; + font-size: 16px; + z-index: 9999; + width: 400px; + max-height: 512px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.keyboard-shortcuts-menu h3 { + font-size: 18px; + font-weight: bold; + margin: 0; + margin-bottom: 10px; + padding: 10px; +} + +.keyboard-shortcuts-menu kbd { + background-color: #e4e1e1; + border: 1px solid #ccc; + border-radius: 3px; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.5); + color: #333; + display: inline-block; + font-size: 0.85em; + font-weight: 600; + line-height: 1; + padding: 2px 4px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + white-space: nowrap; +} + +.keyboard-shortcuts-menu kbd:active { + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2) inset; +} + +.keyboard-shortcuts-container { + display: flex; + flex-direction: column; + max-height: 100%; + padding: 10px; + padding-top: 3px; + overflow: auto; + right: 40px; + top: 25px; + text-align: left; + display: block; + z-index: 1000; + color: black; +} + +.shortcut-entry-container { + display: flex; + justify-content: space-between; + margin-right: 10px; +} + +#key-shortcut-close-btn { + position: absolute; + top: 14px; + right: 5px; + font-size: 16px; + font-weight: bold; + background: #cccccc; + border: none; + cursor: pointer; +} + +.columnTitle { + text-align: left; +} + +.menu-header { + padding: 0.4em; + text-align: left; + background: #cccccc; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; +} diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 98077d0..84ebfd5 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { FeatureModule } from '~glsp-sprotty'; +import { configureShortcutHelpTool } from './key-shortcut/di.config'; import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; @@ -29,4 +30,5 @@ export const accessibilityModule = new FeatureModule((bind, unbind, isBound, reb configureViewKeyTools(context); configureMoveZoom(context); configureSearchPaletteModule(context); + configureShortcutHelpTool(context); }); diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts new file mode 100644 index 0000000..be7d04e --- /dev/null +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { Action, KeyListener, KeyTool, matchesKeystroke, SetUIExtensionVisibilityAction, SModelElement } from '~glsp-sprotty'; +import { BaseGLSPTool } from '../../tools/base-glsp-tool'; +import { KeyShortcutUIExtension } from './accessible-key-shortcut'; + +@injectable() +export class AccessibleKeyShortcutTool extends BaseGLSPTool { + static ID = 'accessible-key-shortcut-tool'; + + @inject(KeyTool) protected readonly keytool: KeyTool; + + protected shortcutKeyListener = new AccessibleShortcutKeyListener(); + + get id(): string { + return AccessibleKeyShortcutTool.ID; + } + + enable(): void { + this.keytool.register(this.shortcutKeyListener); + } + + override disable(): void { + this.keytool.deregister(this.shortcutKeyListener); + } +} + +export class AccessibleShortcutKeyListener extends KeyListener { + protected readonly token = Symbol(AccessibleShortcutKeyListener.name); + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (this.matchesActivateShortcutHelpKeystroke(event)) { + return [SetUIExtensionVisibilityAction.create({ extensionId: KeyShortcutUIExtension.ID, visible: true })]; + } + return []; + } + + protected matchesActivateShortcutHelpKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyH', 'alt'); + } +} diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts new file mode 100644 index 0000000..43b7284 --- /dev/null +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -0,0 +1,182 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { groupBy } from 'lodash'; +import { AbstractUIExtension, Action, IActionHandler, ICommand, matchesKeystroke, SModelRoot } from '~glsp-sprotty'; + +export interface AccessibleKeyShortcutProvider { + registerShortcutKey(): void; +} + +export interface AccessibleKeyShortcut { + shortcuts: string[]; + description: string; + group: string; + position: number; +} + +export interface SetAccessibleKeyShortcutAction extends Action { + kind: typeof SetAccessibleKeyShortcutAction.KIND; + token: string; + keys: AccessibleKeyShortcut[]; +} + +export namespace SetAccessibleKeyShortcutAction { + export const KIND = 'setAccessibleKeyShortcut'; + + export function is(object: any): object is SetAccessibleKeyShortcutAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { token: string; keys: AccessibleKeyShortcut[] }): SetAccessibleKeyShortcutAction { + return { kind: KIND, token: options.token, keys: options.keys }; + } +} + +@injectable() +export class KeyShortcutUIExtension extends AbstractUIExtension implements IActionHandler { + static readonly ID = 'key-shortcut'; + protected container: HTMLDivElement; + protected shortcutsContainer: HTMLDivElement; + protected registrations: Record = {}; + + handle(action: Action): ICommand | Action | void { + if (SetAccessibleKeyShortcutAction.is(action)) { + this.registrations[action.token] = action.keys; + if (this.containerElement) { + this.refreshUI(); + } + } + } + id(): string { + return KeyShortcutUIExtension.ID; + } + + containerClass(): string { + return KeyShortcutUIExtension.ID; + } + + override show(root: Readonly, ...contextElementIds: string[]): void { + super.show(root, ...contextElementIds); + this.shortcutsContainer.focus(); + } + + protected refreshUI(): void { + this.shortcutsContainer.innerHTML = ''; + + const registrations = Object.values(this.registrations).flatMap(r => r); + registrations.sort((a, b) => { + if (a.group < b.group) { + return -1; + } + if (a.group > b.group) { + return 1; + } + + return a.position - b.position; + }); + + const grouped = groupBy(registrations, k => k.group); + + const groupTable = document.createElement('table'); + const tableHead = document.createElement('thead'); + const tableBody = document.createElement('tbody'); + + const headerRow = document.createElement('tr'); + const commandCell = document.createElement('th'); + const keybindingCell = document.createElement('th'); + + commandCell.classList.add('columnTitle'); + commandCell.classList.add('columnTitle'); + + commandCell.innerText = 'Command'; + keybindingCell.innerText = 'Keybinding'; + + headerRow.appendChild(commandCell); + headerRow.appendChild(keybindingCell); + tableHead.appendChild(headerRow); + + for (const [, shortcuts] of Object.entries(grouped)) { + shortcuts.forEach(s => { + tableBody.appendChild(this.createEntry(s)); + }); + } + + groupTable.appendChild(tableHead); + groupTable.appendChild(tableBody); + + this.shortcutsContainer.append(groupTable); + } + + protected getShortcutHTML(shortcuts: string[]): HTMLElement { + const shortcutKeys = document.createElement('span'); + shortcutKeys.innerHTML = shortcuts.map(key => `${key}`).join(' + '); + + return shortcutKeys; + } + + protected createEntry(registration: AccessibleKeyShortcut): HTMLDivElement { + const entryRow = document.createElement('tr'); + const shortcutElement = document.createElement('td'); + const descElement = document.createElement('td'); + + const shortcut = this.getShortcutHTML(registration.shortcuts); + descElement.innerText = registration.description; + + shortcutElement.appendChild(shortcut); + entryRow.appendChild(descElement); + entryRow.appendChild(shortcutElement); + + return entryRow; + } + + protected initializeContents(containerElement: HTMLElement): void { + this.container = document.createElement('div'); + this.container.classList.add('keyboard-shortcuts-menu'); + + // create title + const menuTitle = document.createElement('h3'); + menuTitle.classList.add('menu-header'); + menuTitle.innerText = 'Keyboard Shortcuts'; + this.container.appendChild(menuTitle); + + const closeBtn = document.createElement('button'); + closeBtn.id = 'key-shortcut-close-btn'; + closeBtn.textContent = 'x'; + closeBtn.addEventListener('click', () => { + this.hide(); + }); + + this.container.appendChild(closeBtn); + + // create shortcuts container + this.shortcutsContainer = document.createElement('div'); + this.shortcutsContainer.classList.add('keyboard-shortcuts-container'); + this.shortcutsContainer.tabIndex = 30; + this.shortcutsContainer.addEventListener('keydown', (event: KeyboardEvent) => { + if (event.key === 'Escape' || matchesKeystroke(event, 'KeyH', 'alt')) { + this.hide(); + } + }); + + this.container.appendChild(this.shortcutsContainer); + containerElement.appendChild(this.container); + containerElement.ariaLabel = 'Shortcut-Menu'; + + this.refreshUI(); + } +} diff --git a/packages/client/src/features/accessibility/key-shortcut/di.config.ts b/packages/client/src/features/accessibility/key-shortcut/di.config.ts new file mode 100644 index 0000000..3d93ca2 --- /dev/null +++ b/packages/client/src/features/accessibility/key-shortcut/di.config.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; +import '../../../../css/key-shortcut.css'; +import { KeyShortcutUIExtension, SetAccessibleKeyShortcutAction } from './accessible-key-shortcut'; +import { AccessibleKeyShortcutTool } from './accessible-key-shortcut-tool'; + +/** + * Handles actions for displaying help/information about keyboard shortcuts. + */ +export const glspShortcutHelpModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureShortcutHelpTool(context); +}); + +export function configureShortcutHelpTool(context: BindingContext): void { + bindAsService(context, TYPES.IDefaultTool, AccessibleKeyShortcutTool); + bindAsService(context, TYPES.IUIExtension, KeyShortcutUIExtension); + configureActionHandler(context, SetAccessibleKeyShortcutAction.KIND, KeyShortcutUIExtension); +} diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index 368383b..d2a2a82 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -17,9 +17,11 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; +import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ResizeElementAction, ResizeType } from './resize-key-handler'; @injectable() @@ -31,6 +33,7 @@ export class ResizeKeyTool implements GLSPTool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(SelectionService) readonly selectionService: SelectionService; protected resizeKeyListener: ResizeKeyListener = new ResizeKeyListener(this); @@ -41,6 +44,7 @@ export class ResizeKeyTool implements GLSPTool { enable(): void { this.keytool.register(this.resizeKeyListener); + this.resizeKeyListener.registerShortcutKey(); } disable(): void { @@ -48,13 +52,30 @@ export class ResizeKeyTool implements GLSPTool { } } @injectable() -export class ResizeKeyListener extends KeyListener { +export class ResizeKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { protected isEditMode = false; + protected readonly token = ResizeKeyListener.name; constructor(protected readonly tool: ResizeKeyTool) { super(); } + registerShortcutKey(): void { + this.tool.actionDispatcher.onceModelInitialized().then(() => { + this.tool.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 }, + { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, + { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, + { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } + ] + }) + ]); + }); + } + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { const actions = []; const selectedElementsIds = this.tool.selectionService.getSelectedElementIDs(); diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index db3fa8b..612711b 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -16,15 +16,16 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, Tool } from '~glsp-sprotty'; +import { Action, KeyListener, KeyTool, SetUIExtensionVisibilityAction, SModelElement, Tool, TYPES } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { SearchAutocompletePalette } from './search-palette'; - @injectable() export class SearchAutocompletePaletteTool implements Tool { static readonly ID = 'glsp.search-autocomplete-palette-tool'; - protected readonly keyListener = new SearchAutocompletePaletteKeyListener(); - + protected readonly keyListener = new SearchAutocompletePaletteKeyListener(this); + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(KeyTool) protected keyTool: KeyTool; get id(): string { @@ -33,6 +34,7 @@ export class SearchAutocompletePaletteTool implements Tool { enable(): void { this.keyTool.register(this.keyListener); + this.keyListener.registerShortcutKey(); } disable(): void { @@ -40,7 +42,24 @@ export class SearchAutocompletePaletteTool implements Tool { } } -export class SearchAutocompletePaletteKeyListener extends KeyListener { +export class SearchAutocompletePaletteKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { + protected readonly token = SearchAutocompletePalette.name; + + constructor(protected tool: SearchAutocompletePaletteTool) { + super(); + } + + registerShortcutKey(): void { + this.tool.actionDispatcher.onceModelInitialized().then(() => { + this.tool.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [{ shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 }] + }) + ]); + }); + } + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (this.matchesSearchActivateKeystroke(event)) { return [ diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 9326135..e33bf18 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -17,10 +17,11 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { SelectionService } from '../../../base/selection-service'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { GridSnapper } from '../../change-bounds/snap'; - -import { SelectionService } from '../../../base/selection-service'; +import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; /** @@ -37,6 +38,7 @@ export class MovementKeyTool implements GLSPTool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(SelectionService) selectionService: SelectionService; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; get id(): string { return MovementKeyTool.ID; @@ -44,6 +46,7 @@ export class MovementKeyTool implements GLSPTool { enable(): void { this.keytool.register(this.movementKeyListener); + this.movementKeyListener.registerShortcutKey(); } disable(): void { @@ -51,13 +54,15 @@ export class MovementKeyTool implements GLSPTool { } } -export class MoveKeyListener extends KeyListener { +export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { // Default x distance used if GridSnapper is not provided static readonly defaultMoveX = 20; // Default y distance used if GridSnapper is not provided static readonly defaultMoveY = 20; + protected readonly token = MoveKeyListener.name; + protected grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY }; constructor(protected readonly tool: MovementKeyTool) { @@ -68,6 +73,17 @@ export class MoveKeyListener extends KeyListener { } } + registerShortcutKey(): void { + this.tool.actionDispatcher.onceModelInitialized().then(() => { + this.tool.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [{ shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element or viewport', group: 'Move', position: 0 }] + }) + ]); + }); + } + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index d6a8ff5..19dce18 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -16,9 +16,11 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, CenterAction, KeyListener, KeyTool, SModelElement } from '~glsp-sprotty'; +import { Action, CenterAction, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; /** @@ -33,6 +35,7 @@ export class ZoomKeyTool implements GLSPTool { protected readonly zoomKeyListener = new ZoomKeyListener(this); @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(SelectionService) selectionService: SelectionService; get id(): string { @@ -41,6 +44,7 @@ export class ZoomKeyTool implements GLSPTool { enable(): void { this.keytool.register(this.zoomKeyListener); + this.zoomKeyListener.registerShortcutKey(); } disable(): void { @@ -51,11 +55,28 @@ export class ZoomKeyTool implements GLSPTool { export class ZoomKeyListener extends KeyListener { static readonly defaultZoomInFactor = 1.1; static readonly defaultZoomOutFactor = 0.9; + protected readonly token = ZoomKeyListener.name; constructor(protected tool: ZoomKeyTool) { super(); } + registerShortcutKey(): void { + this.tool.actionDispatcher.onceModelInitialized().then(() => { + this.tool.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['+'], description: 'Zoom in to element or viewport', group: 'Zoom', position: 0 }, + { shortcuts: ['-'], description: 'Zoom out to element or viewport', group: 'Zoom', position: 1 }, + { shortcuts: ['CTRL', '0'], description: 'Reset zoom to default', group: 'Zoom', position: 2 }, + { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 3 } + ] + }) + ]); + }); + } + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index b57a75c..3abd12e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -46,6 +46,7 @@ export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; +export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; export * from './features/bounds/hbox-layout'; From a2f75436ea7f8594282fb6761e478976717b58d7 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Wed, 26 Jul 2023 07:18:32 -0700 Subject: [PATCH 239/566] GLSP-1078: Harden property type checks if optional flag is set (#271) - Harden type checks to allow undefined value for a property if the optional flag is set - Add respective test cases Resolves https://github.com/eclipse-glsp/glsp/issues/1078 Contributed on behalf of STMicroelectronics --- packages/protocol/src/utils/type-util.spec.ts | 37 +++++++++++++++++++ packages/protocol/src/utils/type-util.ts | 18 ++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/protocol/src/utils/type-util.spec.ts b/packages/protocol/src/utils/type-util.spec.ts index 4c0d3d3..d51a996 100644 --- a/packages/protocol/src/utils/type-util.spec.ts +++ b/packages/protocol/src/utils/type-util.spec.ts @@ -63,6 +63,12 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasStringProp({ someProp: 123 }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasStringProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + expect(hasStringProp({ someProp: 'someKey' }, 'someProp', true)).to.be.true; + }); }); describe('hasBooleanProp', () => { @@ -81,6 +87,12 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasBooleanProp({ someProp: 123 }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasBooleanProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + expect(hasBooleanProp({ someProp: true }, 'someProp', true)).to.be.true; + }); }); describe('hasNumberProp', () => { @@ -99,6 +111,12 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasNumberProp({ someProp: '123' }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasNumberProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + expect(hasNumberProp({ someProp: 123 }, 'someProp', true)).to.be.true; + }); }); describe('hasObjectProp', () => { @@ -117,6 +135,12 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasObjectProp({ someProp: 123 }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasObjectProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + expect(hasObjectProp({ someProp: { value: 'someKey' } }, 'someProp', true)).to.be.true; + }); }); describe('hasFunctionProp', () => { @@ -136,6 +160,13 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasFunctionProp({ someProp: 123 }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasFunctionProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + expect(hasFunctionProp({ someProp: () => {} }, 'someProp', true)).to.be.true; + }); }); describe('hasArrayProp', () => { @@ -154,5 +185,11 @@ describe('TypeUtil', () => { it('should return false for an object that has a property with matching name but invalid type when using the optional flag', () => { expect(hasArrayProp({ someProp: 123 }, 'someProp', true)).to.be.false; }); + it('should return true for an object that has a property with matching name but without value when using the optional flag', () => { + expect(hasArrayProp({ someProp: undefined }, 'someProp', true)).to.be.true; + }); + it('should return true for an object that has a property that matches the given key and type when using the optional flag', () => { + expect(hasArrayProp({ someProp: ['some', 'prop'] }, 'someProp', true)).to.be.true; + }); }); }); diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index ca24947..13b5747 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -82,7 +82,8 @@ export type SafeFunction = (...args: any[]) => T; * @returns `true` if the object has property with matching key of type `string`. */ export function hasStringProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? typeof (object as any)[propertyKey] === 'string' : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? typeof property === 'string' : optional; } /** @@ -93,7 +94,8 @@ export function hasStringProp(object: AnyObject, propertyKey: string, optional = * @returns `true` if the object has property with matching key of type `boolean`. */ export function hasBooleanProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? typeof (object as any)[propertyKey] === 'boolean' : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? typeof property === 'boolean' : optional; } /** @@ -104,7 +106,8 @@ export function hasBooleanProp(object: AnyObject, propertyKey: string, optional * @returns `true` if the object has property with matching key of type `number`. */ export function hasNumberProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? typeof (object as any)[propertyKey] === 'number' : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? typeof property === 'number' : optional; } /** @@ -115,7 +118,8 @@ export function hasNumberProp(object: AnyObject, propertyKey: string, optional = * @returns `true` if the object has property with matching key of type `object`. */ export function hasObjectProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? AnyObject.is((object as any)[propertyKey]) : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? AnyObject.is(property) : optional; } /** @@ -126,7 +130,8 @@ export function hasObjectProp(object: AnyObject, propertyKey: string, optional = * @returns `true` if the object has property with matching key of type `function`. */ export function hasFunctionProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? typeof (object as any)[propertyKey] === 'function' : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? typeof property === 'function' : optional; } /** @@ -137,5 +142,6 @@ export function hasFunctionProp(object: AnyObject, propertyKey: string, optional * @returns `true` if the object has property with matching key of type `Array`. */ export function hasArrayProp(object: AnyObject, propertyKey: string, optional = false): boolean { - return propertyKey in object ? Array.isArray((object as any)[propertyKey]) : optional; + const property = (object as any)[propertyKey]; + return property !== undefined ? Array.isArray(property) : optional; } From a1810ee46627321e1064f2507de81c5a061de55c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 7 Aug 2023 16:36:01 +0200 Subject: [PATCH 240/566] GLSP-900: Introduce `GLSPModelSource` (#272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GLSP-900: Introduce `GLSPModelSource` GLSP-900 - Remove the `GLSPDiagramServer` model source and introduce a new `GLSPModelSource`. In contrast to the `GLSPDiagramServer` the new model source is only responsible for sending actions to and receiving actions for the GLSP server. In addition, we get rid of the `DiagramServer` terminology which was just confusing for adopters. It no longer serves as dedicated action handler. In addition, to a cleaner separation of concerns this also enables using a generic `ModelSource` for all platform integrations i.e. we no longer have the separation between `GLSPDiagramServer´ and `GLSPTheiaDiagramServer` - Remove the `SourceUriAware` interface (no longer needed since we only have one model source implementation) - Introduce status-module that provides a UIExtension to handle and display `ServerStatusActions`. This overlay is a generic replacement for the status div that we typically add to the application specific diagram widgets (theia,vsode) manually. Effectively the behavior has been extracted from the `GLSPDiagramWidget` of the Theia integration - Provide generic dirty state handling. The `EditorContextService` now also manages the dirty state and offers an event to react to dirty state changes. GLSP-850: Refactor & improve base GLSP protocol - Extend `onActionMessage` method of GLSPClient with optional `clientId` to be able to register handlers that are scoped to one client id/session. (no-breaking) - Add a `initializeResult` property to the `GLSPClient` interface which caches the `initializeResult` of the first `initializeServer` invocation. Consecutive `initializeServer` calls return the cached result. (non-breaking) - Ensure that calling `start` when the client is already running exits early and does not result in an runtime error Part of @eclipse-glsp/glsp/issues/900 Part of @eclipse-glsp/glsp/issues/600 --- examples/workflow-standalone/css/diagram.css | 4 + examples/workflow-standalone/src/app.ts | 23 +-- examples/workflow-standalone/src/di.config.ts | 13 +- packages/client/css/decoration.css | 7 - packages/client/css/glsp-sprotty.css | 7 + packages/client/css/status-overlay.css | 75 +++++++++ packages/client/src/base/default.module.ts | 4 + .../client/src/base/editor-context-service.ts | 38 +++-- .../src/base/model/glsp-model-source.ts | 149 ++++++++++++++++++ packages/client/src/default-modules.ts | 4 +- .../context-menu/context-menu-module.ts | 4 +- ...rvice-aware-context-menu-mouse-listener.ts | 6 +- .../status/status-module.ts} | 18 ++- .../src/features/status/status-overlay.ts | 108 +++++++++++++ packages/client/src/index.ts | 7 +- .../src/model-source/glsp-diagram-server.ts | 125 --------------- packages/client/src/standalone-modules.ts | 41 ++++- .../base-glsp-client.spec.ts | 46 +++++- .../base-glsp-client.ts | 36 +++-- .../src/client-server-protocol/glsp-client.ts | 18 ++- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 19 ++- .../jsonrpc/base-jsonrpc-glsp-client.ts | 23 ++- 22 files changed, 565 insertions(+), 210 deletions(-) create mode 100644 packages/client/css/status-overlay.css create mode 100644 packages/client/src/base/model/glsp-model-source.ts rename packages/client/src/{base/source-uri-aware.ts => features/status/status-module.ts} (59%) create mode 100644 packages/client/src/features/status/status-overlay.ts delete mode 100644 packages/client/src/model-source/glsp-diagram-server.ts diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 6d52ff7..59029d4 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -13,6 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +:root { + --glsp-info-foreground: blue; +} + .sprotty-graph { background: rgb(179, 196, 202); } diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index c65b4e4..3fb067b 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -21,13 +21,14 @@ import { EnableToolPaletteAction, GLSPActionDispatcher, GLSPClient, - GLSPDiagramServer, + GLSPModelSource, GLSPWebSocketProvider, RequestModelAction, RequestTypeHintsAction, ServerMessageAction, ServerStatusAction, - TYPES, + SetUIExtensionVisibilityAction, + StatusOverlay, configureServerActions } from '@eclipse-glsp/client'; import { join, resolve } from 'path'; @@ -45,26 +46,30 @@ const clientId = ApplicationIdProvider.get() + '_' + examplePath; const webSocketUrl = `ws://localhost:${port}/${id}`; let container = createContainer(); -let diagramServer = container.get(TYPES.ModelSource); -diagramServer.clientId = clientId; +let diagramServer = container.get(GLSPModelSource); const wsProvider = new GLSPWebSocketProvider(webSocketUrl); wsProvider.listen({ onConnection: initialize, onReconnect: reconnect, logger: console }); async function initialize(connectionProvider: MessageConnection, isReconnecting = false): Promise { + const actionDispatcher: GLSPActionDispatcher = container.get(GLSPActionDispatcher); + + await actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: StatusOverlay.ID, visible: true })); + await actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); const client = new BaseJsonrpcGLSPClient({ id, connectionProvider }); - await diagramServer.connect(client); + await diagramServer.connect(client, clientId); const result = await client.initializeServer({ applicationId: ApplicationIdProvider.get(), protocolVersion: GLSPClient.protocolVersion }); + actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); await configureServerActions(result, diagramType, container); - const actionDispatcher: GLSPActionDispatcher = container.get(GLSPActionDispatcher); - await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); + actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: StatusOverlay.ID, visible: true })); + actionDispatcher.dispatch(EnableToolPaletteAction.create()); actionDispatcher.dispatch( RequestModelAction.create({ options: { @@ -75,8 +80,6 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting }) ); actionDispatcher.dispatch(RequestTypeHintsAction.create()); - await actionDispatcher.onceModelInitialized(); - actionDispatcher.dispatch(EnableToolPaletteAction.create()); if (isReconnecting) { const message = `Connection to the ${id} glsp server got closed. Connection was successfully re-established.`; @@ -92,7 +95,7 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting async function reconnect(connectionProvider: MessageConnection): Promise { container = createContainer(); - diagramServer = container.get(TYPES.ModelSource); + diagramServer = container.get(GLSPModelSource); diagramServer.clientId = clientId; initialize(connectionProvider, true /* isReconnecting */); diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 698f9e6..9a72161 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -14,22 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; -import { - bindAsService, - bindOrRebind, - ConsoleLogger, - GLSPDiagramServer, - LogLevel, - STANDALONE_MODULE_CONFIG, - TYPES -} from '@eclipse-glsp/client'; +import { bindOrRebind, ConsoleLogger, LogLevel, STANDALONE_MODULE_CONFIG, TYPES } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import '../css/diagram.css'; - export default function createContainer(): Container { const container = createWorkflowDiagramContainer('sprotty', STANDALONE_MODULE_CONFIG); - bindAsService(container, TYPES.ModelSource, GLSPDiagramServer); - bindOrRebind(container, TYPES.ModelSource).toService(GLSPDiagramServer); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); diff --git a/packages/client/css/decoration.css b/packages/client/css/decoration.css index ba09b53..cc7ee22 100644 --- a/packages/client/css/decoration.css +++ b/packages/client/css/decoration.css @@ -14,13 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -:root { - --glsp-error-foreground: red; - --glsp-warning-foreground: yellow; - --glsp-info-foreground: lightblue; - --glsp-issue-background: rgb(255 255 255 / 85%); -} - .sprotty-issue-background { fill: var(--glsp-issue-background); background-color: var(--glsp-issue-background); diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index b0b4278..52845c3 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -14,6 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +:root { + --glsp-error-foreground: red; + --glsp-warning-foreground: yellow; + --glsp-info-foreground: lightblue; + --glsp-issue-background: rgb(255 255 255 / 85%); +} + .sprotty { padding: 0px; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; diff --git a/packages/client/css/status-overlay.css b/packages/client/css/status-overlay.css new file mode 100644 index 0000000..3f4c724 --- /dev/null +++ b/packages/client/css/status-overlay.css @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.sprotty-status { + position: absolute; + top: 5px; + left: 10px; + display: flex; + align-items: center; +} + +.sprotty-status .fatal { + color: var(--glsp-error-foreground); +} + +.sprotty-status .error { + color: var(--glsp-error-foreground); +} + +.sprotty-status .warning { + color: var(--glsp-warning-foreground); +} + +.sprotty-status .info { + color: var(--glsp-info-foreground); +} + +.sprotty-error { + fill: var(--glsp-issue-background); + color: var(--glsp-error-foreground); +} + +.sprotty-warning { + fill: var(--glsp-issue-background); + color: var(--glsp-warning-foreground); +} + +.sprotty-info { + fill: var(--glsp-issue-background); + color: var(); +} + +.sprotty-infoRow .codicon { + margin-right: 8px; +} +.sprotty-status .ok { + visibility: hidden; +} + +.sprotty-status-message { + visibility: hidden; + padding-left: 10px; +} + +.sprotty-status-message.fatal { + visibility: visible; + padding-left: 10px; +} + +.sprotty-status:hover .sprotty-status-message { + visibility: visible; +} diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index bf9d61b..cdff23b 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -24,6 +24,7 @@ import { ModelSource, MouseTool, MoveCommand, + SetDirtyStateAction, SetEditModeAction, TYPES, bindAsService, @@ -42,6 +43,7 @@ import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; +import { GLSPModelSource } from './model/glsp-model-source'; import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; @@ -72,6 +74,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . ); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); + configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); bind(FocusTracker).toSelf().inSingletonScope(); configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); @@ -93,6 +96,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + bindAsService(context, TYPES.ModelSource, GLSPModelSource); bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); // support re-registration of model elements and views diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index f53626b..2a3a451 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -28,17 +28,19 @@ import { MousePositionTracker, SModelElement, SModelRoot, + SetDirtyStateAction, SetEditModeAction, TYPES, ValueChange } from '~glsp-sprotty'; +import { GLSPModelSource } from './model/glsp-model-source'; import { SelectionService } from './selection-service'; -import { isSourceUriAware } from './source-uri-aware'; export interface IEditModeListener { editModeChanged(newValue: string, oldValue: string): void; } +export type DirtyStateChange = Pick; /** * The `EditorContextService` is a central injectable component that gives read-only access to * certain aspects of the diagram, such as the currently selected elements, the model root, @@ -67,17 +69,22 @@ export class EditorContextService implements IActionHandler, Disposable { protected modelSourceProvider: () => Promise; protected _editMode: string; - protected onEditModeChangedEmitter = new Emitter>(); get onEditModeChanged(): Event> { return this.onEditModeChangedEmitter.event; } + protected _isDirty: boolean; + protected onDirtyStateChangedEmitter = new Emitter(); + get onDirtyStateChanged(): Event { + return this.onDirtyStateChangedEmitter.event; + } + protected toDispose = new DisposableCollection(); @postConstruct() protected initialize(): void { - this.toDispose.push(this.onEditModeChangedEmitter); + this.toDispose.push(this.onEditModeChangedEmitter, this.onDirtyStateChangedEmitter); this.editModeListeners.forEach(listener => this.onEditModeChanged(change => listener.editModeChanged(change.newValue, change.oldValue)) ); @@ -106,20 +113,29 @@ export class EditorContextService implements IActionHandler, Disposable { handle(action: Action): void { if (SetEditModeAction.is(action)) { - const oldValue = this._editMode; - this._editMode = action.editMode; - this.notifyEditModeListeners(oldValue); + this.handleSetEditModeAction(action); + } else if (SetDirtyStateAction.is(action)) { + this.handleSetDirtyStateAction(action); } } - protected notifyEditModeListeners(oldValue: string): void { + protected handleSetEditModeAction(action: SetEditModeAction): void { + const oldValue = this._editMode; + this._editMode = action.editMode; this.onEditModeChangedEmitter.fire({ newValue: this.editMode, oldValue }); } + protected handleSetDirtyStateAction(action: SetDirtyStateAction): void { + if (action.isDirty !== this._isDirty) { + this._isDirty = action.isDirty; + this.onDirtyStateChangedEmitter.fire(action); + } + } + async getSourceUri(): Promise { const modelSource = await this.modelSourceProvider(); - if (isSourceUriAware(modelSource)) { - return modelSource.sourceURI; + if (modelSource instanceof GLSPModelSource) { + return modelSource.sourceUri; } return undefined; } @@ -139,6 +155,10 @@ export class EditorContextService implements IActionHandler, Disposable { get isReadonly(): boolean { return this.editMode === EditMode.READONLY; } + + get isDirty(): boolean { + return this._isDirty; + } } export type EditorContextServiceProvider = () => Promise; diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts new file mode 100644 index 0000000..97f1197 --- /dev/null +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -0,0 +1,149 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { + Action, + ActionHandlerRegistry, + ActionMessage, + Disposable, + DisposableCollection, + GLSPClient, + ILogger, + ModelSource, + RequestModelAction, + SModelRootSchema, + TYPES +} from '~glsp-sprotty'; +/** + * A helper interface that allows the client to mark actions that have been received from the server. + */ +export interface ServerAction extends Action { + _receivedFromServer: true; +} + +export namespace ServerAction { + export function is(object: unknown): object is ServerAction { + return Action.is(object) && '_receivedFromServer' in object && object._receivedFromServer === true; + } + + /** + * Mark the given action as {@link ServerAction} by attaching the "_receivedFromServer" property + * @param action The action that should be marked as server action + */ + export function mark(action: Action): void { + (action as ServerAction)._receivedFromServer = true; + } +} + +/** + * Central component for enabling the client-server action flow with the help of an underlying {@link GLSPClient}. + * Handles & forwards actions that are intended for the GLSP server. In addition, it handles {@link ActionMessage}s received + * from the server and dispatches the corresponding actions locally. + * + * Note that in sprotty a {@link ModelSource} is serving the model to the event cycle and + * is used to commit the local (i.e. client-side) model back to the source. + * However, in GLSP the update flow is reversed meaning that changes to the source model are applied + * on the server side and then an update is sent to the client. + */ +@injectable() +export class GLSPModelSource extends ModelSource implements Disposable { + @inject(TYPES.ILogger) + protected logger: ILogger; + + protected _sourceUri?: string; + protected _glspClient?: GLSPClient; + protected toDispose = new DisposableCollection(); + + clientId: string; + + get glspClient(): GLSPClient | undefined { + return this._glspClient; + } + + get sourceUri(): string | undefined { + return this._sourceUri; + } + + async connect(client: GLSPClient, clientId?: string): Promise { + if (clientId) { + this.clientId = clientId; + } + await client.start(); + this.toDispose.push(client.onActionMessage(message => this.messageReceived(message), this.clientId)); + this._glspClient = client; + return this._glspClient; + } + + protected messageReceived(message: ActionMessage): void { + if (this.clientId !== message.clientId) { + return; + } + const action = message.action; + ServerAction.mark(action); + this.logger.log(this, 'receiving', action); + this.actionDispatcher.dispatch(action); + } + + override initialize(registry: ActionHandlerRegistry): void { + // Registering actions here is discouraged and it's recommended + // to implemented dedicated action handlers. + if (!this.clientId) { + this.clientId = this.viewerOptions.baseDiv; + } + } + + handle(action: Action): void { + // Handling additional actions here is discouraged and it's recommended + // to implemented dedicated action handlers. + if (RequestModelAction.is(action)) { + this._sourceUri = action.options?.sourceUri.toString(); + } + if (this.shouldForwardToServer(action)) { + this.forwardToServer(action); + } + } + + protected forwardToServer(action: Action): void { + const message: ActionMessage = { + clientId: this.clientId, + action: action + }; + this.logger.log(this, 'sending', message); + if (this.glspClient) { + this.glspClient.sendActionMessage(message); + } else { + throw new Error('GLSPClient is not connected'); + } + } + + protected shouldForwardToServer(action: Action): boolean { + return !ServerAction.is(action); + } + + commitModel(newRoot: SModelRootSchema): SModelRootSchema { + /* In GLSP the model update flow is server-driven. i.e. changes to the graphical model are applied + * on server-side an only the server can issue a model update. + * The internal/local model should never be committed back to the model source i.e. GLSP server. + * => no-op implementation that simply returns the `newRoot` + */ + return newRoot; + } + + dispose(): void { + this.toDispose.dispose(); + } +} diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 3643975..87d3a93 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -43,6 +43,7 @@ import { navigationModule } from './features/navigation/navigation-module'; import { routingModule } from './features/routing/routing-module'; import { selectModule } from './features/select/select-module'; import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-wacher-module'; +import { statusModule } from './features/status/status-module'; import { svgMetadataModule } from './features/svg-metadata/svg-metadata-module'; import { toolPaletteModule } from './features/tool-palette/tool-palette-module'; import { changeBoundsToolModule } from './features/tools/change-bounds/change-boounds-tool-module'; @@ -90,7 +91,8 @@ export const DEFAULT_MODULES = [ toolFocusLossModule, validationModule, zorderModule, - svgMetadataModule + svgMetadataModule, + statusModule ] as const; /** diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 2af5802..9d10139 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; -import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; +import { GLSPContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; export const contextMenuModule = new FeatureModule(bind => { @@ -28,7 +28,7 @@ export const contextMenuModule = new FeatureModule(bind => { } }) ); - bindAsService(bind, TYPES.MouseListener, SelectionServiceAwareContextMenuMouseListener); + bindAsService(bind, TYPES.MouseListener, GLSPContextMenuMouseListener); bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); }); diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 33e7acd..19f1604 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -16,10 +16,9 @@ import { inject, injectable, optional } from 'inversify'; import { Action, ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement, TYPES } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; -import { SelectionService } from '../../base/selection-service'; @injectable() -export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { +export class GLSPContextMenuMouseListener extends MouseListener { @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService?: IContextMenuServiceProvider; @@ -28,9 +27,6 @@ export class SelectionServiceAwareContextMenuMouseListener extends MouseListener @optional() protected readonly menuProvider?: ContextMenuProviderRegistry; - @inject(SelectionService) - protected selectionService: SelectionService; - /** * Opens the context menu. */ diff --git a/packages/client/src/base/source-uri-aware.ts b/packages/client/src/features/status/status-module.ts similarity index 59% rename from packages/client/src/base/source-uri-aware.ts rename to packages/client/src/features/status/status-module.ts index 5cfbcfa..80b47d4 100644 --- a/packages/client/src/base/source-uri-aware.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AnyObject, hasStringProp } from '~glsp-sprotty'; -export interface SourceUriAware { - sourceURI: string; -} -export function isSourceUriAware(obj: any): obj is SourceUriAware { - return AnyObject.is(obj) && hasStringProp(obj, 'sourceURI'); -} +import { FeatureModule, ServerStatusAction, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import '../../../css/status-overlay.css'; +import { StatusOverlay } from './status-overlay'; + +export const statusModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IUIExtension, StatusOverlay); + configureActionHandler(context, ServerStatusAction.KIND, StatusOverlay); +}); diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts new file mode 100644 index 0000000..4be6baa --- /dev/null +++ b/packages/client/src/features/status/status-overlay.ts @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { AbstractUIExtension, IActionHandler, ServerStatusAction, codiconCSSClasses } from '~glsp-sprotty'; + +/** + * A reusable status overlay for rendering (icon + message) and handling of {@link ServerStatusAction}'s. + */ +@injectable() +export class StatusOverlay extends AbstractUIExtension implements IActionHandler { + static readonly ID = 'glsp.server.status.overlay'; + + protected statusIconDiv?: HTMLDivElement; + protected statusMessageDiv?: HTMLDivElement; + protected pendingTimeout?: number; + + override id(): string { + return StatusOverlay.ID; + } + + override containerClass(): string { + return 'sprotty-status'; + } + protected override initializeContents(containerElement: HTMLElement): void { + this.statusIconDiv = document.createElement('div'); + containerElement.appendChild(this.statusIconDiv); + + this.statusMessageDiv = document.createElement('div'); + this.statusMessageDiv.classList.add('sprotty-status-message'); + containerElement.appendChild(this.statusMessageDiv); + } + + protected setStatus(status: ServerStatusAction): void { + if (this.statusMessageDiv) { + this.statusMessageDiv.textContent = status.message; + this.removeClasses(this.statusMessageDiv, 1); + this.statusMessageDiv.classList.add(status.severity.toLowerCase()); + } + if (this.statusIconDiv) { + this.removeClasses(this.statusIconDiv, 0); + const classes = this.statusIconDiv.classList; + classes.add(status.severity.toLowerCase()); + switch (status.severity) { + case 'FATAL': + classes.add(...codiconCSSClasses('error')); + break; + case 'ERROR': + classes.add(...codiconCSSClasses('warning')); + break; + case 'WARNING': + classes.add(...codiconCSSClasses('warning')); + break; + case 'INFO': + classes.add(...codiconCSSClasses('info')); + break; + } + } + } + + protected clearStatus(): void { + this.setStatus(ServerStatusAction.create('', { severity: 'NONE' })); + } + + protected clearTimeout(): void { + if (this.pendingTimeout) { + window.clearTimeout(this.pendingTimeout); + this.pendingTimeout = undefined; + } + } + + protected removeClasses(element: Element, keep: number): void { + const classes = element.classList; + while (classes.length > keep) { + const item = classes.item(classes.length - 1); + if (item) { + classes.remove(item); + } + } + } + + handle(action: ServerStatusAction): void { + this.clearTimeout(); + if (action.severity === 'NONE') { + this.clearStatus(); + return; + } + this.setStatus(action); + + // Check for timeout + const statusTimeout = action.timeout ?? -1; + if (statusTimeout > 0) { + this.pendingTimeout = window.setTimeout(() => this.clearStatus(), statusTimeout); + } + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3abd12e..da2d380 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -29,11 +29,11 @@ export * from './base/feedback/update-model-command'; export * from './base/focus/focus-state-change-action'; export * from './base/focus/focus-tracker'; export * from './base/model-initialization-constraint'; +export * from './base/model/glsp-model-source'; export * from './base/model/model-registry'; export * from './base/ranked'; export * from './base/selection-clearing-mouse-listener'; export * from './base/selection-service'; -export * from './base/source-uri-aware'; export * from './base/tool-manager/glsp-tool-manager'; export * from './base/view/mouse-tool'; export * from './base/view/view-registry'; @@ -42,11 +42,11 @@ export * from './base/view/view-registry'; // ------------------ Features ------------------ export * from './base/feedback/css-feedback'; export * from './base/view/mouse-tool'; +export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; -export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; export * from './features/bounds/hbox-layout'; @@ -78,6 +78,7 @@ export * from './features/save/save-keylistener'; export * from './features/select/select-feedback-command'; export * from './features/select/select-mouse-listener'; export * from './features/source-model-watcher/source-model-changed-action-handler'; +export * from './features/status/status-overlay'; export * from './features/svg-metadata/metadata-placer'; export * from './features/tool-palette/tool-palette'; export * from './features/tools/base-glsp-tool'; @@ -108,7 +109,6 @@ export * from './glsp-sprotty/types'; // ------------------ Misc ------------------ export * from './default-modules'; export * from './lib/model'; -export * from './model-source/glsp-diagram-server'; export * from './standalone-modules'; export * from './utils/argument-utils'; export * from './utils/html-utils'; @@ -138,6 +138,7 @@ export * from './features/routing/routing-module'; export * from './features/save/save-module'; export * from './features/select/select-module'; export * from './features/source-model-watcher/source-model-wacher-module'; +export * from './features/status/status-module'; export * from './features/svg-metadata/svg-metadata-module'; export * from './features/tool-palette/tool-palette-module'; export * from './features/tools/change-bounds/change-boounds-tool-module'; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts deleted file mode 100644 index a74decf..0000000 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { injectable } from 'inversify'; -import { - Action, - ActionHandlerRegistry, - ActionMessage, - ComputedBoundsAction, - DiagramServerProxy, - EndProgressAction, - ExportSvgAction, - GLSPClient, - ICommand, - RequestModelAction, - ServerMessageAction, - ServerStatusAction, - SetEditModeAction, - StartProgressAction, - SwitchEditModeCommand, - UpdateProgressAction -} from '~glsp-sprotty'; -import { SourceUriAware } from '../base/source-uri-aware'; - -const receivedFromServerProperty = '__receivedFromServer'; - -@injectable() -export class GLSPDiagramServer extends DiagramServerProxy implements SourceUriAware { - protected _sourceUri: string; - protected _glspClient?: GLSPClient; - protected ready = false; - - async connect(client: GLSPClient): Promise { - await client.start(); - client.onActionMessage(message => this.messageReceived(message)); - this._glspClient = client; - return this._glspClient; - } - - public get glspClient(): GLSPClient | undefined { - return this._glspClient; - } - - protected sendMessage(message: ActionMessage): void { - if (this.glspClient) { - this.glspClient.sendActionMessage(message); - } else { - throw new Error('GLSPClient is not connected'); - } - } - - override initialize(registry: ActionHandlerRegistry): void { - registerDefaultGLSPServerActions(registry, this); - if (!this.clientId) { - this.clientId = this.viewerOptions.baseDiv; - } - } - - override handle(action: Action): void | ICommand | Action { - if (RequestModelAction.is(action) && action.options) { - this._sourceUri = action.options.sourceUri as string; - } - return super.handle(action); - } - - override handleLocally(action: Action): boolean { - if (ServerMessageAction.is(action)) { - return this.handleServerMessageAction(action); - } - if (SetEditModeAction.is(action)) { - return this.handleSetEditModeAction(action); - } - return super.handleLocally(action); - } - - protected override handleExportSvgAction(action: ExportSvgAction): boolean { - return false; - } - - protected handleServerMessageAction(action: ServerMessageAction): boolean { - this.logger.log('GLSPDiagramServer', `[${action.severity}] -${action.message}`); - return false; - } - - protected override handleComputedBounds(_action: ComputedBoundsAction): boolean { - return true; - } - - protected handleSetEditModeAction(action: SetEditModeAction): boolean { - return !isReceivedFromServer(action); - } - - public get sourceURI(): string { - return this._sourceUri; - } -} - -export function isReceivedFromServer(action: Action): boolean { - return (action as any)[receivedFromServerProperty] === true; -} - -export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServerProxy): void { - registry.register(ServerMessageAction.KIND, diagramServer); - registry.register(ServerStatusAction.KIND, diagramServer); - registry.register(ExportSvgAction.KIND, diagramServer); - registry.register(StartProgressAction.KIND, diagramServer); - registry.register(UpdateProgressAction.KIND, diagramServer); - registry.register(EndProgressAction.KIND, diagramServer); - - // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. - // We don't support SwitchEditMode, but Sprotty still sends those actions, so ignore them. - registry.register(SwitchEditModeCommand.KIND, { handle: action => undefined }); -} diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index f1bbb1d..326b096 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -14,7 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModuleConfiguration } from '~glsp-sprotty'; +import { inject, injectable } from 'inversify'; +import { + Action, + EndProgressAction, + FeatureModule, + IActionHandler, + ICommand, + ILogger, + ModuleConfiguration, + ServerMessageAction, + StartProgressAction, + TYPES, + UpdateProgressAction, + configureActionHandler +} from '~glsp-sprotty'; import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; import { standaloneExportModule } from './features/export/export-modules'; import { saveModule } from './features/save/save-module'; @@ -22,6 +36,30 @@ import { standaloneSelectModule } from './features/select/select-module'; import { undoRedoModule } from './features/undo-redo/undo-redo-module'; import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; import { standaloneViewportModule } from './features/viewport/viewport-modules'; + +export const standaloneFallbackModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(FallbackActionHandler).toSelf().inSingletonScope(); + configureActionHandler(context, ServerMessageAction.KIND, FallbackActionHandler); + configureActionHandler(context, StartProgressAction.KIND, FallbackActionHandler); + configureActionHandler(context, UpdateProgressAction.KIND, FallbackActionHandler); + configureActionHandler(context, EndProgressAction.KIND, FallbackActionHandler); +}); + +/** + * A fallback action handler for actions sent by features that are currently not supported by + * default in the standalone context. Unhandled actions will be simply forwarded to the {@link ILogger}. + */ +@injectable() +export class FallbackActionHandler implements IActionHandler { + @inject(TYPES.ILogger) + protected logger: ILogger; + + handle(action: Action): void | Action | ICommand { + this.logger.log(this, 'Unhandled action received:', action); + } +} + /** * Configuration of all `standalone` modules. * @@ -39,6 +77,7 @@ export const STANDALONE_MODULES = [ standaloneMarkerNavigatorModule, standaloneSelectModule, standaloneExportModule, + standaloneFallbackModule, saveModule, undoRedoModule ] as const; diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index db9a459..3cc601c 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -19,7 +19,7 @@ import * as sinon from 'sinon'; import * as util from 'util'; import { Action, ActionMessage } from '../action-protocol'; import { expectToThrowAsync } from '../utils/test-util'; -import { BaseGLSPClient } from './base-glsp-client'; +import { BaseGLSPClient, GLOBAL_HANDLER_ID } from './base-glsp-client'; import { ClientState } from './glsp-client'; import { GLSPServer, GLSPServerListener } from './glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; @@ -115,10 +115,21 @@ describe('Node GLSP Client', () => { resetClient(); const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; server.initialize.returns(Promise.resolve(expectedResult)); - + expect(client.initializeResult).to.be.undefined; const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); expect(result).to.deep.equals(expectedResult); expect(server.initialize.calledOnce).to.be.true; + expect(client.initializeResult).to.be.equal(result); + }); + it('should return cached result on consecutive invocation', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + server.initialize.returns(Promise.resolve(expectedResult)); + client['_initializeResult'] = expectedResult; + const result = await client.initializeServer(params); + expect(result).to.be.deep.equal(client.initializeResult); + expect(server.initialize.called).to.be.false; }); }); @@ -207,25 +218,34 @@ describe('Node GLSP Client', () => { it('should be properly registered if server is not configured', () => { resetClient(false); client.onActionMessage(handler); - expect(client['actionMessageHandlers'].length).to.be.equal(1); + expect(client['actionMessageHandlers'].get(GLOBAL_HANDLER_ID)?.length).to.be.equal(1); expect(handler.called).to.be.false; }); it('should be properly registered if client is not running', () => { resetClient(false); client.configureServer(server); client.onActionMessage(handler); - expect(client['actionMessageHandlers'].length).to.be.equal(1); + expect(client['actionMessageHandlers'].get(GLOBAL_HANDLER_ID)?.length).to.be.equal(1); expect(handler.called).to.be.false; }); - it('should be unregistered if dispose is invoked', () => { + it('should unregister global handler if dispose is invoked', () => { resetClient(false); const toDispose = client.onActionMessage(handler); - expect(client['actionMessageHandlers'].length).to.be.equal(1); + expect(client['actionMessageHandlers'].get(GLOBAL_HANDLER_ID)?.length).to.be.equal(1); + toDispose.dispose(); + expect(client['actionMessageHandlers'].get(GLOBAL_HANDLER_ID)?.length).to.be.equal(0); + }); + it('should unregister client id handler if dispose is invoked', () => { + resetClient(false); + const clientId = 'clientId'; + const toDispose = client.onActionMessage(handler, clientId); + + expect(client['actionMessageHandlers'].size).to.be.equal(2); toDispose.dispose(); - expect(client['actionMessageHandlers'].length).to.be.equal(0); + expect(client['actionMessageHandlers'].get(clientId)?.length).to.be.equal(0); }); - it('handler should be invoked when the an action message is sent via proxy', () => { + it('should invoke global handler when the an action message is sent via proxy', () => { resetClient(); client.onActionMessage(handler); const expectedMessage = { action: { kind: 'someAction' }, clientId: 'someClientId' }; @@ -233,5 +253,15 @@ describe('Node GLSP Client', () => { expect(handler.calledOnce).to.be.true; expect(handler.firstCall.args[0]).to.deep.equals(expectedMessage); }); + it('should invoke client id handler when the an action message is sent via proxy', () => { + resetClient(); + const clientId = 'clientId'; + client.onActionMessage(handler, clientId); + const expectedMessage = { action: { kind: 'someAction' }, clientId }; + client.proxy.process(expectedMessage); + client.proxy.process({ clientId: 'someOtherId', action: { kind: 'someAction' } }); + expect(handler.calledOnce).to.be.true; + expect(handler.firstCall.args[0]).to.deep.equals(expectedMessage); + }); }); }); diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts index 624eb14..2c8eecc 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -22,6 +22,7 @@ import { ActionMessageHandler, ClientState, GLSPClient } from './glsp-client'; import { GLSPClientProxy, GLSPServer } from './glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; +export const GLOBAL_HANDLER_ID = '*'; /** * A simple {@link GLSPClient} implementation for use cases where the client & server are running * in the same context/process without a communication layer (like json-rpc) between. The client @@ -35,7 +36,8 @@ export class BaseGLSPClient implements GLSPClient { protected onStopDeferred = new Deferred(); readonly proxy: GLSPClientProxy; protected startupTimeout = 1500; - protected actionMessageHandlers: ActionMessageHandler[] = []; + protected actionMessageHandlers: Map = new Map([[GLOBAL_HANDLER_ID, []]]); + protected _initializeResult: InitializeResult; constructor(protected options: GLSPClient.Options) { this.state = ClientState.Initial; @@ -45,11 +47,12 @@ export class BaseGLSPClient implements GLSPClient { protected createProxy(): GLSPClientProxy { return { process: message => { - if (this.actionMessageHandlers.length === 0) { + const handlers = this.actionMessageHandlers.get(message.clientId) ?? this.actionMessageHandlers.get(GLOBAL_HANDLER_ID); + if (!handlers) { console.warn('No ActionMessageHandler is configured- Cannot process server message', message); return; } - [...this.actionMessageHandlers].forEach(handler => handler(message)); + handlers.forEach(handler => handler(message)); } }; } @@ -62,7 +65,7 @@ export class BaseGLSPClient implements GLSPClient { } start(): Promise { - if (this.state !== ClientState.Initial) { + if (this.state === ClientState.Running) { return this.onStartDeferred.promise; } @@ -86,8 +89,15 @@ export class BaseGLSPClient implements GLSPClient { return this.onStartDeferred.promise; } - initializeServer(params: InitializeParameters): Promise { - return this.checkedServer.initialize(params); + async initializeServer(params: InitializeParameters): Promise { + if (!this._initializeResult) { + this._initializeResult = await this.checkedServer.initialize(params); + } + return this._initializeResult; + } + + get initializeResult(): InitializeResult | undefined { + return this._initializeResult; } initializeClientSession(params: InitializeClientSessionParameters): Promise { @@ -122,9 +132,17 @@ export class BaseGLSPClient implements GLSPClient { this.checkedServer.process(message); } - onActionMessage(handler: ActionMessageHandler): Disposable { - distinctAdd(this.actionMessageHandlers, handler); - return Disposable.create(() => remove(this.actionMessageHandlers, handler)); + onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable { + if (!clientId) { + distinctAdd(this.actionMessageHandlers.get(GLOBAL_HANDLER_ID)!, handler); + return Disposable.create(() => remove(this.actionMessageHandlers.get(GLOBAL_HANDLER_ID)!, handler)); + } + if (!this.actionMessageHandlers.has(clientId)) { + this.actionMessageHandlers.set(clientId, [handler]); + } else { + distinctAdd(this.actionMessageHandlers.get(clientId)!, handler); + } + return Disposable.create(() => remove(this.actionMessageHandlers.get(clientId)!, handler)); } get currentState(): ClientState { diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index f4a7ee2..3996472 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -75,7 +75,8 @@ export interface GLSPClient { /** * Initializes the client and the server connection. During the start procedure the client is in the - * `Starting` state and will transition to either `Running` or `StartFailed`. + * `Starting` state and will transition to either `Running` or `StartFailed`. Calling this method + * if the client is already running has no effect. * * @returns A promise that resolves if the startup was successful. */ @@ -83,13 +84,22 @@ export interface GLSPClient { /** * Send an `initialize` request to the server. The server needs to be initialized in order to accept and - * process other requests and notifications. + * process other requests and notifications. The {@link InitializeResult} ist cached and can be retrieved + * via the {@link GLSPClient.initializeResult} property. + * Only the first method invocation actually sends a request to the server. Subsequent invocations simply + * return the cached result. * * @param params Initialize parameters * @returns A promise of the {@link InitializeResult}. */ initializeServer(params: InitializeParameters): Promise; + /** + * The cached {@link {InitializeResult}. Is `undefined` if the server has not been initialized yet via + * the {@link GLSPClient.initializeServer} method. + */ + readonly initializeResult: InitializeResult | undefined; + /** * Send an `initializeClientSession` request to the server. One client application may open several session. * Each individual diagram on the client side counts as one session and has to provide @@ -131,11 +141,13 @@ export interface GLSPClient { /** * Sets a handler/listener for action messages received from the server. + * Can be scoped to a particular client session by passing the corresponding `clientId`. * * @param handler The action message handler + * @param clientId If passed given action message handler will only be invoked for action messages with this client id. * @returns A {@link Disposable} that can be used to unregister the handler */ - onActionMessage(handler: ActionMessageHandler): Disposable; + onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable; } export namespace GLSPClient { export interface Options { diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 65d75e0..60c067c 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -116,15 +116,28 @@ describe('Base JSON-RPC GLSP Client', () => { await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); expect(connection.sendRequest.called).to.be.false; }); - it('should forward the corresponding initialize request', async () => { + it('should forward the corresponding initialize request and cache result', async () => { await resetClient(); const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; const params = { applicationId: 'id', protocolVersion: '1.0.0' }; const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); initializeMock.returns(expectedResult); + expect(client.initializeResult).to.be.undefined; const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); expect(result).to.deep.equals(expectedResult); expect(initializeMock.calledOnce).to.be.true; + expect(client.initializeResult).to.be.equal(result); + }); + it('should return cached result on consecutive invocation', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); + initializeMock.returns(expectedResult); + client['_initializeResult'] = expectedResult; + const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); + expect(result).to.be.deep.equal(client.initializeResult); + expect(initializeMock.called).to.be.false; }); }); @@ -202,8 +215,8 @@ describe('Base JSON-RPC GLSP Client', () => { it('should invoked the corresponding connection method', async () => { await resetClient(); - client.onActionMessage(handler); - expect(connection.onNotification.withArgs(JsonrpcGLSPClient.ActionMessageNotification, handler).calledOnce).to.be.true; + client.onActionMessage(handler, 'someId'); + expect(connection.onNotification.withArgs(JsonrpcGLSPClient.ActionMessageNotification).calledOnce).to.be.true; }); }); diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 4522240..9d146c4 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -28,6 +28,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { protected resolvedConnection?: MessageConnection; protected state: ClientState; protected onStop?: Promise; + protected _initializeResult: InitializeResult | undefined; constructor(options: JsonrpcGLSPClient.Options) { Object.assign(this, options); @@ -38,8 +39,15 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification); } - initializeServer(params: InitializeParameters): Promise { - return this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); + async initializeServer(params: InitializeParameters): Promise { + if (!this._initializeResult) { + this._initializeResult = await this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); + } + return this._initializeResult; + } + + get initializeResult(): InitializeResult | undefined { + return this._initializeResult; } initializeClientSession(params: InitializeClientSessionParameters): Promise { @@ -50,8 +58,12 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.checkedConnection.sendRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, params); } - onActionMessage(handler: ActionMessageHandler): Disposable { - return this.checkedConnection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, handler); + onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable { + return this.checkedConnection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, msg => { + if (!clientId || msg.clientId === clientId) { + handler(msg); + } + }); } sendActionMessage(message: ActionMessage): void { @@ -73,6 +85,9 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { } async start(): Promise { + if (this.state === ClientState.Running) { + return; + } try { this.state = ClientState.Starting; const connection = await this.resolveConnection(); From 29e2427640ebd391edfa6a0c5583880f28a19f1c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 8 Aug 2023 08:46:14 +0200 Subject: [PATCH 241/566] GLSP-1082: Fix edge edit (#273) Update selection-mouse listener. The base mouse listener in sprotty has been reworked. This change adjusts the `RankedSelectMouseListener` to the new basae implementation and overrides it where necessary to avoid disposal of `SetEditModeActions` Fixes https://github.com/eclipse-glsp/glsp/issues/1082 --- .../features/select/select-mouse-listener.ts | 106 +++++------------- 1 file changed, 27 insertions(+), 79 deletions(-) diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 5b99814..90e4370 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,22 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - BringToFrontAction, - SButton, - SModelElement, - SModelRoot, - SRoutableElement, - SRoutingHandle, - SelectAction, - SelectMouseListener, - SwitchEditModeAction, - findParentByFeature, - isCtrlOrCmd, - isSelectable, - toArray -} from '~glsp-sprotty'; +import { Action, BringToFrontAction, SModelElement, SelectAction, SelectMouseListener, Selectable } from '~glsp-sprotty'; import { Ranked } from '../../base/ranked'; /** @@ -36,71 +21,34 @@ import { Ranked } from '../../base/ranked'; * This ensures that default mouse listeners are working on a model that has selection changes already applied. */ export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { - rank: number = Ranked.DEFAULT_RANK - 1; /* we want to be executed before all default mouse listeners */ + rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ - override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + protected override handleSelectTarget( + selectableTarget: SModelElement & Selectable, + deselectedElements: SModelElement[], + event: MouseEvent + ): (Action | Promise)[] { const result: Action[] = []; - if (this.buttonHandlerRegistry !== undefined && target instanceof SButton && target.enabled) { - const buttonHandler = this.buttonHandlerRegistry.get(target.type); - if (buttonHandler !== undefined) { - return buttonHandler.buttonPressed(target); - } - } - const selectableTarget = findParentByFeature(target, isSelectable); - if (selectableTarget !== undefined || target instanceof SModelRoot) { - this.hasDragged = false; - let deselect: SModelElement[] = []; - // multi-selection? - if (!isCtrlOrCmd(event)) { - deselect = toArray( - target.root.index - .all() - .filter( - element => - isSelectable(element) && - element.selected && - !(selectableTarget instanceof SRoutingHandle && element === (selectableTarget.parent as SModelElement)) - ) - ); - } - if (selectableTarget !== undefined) { - if (!selectableTarget.selected) { - this.wasSelected = false; - result.push( - SelectAction.create({ - selectedElementsIDs: [selectableTarget.id], - deselectedElementsIDs: deselect.map(e => e.id) - }) - ); - result.push(BringToFrontAction.create([selectableTarget.id])); - const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); - if (selectableTarget instanceof SRoutableElement) { - result.push( - SwitchEditModeAction.create({ - elementsToActivate: [selectableTarget.id], - elementsToDeactivate: routableDeselect - }) - ); - } else if (routableDeselect.length > 0) { - result.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); - } - } else if (isCtrlOrCmd(event)) { - this.wasSelected = false; - result.push(SelectAction.create({ deselectedElementsIDs: [selectableTarget.id] })); - if (selectableTarget instanceof SRoutableElement) { - result.push(SwitchEditModeAction.create({ elementsToDeactivate: [selectableTarget.id] })); - } - } else { - this.wasSelected = true; - } - } else { - result.push(SelectAction.create({ deselectedElementsIDs: deselect.map(e => e.id) })); - const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); - if (routableDeselect.length > 0) { - result.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); - } - } - } + result.push( + SelectAction.create({ + selectedElementsIDs: [selectableTarget.id], + deselectedElementsIDs: deselectedElements.map(e => e.id) + }) + ); + result.push(BringToFrontAction.create([selectableTarget.id])); + + return result; + } + + protected override handleDeselectTarget(selectableTarget: SModelElement & Selectable, event: MouseEvent): (Action | Promise)[] { + const result: Action[] = []; + result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: [selectableTarget.id] })); + return result; + } + + protected override handleDeselectAll(deselectedElements: SModelElement[], event: MouseEvent): (Action | Promise)[] { + const result: Action[] = []; + result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: deselectedElements.map(e => e.id) })); return result; } } From 06d001c5290fe4bd80e8a07fc4ce7f9a1ffa437e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 10 Aug 2023 15:11:27 +0200 Subject: [PATCH 242/566] GLSP-960 Improve Diagram container configuration (#274) * GLSP-960 Improve Diagram container configuration - Make import configuration constants available in DI container via `IDiagramOptions`. This includes diagramType, clientId, sourceUri and glspClient. - Introduce `DiagramLoader` as new central component responsible for configuring the glsp client and dispatching the intial model loading request - Introduce `IDiagramStartup` services that can hook into the diagram loader lifecycle (GLSP-587) - This means that feature modules are now self containted and can dispatch their initial actions, activate UI extensions on startup etc. without having to modifiy the `GLSPDiagram` (or similiar) componenent. -Add `onServerInitialized` event + tests to `GLSPClient` Part of https://github.com/eclipse-glsp/glsp/issues/960 Part of https://github.com/eclipse-glsp/glsp/issues/587 --- .../src/workflow-diagram-module.ts | 20 +-- examples/workflow-standalone/src/app.ts | 57 ++---- examples/workflow-standalone/src/di.config.ts | 14 +- packages/client/src/base/default.module.ts | 23 +-- .../client/src/base/editor-context-service.ts | 40 +++-- .../client/src/base/model/diagram-loader.ts | 165 ++++++++++++++++++ .../src/base/model/glsp-model-source.ts | 45 ++--- packages/client/src/default-modules.ts | 33 ++++ .../src/features/hints/type-hints-module.ts | 13 +- .../client/src/features/hints/type-hints.ts | 13 +- .../navigation/navigation-target-resolver.ts | 17 +- .../src/features/status/status-module.ts | 1 + .../src/features/status/status-overlay.ts | 17 +- .../tool-palette/tool-palette-module.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 44 +++-- .../src/features/validation/validate.ts | 2 +- packages/client/src/glsp-sprotty/types.ts | 4 +- packages/client/src/index.ts | 1 + .../base-glsp-client.spec.ts | 13 ++ .../base-glsp-client.ts | 7 + .../src/client-server-protocol/glsp-client.ts | 6 + .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 14 ++ .../jsonrpc/base-jsonrpc-glsp-client.ts | 7 + 23 files changed, 401 insertions(+), 159 deletions(-) create mode 100644 packages/client/src/base/model/diagram-loader.ts diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 2f37df0..af120f8 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -39,8 +39,7 @@ import { configureDefaultModelElements, configureModelElement, editLabelFeature, - initializeDiagramContainer, - overrideViewerOptions + initializeDiagramContainer } from '@eclipse-glsp/client'; import 'balloon-css/balloon.min.css'; import { Container, ContainerModule } from 'inversify'; @@ -78,19 +77,10 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, configureModelElement(context, 'struct', SCompartment, StructureCompartmentView); }); -export function createWorkflowDiagramContainer(widgetId: string, ...containerConfiguration: ContainerConfiguration): Container { - return initializeWorkflowDiagramContainer(new Container(), widgetId, ...containerConfiguration); +export function createWorkflowDiagramContainer(...containerConfiguration: ContainerConfiguration): Container { + return initializeWorkflowDiagramContainer(new Container(), ...containerConfiguration); } -export function initializeWorkflowDiagramContainer( - container: Container, - widgetId: string, - ...containerConfiguration: ContainerConfiguration -): Container { - initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, accessibilityModule, ...containerConfiguration); - overrideViewerOptions(container, { - baseDiv: widgetId, - hiddenDiv: widgetId + '_hidden' - }); - return container; +export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { + return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, accessibilityModule, ...containerConfiguration); } diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index 3fb067b..d6c5792 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -16,21 +16,15 @@ import 'reflect-metadata'; import { - ApplicationIdProvider, BaseJsonrpcGLSPClient, - EnableToolPaletteAction, + DiagramLoader, GLSPActionDispatcher, GLSPClient, - GLSPModelSource, GLSPWebSocketProvider, - RequestModelAction, - RequestTypeHintsAction, ServerMessageAction, - ServerStatusAction, - SetUIExtensionVisibilityAction, - StatusOverlay, - configureServerActions + ServerStatusAction } from '@eclipse-glsp/client'; +import { Container } from 'inversify'; import { join, resolve } from 'path'; import { MessageConnection } from 'vscode-jsonrpc'; import createContainer from './di.config'; @@ -41,45 +35,21 @@ const diagramType = 'workflow-diagram'; const loc = window.location.pathname; const currentDir = loc.substring(0, loc.lastIndexOf('/')); const examplePath = resolve(join(currentDir, '../app/example1.wf')); -const clientId = ApplicationIdProvider.get() + '_' + examplePath; +const clientId = 'sprotty'; const webSocketUrl = `ws://localhost:${port}/${id}`; -let container = createContainer(); -let diagramServer = container.get(GLSPModelSource); - +let glspClient: GLSPClient; +let container: Container; const wsProvider = new GLSPWebSocketProvider(webSocketUrl); wsProvider.listen({ onConnection: initialize, onReconnect: reconnect, logger: console }); async function initialize(connectionProvider: MessageConnection, isReconnecting = false): Promise { - const actionDispatcher: GLSPActionDispatcher = container.get(GLSPActionDispatcher); - - await actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: StatusOverlay.ID, visible: true })); - await actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); - const client = new BaseJsonrpcGLSPClient({ id, connectionProvider }); - - await diagramServer.connect(client, clientId); - const result = await client.initializeServer({ - applicationId: ApplicationIdProvider.get(), - protocolVersion: GLSPClient.protocolVersion - }); - actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); - await configureServerActions(result, diagramType, container); - - await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); - - actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: StatusOverlay.ID, visible: true })); - actionDispatcher.dispatch(EnableToolPaletteAction.create()); - actionDispatcher.dispatch( - RequestModelAction.create({ - options: { - sourceUri: `file://${examplePath}`, - diagramType, - isReconnecting - } - }) - ); - actionDispatcher.dispatch(RequestTypeHintsAction.create()); + glspClient = new BaseJsonrpcGLSPClient({ id, connectionProvider }); + container = createContainer({ clientId, diagramType, glspClient, sourceUri: examplePath }); + const actionDispatcher = container.get(GLSPActionDispatcher); + const diagramLoader = container.get(DiagramLoader); + await diagramLoader.load({ isReconnecting }); if (isReconnecting) { const message = `Connection to the ${id} glsp server got closed. Connection was successfully re-established.`; @@ -94,9 +64,6 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting } async function reconnect(connectionProvider: MessageConnection): Promise { - container = createContainer(); - diagramServer = container.get(GLSPModelSource); - diagramServer.clientId = clientId; - + glspClient.stop(); initialize(connectionProvider, true /* isReconnecting */); } diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 9a72161..3e0b9d6 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -14,11 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; -import { bindOrRebind, ConsoleLogger, LogLevel, STANDALONE_MODULE_CONFIG, TYPES } from '@eclipse-glsp/client'; +import { + bindOrRebind, + ConsoleLogger, + createDiagramOptionsModule, + IDiagramOptions, + LogLevel, + STANDALONE_MODULE_CONFIG, + TYPES +} from '@eclipse-glsp/client'; import { Container } from 'inversify'; import '../css/diagram.css'; -export default function createContainer(): Container { - const container = createWorkflowDiagramContainer('sprotty', STANDALONE_MODULE_CONFIG); +export default function createContainer(options: IDiagramOptions): Container { + const container = createWorkflowDiagramContainer(createDiagramOptionsModule(options), STANDALONE_MODULE_CONFIG); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index cdff23b..e9bf7a2 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -14,14 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import '@vscode/codicons/dist/codicon.css'; -import { Container } from 'inversify'; import { - ActionHandlerRegistry, FeatureModule, - InitializeResult, KeyTool, LocationPostprocessor, - ModelSource, MouseTool, MoveCommand, SetDirtyStateAction, @@ -43,6 +39,7 @@ import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; +import { DiagramLoader } from './model/diagram-loader'; import { GLSPModelSource } from './model/glsp-model-source'; import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; @@ -97,6 +94,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); bindAsService(context, TYPES.ModelSource, GLSPModelSource); + bind(DiagramLoader).toSelf().inSingletonScope(); bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); // support re-registration of model elements and views @@ -115,20 +113,3 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); }); - -/** - * Utility function to configure the {@link ModelSource}, i.e. the `DiagramServer`, as action handler for all server actions for the given - * diagramType. - * @param result A promise that resolves after all server actions have been registered. - * @param diagramType The diagram type. - * @param container The di container. - */ -export async function configureServerActions(result: InitializeResult, diagramType: string, container: Container): Promise { - const modelSource = container.get(TYPES.ModelSource); - const actionHandlerRegistry = container.get(ActionHandlerRegistry); - const serverActions = result.serverActions[diagramType]; - if (serverActions.length === 0) { - throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${diagramType}!`); - } - serverActions.forEach(actionKind => actionHandlerRegistry.register(actionKind, modelSource)); -} diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 2a3a451..b195746 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -23,8 +23,9 @@ import { EditorContext, Emitter, Event, + GLSPClient, IActionHandler, - ModelSource, + MaybePromise, MousePositionTracker, SModelElement, SModelRoot, @@ -33,7 +34,8 @@ import { TYPES, ValueChange } from '~glsp-sprotty'; -import { GLSPModelSource } from './model/glsp-model-source'; +import { GLSPActionDispatcher } from './action-dispatcher'; +import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; import { SelectionService } from './selection-service'; export interface IEditModeListener { @@ -54,19 +56,22 @@ export type DirtyStateChange = Pick; * position, etc.). */ @injectable() -export class EditorContextService implements IActionHandler, Disposable { +export class EditorContextService implements IActionHandler, Disposable, IDiagramStartup { @inject(SelectionService) protected selectionService: SelectionService; @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; + @inject(TYPES.IDiagramOptions) + protected diagramOptions: IDiagramOptions; + @multiInject(TYPES.IEditModeListener) @optional() protected editModeListeners: IEditModeListener[] = []; - @inject(TYPES.ModelSourceProvider) - protected modelSourceProvider: () => Promise; + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; protected _editMode: string; protected onEditModeChangedEmitter = new Emitter>(); @@ -84,6 +89,7 @@ export class EditorContextService implements IActionHandler, Disposable { @postConstruct() protected initialize(): void { + this._editMode = this.diagramOptions.editMode ?? EditMode.EDITABLE; this.toDispose.push(this.onEditModeChangedEmitter, this.onDirtyStateChangedEmitter); this.editModeListeners.forEach(listener => this.onEditModeChanged(change => listener.editModeChanged(change.newValue, change.oldValue)) @@ -132,18 +138,26 @@ export class EditorContextService implements IActionHandler, Disposable { } } - async getSourceUri(): Promise { - const modelSource = await this.modelSourceProvider(); - if (modelSource instanceof GLSPModelSource) { - return modelSource.sourceUri; - } - return undefined; + get sourceUri(): string | undefined { + return this.diagramOptions.sourceUri; } get editMode(): string { return this._editMode; } + get diagramType(): string { + return this.diagramOptions.diagramType; + } + + get clientId(): string { + return this.diagramOptions.clientId; + } + + get glspClient(): GLSPClient { + return this.diagramOptions.glspClient; + } + get modelRoot(): Readonly { return this.selectionService.getModelRoot(); } @@ -159,6 +173,10 @@ export class EditorContextService implements IActionHandler, Disposable { get isDirty(): boolean { return this._isDirty; } + + postRequestModel(): MaybePromise { + this.actionDispatcher.dispatch(SetEditModeAction.create(this.editMode)); + } } export type EditorContextServiceProvider = () => Promise; diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts new file mode 100644 index 0000000..b160245 --- /dev/null +++ b/packages/client/src/base/model/diagram-loader.ts @@ -0,0 +1,165 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; +import { + AnyObject, + ApplicationIdProvider, + Args, + EMPTY_ROOT, + EndProgressAction, + GLSPClient, + MaybePromise, + RequestModelAction, + ServerStatusAction, + SetModelAction, + StartProgressAction, + TYPES, + hasNumberProp +} from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../action-dispatcher'; +import { Ranked } from '../ranked'; + +/** + * Configuration options for a specific GLSP diagram instance. + */ +export interface IDiagramOptions { + /** + * Unique id associated with this diagram. Used on the server side to identify the + * corresponding client session. + */ + clientId: string; + /** + * The diagram type i.e. diagram language this diagram is associated with. + */ + diagramType: string; + /** + * The GLSP client used by this diagram to communicate with the server. + */ + glspClient: GLSPClient; + /** + * The file source URI associated with this diagram. + */ + sourceUri?: string; + /** + * The initial edit mode of diagram. Defaults to `editable`. + */ + editMode?: string; +} + +/** + * Services that implement startup hooks which are invoked during the {@link DiagramLoader.load} process. + * Typically used to dispatch additional initial actions and/or activate UI extensions on startup. + * Execution order is derived by the `rank` property of the service. If not present, the {@link Ranked.DEFAULT_RANK} will be assumed. + */ +export interface IDiagramStartup extends Partial { + /** + * Hook for services that should be executed before the underlying GLSP client is configured and the server is initialized. + */ + preInitialize?(): MaybePromise; + /** + * Hook for services that should be executed before the initial model loading request (i.e. `RequestModelAction`) but + * after the underlying GLSP client has been configured and the server is initialized. + */ + preRequestModel?(): MaybePromise; + /** + * Hook for services that should be executed after the initial model loading request (i.e. `RequestModelAction`). + * Note that this hook is invoked directly after the `RequestModelAction` has been dispatched. It does not necessarily wait + * until the client-server update roundtrip is completed. If you need to wait until the diagram is fully initialized use the + * {@link GLSPActionDispatcher.onceModelInitialized} constraint. + */ + postRequestModel?(): MaybePromise; +} + +export namespace IDiagramStartup { + export function is(object: unknown): object is IDiagramStartup { + return ( + AnyObject.is(object) && + hasNumberProp(object, 'rank', true) && + ('preInitialize' in object || 'preRequestModel' in object || 'postRequestModel' in object) + ); + } +} + +/** + * The central component responsible for initializing the diagram and loading the graphical model + * from the GLSP server. + * Invoking the {@link DiagramLoader.load} method is typically the first operation that is executed after + * a diagram DI container has been created. + */ +@injectable() +export class DiagramLoader { + @inject(TYPES.IDiagramOptions) + protected options: IDiagramOptions; + + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + @multiInject(TYPES.IDiagramStartup) + @optional() + protected diagramStartups: IDiagramStartup[] = []; + + protected enableLoadingNotifications = true; + + @postConstruct() + protected postConstruct(): void { + this.diagramStartups.sort((a, b) => Ranked.getRank(a) - Ranked.getRank(b)); + } + + async load(requestModelOptions: Args = {}): Promise { + // Set placeholder model until real model from server is available + await this.actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT)); + await this.invokeStartupHook('preInitialize'); + await this.configureGLSPClient(); + await this.invokeStartupHook('preRequestModel'); + await this.requestModel(requestModelOptions); + await this.invokeStartupHook('postRequestModel'); + } + + protected async invokeStartupHook(hook: keyof Omit): Promise { + for (const startup of this.diagramStartups) { + await startup[hook]?.(); + } + } + + protected requestModel(requestModelOptions: Args = {}): Promise { + const options = { sourceUri: this.options.sourceUri, diagramType: this.options.diagramType, ...requestModelOptions } as Args; + const result = this.actionDispatcher.dispatch(RequestModelAction.create({ options })); + if (this.enableLoadingNotifications) { + this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); + this.actionDispatcher.dispatch(EndProgressAction.create('initializeClient')); + } + return result; + } + + protected async configureGLSPClient(): Promise { + const glspClient = this.options.glspClient; + + if (this.enableLoadingNotifications) { + this.actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); + this.actionDispatcher.dispatch(StartProgressAction.create({ progressId: 'initializeClient', title: 'Initializing' })); + } + + await glspClient.start(); + + if (!glspClient.initializeResult) { + await glspClient.initializeServer({ + applicationId: ApplicationIdProvider.get(), + protocolVersion: GLSPClient.protocolVersion + }); + } + } +} diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index 97f1197..449c60c 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -23,11 +23,12 @@ import { DisposableCollection, GLSPClient, ILogger, + InitializeResult, ModelSource, - RequestModelAction, SModelRootSchema, TYPES } from '~glsp-sprotty'; +import { IDiagramOptions } from './diagram-loader'; /** * A helper interface that allows the client to mark actions that have been received from the server. */ @@ -64,28 +65,28 @@ export class GLSPModelSource extends ModelSource implements Disposable { @inject(TYPES.ILogger) protected logger: ILogger; - protected _sourceUri?: string; - protected _glspClient?: GLSPClient; protected toDispose = new DisposableCollection(); - clientId: string; - - get glspClient(): GLSPClient | undefined { - return this._glspClient; - } - - get sourceUri(): string | undefined { - return this._sourceUri; + readonly glspClient: GLSPClient; + readonly sourceUri?: string; + readonly diagramType: string; + + constructor(@inject(TYPES.IDiagramOptions) options: IDiagramOptions) { + super(); + this.glspClient = options.glspClient; + this.clientId = options.clientId ?? this.viewerOptions.baseDiv; + this.diagramType = options.diagramType; + this.sourceUri = options.sourceUri; } - async connect(client: GLSPClient, clientId?: string): Promise { - if (clientId) { - this.clientId = clientId; + configure(registry: ActionHandlerRegistry, initializeResult: InitializeResult): Promise { + const serverActions = initializeResult.serverActions[this.diagramType]; + if (!serverActions || serverActions.length === 0) { + throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${this.diagramType}!`); } - await client.start(); - this.toDispose.push(client.onActionMessage(message => this.messageReceived(message), this.clientId)); - this._glspClient = client; - return this._glspClient; + serverActions.forEach(action => registry.register(action, this)); + this.toDispose.push(this.glspClient.onActionMessage(message => this.messageReceived(message), this.clientId)); + return this.glspClient.initializeClientSession({ clientSessionId: this.clientId, diagramType: this.diagramType }); } protected messageReceived(message: ActionMessage): void { @@ -104,14 +105,16 @@ export class GLSPModelSource extends ModelSource implements Disposable { if (!this.clientId) { this.clientId = this.viewerOptions.baseDiv; } + if (this.glspClient.initializeResult) { + this.configure(registry, this.glspClient.initializeResult); + } else { + this.glspClient.onServerInitialized(result => this.configure(registry, result)); + } } handle(action: Action): void { // Handling additional actions here is discouraged and it's recommended // to implemented dedicated action handlers. - if (RequestModelAction.is(action)) { - this._sourceUri = action.options?.sourceUri.toString(); - } if (this.shouldForwardToServer(action)) { this.forwardToServer(action); } diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 87d3a93..e3ea873 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -16,9 +16,13 @@ import { Container, ContainerModule } from 'inversify'; import { + BindingContext, ContainerConfiguration, FeatureModule, + TYPES, + ViewerOptions, buttonModule, + configureViewerOptions, edgeIntersectionModule, edgeLayoutModule, expandModule, @@ -29,6 +33,7 @@ import { zorderModule } from '~glsp-sprotty'; import { defaultModule } from './base/default.module'; +import { IDiagramOptions } from './base/model/diagram-loader'; import { boundsModule } from './features/bounds/bounds-module'; import { commandPaletteModule } from './features/command-palette/command-palette-module'; import { contextMenuModule } from './features/context-menu/context-menu-module'; @@ -95,6 +100,34 @@ export const DEFAULT_MODULES = [ statusModule ] as const; +/** + * Wraps the {@link configureDiagramOptions} utility function in a module. Adopters can either include this + * module into the container {@link ModuleConfiguration} or configure the container after its creation + * (e.g. using the {@link configureDiagramOptions} utility function). + * @param options The diagram instance specific configuration options + * @returns The corresponding {@link FeatureModule} + */ +export function createDiagramOptionsModule(options: IDiagramOptions): FeatureModule { + return new FeatureModule((bind, unbind, isBound, rebind) => configureDiagramOptions({ bind, unbind, isBound, rebind }, options)); +} + +/** + * Utility function to bind the diagram instance specific configuration options. + * In addition to binding the {@link IDiagramOptions} this function also overrides the + * {@link ViewerOptions} to match the given client id. + * @param context The binding context + * @param options The {@link IDiagramOptions} that should be bound + */ +export function configureDiagramOptions(context: BindingContext, options: IDiagramOptions): void { + const viewerOptions: Partial = { + baseDiv: options.clientId, + hiddenDiv: options.clientId + '_hidden' + }; + configureViewerOptions(context, viewerOptions); + + context.bind(TYPES.IDiagramOptions).toConstantValue(options); +} + /** * Initializes a GLSP Diagram container with the GLSP default modules and the specified custom `modules`. * Additional modules can be passed as direct arguments or as part of a {@link ModuleConfiguration}. diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index f91d5a7..0ba11f9 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -13,12 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, SetTypeHintsAction, TYPES, configureActionHandler, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SetTypeHintsAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; -export const typeHintsModule = new FeatureModule((bind, _unbind, isBound) => { - bind(TypeHintProvider).toSelf().inSingletonScope(); - bind(TYPES.ITypeHintProvider).toService(TypeHintProvider); - configureActionHandler({ bind, isBound }, SetTypeHintsAction.KIND, TypeHintProvider); - configureCommand({ bind, isBound }, ApplyTypeHintsCommand); +export const typeHintsModule = new FeatureModule((bind, unbind, isBound) => { + const context = { bind, unbind, isBound }; + bindAsService(context, TYPES.ITypeHintProvider, TypeHintProvider); + bind(TYPES.IDiagramStartup).toService(TypeHintProvider); + configureActionHandler(context, SetTypeHintsAction.KIND, TypeHintProvider); + configureCommand(context, ApplyTypeHintsCommand); }); diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 292caa1..67492ce 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -23,6 +23,8 @@ import { FeatureSet, IActionHandler, ICommand, + MaybePromise, + RequestTypeHintsAction, SEdge, SModelElement, SModelRoot, @@ -36,8 +38,10 @@ import { editFeature, moveFeature } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; @@ -149,10 +153,13 @@ export interface ITypeHintProvider { } @injectable() -export class TypeHintProvider implements IActionHandler, ITypeHintProvider { +export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDiagramStartup { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + protected shapeHints: Map = new Map(); protected edgeHints: Map = new Map(); @@ -192,6 +199,10 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider { getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined { return getTypeHint(input, this.edgeHints); } + + preRequestModel(): MaybePromise { + this.actionDispatcher.dispatch(RequestTypeHintsAction.create()); + } } function getTypeHint(input: SModelElement | SModelElement | string, hints: Map): T | undefined { diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 12cbb63..c8634f7 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -23,7 +23,7 @@ import { SetResolvedNavigationTargetAction, TYPES } from '~glsp-sprotty'; -import { EditorContextServiceProvider } from '../../base/editor-context-service'; +import { IDiagramOptions } from '../../base/model/diagram-loader'; /** * Resolves `NavigationTargets` to element ids. @@ -33,14 +33,17 @@ import { EditorContextServiceProvider } from '../../base/editor-context-service' */ @injectable() export class NavigationTargetResolver { - @inject(TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - @inject(TYPES.ILogger) protected readonly logger: ILogger; + @inject(TYPES.IActionDispatcher) + protected dispatcher: IActionDispatcher; + + @inject(TYPES.ILogger) + protected logger: ILogger; + + @inject(TYPES.IDiagramOptions) + protected diagramOptions: IDiagramOptions; async resolve(navigationTarget: NavigationTarget): Promise { - const contextService = await this.editorContextService(); - const sourceUri = await contextService.getSourceUri(); - return this.resolveWithSourceUri(sourceUri, navigationTarget); + return this.resolveWithSourceUri(this.diagramOptions.sourceUri, navigationTarget); } async resolveWithSourceUri( diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 80b47d4..53520d4 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -21,5 +21,6 @@ import { StatusOverlay } from './status-overlay'; export const statusModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IUIExtension, StatusOverlay); + bind(TYPES.IDiagramStartup).toService(StatusOverlay); configureActionHandler(context, ServerStatusAction.KIND, StatusOverlay); }); diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 4be6baa..14f8bcc 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -13,16 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import { AbstractUIExtension, IActionHandler, ServerStatusAction, codiconCSSClasses } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { EditorContextService } from '../../base/editor-context-service'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; /** * A reusable status overlay for rendering (icon + message) and handling of {@link ServerStatusAction}'s. */ @injectable() -export class StatusOverlay extends AbstractUIExtension implements IActionHandler { +export class StatusOverlay extends AbstractUIExtension implements IActionHandler, IDiagramStartup { static readonly ID = 'glsp.server.status.overlay'; + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + @inject(EditorContextService) + protected editorContext: EditorContextService; + protected statusIconDiv?: HTMLDivElement; protected statusMessageDiv?: HTMLDivElement; protected pendingTimeout?: number; @@ -105,4 +114,8 @@ export class StatusOverlay extends AbstractUIExtension implements IActionHandler this.pendingTimeout = window.setTimeout(() => this.clearStatus(), statusTimeout); } } + + preInitialize(): void { + this.show(this.editorContext.modelRoot); + } } diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index dd1dd56..dd58131 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -15,10 +15,10 @@ ********************************************************************************/ import { bindAsService, configureActionHandler, EnableDefaultToolsAction, FeatureModule, TYPES } from '~glsp-sprotty'; import '../../../css/tool-palette.css'; -import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; +import { ToolPalette } from './tool-palette'; export const toolPaletteModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { bindAsService(bind, TYPES.IUIExtension, ToolPalette); - configureActionHandler({ bind, isBound }, EnableToolPaletteAction.KIND, ToolPalette); + bind(TYPES.IDiagramStartup).toService(ToolPalette); configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); }); diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 8b93a1b..7b0da20 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -36,6 +36,7 @@ import { import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; @@ -61,7 +62,7 @@ export namespace EnableToolPaletteAction { } } @injectable() -export class ToolPalette extends AbstractUIExtension implements IActionHandler, IEditModeListener { +export class ToolPalette extends AbstractUIExtension implements IActionHandler, IEditModeListener, IDiagramStartup { static readonly ID = 'tool-palette'; @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; @@ -319,28 +320,11 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - handle(action: Action): ICommand | Action | void { - if (action.kind === EnableToolPaletteAction.KIND) { - const requestAction = RequestContextActions.create({ - contextId: ToolPalette.ID, - editorContext: { - selectedElementIds: [] - } - }); - this.actionDispatcher.requestUntil(requestAction).then(response => { - if (SetContextActions.is(response)) { - this.paletteItems = response.actions.map(e => e as PaletteItem); - this.actionDispatcher.dispatch( - SetUIExtensionVisibilityAction.create({ extensionId: ToolPalette.ID, visible: !this.editorContext.isReadonly }) - ); - } - }); - } else if (action.kind === EnableDefaultToolsAction.KIND) { - this.changeActiveButton(); - if (this.focusTracker.hasFocus) { - // if focus was deliberately taken do not restore focus to the palette - this.restoreFocus(); - } + handle(action: EnableDefaultToolsAction): ICommand | Action | void { + this.changeActiveButton(); + if (this.focusTracker.hasFocus) { + // if focus was deliberately taken do not restore focus to the palette + this.restoreFocus(); } } @@ -396,6 +380,20 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.paletteItems = filteredPaletteItems; this.createBody(); } + + async preRequestModel(): Promise { + const requestAction = RequestContextActions.create({ + contextId: ToolPalette.ID, + editorContext: { + selectedElementIds: [] + } + }); + const response = await this.actionDispatcher.request(requestAction); + this.paletteItems = response.actions.map(e => e as PaletteItem); + if (!this.editorContext.isReadonly) { + this.show(this.editorContext.modelRoot); + } + } } export function compare(a: PaletteItem, b: PaletteItem): number { diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index b5e6dcc..388c7d9 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -110,7 +110,7 @@ export class SetMarkersActionHandler implements IActionHandler { } async setMarkers(markers: Marker[], reason: string | undefined): Promise { - const uri = await this.editorContextService.getSourceUri(); + const uri = this.editorContextService.sourceUri; this.externalMarkerManager?.setMarkers(markers, reason, uri); const applyMarkersAction = ApplyMarkersAction.create(markers); this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction, reason); diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index 4de45f4..fd2ce13 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -34,7 +34,9 @@ export const TYPES = { ITool: Symbol('ITool'), IDefaultTool: Symbol('IDefaultTool'), IEditModeListener: Symbol('IEditModeListener'), - IMarqueeBehavior: Symbol('IMarqueeBehavior') + IMarqueeBehavior: Symbol('IMarqueeBehavior'), + IDiagramOptions: Symbol('IDiagramOptions'), + IDiagramStartup: Symbol('IDiagramStartup') }; /** diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index da2d380..d70a4af 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -29,6 +29,7 @@ export * from './base/feedback/update-model-command'; export * from './base/focus/focus-state-change-action'; export * from './base/focus/focus-tracker'; export * from './base/model-initialization-constraint'; +export * from './base/model/diagram-loader'; export * from './base/model/glsp-model-source'; export * from './base/model/model-registry'; export * from './base/ranked'; diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index 3cc601c..eadf305 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -131,6 +131,19 @@ describe('Node GLSP Client', () => { expect(result).to.be.deep.equal(client.initializeResult); expect(server.initialize.called).to.be.false; }); + it('should fire event on first invocation', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + server.initialize.returns(Promise.resolve(expectedResult)); + // eslint-disable-next-line @typescript-eslint/no-empty-function + const eventHandler = (result: InitializeResult): void => {}; + const eventHandlerSpy = sinon.spy(eventHandler); + client.onServerInitialized(eventHandlerSpy); + await client.initializeServer(params); + await client.initializeServer(params); + expect(eventHandlerSpy.calledOnceWith(expectedResult)).to.be.true; + }); }); describe('initializeClientSession', () => { diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts index 2c8eecc..edc0a2d 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -18,6 +18,7 @@ import { Deferred } from 'sprotty-protocol'; import { Disposable } from 'vscode-jsonrpc'; import { Action, ActionMessage } from '../action-protocol'; import { distinctAdd, remove } from '../utils/array-util'; +import { Emitter, Event } from '../utils/event'; import { ActionMessageHandler, ClientState, GLSPClient } from './glsp-client'; import { GLSPClientProxy, GLSPServer } from './glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; @@ -39,6 +40,11 @@ export class BaseGLSPClient implements GLSPClient { protected actionMessageHandlers: Map = new Map([[GLOBAL_HANDLER_ID, []]]); protected _initializeResult: InitializeResult; + protected onServerInitializedEmitter = new Emitter(); + get onServerInitialized(): Event { + return this.onServerInitializedEmitter.event; + } + constructor(protected options: GLSPClient.Options) { this.state = ClientState.Initial; this.proxy = this.createProxy(); @@ -92,6 +98,7 @@ export class BaseGLSPClient implements GLSPClient { async initializeServer(params: InitializeParameters): Promise { if (!this._initializeResult) { this._initializeResult = await this.checkedServer.initialize(params); + this.onServerInitializedEmitter.fire(this._initializeResult); } return this._initializeResult; } diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 3996472..aba78ff 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -17,6 +17,7 @@ import * as uuid from 'uuid'; import { ActionMessage } from '../action-protocol'; import { Disposable } from '../utils/disposable'; +import { Event } from '../utils/event'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export class ApplicationIdProvider { @@ -100,6 +101,11 @@ export interface GLSPClient { */ readonly initializeResult: InitializeResult | undefined; + /** + * Event that is fired once the first invocation of {@link GLSPClient.initializeServer} has been completed. + */ + readonly onServerInitialized: Event; + /** * Send an `initializeClientSession` request to the server. One client application may open several session. * Each individual diagram on the client side counts as one session and has to provide diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 60c067c..64a32b4 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -21,6 +21,7 @@ import { ActionMessage } from '../../action-protocol/base-protocol'; import { remove } from '../../utils/array-util'; import { expectToThrowAsync } from '../../utils/test-util'; import { ClientState } from '../glsp-client'; +import { InitializeResult } from '../types'; import { BaseJsonrpcGLSPClient } from './base-jsonrpc-glsp-client'; import { JsonrpcGLSPClient } from './glsp-jsonrpc-client'; @@ -139,6 +140,19 @@ describe('Base JSON-RPC GLSP Client', () => { expect(result).to.be.deep.equal(client.initializeResult); expect(initializeMock.called).to.be.false; }); + it('should fire event on first invocation', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); + initializeMock.returns(expectedResult); + const eventHandler = (result: InitializeResult): void => {}; + const eventHandlerSpy = sinon.spy(eventHandler); + client.onServerInitialized(eventHandlerSpy); + await client.initializeServer(params); + await client.initializeServer(params); + expect(eventHandlerSpy.calledOnceWith(expectedResult)).to.be.true; + }); }); describe('initializeClientSession', () => { diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 9d146c4..5e392dd 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -16,6 +16,7 @@ import { injectable } from 'inversify'; import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; import { ActionMessage } from '../../action-protocol'; +import { Emitter, Event } from '../../utils/event'; import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; import { GLSPClientProxy } from '../glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from '../types'; @@ -30,6 +31,11 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { protected onStop?: Promise; protected _initializeResult: InitializeResult | undefined; + protected onServerInitializedEmitter = new Emitter(); + get onServerInitialized(): Event { + return this.onServerInitializedEmitter.event; + } + constructor(options: JsonrpcGLSPClient.Options) { Object.assign(this, options); this.state = ClientState.Initial; @@ -42,6 +48,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { async initializeServer(params: InitializeParameters): Promise { if (!this._initializeResult) { this._initializeResult = await this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); + this.onServerInitializedEmitter.fire(this._initializeResult); } return this._initializeResult; } From 1c231318156b137621eabb3cee63e681052ad12f Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 10 Aug 2023 18:33:29 +0200 Subject: [PATCH 243/566] Add edge routing in GLSP projection view We should compute all routes at once in order to enable edge route postprocessors and with that also the intersection detection. When only computing one route after the other we cannot detect intersections and thus don't render gaps on edge intersections. Fixes https://github.com/eclipse-glsp/glsp/issues/1084 --- .../client/src/views/glsp-projection-view.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index ad566b4..a39015f 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -14,19 +14,20 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { h, VNode, VNodeStyle } from 'snabbdom'; +import { inject, injectable } from 'inversify'; +import { VNode, VNodeStyle, h } from 'snabbdom'; import { Bounds, - html, + EdgeRouterRegistry, IViewArgs, ProjectedViewportView, ProjectionParams, RenderingContext, - setAttr, - setClass, + ViewProjection, ViewportRootElement, - ViewProjection + html, + setAttr, + setClass } from '~glsp-sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -37,6 +38,8 @@ const JSX = { createElement: html }; */ @injectable() export class GLSPProjectionView extends ProjectedViewportView { + @inject(EdgeRouterRegistry) edgeRouterRegistry: EdgeRouterRegistry; + override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { const svgElem = this.renderSvg(model, context, args); if (svgElem.data) { @@ -53,9 +56,14 @@ export class GLSPProjectionView extends ProjectedViewportView { } protected override renderSvg(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { + const edgeRouting = this.edgeRouterRegistry.routeAllChildren(model); const transform = `scale(${model.zoom}) translate(${-model.scroll.x},${-model.scroll.y})`; const ns = 'http://www.w3.org/2000/svg'; - const svg = h('svg', { ns, style: { height: '100%' } }, h('g', { ns, attrs: { transform } }, context.renderChildren(model))); + const svg = h( + 'svg', + { ns, style: { height: '100%' } }, + h('g', { ns, attrs: { transform } }, context.renderChildren(model, { edgeRouting })) + ); return svg; } From 171c696d583364fba59c0f6511ffab9328d27397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:30:45 +0200 Subject: [PATCH 244/566] GLSP-1029: Introducing element navigation via keyboard (#276) - Introduce tool and handler for element navigation via keyboard shortcut - Introduce tool and handler for tracking the current focus of the cursor - Introduce tool and handler for the creation of notification/toast - Introduce new reposition action for focusing on element without zooming in Part of https://github.com/eclipse-glsp/glsp/issues/1029 --- packages/client/css/key-shortcut.css | 6 +- packages/client/css/navigation.css | 26 ++ packages/client/css/toast.css | 45 +++ .../src/features/accessibility/README.md | 18 + .../accessibility/accessibility-module.ts | 7 +- .../diagram-navigation-tool.ts | 311 ++++++++++++++++++ .../element-navigation-module.ts | 36 ++ .../element-navigation/element-navigator.ts | 57 ++++ .../left-right-top-bottom-navigator.ts | 113 +++++++ .../local-element-navigator.ts | 147 +++++++++ .../element-navigation/position-navigator.ts | 102 ++++++ .../focus-tracker/focus-tracker-module.ts | 32 ++ .../focus-tracker/focus-tracker-tool.ts | 129 ++++++++ .../key-shortcut/accessible-key-shortcut.ts | 21 +- .../resize-key-tool/resize-key-tool.ts | 16 + .../accessibility/search/search-palette.ts | 6 +- .../accessibility/toast/messages.json | 16 + .../accessibility/toast/toast-handler.ts | 69 ++++ .../accessibility/toast/toast-module.ts | 36 ++ .../accessibility/toast/toast-tool.ts | 108 ++++++ .../src/features/tool-palette/tool-palette.ts | 16 +- .../src/features/viewport/reposition.ts | 119 +++++++ .../src/features/viewport/viewport-modules.ts | 2 + packages/client/src/glsp-sprotty/types.ts | 2 + packages/client/src/index.ts | 6 + .../client/src/views/glsp-projection-view.tsx | 4 +- 26 files changed, 1440 insertions(+), 10 deletions(-) create mode 100644 packages/client/css/navigation.css create mode 100644 packages/client/css/toast.css create mode 100644 packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/element-navigator.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/position-navigator.ts create mode 100644 packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts create mode 100644 packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts create mode 100644 packages/client/src/features/accessibility/toast/messages.json create mode 100644 packages/client/src/features/accessibility/toast/toast-handler.ts create mode 100644 packages/client/src/features/accessibility/toast/toast-module.ts create mode 100644 packages/client/src/features/accessibility/toast/toast-tool.ts create mode 100644 packages/client/src/features/viewport/reposition.ts diff --git a/packages/client/css/key-shortcut.css b/packages/client/css/key-shortcut.css index 91d8386..796ed1c 100644 --- a/packages/client/css/key-shortcut.css +++ b/packages/client/css/key-shortcut.css @@ -92,7 +92,7 @@ cursor: pointer; } -.columnTitle { +.column-title { text-align: left; } @@ -105,3 +105,7 @@ justify-content: space-between; flex-wrap: wrap; } + +.shortcut-table thead th { + border-bottom: 2px solid #000; +} diff --git a/packages/client/css/navigation.css b/packages/client/css/navigation.css new file mode 100644 index 0000000..e826d22 --- /dev/null +++ b/packages/client/css/navigation.css @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +:root { + --glsp-navigation-highlight: rgba(136, 68, 68, 0.2); +} +.navigable-element { + stroke: var(--glsp-navigation-highlight) !important; +} +.navigable-element .sprotty-edge.arrow { + fill: var(--glsp-navigation-highlight) !important; + stroke: var(--glsp-navigation-highlight) !important; +} diff --git a/packages/client/css/toast.css b/packages/client/css/toast.css new file mode 100644 index 0000000..9746112 --- /dev/null +++ b/packages/client/css/toast.css @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +.toast { + position: absolute; + bottom: 1rem; + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-column-gap: 1rem; + margin: 0 1rem; +} + +.toast-column-left { + grid-column: 1 / span 1; +} + +.toast-column-center { + grid-column: 2 / span 1; +} + +.toast-column-right { + grid-column: 3 / span 1; +} + +.toast-container { + display: inline-block; + min-width: 256px; + max-width: 516px; + background-color: #ccc; + color: #000; + padding: 0.5rem; +} diff --git a/packages/client/src/features/accessibility/README.md b/packages/client/src/features/accessibility/README.md index 9ec02b2..f403e87 100644 --- a/packages/client/src/features/accessibility/README.md +++ b/packages/client/src/features/accessibility/README.md @@ -63,3 +63,21 @@ The resize functionality helps to set the size of the nodes, by either increasin 3. + or - to increase or decrease the size of the element(s) gradually. 4. CTRL+0: Set the size of the element(s) to default. 5. ESC: Deactivate the resize mode. + +### Model Navigation + +#### Default Navigation (following directions of relations) + +1. Select element as starting point. +2. N: Activate default navigation. +3. Use arrow keys to iterate through model according to the directions of the given relations. + +#### Position-based Navigation (following x and y coordinates) + +1. Select element as starting point. +2. ALT+N: Activate position based navigation. +3. Use arrow keys ( || || || ) to iterate through model according to the positions of the elements, i.e. depending on the order of the elements' x and y coordinates. + +### Help + +1. ALT+H: Display list of keyboard shortcuts. diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 84ebfd5..b5101b9 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -20,7 +20,9 @@ import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; - +import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; +import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; +import { configureToastTool } from './toast/toast-module'; /** * Enables the accessibility tools for a keyboard-only-usage */ @@ -31,4 +33,7 @@ export const accessibilityModule = new FeatureModule((bind, unbind, isBound, reb configureMoveZoom(context); configureSearchPaletteModule(context); configureShortcutHelpTool(context); + configureElementNavigationTool(context); + configureFocusTrackerTool(context); + configureToastTool(context); }); diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts new file mode 100644 index 0000000..a06b093 --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -0,0 +1,311 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + SelectAction, + findParentByFeature, + isBoundsAware, + isSelectable, + isSelected, + KeyListener, + KeyTool, + SEdge, + Selectable, + SModelElement, + SModelRoot, + TYPES, + EnableToolsAction, + EnableDefaultToolsAction +} from '~glsp-sprotty'; +import { inject, injectable } from 'inversify'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { SelectableBoundsAware } from '../../../utils/smodel-util'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import * as messages from '../toast/messages.json'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import { RepositionAction } from '../../viewport/reposition'; +import { ElementNavigator } from './element-navigator'; +import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; +import { SearchAutocompletePaletteTool } from '../search/search-tool'; +import { AccessibleKeyShortcutTool } from '../key-shortcut/accessible-key-shortcut-tool'; + +@injectable() +export class ElementNavigatorTool implements GLSPTool { + static ID = 'glsp.diagram-navigation'; + + isEditTool = false; + + protected elementNavigatorKeyListener: ElementNavigatorKeyListener = new ElementNavigatorKeyListener(this); + @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.IElementNavigator) readonly elementNavigator: ElementNavigator; + @inject(TYPES.ILocalElementNavigator) readonly localElementNavigator: ElementNavigator; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + + get id(): string { + return ElementNavigatorTool.ID; + } + + enable(): void { + this.keytool.register(this.elementNavigatorKeyListener); + this.elementNavigatorKeyListener.registerShortcutKey(); + } + + disable(): void { + this.keytool.deregister(this.elementNavigatorKeyListener); + } +} + +enum NavigationMode { + POSITION = 'position', + DEFAULT = 'default', + NONE = 'none' +} + +export class ElementNavigatorKeyListener extends KeyListener { + protected mode = NavigationMode.NONE; + protected previousNode?: SelectableBoundsAware; + protected navigator?: ElementNavigator; + protected readonly token = ElementNavigatorKeyListener.name; + + constructor(protected readonly tool: ElementNavigatorTool) { + super(); + } + + registerShortcutKey(): void { + this.tool.actionDispatcher.onceModelInitialized().then(() => { + this.tool.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['N'], description: 'Activate default navigation', group: 'Navigation', position: 0 }, + { + shortcuts: ['ALT', 'N'], + description: 'Activate position based navigation', + group: 'Navigation', + position: 1 + }, + { + shortcuts: ['⬅ ⬆ ➡ ⬇'], + description: 'Navigate by relation or neighbors according to navigation mode', + group: 'Navigation', + position: 2 + } + ] + }) + ]); + }); + } + + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + this.resetOnEscape(event, element); + + if (this.getSelectedElements(element.root).length > 0) { + if (!this.triggerPositionNavigationOnEvent(event, element)) { + this.triggerDefaultNavigationOnEvent(event, element); + } + + return this.navigate(element, event); + } + + this.resetPositionNavigationOnEvent(event, element); + this.resetDefaultNavigationOnEvent(event, element); + + return []; + } + protected resetOnEscape(event: KeyboardEvent, element: SModelElement): void { + if (this.mode !== NavigationMode.NONE && this.matchesDeactivateNavigationMode(event)) { + this.navigator?.clean?.(element.root); + this.clean(); + + if (this.mode === NavigationMode.POSITION) { + this.tool.actionDispatcher.dispatchAll([ + EnableDefaultToolsAction.create(), + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.local_navigation_mode_deactivated + }) + ]); + } else if (this.mode === NavigationMode.DEFAULT) { + this.tool.actionDispatcher.dispatchAll([ + EnableDefaultToolsAction.create(), + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.default_navigation_mode_deactivated + }) + ]); + } + + this.mode = NavigationMode.NONE; + } + } + + protected triggerPositionNavigationOnEvent(event: KeyboardEvent, element: SModelElement): boolean { + if (this.matchesActivatePositionNavigation(event)) { + if (this.mode !== NavigationMode.POSITION) { + this.clean(); + this.tool.actionDispatcher.dispatchAll([ + EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AccessibleKeyShortcutTool.ID]), + ShowToastMessageAction.create({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.local_navigation_mode_activated + }) + ]); + this.navigator = this.tool.elementNavigator; + this.mode = NavigationMode.POSITION; + } else { + this.resetPositionNavigationOnEvent(event, element); + } + + return true; + } + + return false; + } + + protected resetPositionNavigationOnEvent(event: KeyboardEvent, element: SModelElement): void { + if (this.mode === NavigationMode.POSITION && this.matchesActivatePositionNavigation(event)) { + this.navigator?.clean?.(element.root); + this.clean(); + this.mode = NavigationMode.NONE; + this.tool.actionDispatcher.dispatchAll([ + EnableDefaultToolsAction.create(), + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.local_navigation_mode_deactivated + }) + ]); + } + } + protected triggerDefaultNavigationOnEvent(event: KeyboardEvent, element: SModelElement): boolean { + if (this.matchesActivateDefaultNavigation(event)) { + if (this.mode !== NavigationMode.DEFAULT) { + this.clean(); + + this.tool.actionDispatcher.dispatchAll([ + EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AccessibleKeyShortcutTool.ID]), + ShowToastMessageAction.create({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.default_navigation_mode_activated + }) + ]); + this.navigator = this.tool.localElementNavigator; + this.mode = NavigationMode.DEFAULT; + } else { + this.resetDefaultNavigationOnEvent(event, element); + } + + return true; + } + return false; + } + + protected resetDefaultNavigationOnEvent(event: KeyboardEvent, element: SModelElement): void { + if (this.mode === NavigationMode.DEFAULT && this.matchesActivateDefaultNavigation(event)) { + this.navigator?.clean?.(element.root); + this.clean(); + this.mode = NavigationMode.NONE; + this.tool.actionDispatcher.dispatchAll([ + EnableDefaultToolsAction.create(), + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.navigation.default_navigation_mode_deactivated + }) + ]); + } + } + + protected navigate(element: SModelElement, event: KeyboardEvent): Action[] { + const selected = this.getSelectedElements(element.root); + const current = selected.length > 0 ? selected[0] : undefined; + + if (this.mode !== NavigationMode.NONE && this.navigator !== undefined && current !== undefined && isBoundsAware(current)) { + this.navigator.clean?.(current.root, current, this.previousNode); + + const target = this.navigateOnEvent(event, this.navigator, current); + + if (target !== undefined) { + this.navigator.process?.(current.root, current, target as SelectableBoundsAware, this.previousNode); + } + const selectableTarget = target ? findParentByFeature(target, isSelectable) : undefined; + + if (selectableTarget) { + if (!(current instanceof SEdge)) { + this.previousNode = current; + } + const deselectedElementsIDs = selected.map(e => e.id).filter(id => id !== selectableTarget.id); + + return [ + SelectAction.create({ selectedElementsIDs: [selectableTarget.id], deselectedElementsIDs }), + RepositionAction.create([selectableTarget.id]) + ]; + } + } + + return []; + } + + protected navigateOnEvent( + event: KeyboardEvent, + navigator: ElementNavigator, + current: SelectableBoundsAware + ): SModelElement | undefined { + if (this.matchesNavigatePrevious(event)) { + return navigator.previous(current.root, current); + } else if (this.matchesNavigateNext(event)) { + return navigator.next(current.root, current); + } else if (this.matchesNavigateUp(event)) { + return navigator.up?.(current.root, current, this.previousNode); + } else if (this.matchesNavigateDown(event)) { + return navigator.down?.(current.root, current, this.previousNode); + } + + return undefined; + } + + protected clean(): void { + this.previousNode = undefined; + this.navigator = undefined; + } + + protected getSelectedElements(root: SModelRoot): (SModelElement & Selectable)[] { + return toArray(root.index.all().filter(e => isSelected(e))) as (SModelElement & Selectable)[]; + } + + protected matchesDeactivateNavigationMode(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } + protected matchesActivateDefaultNavigation(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyN'); + } + protected matchesActivatePositionNavigation(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyN', 'alt'); + } + protected matchesNavigatePrevious(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowLeft'); + } + protected matchesNavigateNext(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowRight'); + } + protected matchesNavigateUp(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowUp'); + } + protected matchesNavigateDown(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowDown'); + } +} diff --git a/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts new file mode 100644 index 0000000..a0f53e2 --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { BindingContext, TYPES, bindAsService } from '~glsp-sprotty'; +import { PositionNavigator } from './position-navigator'; +import { LocalElementNavigator } from './local-element-navigator'; +import { ElementNavigatorTool } from './diagram-navigation-tool'; +import '../../../../css/navigation.css'; + +/** + * Handles element navigation actions. + */ + +export const glspElementNavigationModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureElementNavigationTool(context); +}); + +export function configureElementNavigationTool(context: BindingContext): void { + bindAsService(context, TYPES.IDefaultTool, ElementNavigatorTool); + bindAsService(context, TYPES.IElementNavigator, PositionNavigator); + bindAsService(context, TYPES.ILocalElementNavigator, LocalElementNavigator); +} diff --git a/packages/client/src/features/accessibility/element-navigation/element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/element-navigator.ts new file mode 100644 index 0000000..107e110 --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/element-navigator.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { SModelElement, SModelRoot } from '~glsp-sprotty'; +import { SelectableBoundsAware } from '../../../utils/smodel-util'; + +export interface ElementNavigator { + previous( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined; + + next( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined; + + up?( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined; + + down?( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined; + + process?( + root: Readonly, + current: SelectableBoundsAware, + target: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): void; + clean?(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void; +} diff --git a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts new file mode 100644 index 0000000..dd39454 --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { EdgeRouterRegistry, findParentByFeature, SEdge, SModelElement, SModelRoot, Point } from '~glsp-sprotty'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { calcElementAndRoute, isRoutable, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; +import { ElementNavigator } from './element-navigator'; +import { inject, injectable, optional } from 'inversify'; + +@injectable() +export class LeftToRightTopToBottomElementNavigator implements ElementNavigator { + @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; + + previous( + root: Readonly, + current?: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement | undefined { + const elements = this.getElements(root, predicate); + + if (current === undefined) { + return elements.length > 0 ? elements[0] : undefined; + } + return elements[this.getPreviousIndex(current, elements) % elements.length]; + } + + next( + root: Readonly, + current?: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement | undefined { + const elements = this.getElements(root, predicate); + if (current === undefined) { + return elements.length > 0 ? elements[0] : undefined; + } + return elements[this.getNextIndex(current, elements) % elements.length]; + } + + protected getElements(root: Readonly, predicate: (element: SModelElement) => boolean): SModelElement[] { + const elements = toArray(root.index.all().filter(e => isSelectableAndBoundsAware(e))) as SelectableBoundsAware[]; + return elements.sort((a, b) => this.compare(a, b)).filter(predicate); + } + + protected getNextIndex(current: SelectableBoundsAware, elements: SModelElement[]): number { + for (let index = 0; index < elements.length; index++) { + if (this.compare(elements[index], current) > 0) { + return index; + } + } + + return 0; + } + + protected getPreviousIndex(current: SelectableBoundsAware, elements: SModelElement[]): number { + for (let index = elements.length - 1; index >= 0; index--) { + if (this.compare(elements[index], current) < 0) { + return index; + } + } + + return elements.length - 1; + } + + protected compare(one: SModelElement, other: SModelElement): number { + let positionOne: Point | undefined = undefined; + let positionOther: Point | undefined = undefined; + + if (one instanceof SEdge && isRoutable(one)) { + positionOne = calcElementAndRoute(one, this.edgeRouterRegistry).newRoutingPoints?.[0]; + } + + if (other instanceof SEdge && isRoutable(other)) { + positionOther = calcElementAndRoute(other, this.edgeRouterRegistry).newRoutingPoints?.[0]; + } + + const boundsOne = findParentByFeature(one, isSelectableAndBoundsAware); + const boundsOther = findParentByFeature(other, isSelectableAndBoundsAware); + + if (positionOne === undefined && boundsOne) { + positionOne = boundsOne.bounds; + } + + if (positionOther === undefined && boundsOther) { + positionOther = boundsOther.bounds; + } + + if (positionOne && positionOther) { + if (positionOne.y !== positionOther.y) { + return positionOne.y - positionOther.y; + } + if (positionOne.x !== positionOther.x) { + return positionOne.x - positionOther.x; + } + } + + return 0; + } +} diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts new file mode 100644 index 0000000..a0ca670 --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -0,0 +1,147 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { BoundsAware, EdgeRouterRegistry, SConnectableElement, SEdge, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; +import { ElementNavigator } from './element-navigator'; +import { inject, injectable, optional } from 'inversify'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { SelectableBoundsAware } from '../../../utils/smodel-util'; + +@injectable() +export class LocalElementNavigator implements ElementNavigator { + navigableElementCSS = 'navigable-element'; + @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + + previous( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getPreviousElement(current, predicate); + } + + next( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getNextElement(current, predicate); + } + + up( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getIterable(current, previousCurrent, predicate); + } + + down( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getIterable(current, previousCurrent, predicate); + } + + process( + root: Readonly, + current: SelectableBoundsAware, + target: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): void { + let elements: SModelElement[] = []; + + // Mark only edges + if (target instanceof SEdge) { + // If current is a edge, we have to check the source and target + if (current instanceof SEdge) { + elements = this.getIterables(target, current.source === target.source ? current.source : current.target, predicate); + } else { + // Otherwise take the current as it is + elements = this.getIterables(target, current, predicate); + } + } + elements.filter(e => e.id !== target.id).forEach(e => this.actionDispatcher.dispatch(applyCssClasses(e, this.navigableElementCSS))); + } + + clean(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void { + root.index.all().forEach(e => this.actionDispatcher.dispatch(deleteCssClasses(e, this.navigableElementCSS))); + } + + protected getIterables( + current: SelectableBoundsAware, + previousCurrent?: SModelElement & BoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement[] { + const elements: SModelElement[] = []; + + if (current instanceof SEdge) { + if (current.target === previousCurrent) { + current.target?.incomingEdges.forEach(e => elements.push(e)); + } else { + current.source?.outgoingEdges.forEach(e => elements.push(e)); + } + } + + return elements.filter(predicate); + } + + protected getIterable( + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement | undefined { + return this.getIterables(current, previousCurrent, predicate).filter(e => e.id !== current.id)[0]; + } + protected getNextElement( + current: SelectableBoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement | undefined { + const elements: SModelElement[] = []; + + if (current instanceof SConnectableElement) { + current.outgoingEdges.forEach(e => elements.push(e)); + } else if (current instanceof SEdge) { + const target = current.target as SModelElement; + elements.push(target); + } + + return elements.filter(predicate)[0]; + } + + protected getPreviousElement( + current: SelectableBoundsAware, + predicate: (element: SModelElement) => boolean = () => true + ): SModelElement | undefined { + const elements: SModelElement[] = []; + + if (current instanceof SConnectableElement) { + current.incomingEdges.forEach(e => elements.push(e)); + } else if (current instanceof SEdge) { + const source = current.source as SModelElement; + elements.push(source); + } + + return elements.filter(predicate)[0]; + } +} diff --git a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts new file mode 100644 index 0000000..022d94b --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts @@ -0,0 +1,102 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isBoundsAware, isSelectable, SChildElement, SModelElement, SModelRoot, SNode, TYPES, Bounds, Point } from '~glsp-sprotty'; +import { ElementNavigator } from './element-navigator'; +import { inject, injectable } from 'inversify'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { SelectableBoundsAware } from '../../../utils/smodel-util'; + +@injectable() +export class PositionNavigator implements ElementNavigator { + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + + previous( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getNearestElement(root, current, e => this.bounds(root, e).x < this.bounds(root, current).x); + } + + next( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getNearestElement(root, current, e => this.bounds(root, e).x > this.bounds(root, current).x); + } + + up( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getNearestElement(root, current, e => this.bounds(root, e).y < this.bounds(root, current).y); + } + + down( + root: Readonly, + current: SelectableBoundsAware, + previousCurrent?: SelectableBoundsAware, + predicate?: (element: SModelElement) => boolean + ): SModelElement | undefined { + return this.getNearestElement(root, current, e => this.bounds(root, e).y > this.bounds(root, current).y); + } + + protected getNearestElement( + root: Readonly, + current: SelectableBoundsAware, + filter: (e: SelectableBoundsAware) => boolean + ): SModelElement | undefined { + const elements = this.boundElements(root).filter(filter); + return this.sortByDistance(root, current, elements)[0]; + } + + protected sortByDistance(root: SModelRoot, current: SelectableBoundsAware, elements: SelectableBoundsAware[]): SelectableBoundsAware[] { + // https://www.tutorialspoint.com/sort-array-of-points-by-ascending-distance-from-a-given-point-javascript + const distance = (coor1: Point, coor2: Point): number => { + const x = coor2.x - coor1.x; + const y = coor2.y - coor1.y; + return Math.sqrt(x * x + y * y); + }; + + return elements.sort( + (a, b) => + distance(this.bounds(root, a), this.bounds(root, current)) - distance(this.bounds(root, b), this.bounds(root, current)) + ); + } + + protected boundElements(root: Readonly): SelectableBoundsAware[] { + return toArray(root.index.all().filter(e => e instanceof SNode && isSelectable(e) && isBoundsAware(e))) as SelectableBoundsAware[]; + } + + protected bounds(root: Readonly, element: SelectableBoundsAware): Bounds { + return this.boundsInViewport(element, element.bounds, root); + } + + protected boundsInViewport(element: SModelElement, bounds: Bounds, viewport: SModelRoot): Bounds { + if (element instanceof SChildElement && element.parent !== viewport) { + return this.boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds, viewport); + } else { + return bounds; + } + } +} diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts new file mode 100644 index 0000000..5a30818 --- /dev/null +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { FocusTrackerTool } from './focus-tracker-tool'; +import { BindingContext, TYPES, bindAsService } from '~glsp-sprotty'; + +/** + * Handles actions for tracking the focus of the cursor. + */ + +export const glspFocusTrackerModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureFocusTrackerTool(context); +}); + +export function configureFocusTrackerTool(context: BindingContext): void { + bindAsService(context, TYPES.IDefaultTool, FocusTrackerTool); +} diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts new file mode 100644 index 0000000..db2575e --- /dev/null +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject } from 'inversify'; +import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import * as messages from '../toast/messages.json'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { ViewerOptions, TYPES } from '~glsp-sprotty'; + +@injectable() +export class FocusTrackerTool implements GLSPTool { + static ID = 'glsp.focus-tracker'; + + isEditTool = false; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.ViewerOptions) + protected readonly viewerOptions: ViewerOptions; + + protected readonly focusInFunction = (event: FocusEvent): Promise => this.focusIn(event); + protected readonly focusOutFunction = (event: FocusEvent): Promise => this.focusOut(event); + protected registered = false; + + get id(): string { + return FocusTrackerTool.ID; + } + + enable(): void { + if (!this.registered) { + document.addEventListener('focusin', this.focusInFunction); + document.addEventListener('focusout', this.focusOutFunction); + } + this.registered = true; + } + + disable(): void { + // the focus tracker cannot be disabled after enabling it + } + + protected async focusOut(event: FocusEvent): Promise { + await this.showToast('Focus not set'); + } + + protected async focusIn(event: FocusEvent): Promise { + let message: string | undefined; + const target = event.target; + + if (target instanceof HTMLElement) { + const parent = this.parentWithAriaLabel(target); + const textMessage = this.handleTextNode(target); + // eslint-disable-next-line no-null/no-null + if (target.ariaLabel !== null) { + message = this.handleAriaLabel(target); + } else { + if (parent === undefined && textMessage !== undefined) { + message = textMessage; + } else if (parent !== undefined && textMessage === undefined) { + message = `Focus is in ${parent.ariaLabel}`; + } else if (parent !== undefined && textMessage !== undefined) { + message = `${parent.ariaLabel} -> ${textMessage}`; + } + } + } + + await this.showToast(message); + } + + protected handleTextNode(target: HTMLElement): string | undefined { + const textNode = Array.prototype.filter + .call(target.childNodes, element => element.nodeType === Node.TEXT_NODE) + .map(element => element.textContent) + .join(''); + + if (textNode.trim().length !== 0) { + return textNode; + } + + return undefined; + } + + protected handleAriaLabel(target: HTMLElement): string | undefined { + // eslint-disable-next-line no-null/no-null + return target.ariaLabel === null ? undefined : target.ariaLabel; + } + + protected showToast(message?: string): Promise { + return this.actionDispatcher.dispatchAll([ + ShowToastMessageAction.create({ + id: Symbol.for(FocusTrackerTool.ID), + message: `${messages.focus.focus_on} ${message ?? 'unknown'}`, + position: 'left' + }) + ]); + } + protected parentWithAriaLabel(target: HTMLElement): HTMLElement | undefined { + let current = target.parentElement; + + while ( + // eslint-disable-next-line no-null/no-null + current !== null && + current !== document.body && + current !== document.getElementById(this.viewerOptions.baseDiv) && + // eslint-disable-next-line no-null/no-null + current.ariaLabel === null + ) { + current = current.parentElement; + } + + if (current === document.getElementById(this.viewerOptions.baseDiv) || current === document.body) { + return undefined; + } + + return current ?? undefined; + } +} diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index 43b7284..5e955c3 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -93,6 +93,7 @@ export class KeyShortcutUIExtension extends AbstractUIExtension implements IActi const grouped = groupBy(registrations, k => k.group); const groupTable = document.createElement('table'); + groupTable.classList.add('shortcut-table'); const tableHead = document.createElement('thead'); const tableBody = document.createElement('tbody'); @@ -100,8 +101,7 @@ export class KeyShortcutUIExtension extends AbstractUIExtension implements IActi const commandCell = document.createElement('th'); const keybindingCell = document.createElement('th'); - commandCell.classList.add('columnTitle'); - commandCell.classList.add('columnTitle'); + commandCell.classList.add('column-title'); commandCell.innerText = 'Command'; keybindingCell.innerText = 'Keybinding'; @@ -110,7 +110,8 @@ export class KeyShortcutUIExtension extends AbstractUIExtension implements IActi headerRow.appendChild(keybindingCell); tableHead.appendChild(headerRow); - for (const [, shortcuts] of Object.entries(grouped)) { + for (const [group, shortcuts] of Object.entries(grouped)) { + tableBody.appendChild(this.createGroupHeader(group)); shortcuts.forEach(s => { tableBody.appendChild(this.createEntry(s)); }); @@ -122,6 +123,20 @@ export class KeyShortcutUIExtension extends AbstractUIExtension implements IActi this.shortcutsContainer.append(groupTable); } + protected createGroupHeader(group: string): HTMLElement { + const entryRow = document.createElement('tr'); + const groupElement = document.createElement('td'); + const text = document.createElement('strong'); + const emptyElement = document.createElement('td'); + + text.innerText = group; + groupElement.appendChild(text); + entryRow.appendChild(groupElement); + entryRow.appendChild(emptyElement); + + return entryRow; + } + protected getShortcutHTML(shortcuts: string[]): HTMLElement { const shortcutKeys = document.createElement('span'); shortcutKeys.innerHTML = shortcuts.map(key => `${key}`).join(' + '); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index d2a2a82..fbad4c9 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -23,6 +23,8 @@ import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ResizeElementAction, ResizeType } from './resize-key-handler'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import * as messages from '../toast/messages.json'; @injectable() export class ResizeKeyTool implements GLSPTool { @@ -82,12 +84,26 @@ export class ResizeKeyListener extends KeyListener implements AccessibleKeyShort if (this.isEditMode && this.matchesDeactivateResizeModeKeystroke(event)) { this.isEditMode = false; + + this.tool.actionDispatcher.dispatch( + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ResizeKeyListener.name), + message: messages.resize.resize_mode_deactivated + }) + ); + actions.push(EnableDefaultToolsAction.create()); } if (selectedElementsIds.length > 0) { if (!this.isEditMode && this.matchesActivateResizeModeKeystroke(event)) { this.isEditMode = true; + this.tool.actionDispatcher.dispatch( + ShowToastMessageAction.create({ + id: Symbol.for(ResizeKeyListener.name), + message: messages.resize.resize_mode_activated + }) + ); actions.push(EnableToolsAction.create([ResizeKeyTool.ID])); } diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index f2e8060..daca465 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -35,6 +35,7 @@ import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../a import { BaseAutocompletePalette } from '../../autocomplete-palette/base-autocomplete-palette'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { RepositionAction } from '../../../features/viewport/reposition'; const CSS_SEARCH_HIDDEN = 'search-hidden'; const CSS_SEARCH_HIGHLIGHTED = 'search-highlighted'; @@ -103,6 +104,7 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { super.initializeContents(containerElement); this.autocompleteWidget.inputField.placeholder = 'Search for elements'; + containerElement.setAttribute('aria-label', 'Search Field'); } protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { return [new RevealNamedElementAutocompleteSuggestionProvider(), new RevealEdgeElementAutocompleteSuggestionProvider()]; @@ -132,8 +134,8 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { if (labeledAction !== undefined) { const suggestions = this.getSuggestionsFromLabeledActions([labeledAction]); - const actions: CenterAction[] = []; - suggestions.map(currElem => actions.push(CenterAction.create([currElem.element.id], { animate: true, retainZoom: true }))); + const actions: RepositionAction[] = []; + suggestions.map(currElem => actions.push(RepositionAction.create([currElem.element.id]))); this.actionDispatcher.dispatchAll(actions); await this.applyCSS( diff --git a/packages/client/src/features/accessibility/toast/messages.json b/packages/client/src/features/accessibility/toast/messages.json new file mode 100644 index 0000000..27afcfa --- /dev/null +++ b/packages/client/src/features/accessibility/toast/messages.json @@ -0,0 +1,16 @@ +{ + "navigation": { + "default_navigation_mode_activated": "Navigation On: Use arrow keys to select preceding (←) or succeding (→) elements. Use the up (↑) and down (↓) arrows to navigate paths. Press 'N' to exit.", + "default_navigation_mode_deactivated": "Navigation Off: Press 'N' for default navigation, 'ALT+N' for position based navigation.", + "local_navigation_mode_activated": "Position based Nav On: Navigate nearest elements using arrow keys: (↑) for above, (↓) for below, (←) for previous, (→) for next element. Press 'ALT+N' to exit.", + "local_navigation_mode_deactivated": "Position based Nav Off: Press 'N' for default navigation, 'ALT+N' for position based navigation." + }, + "focus": { + "focus_on": "Currently focused: ", + "focus_off": "Currently no element is focused." + }, + "resize": { + "resize_mode_activated": "Resize On: Use plus(+) and minus(-) to resize, 'CTRL'+'0' for default size. Press 'ESC' to exit.", + "resize_mode_deactivated": "Resize Off: Press 'ALT'+'A' for resize mode." + } +} diff --git a/packages/client/src/features/accessibility/toast/toast-handler.ts b/packages/client/src/features/accessibility/toast/toast-handler.ts new file mode 100644 index 0000000..8547529 --- /dev/null +++ b/packages/client/src/features/accessibility/toast/toast-handler.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '~glsp-sprotty'; + +export interface HideToastAction extends Action { + kind: typeof HideToastAction.KIND; + options: HideToastAction.Options; +} + +export namespace HideToastAction { + export const KIND = 'hideToastMessageAction'; + + export type Options = Pick; + + export function is(object: any): object is HideToastAction { + return Action.hasKind(object, KIND); + } + + export function create(options: Options): HideToastAction { + return { kind: KIND, options }; + } +} + +export interface ShowToastMessageAction extends Action { + kind: typeof ShowToastMessageAction.KIND; + options: ToastOptions; +} + +export namespace ShowToastMessageAction { + export const KIND = 'showToastMessageAction'; + export const TIMEOUT = 2000; + + export type CreateOptions = Partial & Required>; + + export function is(object: any): object is ShowToastMessageAction { + return Action.hasKind(object, KIND); + } + + export function create(options: CreateOptions): ShowToastMessageAction { + return { kind: KIND, options: { ...options, position: options.position ?? 'center', id: options.id ?? Symbol('toast id') } }; + } + export function createWithTimeout(options: CreateOptions): ShowToastMessageAction { + return { + kind: KIND, + options: { timeout: TIMEOUT, ...options, position: options.position ?? 'center', id: options.id ?? Symbol('toast id') } + }; + } +} + +export interface ToastOptions { + id: symbol; + timeout?: number; + position: 'left' | 'center' | 'right'; + message: string; +} diff --git a/packages/client/src/features/accessibility/toast/toast-module.ts b/packages/client/src/features/accessibility/toast/toast-module.ts new file mode 100644 index 0000000..932a333 --- /dev/null +++ b/packages/client/src/features/accessibility/toast/toast-module.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ContainerModule } from 'inversify'; +import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; +import '../../../../css/toast.css'; +import { HideToastAction, ShowToastMessageAction } from './toast-handler'; +import { Toast } from './toast-tool'; + +/** + * Handles toast/user notification actions. + */ + +export const glspToastModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureToastTool(context); +}); + +export function configureToastTool(context: BindingContext): void { + bindAsService(context, TYPES.IUIExtension, Toast); + context.bind(TYPES.IDiagramStartup).toService(Toast); + configureActionHandler(context, ShowToastMessageAction.KIND, Toast); + configureActionHandler(context, HideToastAction.KIND, Toast); +} diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts new file mode 100644 index 0000000..1c85d73 --- /dev/null +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject } from 'inversify'; +import { Action, AbstractUIExtension, IActionHandler, ICommand, TYPES } from '~glsp-sprotty'; +import { IDiagramStartup } from '../../../base/model/diagram-loader'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { HideToastAction, ShowToastMessageAction, ToastOptions } from './toast-handler'; + +/** + * This extension is used to create customized user notifications as toast messages. + */ +@injectable() +export class Toast extends AbstractUIExtension implements IActionHandler, IDiagramStartup { + static readonly ID = 'toast'; + protected messages: { [key: symbol]: ToastOptions } = {}; + + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + @inject(EditorContextService) + protected editorContext: EditorContextService; + + id(): string { + return Toast.ID; + } + containerClass(): string { + return Toast.ID; + } + + protected initializeContents(_containerElement: HTMLElement): void { + this.render(); + } + + handle(action: Action): ICommand | Action | void { + if (ShowToastMessageAction.is(action)) { + this.messages[action.options.id] = action.options; + this.render(); + + if (action.options.timeout) { + setTimeout(() => { + this.delete(action.options.id); + }, action.options.timeout); + } + } else if (HideToastAction.is(action)) { + if (action.options.timeout) { + setTimeout(() => { + this.delete(action.options.id); + }, action.options.timeout); + } else { + this.delete(action.options.id); + } + } + } + + protected render(): void { + if (this.containerElement === undefined) { + return; + } + + this.containerElement.innerHTML = ''; + + this.values(this.messages).forEach(message => { + this.containerElement.appendChild(this.createToastMessage(message)); + }); + } + + protected delete(id: symbol): void { + delete this.messages[id]; + this.render(); + } + + protected createToastMessage(option: ToastOptions): HTMLDivElement { + const cell = document.createElement('div'); + cell.classList.add('toast-cell', `toast-column-${option.position}`); + + const container = document.createElement('div'); + container.classList.add('toast-container'); + + const text = document.createElement('span'); + text.textContent = option.message; + + container.appendChild(text); + cell.appendChild(container); + + return cell; + } + + preInitialize(): void { + this.show(this.editorContext.modelRoot); + } + + values(obj: { [key: symbol]: ToastOptions }): ToastOptions[] { + return Object.getOwnPropertySymbols(obj).map(s => obj[s]); + } +} diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 7b0da20..2e2dc8b 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -97,10 +97,11 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, return super.initialize(); } - protected initializeContents(_containerElement: HTMLElement): void { + protected initializeContents(containerElement: HTMLElement): void { this.createHeader(); this.createBody(); this.lastActiveButton = this.defaultToolsButton; + containerElement.setAttribute('aria-label', 'Tool-Palette'); } protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { @@ -207,7 +208,9 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const button = createIcon('inspect'); button.id = 'btn_default_tools'; button.title = 'Enable selection tool'; - button.onclick = this.onClickStaticToolButton(this.defaultToolsButton); + button.onclick = this.onClickStaticToolButton(button); + button.ariaLabel = button.title; + button.tabIndex = 1; return button; } @@ -215,6 +218,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const deleteToolButton = createIcon('chrome-close'); deleteToolButton.title = 'Enable deletion tool'; deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); + deleteToolButton.ariaLabel = deleteToolButton.title; + deleteToolButton.tabIndex = 1; return deleteToolButton; } @@ -222,6 +227,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const marqueeToolButton = createIcon('screen-full'); marqueeToolButton.title = 'Enable marquee tool'; marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID); + marqueeToolButton.ariaLabel = marqueeToolButton.title; + marqueeToolButton.tabIndex = 1; return marqueeToolButton; } @@ -231,7 +238,10 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds, { reason: MarkersReason.BATCH })); + validateActionButton.focus(); }; + validateActionButton.ariaLabel = validateActionButton.title; + validateActionButton.tabIndex = 1; return validateActionButton; } @@ -250,6 +260,8 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, }; searchIcon.classList.add('search-icon'); searchIcon.title = 'Filter palette entries'; + searchIcon.ariaLabel = searchIcon.title; + searchIcon.tabIndex = 1; return searchIcon; } diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts new file mode 100644 index 0000000..97e40b1 --- /dev/null +++ b/packages/client/src/features/viewport/reposition.ts @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + Bounds, + Dimension, + hasArrayProp, + Viewport, + BoundsAwareViewportCommand, + getRouteBounds, + isViewport, + SChildElement, + SEdge, + SModelElement, + SModelRoot, + TYPES +} from '~glsp-sprotty'; +import { inject, injectable } from 'inversify'; +import { calcElementAndRoute } from '../../utils/smodel-util'; + +export interface RepositionAction extends Action { + kind: typeof RepositionAction.KIND; + elementIDs: string[]; +} + +export namespace RepositionAction { + export const KIND = 'repositionAction'; + + export function is(object: any): object is RepositionAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIDs'); + } + + export function create(elementIDs: string[]): RepositionAction { + return { + kind: KIND, + elementIDs + }; + } +} + +/** + * Moves the viewport to an unvisible element, while maintaining the current zoom level. + */ +@injectable() +export class RepositionCommand extends BoundsAwareViewportCommand { + static readonly KIND = RepositionAction.KIND; + + constructor(@inject(TYPES.Action) protected action: RepositionAction) { + super(true); + } + + protected override boundsInViewport(element: SModelElement, bounds: Bounds, viewport: SModelRoot & Viewport): Bounds { + if (element instanceof SChildElement && element.parent !== viewport) { + return this.boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds, viewport); + } else if (element instanceof SEdge) { + const edgeBounds = getRouteBounds(calcElementAndRoute(element).newRoutingPoints ?? []); + + if (element instanceof SChildElement && element.parent !== viewport) { + return this.boundsInViewport(element.parent, element.parent.localToParent(edgeBounds), viewport); + } + + return edgeBounds; + } + + return bounds; + } + + getElementIds(): string[] { + return this.action.elementIDs; + } + + getNewViewport(bounds: Bounds, model: SModelRoot): Viewport | undefined { + if (!Dimension.isValid(model.canvasBounds)) { + return undefined; + } + + if (isViewport(model)) { + const zoom = model.zoom; + const c = Bounds.center(bounds); + + if (this.isFullyVisible(bounds, model)) { + return undefined; + } else { + return { + scroll: { + x: c.x - (0.5 * model.canvasBounds.width) / zoom, + y: c.y - (0.5 * model.canvasBounds.height) / zoom + }, + zoom: zoom + }; + } + } + + return undefined; + } + + protected isFullyVisible(bounds: Bounds, viewport: SModelRoot & Viewport): boolean { + return ( + bounds.x >= viewport.scroll.x && + bounds.x + bounds.width <= viewport.scroll.x + viewport.canvasBounds.width / viewport.zoom && + bounds.y >= viewport.scroll.y && + bounds.y + bounds.height <= viewport.scroll.y + viewport.canvasBounds.height / viewport.zoom + ); + } +} diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 18398a0..fd62a34 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -29,6 +29,7 @@ import { ZoomMouseListener } from '~glsp-sprotty'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; +import { RepositionCommand } from './reposition'; export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; @@ -36,6 +37,7 @@ export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { configureCommand(context, FitToScreenCommand); configureCommand(context, GetViewportCommand); configureCommand(context, SetViewportCommand); + configureCommand(context, RepositionCommand); bindAsService(context, TYPES.MouseListener, ZoomMouseListener); bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index fd2ce13..5412758 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -35,6 +35,8 @@ export const TYPES = { IDefaultTool: Symbol('IDefaultTool'), IEditModeListener: Symbol('IEditModeListener'), IMarqueeBehavior: Symbol('IMarqueeBehavior'), + IElementNavigator: Symbol('IElementNavigator'), + ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup') }; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index d70a4af..3400cc6 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -48,6 +48,9 @@ export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; +export * from './features/accessibility/element-navigation/diagram-navigation-tool'; +export * from './features/accessibility/focus-tracker/focus-tracker-tool'; +export * from './features/accessibility/toast/toast-tool'; export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; export * from './features/bounds/hbox-layout'; @@ -125,6 +128,9 @@ export * from './features/accessibility/move-zoom/move-zoom-module'; export * from './features/accessibility/resize-key-tool/resize-key-module'; export * from './features/accessibility/search/search-palette-module'; export * from './features/accessibility/view-key-tools/view-key-tools-module'; +export * from './features/accessibility/element-navigation/element-navigation-module'; +export * from './features/accessibility/focus-tracker/focus-tracker-module'; +export * from './features/accessibility/toast/toast-module'; export * from './features/command-palette/command-palette-module'; export * from './features/context-menu/context-menu-module'; export * from './features/copy-paste/copy-paste-modules'; diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index a39015f..d432885 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -51,7 +51,9 @@ export class GLSPProjectionView extends ProjectedViewportView { {this.renderProjections(model, context, args)}
); - setAttr(rootNode, 'tabindex', 0); + setAttr(rootNode, 'tabindex', 1); + setAttr(rootNode, 'aria-label', 'Diagram'); + return rootNode; } From d2b9adac33266c7e72f63848654646cc9c28d37d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 25 Aug 2023 23:48:28 +0200 Subject: [PATCH 245/566] GLSP-1096: Update to sprotty 0.14.0 (#277) Update to sprotty 0.14.0 and adapt code to conform to latest changes/API breaks. With 0.14.0 the `ToolManager API` was deprecated and it will be removed in future versions. The intention is that this API moves to GLSP as it is only used there => refactor `ToolManager API` GLSP-960: Make glspClient retrieval async Fixes https://github.com/eclipse-glsp/glsp/issues/1096 Fixes https://github.com/eclipse-glsp/glsp/issues/1094 --- examples/workflow-standalone/src/app.ts | 2 +- packages/client/package.json | 2 +- packages/client/src/base/default.module.ts | 13 +- .../client/src/base/editor-context-service.ts | 5 - .../client/src/base/model/diagram-loader.ts | 10 +- .../src/base/model/glsp-model-source.ts | 55 ++++-- .../base/tool-manager/glsp-tool-manager.ts | 96 --------- .../src/base/tool-manager/tool-manager.ts | 183 ++++++++++++++++++ packages/client/src/base/tool-manager/tool.ts | 85 ++++++++ .../diagram-navigation-tool.ts | 34 ++-- .../focus-tracker/focus-tracker-tool.ts | 12 +- .../accessible-key-shortcut-tool.ts | 6 +- .../resize-key-tool/resize-key-tool.ts | 6 +- .../accessibility/search/search-tool.ts | 3 +- .../view-key-tools/deselect-key-tool.ts | 4 +- .../view-key-tools/movement-key-tool.ts | 4 +- .../view-key-tools/zoom-key-tool.ts | 4 +- .../command-palette/command-palette-tool.ts | 3 +- .../client/src/features/hover/hover-module.ts | 3 +- packages/client/src/features/hover/hover.ts | 3 +- .../features/label-edit/edit-label-tool.ts | 4 +- .../tool-palette/tool-palette-module.ts | 3 +- .../src/features/tool-palette/tool-palette.ts | 17 +- .../features/tools/base-glsp-creation-tool.ts | 40 ---- .../{base-glsp-tool.ts => base-tools.ts} | 31 ++- .../tools/change-bounds/change-bounds-tool.ts | 4 +- .../features/tools/deletion/delete-tool.ts | 9 +- .../tools/edge-creation/edge-creation-tool.ts | 6 +- .../tools/edge-edit/edge-edit-tool.ts | 4 +- .../marquee-selection/marquee-mouse-tool.ts | 6 +- .../tools/marquee-selection/marquee-tool.ts | 7 +- .../tools/node-creation/node-creation-tool.ts | 6 +- .../features/tools/tool-focus-loss-module.ts | 3 +- .../viewport/glsp-scroll-mouse-listener.ts | 11 +- .../src/features/viewport/viewport-modules.ts | 3 +- .../src/glsp-sprotty/augmented-actions.ts | 17 +- packages/client/src/glsp-sprotty/index.ts | 2 - packages/client/src/glsp-sprotty/types.ts | 3 +- packages/client/src/index.ts | 5 +- 39 files changed, 432 insertions(+), 282 deletions(-) delete mode 100644 packages/client/src/base/tool-manager/glsp-tool-manager.ts create mode 100644 packages/client/src/base/tool-manager/tool-manager.ts create mode 100644 packages/client/src/base/tool-manager/tool.ts delete mode 100644 packages/client/src/features/tools/base-glsp-creation-tool.ts rename packages/client/src/features/tools/{base-glsp-tool.ts => base-tools.ts} (78%) diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index d6c5792..e79fbf7 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -46,7 +46,7 @@ wsProvider.listen({ onConnection: initialize, onReconnect: reconnect, logger: co async function initialize(connectionProvider: MessageConnection, isReconnecting = false): Promise { glspClient = new BaseJsonrpcGLSPClient({ id, connectionProvider }); - container = createContainer({ clientId, diagramType, glspClient, sourceUri: examplePath }); + container = createContainer({ clientId, diagramType, glspClientProvider: async () => glspClient, sourceUri: examplePath }); const actionDispatcher = container.get(GLSPActionDispatcher); const diagramLoader = container.get(DiagramLoader); await diagramLoader.load({ isReconnecting }); diff --git a/packages/client/package.json b/packages/client/package.json index 43cf281..c8666bd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -50,7 +50,7 @@ "@eclipse-glsp/protocol": "1.1.0-next", "autocompleter": "5.1.0", "lodash": "4.17.21", - "sprotty": "0.14.0-next.02bbac0.26" + "sprotty": "~0.14.0" }, "devDependencies": { "@types/file-saver": "^2.0.3", diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index e9bf7a2..6f20ea0 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -44,7 +44,8 @@ import { GLSPModelSource } from './model/glsp-model-source'; import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; -import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; +import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; +import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; import { GLSPKeyTool } from './view/key-tool'; import { GLSPMouseTool } from './view/mouse-tool'; import { GLSPViewRegistry } from './view/view-registry'; @@ -88,8 +89,6 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); - bind(GLSPToolManager).toSelf().inSingletonScope(); - bindOrRebind(context, TYPES.IToolManager).toService(GLSPToolManager); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); @@ -112,4 +111,12 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); + + // Tool manager initialization ------------------------------------ + bind(TYPES.IToolManager).to(ToolManager).inSingletonScope(); + bind(DefaultToolsEnablingKeyListener).toSelf().inSingletonScope(); + bind(TYPES.KeyListener).toService(DefaultToolsEnablingKeyListener); + bind(ToolManagerActionHandler).toSelf().inSingletonScope(); + configureActionHandler(context, EnableDefaultToolsAction.KIND, ToolManagerActionHandler); + configureActionHandler(context, EnableToolsAction.KIND, ToolManagerActionHandler); }); diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index b195746..629bc2c 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -23,7 +23,6 @@ import { EditorContext, Emitter, Event, - GLSPClient, IActionHandler, MaybePromise, MousePositionTracker, @@ -154,10 +153,6 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this.diagramOptions.clientId; } - get glspClient(): GLSPClient { - return this.diagramOptions.glspClient; - } - get modelRoot(): Readonly { return this.selectionService.getModelRoot(); } diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index b160245..ec2e893 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -20,13 +20,11 @@ import { ApplicationIdProvider, Args, EMPTY_ROOT, - EndProgressAction, GLSPClient, MaybePromise, RequestModelAction, ServerStatusAction, SetModelAction, - StartProgressAction, TYPES, hasNumberProp } from '~glsp-sprotty'; @@ -47,9 +45,9 @@ export interface IDiagramOptions { */ diagramType: string; /** - * The GLSP client used by this diagram to communicate with the server. + * The provider function to retrieve the GLSP client used by this diagram to communicate with the server. */ - glspClient: GLSPClient; + glspClientProvider: () => Promise; /** * The file source URI associated with this diagram. */ @@ -140,17 +138,15 @@ export class DiagramLoader { const result = this.actionDispatcher.dispatch(RequestModelAction.create({ options })); if (this.enableLoadingNotifications) { this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); - this.actionDispatcher.dispatch(EndProgressAction.create('initializeClient')); } return result; } protected async configureGLSPClient(): Promise { - const glspClient = this.options.glspClient; + const glspClient = await this.options.glspClientProvider(); if (this.enableLoadingNotifications) { this.actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); - this.actionDispatcher.dispatch(StartProgressAction.create({ progressId: 'initializeClient', title: 'Initializing' })); } await glspClient.start(); diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index 449c60c..307bae1 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; +import { inject, injectable, postConstruct } from 'inversify'; import { Action, ActionHandlerRegistry, @@ -26,7 +26,8 @@ import { InitializeResult, ModelSource, SModelRootSchema, - TYPES + TYPES, + Writable } from '~glsp-sprotty'; import { IDiagramOptions } from './diagram-loader'; /** @@ -65,18 +66,25 @@ export class GLSPModelSource extends ModelSource implements Disposable { @inject(TYPES.ILogger) protected logger: ILogger; + @inject(TYPES.IDiagramOptions) + protected options: IDiagramOptions; + protected toDispose = new DisposableCollection(); clientId: string; - readonly glspClient: GLSPClient; - readonly sourceUri?: string; - readonly diagramType: string; - - constructor(@inject(TYPES.IDiagramOptions) options: IDiagramOptions) { - super(); - this.glspClient = options.glspClient; - this.clientId = options.clientId ?? this.viewerOptions.baseDiv; - this.diagramType = options.diagramType; - this.sourceUri = options.sourceUri; + readonly glspClient: GLSPClient | undefined; + protected _currentRoot: SModelRootSchema; + + get diagramType(): string { + return this.options.diagramType; + } + + get sourceUri(): string | undefined { + return this.options.sourceUri; + } + + @postConstruct() + protected postConstruct(): void { + this.clientId = this.options.clientId ?? this.viewerOptions.baseDiv; } configure(registry: ActionHandlerRegistry, initializeResult: InitializeResult): Promise { @@ -85,8 +93,8 @@ export class GLSPModelSource extends ModelSource implements Disposable { throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${this.diagramType}!`); } serverActions.forEach(action => registry.register(action, this)); - this.toDispose.push(this.glspClient.onActionMessage(message => this.messageReceived(message), this.clientId)); - return this.glspClient.initializeClientSession({ clientSessionId: this.clientId, diagramType: this.diagramType }); + this.toDispose.push(this.glspClient!.onActionMessage(message => this.messageReceived(message), this.clientId)); + return this.glspClient!.initializeClientSession({ clientSessionId: this.clientId, diagramType: this.diagramType }); } protected messageReceived(message: ActionMessage): void { @@ -105,11 +113,15 @@ export class GLSPModelSource extends ModelSource implements Disposable { if (!this.clientId) { this.clientId = this.viewerOptions.baseDiv; } - if (this.glspClient.initializeResult) { - this.configure(registry, this.glspClient.initializeResult); - } else { - this.glspClient.onServerInitialized(result => this.configure(registry, result)); - } + + this.options.glspClientProvider().then(glspClient => { + (this as Writable).glspClient = glspClient; + if (glspClient.initializeResult) { + this.configure(registry, glspClient.initializeResult); + } else { + glspClient.onServerInitialized(result => this.configure(registry, result)); + } + }); } handle(action: Action): void { @@ -143,9 +155,14 @@ export class GLSPModelSource extends ModelSource implements Disposable { * The internal/local model should never be committed back to the model source i.e. GLSP server. * => no-op implementation that simply returns the `newRoot` */ + this._currentRoot = newRoot; return newRoot; } + override get model(): SModelRootSchema { + return this._currentRoot; + } + dispose(): void { this.toDispose.dispose(); } diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts deleted file mode 100644 index a44736b..0000000 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; -import { AnyObject, EditMode, TYPES, Tool, ToolManager, distinctAdd, hasBooleanProp, hasFunctionProp, hasStringProp } from '~glsp-sprotty'; -import { EditorContextService, EditorContextServiceProvider, IEditModeListener } from '../editor-context-service'; - -@injectable() -export class GLSPToolManager extends ToolManager implements IEditModeListener { - protected editorContext?: EditorContextService; - - @multiInject(TYPES.ITool) @optional() override tools: Tool[]; - @multiInject(TYPES.IDefaultTool) @optional() override defaultTools: Tool[]; - @inject(TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider; - - @postConstruct() - protected initialize(): void { - this.registerTools(...this.tools); - this.registerDefaultTools(...this.defaultTools); - this.enableDefaultTools(); - this.contextServiceProvider().then(editorContext => { - editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); - this.editorContext = editorContext; - }); - } - - override registerDefaultTools(...tools: Tool[]): void { - for (const tool of tools) { - distinctAdd(this.defaultTools, tool); - } - } - - override registerTools(...tools: Tool[]): void { - for (const tool of tools) { - distinctAdd(this.tools, tool); - } - } - - override enable(toolIds: string[]): void { - this.disableActiveTools(); - let tools = toolIds.map(id => this.tool(id)); - if (this.editorContext && this.editorContext.isReadonly) { - tools = tools.filter(tool => tool && (!GLSPTool.is(tool) || tool.isEditTool === false)); - } - tools.forEach(tool => { - if (tool !== undefined) { - tool.enable(); - this.actives.push(tool); - } - }); - } - - disableEditTools(): void { - this.disableActiveTools(); - this.enable(this.defaultTools.filter(tool => !GLSPTool.is(tool) || tool.isEditTool === false).map(tool => tool.id)); - } - - editModeChanged(newValue: string, oldValue: string): void { - if (oldValue === newValue) { - return; - } - if (newValue === EditMode.READONLY) { - this.disableEditTools(); - } else if (newValue === EditMode.EDITABLE) { - this.enableDefaultTools(); - } - } -} - -export interface GLSPTool extends Tool { - isEditTool: boolean; -} - -export namespace GLSPTool { - export function is(object: unknown): object is GLSPTool { - return ( - AnyObject.is(object) && - hasStringProp(object, 'id') && - hasFunctionProp(object, 'enable') && - hasFunctionProp(object, 'disable') && - hasBooleanProp(object, 'isEditTool') - ); - } -} diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts new file mode 100644 index 0000000..7de653d --- /dev/null +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -0,0 +1,183 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable, multiInject, optional } from 'inversify'; +import { + Action, + EditMode, + IActionHandler, + ICommand, + KeyListener, + SModelElement, + TYPES, + distinctAdd, + matchesKeystroke +} from '~glsp-sprotty'; +import { EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; +import { EnableDefaultToolsAction, EnableToolsAction, Tool } from './tool'; +/** + * A tool manager coordinates the state of tools in the context of an editor. + * + * One instance of a tool manager is intended per editor, coordinating the state of all tools within + * this editor. A tool can be active or not. A tool manager ensures that activating a set of tools + * will disable all other tools, allowing them to invoke behavior when they become enabled or disabled. + */ +export interface IToolManager { + /** All tools managed by this tool manager. */ + readonly managedTools: Tool[]; + + /** The tools that are enabled by default, whenever no other tool is enabled. */ + readonly defaultTools: Tool[]; + + /** The currently active tools, which are either specifically enabled tools, or the default tools. */ + readonly activeTools: Tool[]; + + /** + * Enables the tools with the specified `toolIds`. + * Therefore, this manager first disables currently active tools and then enable the + * tools indicated in `toolIds`, making them the currently active tools. If this manager + * doesn't manage one or more tools specified in `toolIds`, it'll do nothing. If not a + * single tool that shall be enabled was found in the managed tools, it'll fall back to + * the default tools. + * + * @param tools The tools to be enabled. + */ + enable(toolIds: string[]): void; + + /** + * Enables all default tools. + */ + enableDefaultTools(): void; + + /** Disables all currently active tools. After this call, no tool will be active anymore. */ + disableActiveTools(): void; + + registerDefaultTools(...tools: Tool[]): void; + + registerTools(...tools: Tool[]): void; +} + +/** + * The default {@link IToolManager} implementation. Allows + * registration of tools via Dependency Injection. + */ +@injectable() +export class ToolManager implements IToolManager { + readonly actives: Tool[] = []; + + protected editorContext?: EditorContextService; + + constructor( + @multiInject(TYPES.ITool) + @optional() + readonly tools: Tool[] = [], + @multiInject(TYPES.IDefaultTool) + @optional() + readonly defaultTools: Tool[], + @inject(TYPES.IEditorContextServiceProvider) protected contextServiceProvider: EditorContextServiceProvider + ) { + this.registerTools(...this.tools); + this.registerDefaultTools(...this.defaultTools); + this.enableDefaultTools(); + this.contextServiceProvider().then(editorContext => { + editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); + this.editorContext = editorContext; + }); + } + + get managedTools(): Tool[] { + return this.defaultTools.concat(this.tools); + } + + get activeTools(): Tool[] { + return this.actives; + } + + registerDefaultTools(...tools: Tool[]): void { + distinctAdd(this.defaultTools, ...tools); + } + + registerTools(...tools: Tool[]): void { + distinctAdd(this.tools, ...tools); + } + + disableActiveTools(): void { + this.actives.forEach(tool => tool.disable()); + this.actives.splice(0, this.actives.length); + } + + enableDefaultTools(): void { + this.enable(this.defaultTools.map(tool => tool.id)); + } + + enable(toolIds: string[]): void { + this.disableActiveTools(); + let tools = toolIds.map(id => this.tool(id)); + if (this.editorContext && this.editorContext.isReadonly) { + tools = tools.filter(tool => !tool?.isEditTool); + } + tools.forEach(tool => { + if (tool !== undefined) { + tool.enable(); + this.actives.push(tool); + } + }); + } + + tool(toolId: string): Tool | undefined { + return this.managedTools.find(tool => tool.id === toolId); + } + + disableEditTools(): void { + this.disableActiveTools(); + this.enable(this.defaultTools.filter(tool => !tool.isEditTool).map(tool => tool.id)); + } + + editModeChanged(newValue: string, oldValue: string): void { + if (oldValue === newValue) { + return; + } + if (newValue === EditMode.READONLY) { + this.disableEditTools(); + } else if (newValue === EditMode.EDITABLE) { + this.enableDefaultTools(); + } + } +} + +@injectable() +export class ToolManagerActionHandler implements IActionHandler { + @inject(TYPES.IToolManager) + readonly toolManager: IToolManager; + + handle(action: Action): void | ICommand | Action { + if (EnableDefaultToolsAction.is(action)) { + this.toolManager.enableDefaultTools(); + } else if (EnableToolsAction.is(action)) { + this.toolManager.enable((action as EnableToolsAction).toolIds); + } + } +} + +@injectable() +export class DefaultToolsEnablingKeyListener extends KeyListener { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'Escape')) { + return [EnableDefaultToolsAction.create()]; + } + return []; + } +} diff --git a/packages/client/src/base/tool-manager/tool.ts b/packages/client/src/base/tool-manager/tool.ts new file mode 100644 index 0000000..0331bdf --- /dev/null +++ b/packages/client/src/base/tool-manager/tool.ts @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, AnyObject, hasArrayProp, hasBooleanProp, hasFunctionProp, hasStringProp } from '~glsp-sprotty'; + +/** + * Action to enable the tools of the specified `toolIds`. + */ +export interface EnableToolsAction extends Action { + kind: typeof EnableToolsAction.KIND; + toolIds: string[]; +} +export namespace EnableToolsAction { + export const KIND = 'enable-tools'; + + export function is(object: unknown): object is EnableToolsAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'toolIds'); + } + + export function create(toolIds: string[]): EnableToolsAction { + return { + kind: KIND, + toolIds + }; + } +} + +/** + * Action to disable the currently active tools and enable the default tools instead. + */ +export interface EnableDefaultToolsAction extends Action { + kind: typeof EnableDefaultToolsAction.KIND; +} +export namespace EnableDefaultToolsAction { + export const KIND = 'enable-default-tools'; + + export function is(object: unknown): object is EnableToolsAction { + return Action.hasKind(object, KIND); + } + + export function create(): EnableDefaultToolsAction { + return { + kind: KIND + }; + } +} + +/** A tool that can be managed by a `ToolManager`. + */ +export interface Tool { + readonly id: string; + /* Notifies the tool to become active. */ + enable(): void; + /* Notifies the tool to become inactive. */ + disable(): void; + /** Optional flag indicating wether this tool is an edit tool. + * Edit tools are inactive when a diagram is in `readonly` edit mode. + */ + isEditTool?: boolean; +} + +export namespace Tool { + export function is(object: unknown): object is Tool { + return ( + AnyObject.is(object) && + hasStringProp(object, 'id') && + hasFunctionProp(object, 'enable') && + hasFunctionProp(object, 'disable') && + hasBooleanProp(object, 'isEditTool', true) + ); + } +} diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index a06b093..4a49833 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -14,39 +14,37 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, - SelectAction, - findParentByFeature, - isBoundsAware, - isSelectable, - isSelected, KeyListener, KeyTool, SEdge, - Selectable, SModelElement, SModelRoot, + SelectAction, + Selectable, TYPES, - EnableToolsAction, - EnableDefaultToolsAction + findParentByFeature, + isBoundsAware, + isSelectable, + isSelected } from '~glsp-sprotty'; -import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { SelectableBoundsAware } from '../../../utils/smodel-util'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import * as messages from '../toast/messages.json'; -import { ShowToastMessageAction } from '../toast/toast-handler'; +import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; +import { SelectableBoundsAware } from '../../../utils/smodel-util'; import { RepositionAction } from '../../viewport/reposition'; -import { ElementNavigator } from './element-navigator'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import { SearchAutocompletePaletteTool } from '../search/search-tool'; import { AccessibleKeyShortcutTool } from '../key-shortcut/accessible-key-shortcut-tool'; +import { SearchAutocompletePaletteTool } from '../search/search-tool'; +import * as messages from '../toast/messages.json'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import { ElementNavigator } from './element-navigator'; @injectable() -export class ElementNavigatorTool implements GLSPTool { +export class ElementNavigatorTool implements Tool { static ID = 'glsp.diagram-navigation'; isEditTool = false; diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts index db2575e..ed82cd4 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -14,15 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { ShowToastMessageAction } from '../toast/toast-handler'; -import * as messages from '../toast/messages.json'; +import { inject, injectable } from 'inversify'; +import { TYPES, ViewerOptions } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; -import { ViewerOptions, TYPES } from '~glsp-sprotty'; +import { Tool } from '../../../base/tool-manager/tool'; +import * as messages from '../toast/messages.json'; +import { ShowToastMessageAction } from '../toast/toast-handler'; @injectable() -export class FocusTrackerTool implements GLSPTool { +export class FocusTrackerTool implements Tool { static ID = 'glsp.focus-tracker'; isEditTool = false; diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts index be7d04e..acf1e73 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts @@ -15,12 +15,12 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, KeyListener, KeyTool, matchesKeystroke, SetUIExtensionVisibilityAction, SModelElement } from '~glsp-sprotty'; -import { BaseGLSPTool } from '../../tools/base-glsp-tool'; +import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, matchesKeystroke } from '~glsp-sprotty'; +import { BaseEditTool } from '../../tools/base-tools'; import { KeyShortcutUIExtension } from './accessible-key-shortcut'; @injectable() -export class AccessibleKeyShortcutTool extends BaseGLSPTool { +export class AccessibleKeyShortcutTool extends BaseEditTool { static ID = 'accessible-key-shortcut-tool'; @inject(KeyTool) protected readonly keytool: KeyTool; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index fbad4c9..5d99265 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -16,10 +16,10 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, EnableDefaultToolsAction, EnableToolsAction, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ResizeElementAction, ResizeType } from './resize-key-handler'; @@ -27,7 +27,7 @@ import { ShowToastMessageAction } from '../toast/toast-handler'; import * as messages from '../toast/messages.json'; @injectable() -export class ResizeKeyTool implements GLSPTool { +export class ResizeKeyTool implements Tool { static ID = 'glsp.resize-key-tool'; isEditTool = true; diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index 612711b..e23a9f3 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -16,8 +16,9 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, KeyListener, KeyTool, SetUIExtensionVisibilityAction, SModelElement, Tool, TYPES } from '~glsp-sprotty'; +import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, TYPES } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { Tool } from '../../../base/tool-manager/tool'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { SearchAutocompletePalette } from './search-palette'; @injectable() diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index af01e61..2bf9424 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -27,14 +27,14 @@ import { SwitchEditModeAction, isSelectable } from '~glsp-sprotty'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { Tool } from '../../../base/tool-manager/tool'; import { SResizeHandle } from '../../change-bounds/model'; /** * Deselects the element if there is no interaction possible with element. */ @injectable() -export class DeselectKeyTool implements GLSPTool { +export class DeselectKeyTool implements Tool { static ID = 'glsp.deselect-key-tool'; isEditTool = true; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index e33bf18..12d3b5b 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -19,7 +19,7 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { Tool } from '../../../base/tool-manager/tool'; import { GridSnapper } from '../../change-bounds/snap'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; @@ -28,7 +28,7 @@ import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler * Moves viewport and elements when its focused and arrow keys are hit. */ @injectable() -export class MovementKeyTool implements GLSPTool { +export class MovementKeyTool implements Tool { static ID = 'glsp.movement-key-tool'; isEditTool = true; diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index 19dce18..7018c4a 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -19,7 +19,7 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, CenterAction, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; +import { Tool } from '../../../base/tool-manager/tool'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; @@ -27,7 +27,7 @@ import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler * Zoom viewport and elements when its focused and arrow keys are hit. */ @injectable() -export class ZoomKeyTool implements GLSPTool { +export class ZoomKeyTool implements Tool { static ID = 'glsp.zoom-key-tool'; isEditTool = false; diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index c323d29..f377c92 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -14,7 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, postConstruct } from 'inversify'; -import { CommandPaletteKeyListener, KeyListener, KeyTool, Tool } from '~glsp-sprotty'; +import { CommandPaletteKeyListener, KeyListener, KeyTool } from '~glsp-sprotty'; +import { Tool } from '../../base/tool-manager/tool'; @injectable() export class CommandPaletteTool implements Tool { diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index ab12fd6..2f5389e 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -16,8 +16,6 @@ import { CenterCommand, ClosePopupActionHandler, - EnableDefaultToolsAction, - EnableToolsAction, FeatureModule, FitToScreenCommand, HoverFeedbackCommand, @@ -34,6 +32,7 @@ import { configureCommand } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GlspHoverMouseListener } from './hover'; export const hoverModule = new FeatureModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index 56826b4..a777892 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -18,8 +18,6 @@ import { Action, Bounds, EMPTY_ROOT, - EnableDefaultToolsAction, - EnableToolsAction, HoverFeedbackAction, HoverMouseListener, IActionHandler, @@ -32,6 +30,7 @@ import { SetPopupModelAction } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { EdgeCreationTool } from '../tools/edge-creation/edge-creation-tool'; import { GIssueMarker, getSeverity } from '../validation/issue-marker'; diff --git a/packages/client/src/features/label-edit/edit-label-tool.ts b/packages/client/src/features/label-edit/edit-label-tool.ts index 2fb66ff..93a456e 100644 --- a/packages/client/src/features/label-edit/edit-label-tool.ts +++ b/packages/client/src/features/label-edit/edit-label-tool.ts @@ -15,10 +15,10 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from '~glsp-sprotty'; -import { BaseGLSPTool } from '../tools/base-glsp-tool'; +import { BaseEditTool } from '../tools/base-tools'; @injectable() -export class DirectLabelEditTool extends BaseGLSPTool { +export class DirectLabelEditTool extends BaseEditTool { static readonly ID = 'glsp.direct-label-edit-tool'; get id(): string { diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index dd58131..70c1785 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, configureActionHandler, EnableDefaultToolsAction, FeatureModule, TYPES } from '~glsp-sprotty'; +import { bindAsService, configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; import '../../../css/tool-palette.css'; +import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { ToolPalette } from './tool-palette'; export const toolPaletteModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 2e2dc8b..45cf9d5 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -17,11 +17,8 @@ import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, Action, - EnableDefaultToolsAction, - EnableToolsAction, IActionHandler, ICommand, - IToolManager, MarkersReason, PaletteItem, RequestContextActions, @@ -29,7 +26,6 @@ import { SModelRoot, SetContextActions, SetUIExtensionVisibilityAction, - TYPES, codiconCSSClasses, matchesKeystroke } from '~glsp-sprotty'; @@ -37,6 +33,7 @@ import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; @@ -65,10 +62,14 @@ export namespace EnableToolPaletteAction { export class ToolPalette extends AbstractUIExtension implements IActionHandler, IEditModeListener, IDiagramStartup { static readonly ID = 'tool-palette'; - @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; - @inject(TYPES.IToolManager) protected readonly toolManager: IToolManager; - @inject(EditorContextService) protected readonly editorContext: EditorContextService; - @inject(FocusTracker) protected readonly focusTracker: FocusTracker; + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + @inject(EditorContextService) + protected editorContext: EditorContextService; + + @inject(FocusTracker) + protected focusTracker: FocusTracker; protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; diff --git a/packages/client/src/features/tools/base-glsp-creation-tool.ts b/packages/client/src/features/tools/base-glsp-creation-tool.ts deleted file mode 100644 index 52f9580..0000000 --- a/packages/client/src/features/tools/base-glsp-creation-tool.ts +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { injectable } from 'inversify'; -import { Action, EnableToolsAction, IActionHandler } from '~glsp-sprotty'; -import { BaseGLSPTool } from './base-glsp-tool'; - -@injectable() -export abstract class BaseGLSPCreationTool extends BaseGLSPTool implements IActionHandler { - protected abstract isTriggerAction: (obj: any) => obj is T; - protected triggerAction: T; - - handle(action: Action): Action | void { - if (this.isTriggerAction(action)) { - this.triggerAction = action; - return EnableToolsAction.create([this.id]); - } - } - - override enable(): void { - if (this.triggerAction === undefined) { - throw new TypeError(`Could not enable tool ${this.id}. The triggerAction cannot be undefined.`); - } - this.doEnable(); - } - - protected abstract doEnable(): void; -} diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-tools.ts similarity index 78% rename from packages/client/src/features/tools/base-glsp-tool.ts rename to packages/client/src/features/tools/base-tools.ts index bbb2efc..fe23c1a 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -14,15 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, Disposable, DisposableCollection, IActionDispatcher, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, DisposableCollection, IActionDispatcher, IActionHandler, TYPES } from '~glsp-sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; -import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; +import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; import { GLSPKeyTool } from '../../base/view/key-tool'; import { GLSPMouseTool } from '../../base/view/mouse-tool'; +/** + * A reusable base implementation for edit {@link Tool}s. + */ @injectable() -export abstract class BaseGLSPTool implements GLSPTool { +export abstract class BaseEditTool implements Tool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; @@ -71,3 +74,25 @@ export abstract class BaseGLSPTool implements GLSPTool { this.feedbackDispatcher.deregisterFeedback(feedbackEmitter ?? this, cleanupActions); } } + +@injectable() +export abstract class BaseCreationTool extends BaseEditTool implements IActionHandler { + protected abstract isTriggerAction: (obj: any) => obj is T; + protected triggerAction: T; + + handle(action: Action): Action | void { + if (this.isTriggerAction(action)) { + this.triggerAction = action; + return EnableToolsAction.create([this.id]); + } + } + + override enable(): void { + if (this.triggerAction === undefined) { + throw new TypeError(`Could not enable tool ${this.id}. The triggerAction cannot be undefined.`); + } + this.doEnable(); + } + + protected abstract doEnable(): void; +} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 8123775..0778ca7 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -58,7 +58,7 @@ import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; -import { BaseGLSPTool } from '../base-glsp-tool'; +import { BaseEditTool } from '../base-tools'; import { FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, @@ -79,7 +79,7 @@ import { * resize and send the server updates we install the `ChangeBoundsListener`. */ @injectable() -export class ChangeBoundsTool extends BaseGLSPTool { +export class ChangeBoundsTool extends BaseEditTool { static ID = 'glsp.change-bounds-tool'; @inject(SelectionService) protected selectionService: SelectionService; diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 78c2847..ea38500 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -17,7 +17,6 @@ import { inject, injectable } from 'inversify'; import { Action, DeleteElementOperation, - EnableDefaultToolsAction, KeyListener, KeyTool, MouseListener, @@ -29,14 +28,14 @@ import { matchesKeystroke } from '~glsp-sprotty'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; -import { GLSPTool } from '../../../base/tool-manager/glsp-tool-manager'; -import { BaseGLSPTool } from '../base-glsp-tool'; +import { EnableDefaultToolsAction, Tool } from '../../../base/tool-manager/tool'; +import { BaseEditTool } from '../base-tools'; /** * Deletes selected elements when hitting the `Del` key. */ @injectable() -export class DelKeyDeleteTool implements GLSPTool { +export class DelKeyDeleteTool implements Tool { static ID = 'glsp.delete-keyboard'; isEditTool = true; @@ -80,7 +79,7 @@ export class DeleteKeyListener extends KeyListener { * Deletes selected elements when clicking on them. */ @injectable() -export class MouseDeleteTool extends BaseGLSPTool { +export class MouseDeleteTool extends BaseEditTool { static ID = 'glsp.delete-mouse'; protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 40d26f8..4aca5b6 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -18,7 +18,6 @@ import { Action, AnchorComputerRegistry, CreateEdgeOperation, - EnableDefaultToolsAction, SEdge, SModelElement, TriggerEdgeCreationAction, @@ -29,7 +28,8 @@ import { import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; -import { BaseGLSPCreationTool } from '../base-glsp-creation-tool'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedback'; @@ -37,7 +37,7 @@ import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedbac * Tool to create connections in a Diagram, by selecting a source and target node. */ @injectable() -export class EdgeCreationTool extends BaseGLSPCreationTool { +export class EdgeCreationTool extends BaseCreationTool { static ID = 'tool_create_edge'; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index 9de9a93..17e187a 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -38,7 +38,7 @@ import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feed import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/smodel-util'; import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; -import { BaseGLSPTool } from '../base-glsp-tool'; +import { BaseEditTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { DrawFeedbackEdgeSourceAction, @@ -51,7 +51,7 @@ import { } from './edge-edit-tool-feedback'; @injectable() -export class EdgeEditTool extends BaseGLSPTool { +export class EdgeEditTool extends BaseEditTool { static ID = 'glsp.edge-edit-tool'; @inject(SelectionService) protected selectionService: SelectionService; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index b5d70bc..92d7d17 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -18,7 +18,6 @@ import { Action, BoundsAware, DOMHelper, - EnableDefaultToolsAction, KeyListener, SEdge, SModelElement, @@ -31,13 +30,14 @@ import { } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; -import { BaseGLSPTool } from '../base-glsp-tool'; +import { BaseEditTool } from '../base-tools'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; import { RemoveMarqueeAction } from './marquee-tool-feedback'; @injectable() -export class MarqueeMouseTool extends BaseGLSPTool { +export class MarqueeMouseTool extends BaseEditTool { static ID = 'glsp.marquee-mouse-tool'; @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts index cc23c1d..f23df2c 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts @@ -14,12 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, EnableToolsAction, KeyListener, SModelElement } from '~glsp-sprotty'; -import { BaseGLSPTool } from '../base-glsp-tool'; +import { Action, KeyListener, SModelElement } from '~glsp-sprotty'; +import { EnableToolsAction } from '../../../base/tool-manager/tool'; +import { BaseEditTool } from '../base-tools'; import { MarqueeMouseTool } from './marquee-mouse-tool'; @injectable() -export class MarqueeTool extends BaseGLSPTool { +export class MarqueeTool extends BaseEditTool { static ID = 'glsp.marquee-tool'; protected marqueeKeyListener: MarqueeKeyListener = new MarqueeKeyListener(); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index cd91dc0..641e222 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -17,7 +17,6 @@ import { inject, injectable, optional } from 'inversify'; import { Action, CreateNodeOperation, - EnableDefaultToolsAction, ISnapper, SModelElement, SNode, @@ -28,12 +27,13 @@ import { } from '~glsp-sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { getAbsolutePosition } from '../../../utils/viewpoint-util'; import { Containable, isContainable } from '../../hints/model'; -import { BaseGLSPCreationTool } from '../base-glsp-creation-tool'; +import { BaseCreationTool } from '../base-tools'; @injectable() -export class NodeCreationTool extends BaseGLSPCreationTool { +export class NodeCreationTool extends BaseCreationTool { static ID = 'tool_create_node'; protected isTriggerAction = TriggerNodeCreationAction.is; diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index c58b3fb..e0c766c 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, EnableDefaultToolsAction, FeatureModule, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; +import { Action, FeatureModule, IActionHandler, ICommand, configureActionHandler } from '~glsp-sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; +import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; /** * Action handler that enables the default tools when the diagram lost focus. diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 4cdd4fd..457f080 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -14,15 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { - Action, - EnableDefaultToolsAction, - EnableToolsAction, - IActionHandler, - ICommand, - ScrollMouseListener, - SModelElement -} from '~glsp-sprotty'; +import { Action, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from '~glsp-sprotty'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; @injectable() diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index fd62a34..17d28b6 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -19,8 +19,6 @@ import { CenterKeyboardListener, configureActionHandler, configureCommand, - EnableDefaultToolsAction, - EnableToolsAction, FeatureModule, FitToScreenCommand, GetViewportCommand, @@ -28,6 +26,7 @@ import { TYPES, ZoomMouseListener } from '~glsp-sprotty'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; import { RepositionCommand } from './reposition'; diff --git a/packages/client/src/glsp-sprotty/augmented-actions.ts b/packages/client/src/glsp-sprotty/augmented-actions.ts index bb740ff..00c4b3b 100644 --- a/packages/client/src/glsp-sprotty/augmented-actions.ts +++ b/packages/client/src/glsp-sprotty/augmented-actions.ts @@ -17,7 +17,6 @@ /* eslint-disable-next-line no-restricted-imports*/ import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; import { SetBoundsAction } from 'sprotty-protocol/lib/actions'; -import { EnableDefaultToolsAction, EnableToolsAction } from 'sprotty/lib/base/tool-manager/tool'; /** * Use module augmentation to add namespaces and the `is` utility function to sprotty actions that directly reused by GLSP @@ -31,18 +30,4 @@ declare module 'sprotty-protocol/lib/actions' { SetBoundsAction.is = (object: any): object is SetBoundsAction => Action.hasKind(object, SetBoundsAction.KIND) && hasArrayProp(object, 'bounds'); -declare module 'sprotty/lib/base/tool-manager/tool' { - export namespace EnableDefaultToolsAction { - export function is(object: any): object is EnableDefaultToolsAction; - } - export namespace EnableToolsAction { - export function is(object: any): object is EnableToolsAction; - } -} - -EnableDefaultToolsAction.is = (object: any): object is EnableDefaultToolsAction => Action.hasKind(object, EnableDefaultToolsAction.KIND); - -EnableToolsAction.is = (object: any): object is EnableToolsAction => - Action.hasKind(object, EnableToolsAction.KIND) && hasArrayProp(object, 'toolIds'); - -export { EnableDefaultToolsAction, EnableToolsAction }; +export { SetBoundsAction }; diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/client/src/glsp-sprotty/index.ts index e0011f2..43f764c 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -79,8 +79,6 @@ export { translateBounds, translatePoint } from 'sprotty/lib/base/model/smodel-utils'; -export * from 'sprotty/lib/base/tool-manager/tool'; -export * from 'sprotty/lib/base/tool-manager/tool-manager'; export * from 'sprotty/lib/base/ui-extensions/ui-extension'; export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; export * from 'sprotty/lib/base/views/dom-helper'; diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index 5412758..662a00b 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -38,7 +38,8 @@ export const TYPES = { IElementNavigator: Symbol('IElementNavigator'), ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), - IDiagramStartup: Symbol('IDiagramStartup') + IDiagramStartup: Symbol('IDiagramStartup'), + IToolManager: Symbol('IToolManager') }; /** diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3400cc6..a2ddd95 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -35,7 +35,8 @@ export * from './base/model/model-registry'; export * from './base/ranked'; export * from './base/selection-clearing-mouse-listener'; export * from './base/selection-service'; -export * from './base/tool-manager/glsp-tool-manager'; +export * from './base/tool-manager/tool'; +export * from './base/tool-manager/tool-manager'; export * from './base/view/mouse-tool'; export * from './base/view/view-registry'; @@ -85,7 +86,7 @@ export * from './features/source-model-watcher/source-model-changed-action-handl export * from './features/status/status-overlay'; export * from './features/svg-metadata/metadata-placer'; export * from './features/tool-palette/tool-palette'; -export * from './features/tools/base-glsp-tool'; +export * from './features/tools/base-tools'; export * from './features/tools/change-bounds/change-bounds-tool'; export * from './features/tools/change-bounds/change-bounds-tool-feedback'; export * from './features/tools/change-bounds/view'; From 1a5d1052a6ef4f4f2878c4dbd4a0ac4574913552 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Sat, 26 Aug 2023 00:09:19 +0200 Subject: [PATCH 246/566] GLSP-1088: Ensure we restore focus to the diagram (#278) - UI extensions only track the active element before they are shown - Enhance the focus tracker to provide more info on the active element -- In Theia the tool palette stored the file explorer as active element Fixes https://github.com/eclipse-glsp/glsp/issues/1088 --- .../client/src/base/focus/focus-tracker.ts | 21 ++++++++++++++----- .../src/features/tool-palette/tool-palette.ts | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/client/src/base/focus/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts index d7fd898..fe0e02f 100644 --- a/packages/client/src/base/focus/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -21,6 +21,8 @@ import { FocusStateChangedAction } from './focus-state-change-action'; export class FocusTracker implements IActionHandler { protected inActiveCssClass = 'inactive'; protected _hasFocus = true; + protected _focusElement: HTMLOrSVGElement | null; + protected _diagramElement: HTMLElement | null; @inject(TYPES.ViewerOptions) protected options: ViewerOptions; @@ -28,19 +30,28 @@ export class FocusTracker implements IActionHandler { return this._hasFocus; } + get focusElement(): HTMLOrSVGElement | null { + return this._focusElement; + } + + get diagramElement(): HTMLElement | null { + return this._diagramElement; + } + handle(action: Action): void | Action | ICommand { if (FocusStateChangedAction.is(action)) { this._hasFocus = action.hasFocus; - const placeholder = document.getElementById(this.options.baseDiv); - if (!placeholder) { + this._focusElement = document.activeElement as HTMLOrSVGElement | null; + this._diagramElement = document.getElementById(this.options.baseDiv); + if (!this._diagramElement) { return; } if (this.hasFocus) { - if (placeholder.classList.contains(this.inActiveCssClass)) { - placeholder.classList.remove(this.inActiveCssClass); + if (this._diagramElement.classList.contains(this.inActiveCssClass)) { + this._diagramElement.classList.remove(this.inActiveCssClass); } } else { - placeholder.classList.add(this.inActiveCssClass); + this._diagramElement.classList.add(this.inActiveCssClass); } } } diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 45cf9d5..b877118 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -337,7 +337,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.changeActiveButton(); if (this.focusTracker.hasFocus) { // if focus was deliberately taken do not restore focus to the palette - this.restoreFocus(); + this.focusTracker.diagramElement?.focus(); } } From 2462ff932a865b29be3dbaa02c8a6c9a1981e5ab Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 28 Aug 2023 13:39:33 +0200 Subject: [PATCH 247/566] Update tsconfig (#1098) --- dev-packages/ts-config/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index 83dd56b..a29cb0b 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -19,7 +19,7 @@ "emitDecoratorMetadata": true, "downlevelIteration": true, "resolveJsonModule": true, - "module": "commonjs", + "module": "Node16", "moduleResolution": "Node16", "target": "ES2017", "jsx": "react", From 98f6a59e4641fce33832c1f02347c3b5cb32e554 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 28 Aug 2023 15:23:02 +0200 Subject: [PATCH 248/566] Only enable a11y module in standalone example (#280) The a11y module is currently only tested for the standalone example but is configured per default in the global workflow glsp config. => Remove module from global config and only enable it in the standalone example --- examples/workflow-glsp/src/workflow-diagram-module.ts | 3 +-- examples/workflow-standalone/src/di.config.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index af120f8..54c84bf 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -33,7 +33,6 @@ import { SLabelView, StructureCompartmentView, TYPES, - accessibilityModule, bindAsService, bindOrRebind, configureDefaultModelElements, @@ -82,5 +81,5 @@ export function createWorkflowDiagramContainer(...containerConfiguration: Contai } export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { - return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, accessibilityModule, ...containerConfiguration); + return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, ...containerConfiguration); } diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 3e0b9d6..2843cbe 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; import { + accessibilityModule, bindOrRebind, ConsoleLogger, createDiagramOptionsModule, @@ -26,7 +27,7 @@ import { import { Container } from 'inversify'; import '../css/diagram.css'; export default function createContainer(options: IDiagramOptions): Container { - const container = createWorkflowDiagramContainer(createDiagramOptionsModule(options), STANDALONE_MODULE_CONFIG); + const container = createWorkflowDiagramContainer(createDiagramOptionsModule(options), accessibilityModule, STANDALONE_MODULE_CONFIG); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); From 1ce38bbe626241fb1088aa6fced13c7b2a2fc0a1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 29 Aug 2023 11:23:37 +0200 Subject: [PATCH 249/566] Fix hover support for issue markers (#281) Ensure that hover support for manually constructed issue markers works as expected. --- packages/client/src/features/hover/hover.ts | 2 +- packages/client/src/views/base-view-module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index a777892..d4e1be5 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -74,7 +74,7 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio this.state.mouseOverTimer = window.setTimeout(() => { const popupBounds = this.computePopupBounds(target, { x: event.pageX, y: event.pageY }); if (target instanceof GIssueMarker) { - resolve(SetPopupModelAction.create(this.createPopupModel(target as GIssueMarker, popupBounds))); + resolve(SetPopupModelAction.create(this.createPopupModel(target, popupBounds))); } else { resolve(RequestPopupModelAction.create({ elementId: target.id, bounds: popupBounds })); } diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index bd05350..04ed663 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -35,7 +35,6 @@ import { SCompartmentView, SEdge, SGraphView, - SIssueMarker, SLabel, SLabelView, SNode, @@ -49,6 +48,7 @@ import { moveFeature, selectFeature } from '~glsp-sprotty'; +import { GIssueMarker } from '../features/validation/issue-marker'; import { GLSPGraph } from '../lib/model'; import { GEdgeView } from './glsp-edge-view'; import { GIssueMarkerView } from './issue-marker-view'; @@ -86,7 +86,7 @@ export function configureDefaultModelElements(context: Pick Date: Mon, 4 Sep 2023 04:52:10 -0700 Subject: [PATCH 250/566] GLSP-1101: Fix Windows path conversion (#55) Ensure leading shashes in File urls are properly handled under Windows. Also: Fix rimraf command that was not working under Windows Fixes https://github.com/eclipse-glsp/glsp/issues/1101 --- examples/workflow-server-bundled/package.json | 2 +- packages/server/src/node/abstract-json-model-storage.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 6bf75cd..ce91df4 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -41,7 +41,7 @@ "wf-glsp-server-node.js.map" ], "scripts": { - "clean": "rimraf ./*.js./*.js.map", + "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map", "prepare": "yarn clean", "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", diff --git a/packages/server/src/node/abstract-json-model-storage.ts b/packages/server/src/node/abstract-json-model-storage.ts index dc08401..b10ef82 100644 --- a/packages/server/src/node/abstract-json-model-storage.ts +++ b/packages/server/src/node/abstract-json-model-storage.ts @@ -16,6 +16,7 @@ import { MaybePromise, RequestModelAction, SaveModelAction, TypeGuard } from '@eclipse-glsp/protocol'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; +import * as os from 'os'; import { fileURLToPath } from 'url'; import { ModelState, SOURCE_URI_ARG } from '../common/features/model/model-state'; import { SourceModelStorage } from '../common/features/model/source-model-storage'; @@ -91,7 +92,12 @@ export abstract class AbstractJsonModelStorage implements SourceModelStorage { } protected toPath(sourceUri: string): string { - return sourceUri.startsWith('file://') ? fileURLToPath(sourceUri) : sourceUri; + let path = sourceUri.startsWith('file://') ? fileURLToPath(sourceUri) : sourceUri; + if (os.platform() === 'win32') { + // Remove the leading slash if it exists (Windows paths don't have it) + path = path.replace(/^\//, ''); + } + return path; } protected getFileUri(action: SaveModelAction): string { From d1bb8ec093773a951dd11bf21fc6e2184ba3e937 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 4 Sep 2023 05:35:17 -0700 Subject: [PATCH 251/566] GLSP-960: Follow-up (#282) Provide `DiagamLoaderOptions` to support dynamic configuration of the loading behavior Follow-up for eclipse-glsp/glsp/issues/960 --- examples/workflow-standalone/src/app.ts | 2 +- .../client/src/base/model/diagram-loader.ts | 72 ++++++++++++++----- .../src/base/model/glsp-model-source.ts | 26 ++++--- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index e79fbf7..78d3ff4 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -49,7 +49,7 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting container = createContainer({ clientId, diagramType, glspClientProvider: async () => glspClient, sourceUri: examplePath }); const actionDispatcher = container.get(GLSPActionDispatcher); const diagramLoader = container.get(DiagramLoader); - await diagramLoader.load({ isReconnecting }); + await diagramLoader.load({ requestModelOptions: { isReconnecting } }); if (isReconnecting) { const message = `Connection to the ${id} glsp server got closed. Connection was successfully re-established.`; diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index ec2e893..885bbd7 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -21,6 +21,7 @@ import { Args, EMPTY_ROOT, GLSPClient, + InitializeParameters, MaybePromise, RequestModelAction, ServerStatusAction, @@ -30,6 +31,7 @@ import { } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; +import { GLSPModelSource } from './glsp-model-source'; /** * Configuration options for a specific GLSP diagram instance. @@ -92,6 +94,33 @@ export namespace IDiagramStartup { } } +export interface DiagramLoadingOptions { + /** + * Optional custom options that should be used the initial {@link RequestModelAction}. + * These options will be merged with the default options (`diagramType` and `sourceUri`). + * Defaults to an empty object if not defined. + */ + requestModelOptions?: Args; + + /** + * Optional partial {@link InitializeParameters} that should be used for `initializeServer` request if the underlying + * {@link GLSPClient} has not been initialized yet. + */ + initializeParameters?: Partial; + + /** + * Flag to enable/disable client side notifications during the loading process. + * Defaults to `true` if not defined + */ + enableNotifications?: boolean; +} + +export interface ResolvedDiagramLoadingOptions { + requestModelOptions: Args; + initializeParameters: InitializeParameters; + enableNotifications: boolean; +} + /** * The central component responsible for initializing the diagram and loading the graphical model * from the GLSP server. @@ -110,20 +139,34 @@ export class DiagramLoader { @optional() protected diagramStartups: IDiagramStartup[] = []; - protected enableLoadingNotifications = true; + @inject(GLSPModelSource) + protected modelSource: GLSPModelSource; @postConstruct() protected postConstruct(): void { this.diagramStartups.sort((a, b) => Ranked.getRank(a) - Ranked.getRank(b)); } - async load(requestModelOptions: Args = {}): Promise { + async load(options: DiagramLoadingOptions = {}): Promise { + const resolvedOptions: ResolvedDiagramLoadingOptions = { + requestModelOptions: { + sourceUri: this.options.sourceUri ?? '', + diagramType: this.options.diagramType, + ...options.requestModelOptions + }, + initializeParameters: { + applicationId: ApplicationIdProvider.get(), + protocolVersion: GLSPClient.protocolVersion, + ...options.initializeParameters + }, + enableNotifications: options.enableNotifications ?? true + }; // Set placeholder model until real model from server is available await this.actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT)); await this.invokeStartupHook('preInitialize'); - await this.configureGLSPClient(); + await this.initialize(resolvedOptions); await this.invokeStartupHook('preRequestModel'); - await this.requestModel(requestModelOptions); + await this.requestModel(resolvedOptions); await this.invokeStartupHook('postRequestModel'); } @@ -133,29 +176,26 @@ export class DiagramLoader { } } - protected requestModel(requestModelOptions: Args = {}): Promise { - const options = { sourceUri: this.options.sourceUri, diagramType: this.options.diagramType, ...requestModelOptions } as Args; - const result = this.actionDispatcher.dispatch(RequestModelAction.create({ options })); - if (this.enableLoadingNotifications) { + protected requestModel(options: ResolvedDiagramLoadingOptions): Promise { + const result = this.actionDispatcher.dispatch(RequestModelAction.create({ options: options.requestModelOptions })); + if (options.enableNotifications) { this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); } return result; } - protected async configureGLSPClient(): Promise { - const glspClient = await this.options.glspClientProvider(); - - if (this.enableLoadingNotifications) { + protected async initialize(options: ResolvedDiagramLoadingOptions): Promise { + await this.actionDispatcher.initialize(); + if (options.enableNotifications) { this.actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); } + const glspClient = await this.options.glspClientProvider(); + await glspClient.start(); if (!glspClient.initializeResult) { - await glspClient.initializeServer({ - applicationId: ApplicationIdProvider.get(), - protocolVersion: GLSPClient.protocolVersion - }); + await glspClient.initializeServer(options.initializeParameters); } } } diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index 307bae1..cdac188 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, postConstruct } from 'inversify'; +import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { Action, ActionHandlerRegistry, @@ -26,20 +26,19 @@ import { InitializeResult, ModelSource, SModelRootSchema, - TYPES, - Writable + TYPES } from '~glsp-sprotty'; import { IDiagramOptions } from './diagram-loader'; /** * A helper interface that allows the client to mark actions that have been received from the server. */ export interface ServerAction extends Action { - _receivedFromServer: true; + __receivedFromServer: true; } export namespace ServerAction { export function is(object: unknown): object is ServerAction { - return Action.is(object) && '_receivedFromServer' in object && object._receivedFromServer === true; + return Action.is(object) && '__receivedFromServer' in object && object.__receivedFromServer === true; } /** @@ -47,7 +46,7 @@ export namespace ServerAction { * @param action The action that should be marked as server action */ export function mark(action: Action): void { - (action as ServerAction)._receivedFromServer = true; + (action as ServerAction).__receivedFromServer = true; } } @@ -71,7 +70,8 @@ export class GLSPModelSource extends ModelSource implements Disposable { protected toDispose = new DisposableCollection(); clientId: string; - readonly glspClient: GLSPClient | undefined; + + protected _glspClient: GLSPClient | undefined; protected _currentRoot: SModelRootSchema; get diagramType(): string { @@ -82,6 +82,10 @@ export class GLSPModelSource extends ModelSource implements Disposable { return this.options.sourceUri; } + get glspClient(): GLSPClient | undefined { + return this._glspClient; + } + @postConstruct() protected postConstruct(): void { this.clientId = this.options.clientId ?? this.viewerOptions.baseDiv; @@ -115,11 +119,14 @@ export class GLSPModelSource extends ModelSource implements Disposable { } this.options.glspClientProvider().then(glspClient => { - (this as Writable).glspClient = glspClient; + this._glspClient = glspClient; if (glspClient.initializeResult) { this.configure(registry, glspClient.initializeResult); } else { - glspClient.onServerInitialized(result => this.configure(registry, result)); + const initializeListener = glspClient.onServerInitialized(result => { + this.configure(registry, result); + initializeListener.dispose(); + }); } }); } @@ -163,6 +170,7 @@ export class GLSPModelSource extends ModelSource implements Disposable { return this._currentRoot; } + @preDestroy() dispose(): void { this.toDispose.dispose(); } From 32c057b28c75f2fce73eab1398cc9304024b48b9 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Mon, 4 Sep 2023 23:32:40 +0200 Subject: [PATCH 252/566] GLSP-1109: Remove dependency to fs-extra (#56) Use 'fs' instead of 'fs-extra' in GLSP server node as the additional capatibilities of 'fs-extra' are not used. Also fixes the issue that the dependency to 'fs-extra' was not declared in the 'package.json' which might fail consumer builds. Fixes https://github.com/eclipse-glsp/glsp/issues/1109 Contributed on behalf of STMicroelectronics. --- packages/server/package.json | 1 - packages/server/src/node/abstract-json-model-storage.ts | 2 +- packages/server/src/node/launch/cli-parser.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 32e80b7..7124179 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -68,7 +68,6 @@ "ws": "^8.12.1" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", "@types/ws": "^8.5.4" }, "peerDependencies": { diff --git a/packages/server/src/node/abstract-json-model-storage.ts b/packages/server/src/node/abstract-json-model-storage.ts index b10ef82..ad712f7 100644 --- a/packages/server/src/node/abstract-json-model-storage.ts +++ b/packages/server/src/node/abstract-json-model-storage.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { MaybePromise, RequestModelAction, SaveModelAction, TypeGuard } from '@eclipse-glsp/protocol'; -import * as fs from 'fs-extra'; +import * as fs from 'fs'; import { inject, injectable } from 'inversify'; import * as os from 'os'; import { fileURLToPath } from 'url'; diff --git a/packages/server/src/node/launch/cli-parser.ts b/packages/server/src/node/launch/cli-parser.ts index cd1ca4b..87d4d99 100644 --- a/packages/server/src/node/launch/cli-parser.ts +++ b/packages/server/src/node/launch/cli-parser.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as cmd from 'commander'; -import * as fs from 'fs-extra'; +import * as fs from 'fs'; import * as path from 'path'; import { LogLevel, LoggerConfigOptions, asLogLevel } from '../../common/utils/logger'; From 9a3634b297486ba4c183fc3f8e1f459f82ab0c02 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 5 Sep 2023 05:50:47 -0700 Subject: [PATCH 253/566] Fix download server script (#283) Ensure that old server versions are properly removed before downloading a new version. --- examples/workflow-standalone/scripts/start-example-server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts index 9f6a320..11fa427 100644 --- a/examples/workflow-standalone/scripts/start-example-server.ts +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -44,7 +44,8 @@ async function downloadIfNecessary(): Promise { console.log('Server executable with correct version not found. Download from npm.'); if (existingServer) { - fs.rmSync(existingServer); + fs.rmSync(path.resolve(serverDirPath, existingServer)); + fs.rmSync(path.resolve(serverDirPath, existingServer.replace('.js', '.js.map'))); } sh.cd(serverDirPath); const packResultJson = sh From 8cbb71235c021885fd605926724b94f6ef8c737c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 7 Sep 2023 07:50:51 -0700 Subject: [PATCH 254/566] GLSP-1031: Fix wrong binding in bounds-module (#284) Fixes https://github.com/eclipse-glsp/glsp/issues/1031 --- packages/client/src/features/bounds/bounds-module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 3d99565..beb18fe 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -18,7 +18,6 @@ import { HBoxLayouter, HiddenBoundsUpdater, LayoutRegistry, - Layouter, RequestBoundsCommand, SetBoundsCommand, TYPES, @@ -30,6 +29,7 @@ import { import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; +import { LayouterExt } from './layouter'; import { VBoxLayouterExt } from './vbox-layout'; export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { @@ -38,7 +38,7 @@ export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) configureCommand(context, RequestBoundsCommand); bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); - bind(TYPES.Layouter).to(Layouter).inSingletonScope(); + bind(TYPES.Layouter).to(LayouterExt).inSingletonScope(); bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); configureLayout(context, VBoxLayouter.KIND, VBoxLayouterExt); From 0ef7bd607ddeca99b5f0430dfb5b8c0b8b79b194 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 15 Sep 2023 03:50:24 -0700 Subject: [PATCH 255/566] GLSP-1116 Revise model loading (#287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GLSP-1116 Revise model loading - Refactor diagram loader - Remove dispatching of temporary empty set model action and instead call `actionDispatcher.initialize()` earlier which also dispatches an empty set model action under the hood - Add additional `postModelInitalization` hook for startup services that want execute logic after the model is fully initialized - Rework `ModelInitializationConstraint` - Provide `onInitialized` override that allows sync registration of listener callbacks - Refactor `setCompleted` method and remove the possiblity to set the initialized state to false. Model initialization is a one-time action. Once initialized there should be no way to "uninitialize" the constraint - Provide test cases - Add `dispatchOnceModelInitialized` utility function to action dispatcher - Ensure that type hints are requested after the model has been initialized Part-of: https://github.com/eclipse-glsp/glsp/issues/1116 Part-of: https://github.com/eclipse-glsp/glsp/issues/606 * GLSP-1117: Remove need for explicit definition of client actions -Extend `initializeClientSession` request to also specific the set of client action kinds. This way the server knows which actions should be sent to the client -Adapt `GLSPModelSource´ to retrieve the client actions before sending the `initializeClientSession` request - Add customized `GLSPActionHandlerRegistry` which provides a query method to retrieve all handled action kinds Part of eclipse-glsp/glsp/issues/1117 * GLSP-1071: Rename ServerStatus/ServerMessage action Part of https://github.com/eclipse-glsp/glsp/issues/1071 --- examples/workflow-standalone/src/app.ts | 9 +- packages/client/src/base/action-dispatcher.ts | 16 +++- .../src/base/action-handler-registry.ts | 30 ++++++ packages/client/src/base/default.module.ts | 6 +- .../client/src/base/model/diagram-loader.ts | 34 ++++--- .../src/base/model/glsp-model-source.ts | 17 +++- .../model-initialization-constraint.spec.ts | 95 +++++++++++++++++++ .../model-initialization-constraint.ts | 53 +++++++++-- .../diagram-navigation-tool.ts | 42 ++++---- .../resize-key-tool/resize-key-tool.ts | 28 +++--- .../accessibility/search/search-tool.ts | 14 ++- .../view-key-tools/movement-key-tool.ts | 14 ++- .../view-key-tools/zoom-key-tool.ts | 24 +++-- .../copy-paste/copy-paste-context-menu.ts | 9 +- .../client/src/features/hints/type-hints.ts | 2 +- .../label-edit/edit-label-validator.ts | 8 +- .../navigation/navigation-action-handler.ts | 13 +-- .../source-model-changed-action-handler.ts | 9 +- .../src/features/status/status-module.ts | 4 +- .../src/features/status/status-overlay.ts | 10 +- packages/client/src/index.ts | 15 +-- packages/client/src/standalone-modules.ts | 4 +- .../src/action-protocol/base-protocol.ts | 5 +- .../client-notification.spec.ts | 48 ++++------ .../action-protocol/client-notification.ts | 46 ++++----- .../base-glsp-client.spec.ts | 6 +- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 4 +- .../src/client-server-protocol/types.ts | 6 ++ 28 files changed, 371 insertions(+), 200 deletions(-) create mode 100644 packages/client/src/base/action-handler-registry.ts create mode 100644 packages/client/src/base/model/model-initialization-constraint.spec.ts rename packages/client/src/base/{ => model}/model-initialization-constraint.ts (65%) diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index 78d3ff4..aeea634 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -21,8 +21,8 @@ import { GLSPActionDispatcher, GLSPClient, GLSPWebSocketProvider, - ServerMessageAction, - ServerStatusAction + MessageAction, + StatusAction } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import { join, resolve } from 'path'; @@ -55,10 +55,7 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting const message = `Connection to the ${id} glsp server got closed. Connection was successfully re-established.`; const timeout = 5000; const severity = 'WARNING'; - actionDispatcher.dispatchAll([ - ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity }) - ]); + actionDispatcher.dispatchAll([StatusAction.create(message, { severity, timeout }), MessageAction.create(message, { severity })]); return; } } diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 6af92dc..40349cd 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -15,14 +15,15 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; import { Action, ActionDispatcher, RequestAction, ResponseAction } from '~glsp-sprotty'; -import { ModelInitializationConstraint } from './model-initialization-constraint'; +import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @injectable() export class GLSPActionDispatcher extends ActionDispatcher { protected readonly timeouts: Map = new Map(); protected initializedConstraint = false; - @inject(ModelInitializationConstraint) protected initializationConstraint: ModelInitializationConstraint; + @inject(ModelInitializationConstraint) + protected initializationConstraint: ModelInitializationConstraint; override initialize(): Promise { return super.initialize().then(() => this.startModelInitialization()); @@ -31,7 +32,7 @@ export class GLSPActionDispatcher extends ActionDispatcher { startModelInitialization(): void { if (!this.initializedConstraint) { this.logger.log(this, 'Starting model initialization mode'); - this.initializationConstraint.onInitialized().then(() => this.logger.log(this, 'Model initialization completed')); + this.initializationConstraint.onInitialized(() => this.logger.log(this, 'Model initialization completed')); this.initializedConstraint = true; } } @@ -44,6 +45,15 @@ export class GLSPActionDispatcher extends ActionDispatcher { return this.actionHandlerRegistry.get(action.kind).length > 0; } + /** + * Processes all given actions, by dispatching them to the corresponding handlers, after the model initialization is completed. + * + * @param actions The actions that should be dispatched after the model initialization + */ + dispatchOnceModelInitialized(...actions: Action[]): void { + this.initializationConstraint.onInitialized(() => this.dispatchAll(actions)); + } + override dispatch(action: Action): Promise { const result = super.dispatch(action); this.initializationConstraint.notifyDispatched(action); diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts new file mode 100644 index 0000000..e4220bb --- /dev/null +++ b/packages/client/src/base/action-handler-registry.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { ActionHandlerRegistry } from '~glsp-sprotty'; + +@injectable() +export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { + /** + * Retrieve a set of all action kinds for which (at least) one + * handler is registered + * @returns the set of handled action kinds + */ + getHandledActionKinds(): string[] { + return Array.from(this.elements.keys()); + } +} diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 6f20ea0..78bf8e2 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import '@vscode/codicons/dist/codicon.css'; import { + ActionHandlerRegistry, FeatureModule, KeyTool, LocationPostprocessor, @@ -31,6 +32,7 @@ import { } from '~glsp-sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; +import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; @@ -38,9 +40,9 @@ import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher' import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; -import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; import { DiagramLoader } from './model/diagram-loader'; import { GLSPModelSource } from './model/glsp-model-source'; +import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model/model-initialization-constraint'; import { GLSPModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; @@ -91,6 +93,8 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); + bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); bindAsService(context, TYPES.ModelSource, GLSPModelSource); bind(DiagramLoader).toSelf().inSingletonScope(); diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 885bbd7..8112de0 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -24,14 +24,15 @@ import { InitializeParameters, MaybePromise, RequestModelAction, - ServerStatusAction, SetModelAction, + StatusAction, TYPES, hasNumberProp } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; import { GLSPModelSource } from './glsp-model-source'; +import { ModelInitializationConstraint } from './model-initialization-constraint'; /** * Configuration options for a specific GLSP diagram instance. @@ -79,9 +80,13 @@ export interface IDiagramStartup extends Partial { * Hook for services that should be executed after the initial model loading request (i.e. `RequestModelAction`). * Note that this hook is invoked directly after the `RequestModelAction` has been dispatched. It does not necessarily wait * until the client-server update roundtrip is completed. If you need to wait until the diagram is fully initialized use the - * {@link GLSPActionDispatcher.onceModelInitialized} constraint. + * {@link postModelInitialization} hook. */ postRequestModel?(): MaybePromise; + /* Hook for services that should be executed after the diagram model is fully initialized + * (i.e. `ModelInitializationConstraint` is completed). + */ + postModelInitialization?(): MaybePromise; } export namespace IDiagramStartup { @@ -89,7 +94,10 @@ export namespace IDiagramStartup { return ( AnyObject.is(object) && hasNumberProp(object, 'rank', true) && - ('preInitialize' in object || 'preRequestModel' in object || 'postRequestModel' in object) + ('preInitialize' in object || + 'preRequestModel' in object || + 'postRequestModel' in object || + 'postModelInitialization' in object) ); } } @@ -142,6 +150,9 @@ export class DiagramLoader { @inject(GLSPModelSource) protected modelSource: GLSPModelSource; + @inject(ModelInitializationConstraint) + protected modelInitializationConstraint: ModelInitializationConstraint; + @postConstruct() protected postConstruct(): void { this.diagramStartups.sort((a, b) => Ranked.getRank(a) - Ranked.getRank(b)); @@ -161,6 +172,7 @@ export class DiagramLoader { }, enableNotifications: options.enableNotifications ?? true }; + await this.actionDispatcher.initialize(); // Set placeholder model until real model from server is available await this.actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT)); await this.invokeStartupHook('preInitialize'); @@ -168,6 +180,7 @@ export class DiagramLoader { await this.invokeStartupHook('preRequestModel'); await this.requestModel(resolvedOptions); await this.invokeStartupHook('postRequestModel'); + this.modelInitializationConstraint.onInitialized(() => this.invokeStartupHook('postModelInitialization')); } protected async invokeStartupHook(hook: keyof Omit): Promise { @@ -176,18 +189,13 @@ export class DiagramLoader { } } - protected requestModel(options: ResolvedDiagramLoadingOptions): Promise { - const result = this.actionDispatcher.dispatch(RequestModelAction.create({ options: options.requestModelOptions })); - if (options.enableNotifications) { - this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); - } - return result; + protected async requestModel(options: ResolvedDiagramLoadingOptions): Promise { + return this.actionDispatcher.dispatch(RequestModelAction.create({ options: options.requestModelOptions })); } protected async initialize(options: ResolvedDiagramLoadingOptions): Promise { - await this.actionDispatcher.initialize(); if (options.enableNotifications) { - this.actionDispatcher.dispatch(ServerStatusAction.create('Initializing...', { severity: 'INFO' })); + this.actionDispatcher.dispatch(StatusAction.create('Initializing...', { severity: 'INFO' })); } const glspClient = await this.options.glspClientProvider(); @@ -197,5 +205,9 @@ export class DiagramLoader { if (!glspClient.initializeResult) { await glspClient.initializeServer(options.initializeParameters); } + + if (options.enableNotifications) { + this.actionDispatcher.dispatch(StatusAction.create('', { severity: 'NONE' })); + } } } diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index cdac188..c7b0bf8 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -17,7 +17,6 @@ import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { Action, - ActionHandlerRegistry, ActionMessage, Disposable, DisposableCollection, @@ -28,6 +27,7 @@ import { SModelRootSchema, TYPES } from '~glsp-sprotty'; +import { GLSPActionHandlerRegistry } from '../action-handler-registry'; import { IDiagramOptions } from './diagram-loader'; /** * A helper interface that allows the client to mark actions that have been received from the server. @@ -91,14 +91,23 @@ export class GLSPModelSource extends ModelSource implements Disposable { this.clientId = this.options.clientId ?? this.viewerOptions.baseDiv; } - configure(registry: ActionHandlerRegistry, initializeResult: InitializeResult): Promise { + configure(registry: GLSPActionHandlerRegistry, initializeResult: InitializeResult): Promise { const serverActions = initializeResult.serverActions[this.diagramType]; if (!serverActions || serverActions.length === 0) { throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${this.diagramType}!`); } + // Retrieve all currently handled action kinds. We do this before registering the server actions + // to ensure that the array will only contain client-side handled actions + const clientActionKinds = registry.getHandledActionKinds(); + serverActions.forEach(action => registry.register(action, this)); this.toDispose.push(this.glspClient!.onActionMessage(message => this.messageReceived(message), this.clientId)); - return this.glspClient!.initializeClientSession({ clientSessionId: this.clientId, diagramType: this.diagramType }); + + return this.glspClient!.initializeClientSession({ + clientSessionId: this.clientId, + clientActionKinds, + diagramType: this.diagramType + }); } protected messageReceived(message: ActionMessage): void { @@ -111,7 +120,7 @@ export class GLSPModelSource extends ModelSource implements Disposable { this.actionDispatcher.dispatch(action); } - override initialize(registry: ActionHandlerRegistry): void { + override initialize(registry: GLSPActionHandlerRegistry): void { // Registering actions here is discouraged and it's recommended // to implemented dedicated action handlers. if (!this.clientId) { diff --git a/packages/client/src/base/model/model-initialization-constraint.spec.ts b/packages/client/src/base/model/model-initialization-constraint.spec.ts new file mode 100644 index 0000000..712a1df --- /dev/null +++ b/packages/client/src/base/model/model-initialization-constraint.spec.ts @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import 'reflect-metadata'; +import * as sinon from 'sinon'; +import { Deferred, EMPTY_ROOT, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; +import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; +const sandbox = sinon.createSandbox(); +const container = new Container(); +let constraint: ModelInitializationConstraint; +// eslint-disable-next-line @typescript-eslint/no-empty-function +const listener = sandbox.spy((): void => {}); + +describe('DefaultModelInitializationConstraint', () => { + beforeEach(() => { + constraint = container.resolve(DefaultModelInitializationConstraint); + sandbox.reset(); + }); + it('should complete after dispatching non empty SetModelAction and `InitializeCanvasBoundsAction`', () => { + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched(SetModelAction.create({ id: 'model', type: 'graph' })); + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched({ kind: InitializeCanvasBoundsAction.KIND }); + expect(constraint.isCompleted).to.be.true; + }); + it('should complete after dispatching non empty UpdateModelAction and `InitializeCanvasBoundsAction`', () => { + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched(UpdateModelAction.create({ id: 'model', type: 'graph' })); + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched({ kind: InitializeCanvasBoundsAction.KIND }); + expect(constraint.isCompleted).to.be.true; + }); + it('should note complete after dispatching empty SetModelAction and `InitializeCanvasBoundsAction` ', () => { + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched(SetModelAction.create(EMPTY_ROOT)); + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched({ kind: InitializeCanvasBoundsAction.KIND }); + expect(constraint.isCompleted).to.be.false; + }); + it('should note complete after dispatching empty UpdateModelAction and `InitializeCanvasBoundsAction ', () => { + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched(UpdateModelAction.create(EMPTY_ROOT)); + expect(constraint.isCompleted).to.be.false; + constraint.notifyDispatched({ kind: InitializeCanvasBoundsAction.KIND }); + expect(constraint.isCompleted).to.be.false; + }); + describe('onInitialized', () => { + it('returned promise should resolve once the constraint is initialized', async () => { + const initializeDeferred = new Deferred(); + const initializePromise = constraint.onInitialized(); + initializePromise.then(() => initializeDeferred.resolve()); + expect(initializeDeferred.state).to.be.equal('unresolved'); + // Directly trigger the completion method simplify test logic + constraint['setCompleted'](); + // Short delay of test execution to ensure that the deferred state is updated. + await new Promise(resolve => setTimeout(resolve, 5)); + expect(initializeDeferred.state).to.be.equal('resolved'); + }); + it('registered listener should be invoked once the constraint is initialized', () => { + constraint.onInitialized(listener); + expect(listener.called).to.be.false; + // Directly trigger the completion method simplify test logic + constraint['setCompleted'](); + expect(listener.called).to.be.true; + }); + it('registered listener should be invoked directly on registration if the constraint is already initialized', () => { + // Directly trigger the completion method simplify test logic + constraint['setCompleted'](); + constraint.onInitialized(listener); + expect(listener.called).to.be.true; + }); + it('Disposed listener should not be invoked once the constraint is initialized', () => { + const toDispose = constraint.onInitialized(listener); + expect(listener.called).to.be.false; + toDispose.dispose(); + // Directly trigger the completion method simplify test logic + constraint['setCompleted'](); + expect(listener.called).to.be.false; + }); + }); +}); diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model/model-initialization-constraint.ts similarity index 65% rename from packages/client/src/base/model-initialization-constraint.ts rename to packages/client/src/base/model/model-initialization-constraint.ts index 67021a4..9914321 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model/model-initialization-constraint.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, Deferred, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; +import { Action, Deferred, Disposable, Emitter, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; /** * The constraint defining when the initialization of the GLSP model is completed. @@ -37,21 +37,46 @@ import { Action, Deferred, InitializeCanvasBoundsAction, SetModelAction, UpdateM @injectable() export abstract class ModelInitializationConstraint { protected completion: Deferred = new Deferred(); - protected completed = false; + protected _isCompleted = false; get isCompleted(): boolean { - return this.completed; + return this._isCompleted; } - protected setCompleted(isCompleted: boolean): void { - this.completed = isCompleted; - if (isCompleted) { - this.completion.resolve(); + protected onInitializedEmitter = new Emitter(); + + /** + * Register a listener that will be invoked once the initialization process + * has been completed. If the initialization is already completed on registration + * the given listener will be invoked right away + * @param listener + */ + onInitialized(listener: () => void): Disposable; + + /** + * Retrieve a promise that resolves once the initialization process + * has been completed. + * @returns the initialization promise + */ + onInitialized(): Promise; + onInitialized(listener?: () => void): Promise | Disposable { + if (!listener) { + return this.completion.promise; } + if (this.isCompleted) { + listener(); + return Disposable.empty(); + } + return this.onInitializedEmitter.event(listener); } - onInitialized(): Promise { - return this.completion.promise; + protected setCompleted(): void { + if (!this.isCompleted) { + this._isCompleted = true; + this.completion.resolve(); + this.onInitializedEmitter.fire(); + this.onInitializedEmitter.dispose(); + } } notifyDispatched(action: Action): void { @@ -59,10 +84,18 @@ export abstract class ModelInitializationConstraint { return; } if (this.isInitializedAfter(action)) { - this.setCompleted(true); + this.setCompleted(); } } + /** + * Central method to check the initialization state. Is invoked + * for every action dispatched by the `ActionDispatcher` (until the initialization has completed). + * Should + * return `true` once the action has been passed which marks the end + * of the initialization process. + * @param action The last dispatched action + */ abstract isInitializedAfter(action: Action): boolean; } diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 4a49833..0edab93 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -86,28 +86,26 @@ export class ElementNavigatorKeyListener extends KeyListener { } registerShortcutKey(): void { - this.tool.actionDispatcher.onceModelInitialized().then(() => { - this.tool.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [ - { shortcuts: ['N'], description: 'Activate default navigation', group: 'Navigation', position: 0 }, - { - shortcuts: ['ALT', 'N'], - description: 'Activate position based navigation', - group: 'Navigation', - position: 1 - }, - { - shortcuts: ['⬅ ⬆ ➡ ⬇'], - description: 'Navigate by relation or neighbors according to navigation mode', - group: 'Navigation', - position: 2 - } - ] - }) - ]); - }); + this.tool.actionDispatcher.dispatchOnceModelInitialized( + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['N'], description: 'Activate default navigation', group: 'Navigation', position: 0 }, + { + shortcuts: ['ALT', 'N'], + description: 'Activate position based navigation', + group: 'Navigation', + position: 1 + }, + { + shortcuts: ['⬅ ⬆ ➡ ⬇'], + description: 'Navigate by relation or neighbors according to navigation mode', + group: 'Navigation', + position: 2 + } + ] + }) + ); } override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index 5d99265..7ee776d 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -22,9 +22,9 @@ import { SelectionService } from '../../../base/selection-service'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import { ResizeElementAction, ResizeType } from './resize-key-handler'; -import { ShowToastMessageAction } from '../toast/toast-handler'; import * as messages from '../toast/messages.json'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import { ResizeElementAction, ResizeType } from './resize-key-handler'; @injectable() export class ResizeKeyTool implements Tool { @@ -63,19 +63,17 @@ export class ResizeKeyListener extends KeyListener implements AccessibleKeyShort } registerShortcutKey(): void { - this.tool.actionDispatcher.onceModelInitialized().then(() => { - this.tool.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [ - { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 }, - { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, - { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, - { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } - ] - }) - ]); - }); + this.tool.actionDispatcher.dispatchOnceModelInitialized( + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 }, + { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, + { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, + { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } + ] + }) + ); } override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index e23a9f3..b295d30 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -51,14 +51,12 @@ export class SearchAutocompletePaletteKeyListener extends KeyListener implements } registerShortcutKey(): void { - this.tool.actionDispatcher.onceModelInitialized().then(() => { - this.tool.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [{ shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 }] - }) - ]); - }); + this.tool.actionDispatcher.dispatchOnceModelInitialized( + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [{ shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 }] + }) + ); } override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 12d3b5b..c41389f 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -74,14 +74,12 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu } registerShortcutKey(): void { - this.tool.actionDispatcher.onceModelInitialized().then(() => { - this.tool.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [{ shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element or viewport', group: 'Move', position: 0 }] - }) - ]); - }); + this.tool.actionDispatcher.dispatchOnceModelInitialized( + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [{ shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element or viewport', group: 'Move', position: 0 }] + }) + ); } override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index 7018c4a..eb2b3d0 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -62,19 +62,17 @@ export class ZoomKeyListener extends KeyListener { } registerShortcutKey(): void { - this.tool.actionDispatcher.onceModelInitialized().then(() => { - this.tool.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [ - { shortcuts: ['+'], description: 'Zoom in to element or viewport', group: 'Zoom', position: 0 }, - { shortcuts: ['-'], description: 'Zoom out to element or viewport', group: 'Zoom', position: 1 }, - { shortcuts: ['CTRL', '0'], description: 'Reset zoom to default', group: 'Zoom', position: 2 }, - { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 3 } - ] - }) - ]); - }); + this.tool.actionDispatcher.dispatchOnceModelInitialized( + SetAccessibleKeyShortcutAction.create({ + token: this.token, + keys: [ + { shortcuts: ['+'], description: 'Zoom in to element or viewport', group: 'Zoom', position: 0 }, + { shortcuts: ['-'], description: 'Zoom out to element or viewport', group: 'Zoom', position: 1 }, + { shortcuts: ['CTRL', '0'], description: 'Reset zoom to default', group: 'Zoom', position: 2 }, + { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 3 } + ] + }) + ); } override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 9069715..b1ef242 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -23,8 +23,8 @@ import { MenuItem, Point, SModelRoot, - ServerMessageAction, - ServerStatusAction, + MessageAction, + StatusAction, TYPES, hasStringProp, isSelected @@ -83,10 +83,7 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { const message = `Please use the browser's ${operation} command or shortcut.`; const timeout = 10000; const severity = 'WARNING'; - this.dispatcher.dispatchAll([ - ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity }) - ]); + this.dispatcher.dispatchAll([StatusAction.create(message, { severity, timeout }), MessageAction.create(message, { severity })]); } } diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 67492ce..4b8a09b 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -200,7 +200,7 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDia return getTypeHint(input, this.edgeHints); } - preRequestModel(): MaybePromise { + postModelInitialization(): MaybePromise { this.actionDispatcher.dispatch(RequestTypeHintsAction.create()); } } diff --git a/packages/client/src/features/label-edit/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts index 8a9e490..32e8ba7 100644 --- a/packages/client/src/features/label-edit/edit-label-validator.ts +++ b/packages/client/src/features/label-edit/edit-label-validator.ts @@ -34,11 +34,11 @@ export namespace LabelEditValidation { export function toEditLabelValidationResult(status: ValidationStatus): EditLabelValidationResult { const message = status.message; - let severity = 'ok' as Severity; + let severity: Severity = 'ok'; if (ValidationStatus.isError(status)) { - severity = 'error' as Severity; + severity = 'error'; } else if (ValidationStatus.isWarning(status)) { - severity = 'warning' as Severity; + severity = 'warning'; } return { message, severity }; } @@ -61,7 +61,7 @@ export class ServerEditLabelValidator implements IEditLabelValidator { if (SetEditValidationResultAction.is(action)) { return LabelEditValidation.toEditLabelValidationResult(action.status); } - return { severity: 'ok' as Severity }; + return { severity: 'ok' }; } } diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 11e0c3b..b8944b8 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -23,17 +23,17 @@ import { IActionHandler, ICommand, ILogger, + MessageAction, NavigateToExternalTargetAction, NavigateToTargetAction, NavigationTarget, RequestNavigationTargetsAction, SelectAction, SelectAllAction, - ServerMessageAction, - ServerSeverity, - ServerStatusAction, SetNavigationTargetsAction, SetResolvedNavigationTargetAction, + SeverityLevel, + StatusAction, TYPES, hasObjectProp, hasStringProp @@ -264,11 +264,8 @@ export class NavigationActionHandler implements IActionHandler { this.notify('WARNING', message); } - private notify(severity: ServerSeverity, message: string): void { + private notify(severity: SeverityLevel, message: string): void { const timeout = this.notificationTimeout; - this.dispatcher.dispatchAll([ - ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity }) - ]); + this.dispatcher.dispatchAll([StatusAction.create(message, { severity, timeout }), MessageAction.create(message, { severity })]); } } diff --git a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts index 8cd79a8..c586ae3 100644 --- a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts +++ b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts @@ -18,9 +18,9 @@ import { Action, IActionDispatcher, IActionHandler, - ServerMessageAction, - ServerStatusAction, + MessageAction, SourceModelChangedAction, + StatusAction, TYPES, ViewerOptions } from '~glsp-sprotty'; @@ -68,9 +68,6 @@ export class SourceModelChangedActionHandler implements IActionHandler { const message = `The source model ${action.sourceModelName} has changed. You might want to consider reloading.`; const timeout = 0; const severity = 'WARNING'; - this.dispatcher.dispatchAll([ - ServerStatusAction.create(message, { severity, timeout }), - ServerMessageAction.create(message, { severity }) - ]); + this.dispatcher.dispatchAll([StatusAction.create(message, { severity, timeout }), MessageAction.create(message, { severity })]); } } diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 53520d4..6b1d7c1 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, ServerStatusAction, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, StatusAction, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; import '../../../css/status-overlay.css'; import { StatusOverlay } from './status-overlay'; @@ -22,5 +22,5 @@ export const statusModule = new FeatureModule((bind, unbind, isBound, rebind) => const context = { bind, unbind, isBound, rebind }; bindAsService(context, TYPES.IUIExtension, StatusOverlay); bind(TYPES.IDiagramStartup).toService(StatusOverlay); - configureActionHandler(context, ServerStatusAction.KIND, StatusOverlay); + configureActionHandler(context, StatusAction.KIND, StatusOverlay); }); diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 14f8bcc..3bac805 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -14,13 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { AbstractUIExtension, IActionHandler, ServerStatusAction, codiconCSSClasses } from '~glsp-sprotty'; +import { AbstractUIExtension, IActionHandler, StatusAction, codiconCSSClasses } from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; /** - * A reusable status overlay for rendering (icon + message) and handling of {@link ServerStatusAction}'s. + * A reusable status overlay for rendering (icon + message) and handling of {@link StatusAction}'s. */ @injectable() export class StatusOverlay extends AbstractUIExtension implements IActionHandler, IDiagramStartup { @@ -52,7 +52,7 @@ export class StatusOverlay extends AbstractUIExtension implements IActionHandler containerElement.appendChild(this.statusMessageDiv); } - protected setStatus(status: ServerStatusAction): void { + protected setStatus(status: StatusAction): void { if (this.statusMessageDiv) { this.statusMessageDiv.textContent = status.message; this.removeClasses(this.statusMessageDiv, 1); @@ -80,7 +80,7 @@ export class StatusOverlay extends AbstractUIExtension implements IActionHandler } protected clearStatus(): void { - this.setStatus(ServerStatusAction.create('', { severity: 'NONE' })); + this.setStatus(StatusAction.create('', { severity: 'NONE' })); } protected clearTimeout(): void { @@ -100,7 +100,7 @@ export class StatusOverlay extends AbstractUIExtension implements IActionHandler } } - handle(action: ServerStatusAction): void { + handle(action: StatusAction): void { this.clearTimeout(); if (action.severity === 'NONE') { this.clearStatus(); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index a2ddd95..768b68b 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -16,6 +16,7 @@ // ------------------ Base ------------------ export * from './base/action-dispatcher'; +export * from './base/action-handler-registry'; export * from './base/argumentable'; export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; @@ -28,9 +29,9 @@ export * from './base/feedback/feedback-command'; export * from './base/feedback/update-model-command'; export * from './base/focus/focus-state-change-action'; export * from './base/focus/focus-tracker'; -export * from './base/model-initialization-constraint'; export * from './base/model/diagram-loader'; export * from './base/model/glsp-model-source'; +export * from './base/model/model-initialization-constraint'; export * from './base/model/model-registry'; export * from './base/ranked'; export * from './base/selection-clearing-mouse-listener'; @@ -44,14 +45,14 @@ export * from './base/view/view-registry'; // ------------------ Features ------------------ export * from './base/feedback/css-feedback'; export * from './base/view/mouse-tool'; +export * from './features/accessibility/element-navigation/diagram-navigation-tool'; +export * from './features/accessibility/focus-tracker/focus-tracker-tool'; export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; +export * from './features/accessibility/toast/toast-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; export * from './features/accessibility/view-key-tools/movement-key-tool'; export * from './features/accessibility/view-key-tools/zoom-key-tool'; -export * from './features/accessibility/element-navigation/diagram-navigation-tool'; -export * from './features/accessibility/focus-tracker/focus-tracker-tool'; -export * from './features/accessibility/toast/toast-tool'; export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; export * from './features/bounds/hbox-layout'; @@ -125,13 +126,13 @@ export * from './views'; // ------------------ DI Modules ------------------ export * from './base/default.module'; export * from './features/accessibility/accessibility-module'; +export * from './features/accessibility/element-navigation/element-navigation-module'; +export * from './features/accessibility/focus-tracker/focus-tracker-module'; export * from './features/accessibility/move-zoom/move-zoom-module'; export * from './features/accessibility/resize-key-tool/resize-key-module'; export * from './features/accessibility/search/search-palette-module'; -export * from './features/accessibility/view-key-tools/view-key-tools-module'; -export * from './features/accessibility/element-navigation/element-navigation-module'; -export * from './features/accessibility/focus-tracker/focus-tracker-module'; export * from './features/accessibility/toast/toast-module'; +export * from './features/accessibility/view-key-tools/view-key-tools-module'; export * from './features/command-palette/command-palette-module'; export * from './features/context-menu/context-menu-module'; export * from './features/copy-paste/copy-paste-modules'; diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 326b096..d5e4f65 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -23,7 +23,7 @@ import { ICommand, ILogger, ModuleConfiguration, - ServerMessageAction, + MessageAction, StartProgressAction, TYPES, UpdateProgressAction, @@ -40,7 +40,7 @@ import { standaloneViewportModule } from './features/viewport/viewport-modules'; export const standaloneFallbackModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bind(FallbackActionHandler).toSelf().inSingletonScope(); - configureActionHandler(context, ServerMessageAction.KIND, FallbackActionHandler); + configureActionHandler(context, MessageAction.KIND, FallbackActionHandler); configureActionHandler(context, StartProgressAction.KIND, FallbackActionHandler); configureActionHandler(context, UpdateProgressAction.KIND, FallbackActionHandler); configureActionHandler(context, EndProgressAction.KIND, FallbackActionHandler); diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts index 528a581..9e1d8bd 100644 --- a/packages/protocol/src/action-protocol/base-protocol.ts +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { JsonPrimitive } from 'sprotty-protocol'; import * as sprotty from 'sprotty-protocol/lib/actions'; import { AnyObject, hasArrayProp, hasStringProp, TypeGuard } from '../utils/type-util'; @@ -150,7 +149,7 @@ export interface RejectAction extends ResponseAction, sprotty.RejectAction { /** * Optional additional details. */ - detail?: JsonPrimitive; + detail?: string; } export namespace RejectAction { @@ -160,7 +159,7 @@ export namespace RejectAction { return Action.hasKind(object, RejectAction.KIND) && hasStringProp(object, 'message'); } - export function create(message: string, options: { detail?: JsonPrimitive; responseId?: string } = {}): RejectAction { + export function create(message: string, options: { detail?: string; responseId?: string } = {}): RejectAction { return { kind: KIND, responseId: '', diff --git a/packages/protocol/src/action-protocol/client-notification.spec.ts b/packages/protocol/src/action-protocol/client-notification.spec.ts index bfd0bbf..48480fd 100644 --- a/packages/protocol/src/action-protocol/client-notification.spec.ts +++ b/packages/protocol/src/action-protocol/client-notification.spec.ts @@ -15,80 +15,74 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { - EndProgressAction, - ServerMessageAction, - ServerStatusAction, - StartProgressAction, - UpdateProgressAction -} from './client-notification'; +import { EndProgressAction, MessageAction, StartProgressAction, StatusAction, UpdateProgressAction } from './client-notification'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. */ describe('Client notification actions', () => { - describe('ServerStatusAction', () => { + describe('StatusAction', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { - const statusAction: ServerStatusAction = { kind: 'serverStatus', message: 'Some', severity: 'INFO' }; - expect(ServerStatusAction.is(statusAction)).to.be.true; + const statusAction: StatusAction = { kind: StatusAction.KIND, message: 'Some', severity: 'INFO' }; + expect(StatusAction.is(statusAction)).to.be.true; }); it('should return false for `undefined`', () => { - expect(ServerStatusAction.is(undefined)).to.be.false; + expect(StatusAction.is(undefined)).to.be.false; }); it('should return false for an object that does not have all required interface properties', () => { - expect(ServerStatusAction.is({ kind: 'notTheRightOne' })).to.be.false; + expect(StatusAction.is({ kind: 'notTheRightOne' })).to.be.false; }); }); describe('create', () => { it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { const message = 'someMessage'; - const expected: ServerStatusAction = { kind: ServerStatusAction.KIND, message, severity: 'INFO' }; - expect(ServerStatusAction.create(message)).to.deep.equals(expected); + const expected: StatusAction = { kind: StatusAction.KIND, message, severity: 'INFO' }; + expect(StatusAction.create(message)).to.deep.equals(expected); }); it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { - const expected: ServerStatusAction = { - kind: ServerStatusAction.KIND, + const expected: StatusAction = { + kind: StatusAction.KIND, message: 'someMessage', severity: 'ERROR', timeout: 5 }; const { message, severity, timeout } = expected; - expect(ServerStatusAction.create(message, { severity, timeout })).to.deep.equals(expected); + expect(StatusAction.create(message, { severity, timeout })).to.deep.equals(expected); }); }); }); - describe('ServerMessageAction', () => { + describe('MessageAction', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { - const messageAction: ServerMessageAction = { kind: 'serverMessage', message: '', severity: 'INFO' }; - expect(ServerMessageAction.is(messageAction)).to.be.true; + const messageAction: MessageAction = { kind: MessageAction.KIND, message: '', severity: 'INFO' }; + expect(MessageAction.is(messageAction)).to.be.true; }); it('should return false for `undefined`', () => { - expect(ServerMessageAction.is(undefined)).to.be.false; + expect(MessageAction.is(undefined)).to.be.false; }); it('should return false for an object that does not have all required interface properties', () => { - expect(ServerMessageAction.is({ kind: 'notTheRightOne' })).to.be.false; + expect(MessageAction.is({ kind: 'notTheRightOne' })).to.be.false; }); }); describe('create', () => { it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { const message = 'someMessage'; - const expected: ServerMessageAction = { kind: ServerMessageAction.KIND, message, severity: 'INFO' }; - expect(ServerMessageAction.create(message)).to.deep.equals(expected); + const expected: MessageAction = { kind: MessageAction.KIND, message, severity: 'INFO' }; + expect(MessageAction.create(message)).to.deep.equals(expected); }); it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { - const expected: ServerMessageAction = { - kind: ServerMessageAction.KIND, + const expected: MessageAction = { + kind: MessageAction.KIND, message: 'someMessage', details: 'details', severity: 'ERROR' }; const { message, severity, details } = expected; - expect(ServerMessageAction.create(message, { severity, details })).to.deep.equals(expected); + expect(MessageAction.create(message, { severity, details })).to.deep.equals(expected); }); }); }); diff --git a/packages/protocol/src/action-protocol/client-notification.ts b/packages/protocol/src/action-protocol/client-notification.ts index 13efc41..e9106c2 100644 --- a/packages/protocol/src/action-protocol/client-notification.ts +++ b/packages/protocol/src/action-protocol/client-notification.ts @@ -17,17 +17,17 @@ import { hasStringProp } from '../utils/type-util'; import { Action } from './base-protocol'; /** - * Sent by the server to signal a state change. + * Sent by the server (or the client) to signal a status change. * If a timeout is given the respective status should disappear after the timeout is reached. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `ServerStatusActions`. + * and creating new `StatusAction`s. */ -export interface ServerStatusAction extends Action { - kind: typeof ServerStatusAction.KIND; +export interface StatusAction extends Action { + kind: typeof StatusAction.KIND; /** * The severity of the status. */ - severity: ServerSeverity; + severity: SeverityLevel; /** * The user-facing message describing the status. @@ -40,14 +40,14 @@ export interface ServerStatusAction extends Action { timeout?: number; } -export namespace ServerStatusAction { - export const KIND = 'serverStatus'; +export namespace StatusAction { + export const KIND = 'status'; - export function is(object: any): object is ServerStatusAction { + export function is(object: any): object is StatusAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'severity') && hasStringProp(object, 'message'); } - export function create(message: string, options: { severity?: ServerSeverity; timeout?: number } = {}): ServerStatusAction { + export function create(message: string, options: { severity?: SeverityLevel; timeout?: number } = {}): StatusAction { return { kind: KIND, severity: 'INFO', @@ -61,19 +61,19 @@ export namespace ServerStatusAction { * The possible server status severity levels. */ -export type ServerSeverity = 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL' | 'OK'; +export type SeverityLevel = 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL' | 'OK'; /** - * Sent by the server to notify the user about something of interest. Typically this message is handled by + * Sent by the server (or the client) to notify the user about something of interest. Typically this message is handled by * the client by showing a message to the user with the application's message service. * If a timeout is given the respective message should disappear after the timeout is reached. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `ServerMessageActions`. + * and creating new `MessageAction`s. */ -export interface ServerMessageAction extends Action { - kind: typeof ServerMessageAction.KIND; +export interface MessageAction extends Action { + kind: typeof MessageAction.KIND; - severity: ServerSeverity; + severity: SeverityLevel; /** * The message that shall be shown to the user. @@ -86,20 +86,20 @@ export interface ServerMessageAction extends Action { details?: string; } -export namespace ServerMessageAction { - export const KIND = 'serverMessage'; +export namespace MessageAction { + export const KIND = 'message'; - export function is(object: any): object is ServerMessageAction { + export function is(object: any): object is MessageAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'message') && hasStringProp(object, 'severity'); } export function create( message: string, options: { - severity?: ServerSeverity; + severity?: SeverityLevel; details?: string; } = {} - ): ServerMessageAction { + ): MessageAction { return { kind: KIND, message, @@ -110,7 +110,7 @@ export namespace ServerMessageAction { } /** - * Sent by the server to the client to request presenting the progress of a long running process in the UI. + * Sent to request presenting the progress of a long running process in the UI. */ export interface StartProgressAction extends Action { kind: typeof StartProgressAction.KIND; @@ -149,7 +149,7 @@ export namespace StartProgressAction { } /** - * Sent by the server to the client to presenting an update of the progress of a long running process in the UI. + * Sent to presenting an update of the progress of a long running process in the UI. */ export interface UpdateProgressAction extends Action { kind: typeof UpdateProgressAction.KIND; @@ -191,7 +191,7 @@ export namespace UpdateProgressAction { } /** - * Sent by the server to the client to end the reporting of a progress. + * Sent to end the reporting of a progress. */ export interface EndProgressAction extends Action { kind: typeof EndProgressAction.KIND; diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index eadf305..298b459 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -149,18 +149,18 @@ describe('Node GLSP Client', () => { describe('initializeClientSession', () => { it('should fail if server is not configured', async () => { resetClient(false); - await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '', clientActionKinds: [] })); expect(server.initializeClientSession.called).to.be.false; }); it('should fail if client is not running', async () => { resetClient(false); client.configureServer(server); - await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '', clientActionKinds: [] })); expect(server.initializeClientSession.called).to.be.false; }); it('should invoke the corresponding server method', async () => { resetClient(); - const result = await client.initializeClientSession({ clientSessionId: '', diagramType: '' }); + const result = await client.initializeClientSession({ clientSessionId: '', diagramType: '', clientActionKinds: [] }); expect(result).to.be.undefined; expect(server.initializeClientSession.calledOnce).to.be.true; }); diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 64a32b4..d9c5741 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -158,12 +158,12 @@ describe('Base JSON-RPC GLSP Client', () => { describe('initializeClientSession', () => { it('should fail if client is not running', async () => { await resetClient(false); - await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '' })); + await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '', clientActionKinds: [] })); expect(connection.sendRequest.called).to.be.false; }); it('should invoke the corresponding server method', async () => { await resetClient(); - const params = { clientSessionId: '', diagramType: '' }; + const params = { clientSessionId: '', diagramType: '', clientActionKinds: [] }; const initializeSessionMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeClientSessionRequest, params); const result = await client.initializeClientSession(params); expect(result).to.be.undefined; diff --git a/packages/protocol/src/client-server-protocol/types.ts b/packages/protocol/src/client-server-protocol/types.ts index 44287d7..862c089 100644 --- a/packages/protocol/src/client-server-protocol/types.ts +++ b/packages/protocol/src/client-server-protocol/types.ts @@ -66,6 +66,12 @@ export interface InitializeClientSessionParameters { */ diagramType: string; + /** + * The set of action kinds that can be handled by the client. + * Used by the server to know which dispatched actions should be forwarded to the client. + */ + clientActionKinds: string[]; + /** * Additional custom arguments. */ From 7e25a0a118958469f71cefcef94ddea49309a976 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 18 Sep 2023 00:43:41 -0700 Subject: [PATCH 256/566] Adapt to latest protocol changes (#57) # GLSP-1116 Revise model loading - Refactor `ModelSubmissionHandler` to support proper handling of the `RequestModelAction` as real request action Part-of: https://github.com/eclipse-glsp/glsp/issues/1116 # GLSP-1117: Remove need for explicit definition of client actions Refactor the base GLSP protocol to allow the client to tell the server which actions it is going to handle i.e. which actions should be forwarded to the client - Add `clientActions` array to `InitializeClientSessionParams`. This means the client now has to pass the action kinds it wants to handle as part of the initalize request - Replace `ClientActionHandler` with `ClientActionForwader` a separate component that is not part of the server-side action handlers. - Remove `configureClientActions` method from `DiagramModule` as the explicit configuration is no longer needed Part of eclipse-glsp/glsp/issues/1117 #GLSP-1071: Rename ServerStatus/ServerMessage action Part of eclipse-glsp/glsp/issues/1071 --- .../common/actions/action-dispatcher.spec.ts | 2 + .../src/common/actions/action-dispatcher.ts | 13 ++-- .../common/actions/client-action-handler.ts | 46 ++++++----- .../actions/global-action-provider.spec.ts | 19 +---- .../common/actions/global-action-provider.ts | 46 +++++------ .../src/common/di/client-session-module.ts | 27 ++++--- .../server/src/common/di/diagram-module.ts | 77 ++----------------- .../server/src/common/di/server-module.ts | 1 - .../model/model-submission-handler.ts | 55 +++++++++++-- .../model/request-model-action-handler.ts | 8 +- .../operations/operation-action-handler.ts | 4 +- .../src/common/protocol/client-action.ts | 38 +++++++++ .../src/common/protocol/glsp-server.spec.ts | 21 ++--- .../server/src/common/protocol/glsp-server.ts | 25 +++--- .../session/client-session-factory.spec.ts | 4 +- .../common/session/client-session-factory.ts | 6 +- .../session/client-session-manager.spec.ts | 7 +- 17 files changed, 211 insertions(+), 188 deletions(-) create mode 100644 packages/server/src/common/protocol/client-action.ts diff --git a/packages/server/src/common/actions/action-dispatcher.spec.ts b/packages/server/src/common/actions/action-dispatcher.spec.ts index 9d95a05..f1d3421 100644 --- a/packages/server/src/common/actions/action-dispatcher.spec.ts +++ b/packages/server/src/common/actions/action-dispatcher.spec.ts @@ -24,6 +24,7 @@ import { Logger } from '../utils/logger'; import { DefaultActionDispatcher } from './action-dispatcher'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; +import { ClientActionForwarder } from './client-action-handler'; import assert = require('assert'); function waitSync(timeInMillis: number): void { @@ -48,6 +49,7 @@ describe('test DefaultActionDispatcher', () => { bind(ClientId).toConstantValue(clientId); bind(ActionHandlerRegistry).toConstantValue(actionHandlerRegistry); bind(ClientActionKinds).toConstantValue(['response', 'response1', 'response2']); + bind(ClientActionForwarder).toConstantValue(sinon.createStubInstance(ClientActionForwarder)); }) ); const actionDispatcher = container.resolve(DefaultActionDispatcher); diff --git a/packages/server/src/common/actions/action-dispatcher.ts b/packages/server/src/common/actions/action-dispatcher.ts index 861086f..0c634f4 100644 --- a/packages/server/src/common/actions/action-dispatcher.ts +++ b/packages/server/src/common/actions/action-dispatcher.ts @@ -15,12 +15,13 @@ ********************************************************************************/ import { Action, Disposable, flatPush, MaybeArray, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ClientActionKinds, ClientId } from '../di/service-identifiers'; +import { ClientId } from '../di/service-identifiers'; import { GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; import { PromiseQueue } from '../utils/promise-queue'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; +import { ClientActionForwarder } from './client-action-handler'; export const ActionDispatcher = Symbol('ActionDispatcher'); @@ -62,8 +63,8 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable { @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; - @inject(ClientActionKinds) - protected clientActionKinds: string[]; + @inject(ClientActionForwarder) + protected clientActionForwarder: ClientActionForwarder; @inject(Logger) private logger: Logger; @@ -76,7 +77,7 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable { dispatch(action: Action): Promise { // Dont queue actions that are just delegated to the client - if (this.clientActionKinds.includes(action.kind)) { + if (this.clientActionForwarder.shouldForwardToClient(action)) { return this.doDispatch(action); } return this.actionQueue.enqueue(() => this.doDispatch(action)); @@ -84,8 +85,10 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable { protected async doDispatch(action: Action): Promise { this.logger.debug('Dispatch action:', action.kind); + const handledOnClient = this.clientActionForwarder.handle(action); + const actionHandlers = this.actionHandlerRegistry.get(action.kind); - if (actionHandlers.length === 0) { + if (!handledOnClient && actionHandlers.length === 0) { throw new GLSPServerError(`No handler registered for action kind: ${action.kind}`); } diff --git a/packages/server/src/common/actions/client-action-handler.ts b/packages/server/src/common/actions/client-action-handler.ts index fa7d833..2cfa8d7 100644 --- a/packages/server/src/common/actions/client-action-handler.ts +++ b/packages/server/src/common/actions/client-action-handler.ts @@ -13,37 +13,47 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ActionMessage, GLSPClientProxy } from '@eclipse-glsp/protocol'; -import { inject, injectable, optional } from 'inversify'; +import { Action, ActionMessage, GLSPClientProxy, ResponseAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; import { ClientActionKinds, ClientId } from '../di/service-identifiers'; +import { ClientAction } from '../protocol/client-action'; /** - * The client action handler is responsible of handling action kinds that are intended for the - * GLSP client, by sending them to the client over json-rpc. + * Component responsible for forwarding actions that are (also) handled by the + * client */ - @injectable() -export class ClientActionHandler implements ClientActionHandler { +export class ClientActionForwarder { @inject(GLSPClientProxy) - @optional() - protected glspClient?: GLSPClientProxy; + protected glspClient: GLSPClientProxy; @inject(ClientId) protected readonly clientId: string; - constructor(@inject(ClientActionKinds) @optional() public actionKinds: string[] = []) {} + constructor(@inject(ClientActionKinds) public actionKinds: Set) {} - execute(action: Action): [] { - this.send(action); - return []; + /** + * Processes the given action and checks wether it is a + * `clientAction` i.e. an action that should be forwarded to + * the client to be handled there. If the check is successful + * the action is wrapped in an {@link ActionMessage} and sent to the client. + * + * @param action The action to check and forward + * @return `true` if the action was forwarded to the client, `false` otherwise + */ + handle(action: Action): boolean { + if (this.shouldForwardToClient(action)) { + const message: ActionMessage = { action, clientId: this.clientId }; + this.glspClient.process(message); + return true; + } + return false; } - protected send(action: Action): void { - const message: ActionMessage = { action, clientId: this.clientId }; - if (this.glspClient) { - this.glspClient.process(message); - return; + shouldForwardToClient(action: Action): boolean { + if (ClientAction.is(action)) { + return false; } - throw new Error('Could not send message to client. No GLSPClientProxy is defined'); + return this.actionKinds.has(action.kind) || ResponseAction.is(action); } } diff --git a/packages/server/src/common/actions/global-action-provider.spec.ts b/packages/server/src/common/actions/global-action-provider.spec.ts index 60b4cae..061d0c2 100644 --- a/packages/server/src/common/actions/global-action-provider.spec.ts +++ b/packages/server/src/common/actions/global-action-provider.spec.ts @@ -13,21 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; -import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import * as sinon from 'sinon'; +import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; import { ClientSessionInitializer } from '../session/client-session-initializer'; import * as mock from '../test/mock-util'; import { Logger } from '../utils/logger'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; import { DefaultGlobalActionProvider } from './global-action-provider'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; describe('test DefaultGlobalActionProvider', () => { const container = new Container(); const serverActions = ['A1', 'A2', 'A3']; - const clientActions = ['C1', 'C2']; const diagramType = 'myDiagramType'; const handler1Actions = ['A1', 'A2']; @@ -44,8 +43,6 @@ describe('test DefaultGlobalActionProvider', () => { bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler1Actions)); bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler2Actions)); bind(ActionHandlerRegistry).toConstantValue(handlerRegistry); - bind(ClientActionKinds).toConstantValue(clientActions[0]); - bind(ClientActionKinds).toConstantValue(clientActions[1]); }); const diagramModules = new Map(); @@ -62,18 +59,10 @@ describe('test DefaultGlobalActionProvider', () => { const actionProvider = container.resolve(DefaultGlobalActionProvider); it('serverActionsKinds', () => { - const result = actionProvider.serverActionKinds; + const result = actionProvider.actionKinds; expect(result.size).to.be.equal(1); const resultServerActions = result.get(diagramType); expect(resultServerActions).to.not.be.undefined; expect(serverActions.every(action => resultServerActions!.includes(action))).true; }); - - it('clientActionKinds', () => { - const result = actionProvider.clientActionKinds; - expect(result.size).to.be.equal(1); - const resultClientActions = result.get(diagramType); - expect(resultClientActions).to.not.be.undefined; - expect(clientActions.every(action => resultClientActions!.includes(action))).true; - }); }); diff --git a/packages/server/src/common/actions/global-action-provider.ts b/packages/server/src/common/actions/global-action-provider.ts index 01697af..d7e3e2e 100644 --- a/packages/server/src/common/actions/global-action-provider.ts +++ b/packages/server/src/common/actions/global-action-provider.ts @@ -13,60 +13,56 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { distinctAdd } from '@eclipse-glsp/protocol'; import { Container, ContainerModule, inject, injectable } from 'inversify'; -import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import { createClientSessionModule } from '../di/client-session-module'; +import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; import { ClientSessionInitializer } from '../session/client-session-initializer'; import { ActionHandlerRegistry } from './action-handler-registry'; -import { ClientActionHandler } from './client-action-handler'; export const GlobalActionProvider = Symbol('GlobalActionProvider'); +/** + * Provides a map of handled action kinds grouped by `diagramType` + */ export interface GlobalActionProvider { - readonly serverActionKinds: Map; - readonly clientActionKinds: Map; + readonly actionKinds: Map; } @injectable() export class DefaultGlobalActionProvider implements GlobalActionProvider { - public readonly serverActionKinds: Map; - public readonly clientActionKinds: Map; + public readonly actionKinds: Map; constructor( @inject(InjectionContainer) serverContainer: Container, @inject(DiagramModules) diagramModules: Map ) { - this.serverActionKinds = new Map(); - this.clientActionKinds = new Map(); + this.actionKinds = new Map(); diagramModules.forEach((modules, diagramType) => { const container = this.createDiagramContainer(serverContainer, modules); const initializers = container.getAll(ClientSessionInitializer); initializers.forEach(service => service.initialize()); - this.loadServerActionKinds(diagramType, container); - this.loadClientActionKinds(diagramType, container); + this.loadActionKinds(diagramType, container); container.unbindAll(); }); } createDiagramContainer(serverContainer: Container, modules: ContainerModule[]): Container { const container = serverContainer.createChild(); - container.load(...modules); + const clientSessionModule = createClientSessionModule({ + clientId: 'tempId', + // eslint-disable-next-line @typescript-eslint/no-empty-function + glspClient: { process: () => {} }, + clientActionKinds: [] + }); + container.load(...modules, clientSessionModule); return container; } - loadServerActionKinds(diagramType: string, diagramContainer: Container): void { + loadActionKinds(diagramType: string, diagramContainer: Container): void { const handlerRegistry = diagramContainer.get(ActionHandlerRegistry); - const diagramServerActions = this.serverActionKinds.get(diagramType) ?? []; - handlerRegistry - .getAll() - .filter(handler => !(handler instanceof ClientActionHandler)) - .forEach(handler => diagramServerActions.push(...handler.actionKinds)); - this.serverActionKinds.set(diagramType, [...new Set(diagramServerActions)]); - } - - loadClientActionKinds(diagramType: string, diagramContainer: Container): void { - const clientActionKinds = diagramContainer.getAll(ClientActionKinds); - const diagramClientActions = this.clientActionKinds.get(diagramType) ?? []; - diagramClientActions.push(...clientActionKinds); - this.clientActionKinds.set(diagramType, diagramClientActions); + const diagramServerActions = this.actionKinds.get(diagramType) ?? []; + handlerRegistry.getAll().forEach(handler => distinctAdd(diagramServerActions, ...handler.actionKinds)); + this.actionKinds.set(diagramType, diagramServerActions); } } diff --git a/packages/server/src/common/di/client-session-module.ts b/packages/server/src/common/di/client-session-module.ts index 59bf02b..af267d6 100644 --- a/packages/server/src/common/di/client-session-module.ts +++ b/packages/server/src/common/di/client-session-module.ts @@ -13,17 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GLSPClientProxy } from '@eclipse-glsp/protocol'; +import { GLSPClientProxy, bindOrRebind } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { ClientId } from './service-identifiers'; +import { ClientActionKinds, ClientId } from './service-identifiers'; -export function createClientSessionModule(clientId: string, glspClient: GLSPClientProxy): ContainerModule { - return new ContainerModule((bind, _unbind, isBound, rebind) => { - if (isBound(ClientId)) { - rebind(ClientId).toConstantValue(clientId); - } else { - bind(ClientId).toConstantValue(clientId); - } - bind(GLSPClientProxy).toConstantValue(glspClient); +export interface ClientSessionModuleOptions { + clientId: string; + glspClient: GLSPClientProxy; + clientActionKinds: string[]; +} + +/** + * Creates the DI module that binds client session specific configuration + */ +export function createClientSessionModule(options: ClientSessionModuleOptions): ContainerModule { + return new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindOrRebind(context, ClientId).toConstantValue(options.clientId); + bind(GLSPClientProxy).toConstantValue(options.glspClient); + bind(ClientActionKinds).toConstantValue(new Set(options.clientActionKinds)); }); } diff --git a/packages/server/src/common/di/diagram-module.ts b/packages/server/src/common/di/diagram-module.ts index 0beb575..b85d224 100644 --- a/packages/server/src/common/di/diagram-module.ts +++ b/packages/server/src/common/di/diagram-module.ts @@ -13,44 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - CenterAction, - DeleteMarkersAction, - EndProgressAction, - ExportSvgAction, - FitToScreenAction, - NavigateToExternalTargetAction, - NavigateToTargetAction, - RequestBoundsAction, - SelectAction, - SelectAllAction, - ServerMessageAction, - ServerStatusAction, - SetBoundsAction, - SetClipboardDataAction, - SetContextActions, - SetDirtyStateAction, - SetEditModeAction, - SetEditValidationResultAction, - SetMarkersAction, - SetModelAction, - SetNavigationTargetsAction, - SetPopupModelAction, - SetResolvedNavigationTargetAction, - SetTypeHintsAction, - SetViewportAction, - SourceModelChangedAction, - StartProgressAction, - TriggerEdgeCreationAction, - TriggerNodeCreationAction, - UpdateModelAction, - UpdateProgressAction -} from '@eclipse-glsp/protocol'; import { interfaces } from 'inversify'; import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dispatcher'; import { ActionHandlerConstructor, ActionHandlerFactory } from '../actions/action-handler'; import { ActionHandlerRegistry, ActionHandlerRegistryInitializer } from '../actions/action-handler-registry'; -import { ClientActionHandler } from '../actions/client-action-handler'; +import { ClientActionForwarder } from '../actions/client-action-handler'; import { CommandStack, DefaultCommandStack } from '../command/command-stack'; import { UndoRedoActionHandler } from '../command/undo-redo-action-handler'; import { DiagramConfiguration } from '../diagram/diagram-configuration'; @@ -101,7 +68,6 @@ import { BindingTarget, applyBindingTarget, applyOptionalBindingTarget } from '. import { GLSPModule } from './glsp-module'; import { InstanceMultiBinding, MultiBinding } from './multi-binding'; import { - ClientActionKinds, ClientId, ContextActionsProviders, ContextEditValidators, @@ -167,6 +133,7 @@ export abstract class DiagramModule extends GLSPModule { applyBindingTarget(context, DiagramType, this.bindDiagramType()); applyBindingTarget(context, ClientId, this.bindClientId()); applyBindingTarget(context, DiagramConfiguration, this.bindDiagramConfiguration()).inSingletonScope(); + applyBindingTarget(context, ClientActionForwarder, this.bindClientActionForwarder()).inSingletonScope(); // Model-related bindings applyBindingTarget(context, GModelSerializer, this.bindGModelSerializer()).inSingletonScope(); @@ -191,7 +158,6 @@ export abstract class DiagramModule extends GLSPModule { // Action & operation related bindings applyBindingTarget(context, ActionDispatcher, this.bindActionDispatcher()).inSingletonScope(); - this.configureMultiBinding(new InstanceMultiBinding(ClientActionKinds), binding => this.configureClientActions(binding)); this.configureMultiBinding(new InstanceMultiBinding(ActionHandlerConstructor), binding => this.configureActionHandlers(binding) ); @@ -233,7 +199,6 @@ export abstract class DiagramModule extends GLSPModule { } protected configureActionHandlers(binding: InstanceMultiBinding): void { - binding.add(ClientActionHandler); binding.add(RequestModelActionHandler); binding.add(RequestContextActionsHandler); binding.add(RequestTypeHintsActionHandler); @@ -260,6 +225,10 @@ export abstract class DiagramModule extends GLSPModule { return DefaultGModelSerializer; } + protected bindClientActionForwarder(): BindingTarget { + return ClientActionForwarder; + } + protected bindGModelIndex(): BindingTarget { return GModelIndex; } @@ -330,40 +299,6 @@ export abstract class DiagramModule extends GLSPModule { // empty as default } - protected configureClientActions(binding: InstanceMultiBinding): void { - binding.add(CenterAction.KIND); - binding.add(DeleteMarkersAction.KIND); - binding.add(EndProgressAction.KIND); - binding.add(ExportSvgAction.KIND); - binding.add(FitToScreenAction.KIND); - binding.add(NavigateToExternalTargetAction.KIND); - binding.add(NavigateToTargetAction.KIND); - binding.add(RequestBoundsAction.KIND); - binding.add(SelectAction.KIND); - binding.add(SelectAllAction.KIND); - binding.add(ServerMessageAction.KIND); - binding.add(ServerStatusAction.KIND); - binding.add(SetBoundsAction.KIND); - binding.add(SetClipboardDataAction.KIND); - binding.add(SetContextActions.KIND); - binding.add(SetDirtyStateAction.KIND); - binding.add(SetEditModeAction.KIND); - binding.add(SetEditValidationResultAction.KIND); - binding.add(SetMarkersAction.KIND); - binding.add(SetModelAction.KIND); - binding.add(SetNavigationTargetsAction.KIND); - binding.add(SetPopupModelAction.KIND); - binding.add(SetResolvedNavigationTargetAction.KIND); - binding.add(SetTypeHintsAction.KIND); - binding.add(SetViewportAction.KIND); - binding.add(SourceModelChangedAction.KIND); - binding.add(StartProgressAction.KIND); - binding.add(TriggerEdgeCreationAction.KIND); - binding.add(TriggerNodeCreationAction.KIND); - binding.add(UpdateModelAction.KIND); - binding.add(UpdateProgressAction.KIND); - } - protected bindContextActionsProviderRegistry(): BindingTarget { return ContextActionsProviderRegistry; } diff --git a/packages/server/src/common/di/server-module.ts b/packages/server/src/common/di/server-module.ts index dfaec07..209c873 100644 --- a/packages/server/src/common/di/server-module.ts +++ b/packages/server/src/common/di/server-module.ts @@ -41,7 +41,6 @@ import { DiagramModules, InjectionContainer } from './service-identifiers'; * * {@link ClientSessionFactory} * * {@link ClientSessionManager} * * {@link GlobalActionProvider} - * * {@link GLSPClientProxy} * */ export class ServerModule extends GLSPModule { diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index b8cb290..ad766df 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -13,13 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRootSchema } from '@eclipse-glsp/graph'; +import { GModelRoot, GModelRootSchema } from '@eclipse-glsp/graph'; import { Action, DirtyStateChangeReason, MarkersReason, MaybePromise, RequestBoundsAction, + RequestModelAction, SetDirtyStateAction, SetMarkersAction, SetModelAction, @@ -35,6 +36,15 @@ import { GModelFactory } from './gmodel-factory'; import { GModelSerializer } from './gmodel-serializer'; import { ModelState } from './model-state'; +/** + * Helper class that provides utility methods to handle model updates i.e. + * submit a new model to the client. In addition, to the core model update action this class + * also takes care of related behavior like dirty state handling, validation and client/server side layouting. + * Note that the submissions handler is only responsible for deriving the set of actions that comprise a model update + * but does not actually dispatch them. The returned actions should be either manually dispatched + * to the `ActionDispatcher`, or simply returned as the result of an + * `ActionHandler.execute` method. + */ @injectable() export class ModelSubmissionHandler { @inject(DiagramConfiguration) @@ -60,8 +70,34 @@ export class ModelSubmissionHandler { @optional() protected validator?: ModelValidator; + protected requestModelAction?: RequestModelAction; + /** - * Returns a list of actions to update the client-side model, based on the specified modelState. + * Returns a list of actions to submit the initial revision of the client-side model, based on the injected + * {@link GModelState}. Typically this method is invoked by the {@link RequestModelActionHandler} when the diagram + * is (re)loaded. + *

+ * These actions are not processed by this {@link ModelSubmissionHandler}, but should be either manually dispatched + * to the {@link ActionDispatcher}, or simply returned as the result of an + * {@link ActionHandler#execute(Action)} method. + *

+ * + * @param requestAction The {@link RequestModelAction} that triggere the initial model update + * @return A list of actions to be processed in order to submit the intial model. + * + */ + submitInitialModel(requestAction: RequestModelAction): MaybePromise { + /* + * In the default update action flow a `RequestModelAction` does not directly trigger a `SetModelAction` response + * (RequestModelAction (C) -> RequestBoundsAction (S) -> ComputedBoundsAction (C) -> SetModelACtion (S) + * Therefore we temporarily store the action later retrival + */ + this.requestModelAction = requestAction; + return this.submitModel(); + } + + /** + * Returns a list of actions to update the client-side model, based on the injected {@link ModelState} * * These actions are not processed by this {@link ModelSubmissionHandler}, but should be either manually dispatched * to the `ActionDispatcher`, or simply returned as the result of an `ActionHandler.execute()` method. @@ -71,13 +107,15 @@ export class ModelSubmissionHandler { */ submitModel(reason?: DirtyStateChangeReason): MaybePromise { this.modelFactory.createModel(); - this.modelState.root.revision = (this.modelState.root.revision ?? 0) + 1; + + const revision = this.requestModelAction ? 0 : this.modelState.root.revision! + 1; + this.modelState.root.revision = revision; const root = this.serializeGModel(); if (this.diagramConfiguration.needsClientLayout) { return [RequestBoundsAction.create(root), SetDirtyStateAction.create(this.commandStack.isDirty, { reason })]; } - return [SetModelAction.create(root)]; + return this.submitModelDirectly(reason); } /** @@ -102,7 +140,7 @@ export class ModelSubmissionHandler { } const result: Action[] = []; result.push( - root.revision === 0 + this.requestModelAction ? SetModelAction.create(root) : UpdateModelAction.create(root, { animate: this.diagramConfiguration.animatedUpdate }) ); @@ -116,6 +154,13 @@ export class ModelSubmissionHandler { return result; } + protected createSetModeAction(newRoot: GModelRoot): SetModelAction { + const responseId = this.requestModelAction?.requestId ?? ''; + const response = SetModelAction.create(newRoot, { responseId }); + this.requestModelAction = undefined; + return response; + } + protected serializeGModel(): GModelRootSchema { return this.serializer.createSchema(this.modelState.root); } diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 2b1b633..81e770b 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, RequestModelAction, ServerStatusAction } from '@eclipse-glsp/protocol'; +import { Action, RequestModelAction, StatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../../actions/action-dispatcher'; import { ActionHandler } from '../../actions/action-handler'; @@ -61,7 +61,7 @@ export class RequestModelActionHandler implements ActionHandler { } this.reportModelLoadingFinished(progress); - return this.submissionHandler.submitModel(); + return this.submissionHandler.submitModelDirectly(); } protected async handleReconnect(action: RequestModelAction): Promise { @@ -76,12 +76,12 @@ export class RequestModelActionHandler implements ActionHandler { } protected reportModelLoading(message: string): ProgressMonitor { - this.actionDispatcher.dispatch(ServerStatusAction.create(message, { severity: 'INFO' })); + this.actionDispatcher.dispatch(StatusAction.create(message, { severity: 'INFO' })); return this.progressService.start(message); } protected reportModelLoadingFinished(monitor: ProgressMonitor): void { - this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' })); + this.actionDispatcher.dispatch(StatusAction.create('', { severity: 'NONE' })); monitor.end(); } } diff --git a/packages/server/src/common/operations/operation-action-handler.ts b/packages/server/src/common/operations/operation-action-handler.ts index 7c57037..3412557 100644 --- a/packages/server/src/common/operations/operation-action-handler.ts +++ b/packages/server/src/common/operations/operation-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MaybePromise, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { Action, MaybePromise, MessageAction, Operation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { Command } from '../command/command'; @@ -47,7 +47,7 @@ export class OperationActionHandler implements ActionHandler { } if (this.modelState.isReadonly) { return [ - ServerMessageAction.create(`Server is in readonly-mode! Could not execute operation: ${action.kind}`, { + MessageAction.create(`Server is in readonly-mode! Could not execute operation: ${action.kind}`, { severity: 'WARNING' }) ]; diff --git a/packages/server/src/common/protocol/client-action.ts b/packages/server/src/common/protocol/client-action.ts new file mode 100644 index 0000000..3b4e45b --- /dev/null +++ b/packages/server/src/common/protocol/client-action.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from '@eclipse-glsp/protocol'; + +/** + * A helper interface that allows the server to mark actions that have been received from the client. + */ +export interface ClientAction extends Action { + __receivedFromClient: true; +} + +export namespace ClientAction { + export function is(object: unknown): object is ClientAction { + return Action.is(object) && '__receivedFromClient' in object && object.__receivedFromClient === true; + } + + /** + * Mark the given action as {@link ClientAction} by attaching the "__receivedFromClient" property + * @param action The action that should be marked as client action + */ + export function mark(action: Action): void { + (action as ClientAction).__receivedFromClient = true; + } +} diff --git a/packages/server/src/common/protocol/glsp-server.spec.ts b/packages/server/src/common/protocol/glsp-server.spec.ts index d72a96d..56ac6cc 100644 --- a/packages/server/src/common/protocol/glsp-server.spec.ts +++ b/packages/server/src/common/protocol/glsp-server.spec.ts @@ -20,6 +20,7 @@ import { InitializeClientSessionParameters, InitializeParameters } from '@eclipse-glsp/protocol'; +import * as assert from 'assert'; import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import * as sinon from 'sinon'; @@ -28,7 +29,6 @@ import { ClientSessionManager } from '../session/client-session-manager'; import * as mock from '../test/mock-util'; import { Logger } from '../utils/logger'; import { DefaultGLSPServer } from './glsp-server'; -import assert = require('assert'); describe('test DefaultGLSPServer', () => { const container = new Container(); @@ -36,8 +36,8 @@ describe('test DefaultGLSPServer', () => { const diagramType = 'myDiagram'; const applicationId = 'Test'; const protocolVersion = '1.0.0'; - const serverActionKinds = new Map(); - serverActionKinds.set(diagramType, ['A1', 'A2']); + const actionKinds = new Map(); + actionKinds.set(diagramType, ['A1', 'A2']); const sessionManager = new mock.StubClientSessionManager(); const spy_sessionManager_getOrCreate = sinon.spy(sessionManager, 'getOrCreateClientSession'); const spy_sessionManager_dispose = sinon.spy(sessionManager, 'disposeClientSession'); @@ -53,7 +53,7 @@ describe('test DefaultGLSPServer', () => { bind(Logger).toConstantValue(new mock.StubLogger()); bind(GLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); bind(ClientSessionManager).toConstantValue(sessionManager); - bind(GlobalActionProvider).toConstantValue({ clientActionKinds: new Map(), serverActionKinds }); + bind(GlobalActionProvider).toConstantValue({ actionKinds }); bind(GLSPServerListener).toConstantValue(listener1); }) ); @@ -70,7 +70,7 @@ describe('test DefaultGLSPServer', () => { }); it('Test calls before server initialization (should throw errors)', async () => { - assert.rejects(() => glspServer.initializeClientSession({ clientSessionId: 'id', diagramType: 'type' })); + assert.rejects(() => glspServer.initializeClientSession({ clientSessionId: 'id', diagramType: 'type', clientActionKinds: [] })); assert.rejects(() => glspServer.disposeClientSession({ clientSessionId: 'id' })); assert.throws(() => glspServer.process({ clientId: 'id', action: { kind: 'action' } })); }); @@ -109,8 +109,8 @@ describe('test DefaultGLSPServer', () => { const initializeParameters: InitializeParameters = { applicationId, protocolVersion }; const result = await glspServer.initialize(initializeParameters); expect(result.protocolVersion).to.be.equal(protocolVersion); - expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); - expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(actionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(actionKinds.get(diagramType)); expect(spy_listener1_initialize.calledWith(glspServer)); expect(spy_listener2_initialize.notCalled); }); @@ -119,8 +119,8 @@ describe('test DefaultGLSPServer', () => { const initializeParameters: InitializeParameters = { applicationId, protocolVersion }; const result = await glspServer.initialize(initializeParameters); expect(result.protocolVersion).to.be.equal(protocolVersion); - expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); - expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(actionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(actionKinds.get(diagramType)); }); it('initialize - subsequent call with other parameters', async () => { @@ -131,7 +131,8 @@ describe('test DefaultGLSPServer', () => { it('initialize client session', async () => { const initializeClientSessionParameters: InitializeClientSessionParameters = { clientSessionId, - diagramType + diagramType, + clientActionKinds: [] }; await glspServer.initializeClientSession(initializeClientSessionParameters); expect(spy_sessionManager_getOrCreate.calledWith(initializeClientSessionParameters)); diff --git a/packages/server/src/common/protocol/glsp-server.ts b/packages/server/src/common/protocol/glsp-server.ts index a87cb4e..01331f6 100644 --- a/packages/server/src/common/protocol/glsp-server.ts +++ b/packages/server/src/common/protocol/glsp-server.ts @@ -17,7 +17,6 @@ import { ActionMessage, Args, DisposeClientSessionParameters, - distinctAdd, GLSPClientProxy, GLSPServer, GLSPServerListener, @@ -25,9 +24,10 @@ import { InitializeParameters, InitializeResult, MaybePromise, - remove, + MessageAction, ServerActions, - ServerMessageAction + distinctAdd, + remove } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; import { GlobalActionProvider } from '../actions/global-action-provider'; @@ -35,6 +35,7 @@ import { ClientSession } from '../session/client-session'; import { ClientSessionManager } from '../session/client-session-manager'; import { GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; +import { ClientAction } from './client-action'; @injectable() export class DefaultGLSPServer implements GLSPServer { @@ -50,8 +51,7 @@ export class DefaultGLSPServer implements GLSPServer { protected actionProvider: GlobalActionProvider; @inject(GLSPClientProxy) - @optional() - protected glspClientProxy?: GLSPClientProxy; + protected glspClientProxy: GLSPClientProxy; protected initializeResult?: InitializeResult; @@ -97,7 +97,7 @@ export class DefaultGLSPServer implements GLSPServer { this.applicationId = params.applicationId; const serverActions: ServerActions = {}; - this.actionProvider.serverActionKinds.forEach((kinds, diagramType) => (serverActions[diagramType] = kinds)); + this.actionProvider.actionKinds.forEach((kinds, diagramType) => (serverActions[diagramType] = kinds)); let result = { protocolVersion: DefaultGLSPServer.PROTOCOL_VERSION, serverActions }; @@ -149,8 +149,9 @@ export class DefaultGLSPServer implements GLSPServer { if (!clientSession) { throw new Error(`No client session has been initialized for client id: ${clientSessionId}`); } - - clientSession.actionDispatcher.dispatch(message.action).catch(error => this.handleProcessError(message, error)); + const action = message.action; + ClientAction.mark(action); + clientSession.actionDispatcher.dispatch(action).catch(error => this.handleProcessError(message, error)); } protected handleProcessError(message: ActionMessage, reason: any): void | PromiseLike { @@ -162,16 +163,12 @@ export class DefaultGLSPServer implements GLSPServer { details = reason.cause?.toString?.(); errorMsg = reason.message; } - const errorAction = ServerMessageAction.create(errorMsg, { severity: 'ERROR', details }); + const errorAction = MessageAction.create(errorMsg, { severity: 'ERROR', details }); this.sendToClient({ clientId: message.clientId, action: errorAction }); } protected sendToClient(message: ActionMessage): void { - if (this.glspClientProxy) { - this.glspClientProxy.process(message); - return; - } - throw new Error("Could not send message to client. No 'GLSPClientProxy' is connected"); + this.glspClientProxy.process(message); } getClientSession(sessionId: string): ClientSession | undefined { diff --git a/packages/server/src/common/session/client-session-factory.spec.ts b/packages/server/src/common/session/client-session-factory.spec.ts index 957e1da..b015c00 100644 --- a/packages/server/src/common/session/client-session-factory.spec.ts +++ b/packages/server/src/common/session/client-session-factory.spec.ts @@ -47,7 +47,7 @@ describe('test DefaultClientSessionFactory', () => { const factory = container.resolve(DefaultClientSessionFactory); it('create - new client session', () => { - const session = factory.create({ clientSessionId, diagramType }); + const session = factory.create({ clientSessionId, diagramType, clientActionKinds: [] }); expect(session.id).to.be.equal(clientSessionId); expect(session.diagramType).to.be.equal(diagramType); expect(session.container.parent).to.be.equal(container); @@ -55,6 +55,6 @@ describe('test DefaultClientSessionFactory', () => { }); it('create - unknown diagram type', () => { - expect(() => factory.create({ clientSessionId, diagramType: 'unknown-type' })).to.throw(GLSPServerError); + expect(() => factory.create({ clientSessionId, diagramType: 'unknown-type', clientActionKinds: [] })).to.throw(GLSPServerError); }); }); diff --git a/packages/server/src/common/session/client-session-factory.ts b/packages/server/src/common/session/client-session-factory.ts index 1d11bdc..a55272b 100644 --- a/packages/server/src/common/session/client-session-factory.ts +++ b/packages/server/src/common/session/client-session-factory.ts @@ -52,18 +52,18 @@ export class DefaultClientSessionFactory implements ClientSessionFactory { protected glspClient: GLSPClientProxy; create(params: InitializeClientSessionParameters): ClientSession { - const { clientSessionId: id, diagramType, args } = params; + const { clientSessionId: clientId, diagramType, clientActionKinds, args } = params; const diagramModules = this.diagramModules.get(diagramType); if (!diagramModules || diagramModules.length === 0) { throw new GLSPServerError(`Could not retrieve diagram module configuration for diagram type: '${diagramType}''`); } - const sessionModule = createClientSessionModule(id, this.glspClient); + const sessionModule = createClientSessionModule({ clientId, glspClient: this.glspClient, clientActionKinds }); const sessionContainer = this.serverContainer.createChild(); sessionContainer.load(...diagramModules, sessionModule); const initializers = sessionContainer.getAll(ClientSessionInitializer); initializers.forEach(service => service.initialize(args)); const actionDispatcher = sessionContainer.get(ActionDispatcher); - return new DefaultClientSession(id, diagramType, actionDispatcher, sessionContainer); + return new DefaultClientSession(clientId, diagramType, actionDispatcher, sessionContainer); } } diff --git a/packages/server/src/common/session/client-session-manager.spec.ts b/packages/server/src/common/session/client-session-manager.spec.ts index 5a013e7..0e79b75 100644 --- a/packages/server/src/common/session/client-session-manager.spec.ts +++ b/packages/server/src/common/session/client-session-manager.spec.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; +import * as sinon from 'sinon'; import * as mock from '../test/mock-util'; import { Logger } from '../utils/logger'; import { ClientSessionFactory } from './client-session-factory'; import { DefaultClientSessionManager } from './client-session-manager'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; describe('test DefaultClientSessionManager', () => { const testSession = mock.createClientSession('myId', 'myDiagram'); @@ -47,7 +47,8 @@ describe('test DefaultClientSessionManager', () => { // Test execution const createdSession = sessionManager.getOrCreateClientSession({ clientSessionId: testSession.id, - diagramType: testSession.diagramType + diagramType: testSession.diagramType, + clientActionKinds: [] }); expect(createdSession).to.not.be.undefined; expect(createdSession.id).to.be.equal(testSession.id); From 824298f757faec869ffad2b1365d1d26815a510e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 21 Sep 2023 05:57:44 -0700 Subject: [PATCH 257/566] GLSP-1116: Folllow-up fixes (#288) * GLSP-1116: Folllow-up fixes - Refactor diagramLoader+modelsource: Avoid concurrency/timing issues between diagramloader & modelsource. In some cases the loader did send actions before the model source was fully configured. Now the loader explictly configures the model source before dispatching the model request actions. - Ensure that client session is disposed if `GlspModelSource` is disposed - Ensure that the `dispatch` method correctly awaits the super result - Fix typing of `requstUntil`. This method can actually also return undefined if the timeout was reached and `rejectOnTimeout` is false + adapt affected code - Refactor `handleAction` method to allow dispatching of received responses - Rename that contains `GLSPContextMenuMouseListener` to reflect class name - Refactor `IContextMenuServiceProvider` to return an no-op implementation if no `IContextMenuService` is bound instead of rejecting the promise - Fix typing for Request actions (see https://github.com/eclipse-sprotty/sprotty/issues/378) - Add test cases - Remove deprecated code - Fix return type of `ExportSvgAction.is` Follow up for eclipse-glsp/glsp/issues/1116 * Add early-exit condition to `FeedbackActionDispatcher` Add early-exit condition to `FeedbackActionDispatcher` to avoid unnecesary feedback dispatching (and logging) when the actions array is empty --- .../client/src/base/action-dispatcher.spec.ts | 87 +++++++++++++++++++ packages/client/src/base/action-dispatcher.ts | 18 ++-- packages/client/src/base/default.module.ts | 11 +-- .../feedback/feedback-action-dispatcher.ts | 8 +- .../client/src/base/model/diagram-loader.ts | 14 +-- .../src/base/model/glsp-model-source.ts | 79 ++++++++++------- .../client/src/base/selection-service.spec.ts | 19 ++-- packages/client/src/default-modules.ts | 28 +----- .../server-command-palette-provider.ts | 5 +- .../context-menu/context-menu-module.ts | 21 +++-- ...ts => glsp-context-menu-mouse-listener.ts} | 0 .../server-context-menu-provider.ts | 5 +- .../client/src/features/hints/type-hints.ts | 2 +- .../label-edit/edit-label-validator.ts | 5 +- packages/client/src/glsp-sprotty/index.ts | 9 +- packages/client/src/glsp-sprotty/types.ts | 7 -- packages/client/src/index.ts | 2 +- .../src/action-protocol/base-protocol.ts | 4 + .../src/action-protocol/model-saving.ts | 2 +- .../jsonrpc/base-jsonrpc-glsp-client.ts | 7 -- 20 files changed, 199 insertions(+), 134 deletions(-) create mode 100644 packages/client/src/base/action-dispatcher.spec.ts rename packages/client/src/features/context-menu/{selection-service-aware-context-menu-mouse-listener.ts => glsp-context-menu-mouse-listener.ts} (100%) diff --git a/packages/client/src/base/action-dispatcher.spec.ts b/packages/client/src/base/action-dispatcher.spec.ts new file mode 100644 index 0000000..0cee9db --- /dev/null +++ b/packages/client/src/base/action-dispatcher.spec.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import { ActionHandlerRegistry, IActionHandler, RequestAction, ResponseAction, TYPES } from '~glsp-sprotty'; +import { IDiagramOptions } from '..'; +import { GLSPActionDispatcher } from './action-dispatcher'; +import { defaultModule } from './default.module'; + +const container = new Container(); +container.load(defaultModule); +container.bind(TYPES.IDiagramOptions).toConstantValue(({ + clientId: 'client1', + diagramType: 'diagramType', + glspClientProvider: async () => ({} as any) +})); +const registry = container.get(ActionHandlerRegistry); +const actionDispatcher = container.get(GLSPActionDispatcher); + +let testHandlerDelay = 0; +const testHandler: IActionHandler = { + handle: action => { + const request = action as RequestAction; + new Promise(resolve => setTimeout(resolve, testHandlerDelay)).then(() => + actionDispatcher.dispatch({ + kind: 'response', + responseId: request.requestId + }) + ); + } +}; +registry.register('request', testHandler); +// eslint-disable-next-line @typescript-eslint/no-empty-function +registry.register('response', { handle: () => {} }); +actionDispatcher.initialize().then(() => { + actionDispatcher['blockUntil'] = undefined; +}); + +describe('GLSPActionDispatcher', () => { + describe('requestUntil', () => { + it('should resolve successfully if response dispatched within timeout', async () => { + testHandlerDelay = 15; + const requestAction = { kind: 'request', requestId: '' }; + const response = await actionDispatcher.requestUntil(requestAction, 150); + expect(response?.responseId).to.be.equal(requestAction.requestId); + }); + it('should resolve to `undefined` if no response dispatched within timeout & `rejectOnTimeout` flag is false', async () => { + testHandlerDelay = 30; + const requestAction = { kind: 'request', requestId: '' }; + const response = await actionDispatcher.requestUntil(requestAction, 5); + expect(response).to.be.undefined; + }); + it('should be rejected if no response dispatched within timeout & `rejectOnTimeout` flag is true', async () => { + testHandlerDelay = 30; + const requestAction = { kind: 'request', requestId: '' }; + const gotRejected = await actionDispatcher.requestUntil(requestAction, 5, true).then( + () => false, + () => true + ); + expect(gotRejected, 'Response promise should be rejected').to.be.true; + }); + }); + describe('request & re-dispatch', () => { + it('should be possible to re-dispatch the response of a `request` call', async () => { + const requestAction = { kind: 'request', requestId: '' }; + const response = await actionDispatcher.request(requestAction); + const dispatchSuccessful = await actionDispatcher.dispatch(response).then( + () => true, + err => false + ); + expect(dispatchSuccessful, 'Promise of re-dispatch should resolve successfully').to.be.true; + }); + }); +}); diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 40349cd..cb6cdb2 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -54,8 +54,8 @@ export class GLSPActionDispatcher extends ActionDispatcher { this.initializationConstraint.onInitialized(() => this.dispatchAll(actions)); } - override dispatch(action: Action): Promise { - const result = super.dispatch(action); + override async dispatch(action: Action): Promise { + const result = await super.dispatch(action); this.initializationConstraint.notifyDispatched(action); return result; } @@ -69,11 +69,11 @@ export class GLSPActionDispatcher extends ActionDispatcher { this.timeouts.delete(action.responseId); } - // we might have reached a timeout, so we simply drop the response + // Check if we have a pending request for the response. + // If not the we clear the responseId => action will be dispatched normally const deferred = this.requests.get(action.responseId); if (deferred === undefined) { - this.logger.log(this, 'No matching request for response', action); - return Promise.resolve(); + action.responseId = ''; } } return super.handleAction(action); @@ -97,7 +97,11 @@ export class GLSPActionDispatcher extends ActionDispatcher { * If `rejectOnTimeout` is set to false (default) the returned promise will be resolved with * no value, otherwise it will be rejected. */ - requestUntil(action: RequestAction, timeoutMs = 2000, rejectOnTimeout = false): Promise { + requestUntil( + action: RequestAction, + timeoutMs = 2000, + rejectOnTimeout = false + ): Promise { if (!action.requestId && action.requestId === '') { // No request id has been specified. So we use a generated one. action.requestId = RequestAction.generateRequestId(); @@ -122,6 +126,6 @@ export class GLSPActionDispatcher extends ActionDispatcher { }, timeoutMs); this.timeouts.set(requestId, timeout); - return super.request(action); + return super.request(action); } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 78bf8e2..c8cd96a 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -62,16 +62,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . const context = { bind, unbind, isBound, rebind }; bind(EditorContextService).toSelf().inSingletonScope(); - bind(TYPES.IEditorContextServiceProvider).toProvider( - ctx => () => - new Promise((resolve, reject) => { - if (ctx.container.isBound(EditorContextService)) { - resolve(ctx.container.get(EditorContextService)); - } else { - reject(); - } - }) - ); + bind(TYPES.IEditorContextServiceProvider).toProvider(ctx => async () => ctx.container.get(EditorContextService)); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index 5762605..bafdcbf 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -67,14 +67,16 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { @inject(TYPES.ILogger) protected logger: ILogger; registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable { - this.registeredFeedback.set(feedbackEmitter, feedbackActions); - this.dispatchFeedback(feedbackActions, feedbackEmitter); + if (feedbackActions.length > 0) { + this.registeredFeedback.set(feedbackEmitter, feedbackActions); + this.dispatchFeedback(feedbackActions, feedbackEmitter); + } return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); } deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[] | undefined): void { this.registeredFeedback.delete(feedbackEmitter); - if (cleanupActions) { + if (cleanupActions && cleanupActions.length > 0) { this.dispatchFeedback(cleanupActions, feedbackEmitter); } } diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 8112de0..b0da8cc 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -49,6 +49,7 @@ export interface IDiagramOptions { diagramType: string; /** * The provider function to retrieve the GLSP client used by this diagram to communicate with the server. + * Multiple invocations of the provder function should always return the same `GLSPClient` instance. */ glspClientProvider: () => Promise; /** @@ -172,8 +173,7 @@ export class DiagramLoader { }, enableNotifications: options.enableNotifications ?? true }; - await this.actionDispatcher.initialize(); - // Set placeholder model until real model from server is available + // Set empty place holder model until actual model from server is available await this.actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT)); await this.invokeStartupHook('preInitialize'); await this.initialize(resolvedOptions); @@ -190,21 +190,23 @@ export class DiagramLoader { } protected async requestModel(options: ResolvedDiagramLoadingOptions): Promise { - return this.actionDispatcher.dispatch(RequestModelAction.create({ options: options.requestModelOptions })); + const response = await this.actionDispatcher.request( + RequestModelAction.create({ options: options.requestModelOptions }) + ); + return this.actionDispatcher.dispatch(response); } protected async initialize(options: ResolvedDiagramLoadingOptions): Promise { if (options.enableNotifications) { - this.actionDispatcher.dispatch(StatusAction.create('Initializing...', { severity: 'INFO' })); + await this.actionDispatcher.dispatch(StatusAction.create('Initializing...', { severity: 'INFO' })); } const glspClient = await this.options.glspClientProvider(); - await glspClient.start(); - if (!glspClient.initializeResult) { await glspClient.initializeServer(options.initializeParameters); } + this.modelSource.configure(glspClient); if (options.enableNotifications) { this.actionDispatcher.dispatch(StatusAction.create('', { severity: 'NONE' })); diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index c7b0bf8..a0251ce 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -14,14 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, postConstruct, preDestroy } from 'inversify'; +import { inject, injectable, preDestroy } from 'inversify'; import { Action, ActionMessage, Disposable, DisposableCollection, + DisposeClientSessionParameters, GLSPClient, ILogger, + InitializeClientSessionParameters, InitializeResult, ModelSource, SModelRootSchema, @@ -71,8 +73,9 @@ export class GLSPModelSource extends ModelSource implements Disposable { protected toDispose = new DisposableCollection(); clientId: string; - protected _glspClient: GLSPClient | undefined; protected _currentRoot: SModelRootSchema; + protected registry: GLSPActionHandlerRegistry; + protected glspClient?: GLSPClient; get diagramType(): string { return this.options.diagramType; @@ -82,32 +85,52 @@ export class GLSPModelSource extends ModelSource implements Disposable { return this.options.sourceUri; } - get glspClient(): GLSPClient | undefined { - return this._glspClient; - } + /** + * Configure forwarding of server-handled actions to the given {@link GLSPClient} and + * handling of action received from the `GLSPClient` (i.e. server). It is expected that the + * given GLSP client has already been initialized. + * @param glspClient The GLSP to use. + * @throws An error if the given `GLSPClient` has not been initialized yet or if the set of server handled + * action kinds could not be derived from the initialize result + */ + configure(glspClient: GLSPClient): Promise { + this.glspClient = glspClient; + if (!glspClient.initializeResult) { + throw new Error('Could not configure model source. The GLSP client is not initialized yet!'); + } - @postConstruct() - protected postConstruct(): void { - this.clientId = this.options.clientId ?? this.viewerOptions.baseDiv; - } + const initializeParams = this.createInitializeClientSessionParameters(glspClient.initializeResult); + this.configureServeActions(glspClient.initializeResult); - configure(registry: GLSPActionHandlerRegistry, initializeResult: InitializeResult): Promise { - const serverActions = initializeResult.serverActions[this.diagramType]; - if (!serverActions || serverActions.length === 0) { - throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${this.diagramType}!`); - } - // Retrieve all currently handled action kinds. We do this before registering the server actions - // to ensure that the array will only contain client-side handled actions - const clientActionKinds = registry.getHandledActionKinds(); + this.toDispose.push( + glspClient.onActionMessage(message => this.messageReceived(message), this.clientId), + Disposable.create(() => glspClient.disposeClientSession(this.createDisposeClientSessionParameters())) + ); - serverActions.forEach(action => registry.register(action, this)); - this.toDispose.push(this.glspClient!.onActionMessage(message => this.messageReceived(message), this.clientId)); + return glspClient!.initializeClientSession(initializeParams); + } - return this.glspClient!.initializeClientSession({ + protected createInitializeClientSessionParameters(_initializeResult: InitializeResult): InitializeClientSessionParameters { + const clientActionKinds = this.registry.getHandledActionKinds(); + return { clientSessionId: this.clientId, clientActionKinds, diagramType: this.diagramType - }); + }; + } + + protected createDisposeClientSessionParameters(): DisposeClientSessionParameters { + return { + clientSessionId: this.clientId + }; + } + + protected configureServeActions(initializeResult: InitializeResult): void { + const serverActions = initializeResult.serverActions[this.diagramType]; + if (serverActions?.length === 0) { + throw new Error(`No server-handled actions could be derived from the initialize result for diagramType: ${this.diagramType}!`); + } + serverActions.forEach(action => this.registry.register(action, this)); } protected messageReceived(message: ActionMessage): void { @@ -124,20 +147,10 @@ export class GLSPModelSource extends ModelSource implements Disposable { // Registering actions here is discouraged and it's recommended // to implemented dedicated action handlers. if (!this.clientId) { - this.clientId = this.viewerOptions.baseDiv; + this.clientId = this.options.clientId ?? this.viewerOptions.baseDiv; } - this.options.glspClientProvider().then(glspClient => { - this._glspClient = glspClient; - if (glspClient.initializeResult) { - this.configure(registry, glspClient.initializeResult); - } else { - const initializeListener = glspClient.onServerInitialized(result => { - this.configure(registry, result); - initializeListener.dispose(); - }); - } - }); + this.registry = registry; } handle(action: Action): void { diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index 7a56567..e621e59 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -16,7 +16,7 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { Action, Disposable, SGraphFactory, SModelElementSchema, SModelRoot, TYPES, initializeContainer } from '~glsp-sprotty'; +import { Action, Disposable, SGraphFactory, SModelRoot, SNode, TYPES, initializeContainer } from '~glsp-sprotty'; import { defaultModule } from './default.module'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @@ -62,7 +62,6 @@ function createContainer(): Container { describe('SelectionService', () => { // eslint-disable-next-line deprecation/deprecation - let graphFactory: SGraphFactory; let root: SModelRoot; let selectionService: SelectionService; let feedbackDispatcher: MockFeedbackActionDispatcher; @@ -70,7 +69,6 @@ describe('SelectionService', () => { beforeEach(() => { const container = createContainer(); // eslint-disable-next-line deprecation/deprecation - graphFactory = container.get(TYPES.IModelFactory); root = createRoot('node1', 'node2', 'node3', 'node4', 'node5'); selectionService = container.get(SelectionService); feedbackDispatcher = container.get(TYPES.IFeedbackActionDispatcher); @@ -243,13 +241,16 @@ describe('SelectionService', () => { }); function createRoot(...nodes: string[]): SModelRoot { - const children: SModelElementSchema[] = []; - nodes.forEach(node => children.push({ id: node, type: 'node:circle' })); - return graphFactory.createRoot({ - id: 'selection-service-spec', - type: 'graph', - children: children + const model = new SModelRoot(); + model.id = 'selection-service-spec'; + model.type = 'graph'; + nodes.forEach(id => { + const node = new SNode(); + node.type = 'node:circle'; + node.id = id; + model.add(node); }); + return model; } function assertSelectionAndFeedback(expectedSelection: string[], expectedDeselection: string[]): void { diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index e3ea873..f563567 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Container, ContainerModule } from 'inversify'; +import { Container } from 'inversify'; import { BindingContext, ContainerConfiguration, @@ -169,29 +169,3 @@ export function initializeDiagramContainer(container: Container, ...containerCon container.load(...modules); return container; } - -/** - * Creates a GLSP Client container with the GLSP default modules and the specified custom `modules`. - * - * You can still customize the default modules in two ways. - * - * First, you can unload default modules and load them again with your custom code. - * - * ```typescript - * const container = createClientContainer(myModule1, myModule2); - * container.unload(modelSourceWatcherModule); - * container.load(myModelSourceWatcherModule); - * ``` - * - * Second, you can unbind or rebind implementations that are originally bound in one of the default modules. - * - * ```typescript - * rebind(NavigationTargetResolver).to(MyNavigationTargetResolver); - * ``` - * @param modules Custom modules to be loaded in addition to the default modules. - * @returns The created container. - * @deprecated Please use `initializeDiagramContainer` from `@eclipse-glsp/client` instead - */ -export function createClientContainer(...modules: ContainerModule[]): Container { - return initializeDiagramContainer(new Container(), ...modules); -} diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 71142a9..4fa5e49 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -38,7 +38,7 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; - getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { + async getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { const requestAction = RequestContextActions.create({ contextId: ServerCommandPalette.CONTEXT_ID, editorContext: this.editorContext.get({ @@ -46,7 +46,8 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction [ServerCommandPalette.INDEX]: index ? index : 0 }) }); - return this.actionDispatcher.requestUntil(requestAction).then(response => this.getPaletteActionsFromResponse(response)); + const response = await this.actionDispatcher.requestUntil(requestAction); + return response ? this.getPaletteActionsFromResponse(response) : []; } getPaletteActionsFromResponse(action: Action): LabeledAction[] { diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 9d10139..6ce4f45 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -14,20 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; -import { GLSPContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; +import { GLSPContextMenuMouseListener } from './glsp-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; export const contextMenuModule = new FeatureModule(bind => { - bind(TYPES.IContextMenuServiceProvider).toProvider( - ctx => () => - new Promise((resolve, reject) => { - if (ctx.container.isBound(TYPES.IContextMenuService)) { - resolve(ctx.container.get(TYPES.IContextMenuService)); - } else { - reject(); - } - }) - ); + bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => async () => { + if (ctx.container.isBound(TYPES.IContextMenuService)) { + return ctx.container.get(TYPES.IContextMenuService); + } + console.warn("'TYPES.IContextMenuService' is not bound. Use no-op implementation instead"); + // eslint-disable-next-line @typescript-eslint/no-empty-function + return { show: () => {} }; + }); + bindAsService(bind, TYPES.MouseListener, GLSPContextMenuMouseListener); bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts similarity index 100% rename from packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts rename to packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 4214112..d609246 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -37,7 +37,7 @@ export class ServerContextMenuItemProvider implements IContextMenuItemProvider { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; - getItems(root: Readonly, lastMousePosition?: Point): Promise { + async getItems(root: Readonly, _lastMousePosition?: Point): Promise { const selectedElementIds = Array.from( root.index .all() @@ -46,7 +46,8 @@ export class ServerContextMenuItemProvider implements IContextMenuItemProvider { ); const editorContext = this.editorContext.getWithSelection(selectedElementIds); const requestAction = RequestContextActions.create({ contextId: ServerContextMenu.CONTEXT_ID, editorContext }); - return this.actionDispatcher.requestUntil(requestAction).then(response => this.getContextActionsFromResponse(response)); + const response = await this.actionDispatcher.requestUntil(requestAction); + return response ? this.getContextActionsFromResponse(response) : []; } getContextActionsFromResponse(action: Action): LabeledAction[] { diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 4b8a09b..bf61843 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -200,7 +200,7 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDia return getTypeHint(input, this.edgeHints); } - postModelInitialization(): MaybePromise { + postRequestModel(): MaybePromise { this.actionDispatcher.dispatch(RequestTypeHintsAction.create()); } } diff --git a/packages/client/src/features/label-edit/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts index 32e8ba7..a77bcbe 100644 --- a/packages/client/src/features/label-edit/edit-label-validator.ts +++ b/packages/client/src/features/label-edit/edit-label-validator.ts @@ -52,9 +52,10 @@ export namespace LabelEditValidation { export class ServerEditLabelValidator implements IEditLabelValidator { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; - validate(value: string, label: EditableLabel & SModelElement): Promise { + async validate(value: string, label: EditableLabel & SModelElement): Promise { const action = LabelEditValidation.createValidationRequestAction(value, label.id); - return this.actionDispatcher.requestUntil(action).then(response => this.getValidationResultFromResponse(response)); + const response = await this.actionDispatcher.requestUntil(action); + return response ? this.getValidationResultFromResponse(response) : { severity: 'ok' }; } getValidationResultFromResponse(action: Action): EditLabelValidationResult { diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/client/src/glsp-sprotty/index.ts index 43f764c..5550423 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/client/src/glsp-sprotty/index.ts @@ -13,13 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable no-restricted-imports */ /** - * Sprotty still keeps the old class based action definitions as deprecated API. - * This causes name clashes with the GLSP protocol actions when reexport sprotty. - * The deprecated definitions are planned to be removed with Sprotty 1.0.0. - * Until then we have to manually reexport the sprotty main index and exclude the clashing types. + * Selective custom reexport of the sprotty API. Also provides augmentations/adaptions + * of certain type definitions for GLSP. Using this custom reexport allows us to gracefully + * update to newer sprotty versions without hard API breaks. */ -/* eslint-disable no-restricted-imports */ /** * @eclipse-glsp/protocol diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/client/src/glsp-sprotty/types.ts index 662a00b..9ed2206 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/client/src/glsp-sprotty/types.ts @@ -41,10 +41,3 @@ export const TYPES = { IDiagramStartup: Symbol('IDiagramStartup'), IToolManager: Symbol('IToolManager') }; - -/** - * Keep a reexport under the old deprecated namespace to avoid hard API breaks. - * This gives adopters a grace period but all deprecated API will be removed before the 1.0.0 release. - * @deprecated Please use `TYPES` from `@eclipse-glsp/client` instead - */ -export const GLSP_TYPES = TYPES; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 768b68b..004d5dc 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -64,7 +64,7 @@ export * from './features/change-bounds/snap'; export * from './features/command-palette/command-palette-tool'; export * from './features/command-palette/server-command-palette-provider'; export * from './features/context-menu/delete-element-context-menu'; -export * from './features/context-menu/selection-service-aware-context-menu-mouse-listener'; +export * from './features/context-menu/glsp-context-menu-mouse-listener'; export * from './features/context-menu/server-context-menu-provider'; export * from './features/copy-paste/copy-paste-context-menu'; export * from './features/copy-paste/copy-paste-handler'; diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts index 9e1d8bd..9bd572c 100644 --- a/packages/protocol/src/action-protocol/base-protocol.ts +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -81,6 +81,10 @@ export interface RequestAction extends Action, sprot * Unique id for this request. In order to match a response to this request, the response needs to have the same id. */ requestId: string; + /** + * Used to ensure correct typing. Clients must not use this property + */ + readonly _?: Res; } export namespace RequestAction { diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index 912ab49..9e9f01b 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -121,7 +121,7 @@ export interface ExportSvgAction extends ResponseAction { export namespace ExportSvgAction { export const KIND = 'exportSvg'; - export function is(object: any): object is RequestExportSvgAction { + export function is(object: any): object is ExportSvgAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'svg'); } diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 5e392dd..47e9dd7 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -84,13 +84,6 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.resolvedConnection!; } - /** - * @Deprecated use {@link checkConnection} instead - */ - protected checkConnectionState(): boolean { - return this.checkedConnection !== undefined; - } - async start(): Promise { if (this.state === ClientState.Running) { return; From 7ebf016886c36c8b9b4aeb76745cde081978c667 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 21 Sep 2023 09:30:56 -0700 Subject: [PATCH 258/566] GLSP-1116: Fix RequestModelActionHandler (#58) * GLSP-1116: Fix RequestModelActionHandler - Ensure that `RequestModelActionHandler` invokes correct ModelSubmissionHandler method (`submitInitialModel) - Ensure that `ModelSubmissionHandler` dispatches correct `SetModelAction` Misc: - Fix some aync calls that were not properly awaited - Remove require import from `action-disptacher.spec.ts` --- examples/workflow-server/src/node/app.ts | 4 ++-- .../server/src/common/actions/action-dispatcher.spec.ts | 3 +-- packages/server/src/common/command/recording-command.ts | 4 ++-- .../common/features/model/model-submission-handler.ts | 6 +++--- .../features/model/request-model-action-handler.ts | 2 +- packages/server/src/common/utils/promise-queue.ts | 9 ++++----- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/examples/workflow-server/src/node/app.ts b/examples/workflow-server/src/node/app.ts index de1c26f..6300ca3 100644 --- a/examples/workflow-server/src/node/app.ts +++ b/examples/workflow-server/src/node/app.ts @@ -34,11 +34,11 @@ async function launch(argv?: string[]): Promise { if (options.webSocket) { const launcher = appContainer.resolve(WebSocketServerLauncher); launcher.configure(serverModule); - launcher.start({ port: options.port, host: options.host, path: 'workflow' }); + await launcher.start({ port: options.port, host: options.host, path: 'workflow' }); } else { const launcher = appContainer.resolve(SocketServerLauncher); launcher.configure(serverModule); - launcher.start({ port: options.port, host: options.host }); + await launcher.start({ port: options.port, host: options.host }); } } diff --git a/packages/server/src/common/actions/action-dispatcher.spec.ts b/packages/server/src/common/actions/action-dispatcher.spec.ts index f1d3421..35286f3 100644 --- a/packages/server/src/common/actions/action-dispatcher.spec.ts +++ b/packages/server/src/common/actions/action-dispatcher.spec.ts @@ -25,7 +25,6 @@ import { DefaultActionDispatcher } from './action-dispatcher'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; import { ClientActionForwarder } from './client-action-handler'; -import assert = require('assert'); function waitSync(timeInMillis: number): void { const start = Date.now(); @@ -64,7 +63,7 @@ describe('test DefaultActionDispatcher', () => { describe('test with one-way actions (no response actions)', () => { it('dispatch- unhandled action', async () => { - assert.rejects(actionDispatcher.dispatch({ kind: 'unhandled' })); + mock.expectToThrowAsync(() => actionDispatcher.dispatch({ kind: 'unhandled' })); }); it('dispatch - one action', async () => { diff --git a/packages/server/src/common/command/recording-command.ts b/packages/server/src/common/command/recording-command.ts index 287cfa6..d23c062 100644 --- a/packages/server/src/common/command/recording-command.ts +++ b/packages/server/src/common/command/recording-command.ts @@ -111,8 +111,8 @@ export class GModelRecordingCommand extends AbstractRecordingCommand { - super.applyPatch(rootSchema, patch); + protected override async applyPatch(rootSchema: GModelRootSchema, patch: jsonPatch.Operation[]): Promise { + await super.applyPatch(rootSchema, patch); const newRoot = this.serializer.createRoot(rootSchema); this.modelState.updateRoot(newRoot); } diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index ad766df..bef3144 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRoot, GModelRootSchema } from '@eclipse-glsp/graph'; +import { GModelRootSchema } from '@eclipse-glsp/graph'; import { Action, DirtyStateChangeReason, @@ -141,7 +141,7 @@ export class ModelSubmissionHandler { const result: Action[] = []; result.push( this.requestModelAction - ? SetModelAction.create(root) + ? this.createSetModeAction(root) : UpdateModelAction.create(root, { animate: this.diagramConfiguration.animatedUpdate }) ); if (!this.diagramConfiguration.needsClientLayout) { @@ -154,7 +154,7 @@ export class ModelSubmissionHandler { return result; } - protected createSetModeAction(newRoot: GModelRoot): SetModelAction { + protected createSetModeAction(newRoot: GModelRootSchema): SetModelAction { const responseId = this.requestModelAction?.requestId ?? ''; const response = SetModelAction.create(newRoot, { responseId }); this.requestModelAction = undefined; diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 81e770b..1bdbe57 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -61,7 +61,7 @@ export class RequestModelActionHandler implements ActionHandler { } this.reportModelLoadingFinished(progress); - return this.submissionHandler.submitModelDirectly(); + return this.submissionHandler.submitInitialModel(action); } protected async handleReconnect(action: RequestModelAction): Promise { diff --git a/packages/server/src/common/utils/promise-queue.ts b/packages/server/src/common/utils/promise-queue.ts index 17abf4e..ae256a1 100644 --- a/packages/server/src/common/utils/promise-queue.ts +++ b/packages/server/src/common/utils/promise-queue.ts @@ -62,14 +62,13 @@ export class PromiseQueue { return false; } - try { - this.busy = true; - this.resolveQueuedPromise(element); - } catch (error) { + this.busy = true; + this.resolveQueuedPromise(element).catch(error => { element.reject(error); this.busy = false; this.dequeue(); - } + }); + return true; } From efa889c0053f3364d05e764c53672725c72e81f5 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Tue, 26 Sep 2023 15:16:12 +0200 Subject: [PATCH 259/566] 211: Revise TypeHints and server side feedback for creation actions (#285) - Refactor EdgeTypeHint definition - Add a new 'dynamic' property, indicating that new edges need and additional check with the server before allowing creation - Make source/target element type ids properties optional If not defined, all potential element types are considered to be valid sources/targets - Add `RequestEdgeCheckAction` and `EdgeCheckResultAction` response to implement the dynamic check - Update the EdgeCreationTool to check with the server when trying to create a new edge configured as Dynamic -Refactor `TypeHintsProvider` and `ApplyTypeHintsCommand - Simplify type-hints aware can connect implementation. Just validate against the source/target types of the edge hint that is applicable for the given routable - Ensure that the default (i.e. class-level implementation) of `canConnect` is called if no typehint is applicable to the given routable - Remove `getValidEdgeElementTypes` function from `TypeHintsProvider`. Was only used for the old type-hints aware `canConnect` implementation. Is no longer needed and incomplete anyways (does not consider nested subtypes) - Add tests for type-hints feature Fixes https://github.com/eclipse-glsp/glsp/issues/45 - Remove `hasCompatibleType` utility function from `smodel-util` as it is no longer used and the implementation was incomplete anyways (did not consider nested subtypes) - Also: Don't use stroked lines for edges until eclipse-glsp/glsp/issues/1083 is fixed Part of eclipse-glsp/glsp/issues/211 Co-authored-by: Camille Letavernier --- packages/client/css/glsp-sprotty.css | 7 +- .../client/src/base/feedback/css-feedback.ts | 1 + .../features/hints/type-hint-provider.spec.ts | 356 ++++++++++++++++++ .../src/features/hints/type-hint-provider.ts | 248 ++++++++++++ .../src/features/hints/type-hints-module.ts | 2 +- .../client/src/features/hints/type-hints.ts | 220 ----------- .../client/src/features/tools/base-tools.ts | 5 +- .../tools/edge-creation/edge-creation-tool.ts | 94 +++-- packages/client/src/index.ts | 2 +- packages/client/src/utils/smodel-util.ts | 13 +- .../element-type-hints.spec.ts | 129 ++++++- .../src/action-protocol/element-type-hints.ts | 144 ++++++- 12 files changed, 951 insertions(+), 270 deletions(-) create mode 100644 packages/client/src/features/hints/type-hint-provider.spec.ts create mode 100644 packages/client/src/features/hints/type-hint-provider.ts delete mode 100644 packages/client/src/features/hints/type-hints.ts diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 52845c3..b08cae5 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -98,9 +98,6 @@ .sprotty-edge.selected { stroke: #1d80d1; stroke-width: 1.5px; - stroke-dashoffset: 5; - stroke-dasharray: 5, 5; - stroke-linecap: round; } .sprotty-edge.mouseover:not(.selected) .arrow, @@ -134,6 +131,10 @@ cursor: crosshair; } +.sprotty .edge-check-pending-mode { + cursor: wait; +} + .sprotty .move-mode { cursor: move; } diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 935195b..0a623df 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -76,6 +76,7 @@ export enum CursorCSS { EDGE_CREATION_SOURCE = 'edge-creation-select-source-mode', EDGE_CREATION_TARGET = 'edge-creation-select-target-mode', EDGE_RECONNECT = 'edge-reconnect-select-target-mode', + EDGE_CHECK_PENDING = 'edge-check-pending-mode', OPERATION_NOT_ALLOWED = 'edge-modification-not-allowed-mode', ELEMENT_DELETION = 'element-deletion-mode', RESIZE_NESW = 'resize-nesw-mode', diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts new file mode 100644 index 0000000..f09b66e --- /dev/null +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -0,0 +1,356 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { + AnimationFrameSyncer, + CommandExecutionContext, + ConsoleLogger, + EdgeTypeHint, + SChildElement, + SEdge, + SModelFactory, + SModelRoot, + SNode, + SetTypeHintsAction, + ShapeTypeHint, + TYPES, + Writable, + bindOrRebind, + createFeatureSet, + editFeature, + isDeletable, + isMoveable +} from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { isResizable } from '../change-bounds/model'; +import { isReconnectable } from '../reconnect/model'; +import { Containable, isContainable, isReparentable } from './model'; +import { ApplyTypeHintsAction, ApplyTypeHintsCommand, ITypeHintProvider, TypeHintProvider } from './type-hint-provider'; + +describe('TypeHintProvider', () => { + const container = new Container(); + container.bind(GLSPActionDispatcher).toConstantValue(sinon.createStubInstance(GLSPActionDispatcher)); + container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(sinon.createStubInstance(FeedbackActionDispatcher)); + const typeHintProvider = container.resolve(TypeHintProvider); + + describe('getShapeTypeHint', () => { + const nodeHint: ShapeTypeHint = { + deletable: true, + elementTypeId: 'node', + reparentable: false, + repositionable: true, + resizable: true + }; + const taskHint: ShapeTypeHint = { + deletable: true, + elementTypeId: 'node:task', + reparentable: false, + repositionable: true, + resizable: true + }; + + it('should return `undefined` if no `SetTypeHintsAction` has been handled yet', () => { + expect(typeHintProvider.getShapeTypeHint('some')).to.be.undefined; + }); + it('should return `undefined` if no hint is registered for the given type (exact type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [nodeHint], edgeHints: [] })); + expect(typeHintProvider.getShapeTypeHint('port')).to.be.undefined; + }); + it('should return the corresponding type hint for the given type (exact type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [nodeHint, taskHint], edgeHints: [] })); + expect(typeHintProvider.getShapeTypeHint('node')).to.equal(nodeHint); + expect(typeHintProvider.getShapeTypeHint('node:task')).to.equal(taskHint); + }); + it('should return the corresponding type hint for the given type (sub type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [nodeHint, taskHint], edgeHints: [] })); + expect(typeHintProvider.getShapeTypeHint('node:task:manual')).to.equal(taskHint); + expect(typeHintProvider.getShapeTypeHint('node:task:manual:foo')).to.equal(taskHint); + expect(typeHintProvider.getShapeTypeHint('node:event')).to.equal(nodeHint); + expect(typeHintProvider.getShapeTypeHint('node:event:initial')).to.equal(nodeHint); + }); + }); + describe('getEdgeTypeHint', () => { + const edgeHint: EdgeTypeHint = { + deletable: true, + elementTypeId: 'edge', + repositionable: true, + routable: true, + dynamic: false + }; + const fooEdgeHint: EdgeTypeHint = { + deletable: true, + elementTypeId: 'edge:foo', + repositionable: true, + routable: true, + dynamic: true + }; + it('should return `undefined` if no `SetTypeHintsAction` has been handled yet', () => { + expect(typeHintProvider.getEdgeTypeHint('some')).to.be.undefined; + }); + it('should return `undefined` if no hint is registered for the given type (exact type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [], edgeHints: [edgeHint] })); + expect(typeHintProvider.getEdgeTypeHint('link')).to.be.undefined; + }); + it('should return the corresponding type hint for the given type (exact type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [], edgeHints: [edgeHint, fooEdgeHint] })); + expect(typeHintProvider.getEdgeTypeHint('edge')).to.equal(edgeHint); + expect(typeHintProvider.getEdgeTypeHint('edge:foo')).to.equal(fooEdgeHint); + }); + it('should return the corresponding type hint for the given type (sub type match)', () => { + typeHintProvider.handle(SetTypeHintsAction.create({ shapeHints: [], edgeHints: [edgeHint, fooEdgeHint] })); + expect(typeHintProvider.getEdgeTypeHint('edge:foo:bar')).to.equal(fooEdgeHint); + expect(typeHintProvider.getEdgeTypeHint('edge:foo:bar:baz')).to.equal(fooEdgeHint); + expect(typeHintProvider.getEdgeTypeHint('edge:some')).to.equal(edgeHint); + expect(typeHintProvider.getEdgeTypeHint('edge:some:other')).to.equal(edgeHint); + }); + }); +}); +describe('ApplyTypeHintCommand', () => { + function createCommandExecutionContext(child: SChildElement): CommandExecutionContext { + const root = new SModelRoot(); + root.id = 'root'; + root.type = 'root'; + root.add(child); + return { + root, + modelFactory, + duration: 0, + modelChanged: undefined!, + logger: new ConsoleLogger(), + syncer: new AnimationFrameSyncer() + }; + } + + function createNode(type?: string): SNode { + const node = new SNode(); + node.type = type ?? 'node'; + node.id = 'node'; + node.features = createFeatureSet(SNode.DEFAULT_FEATURES); + return node; + } + + function createEdge(type?: string): SEdge { + const edge = new SEdge(); + edge.type = type ?? 'edge'; + edge.id = 'edge'; + edge.features = createFeatureSet(SEdge.DEFAULT_FEATURES); + return edge; + } + + const sandbox = sinon.createSandbox(); + const container = new Container(); + const modelFactory = sinon.createStubInstance(SModelFactory); + const typeHintProviderMock = sandbox.stub({ + getEdgeTypeHint: () => undefined, + getShapeTypeHint: () => undefined + }); + container.bind(GLSPActionDispatcher).toConstantValue(sandbox.createStubInstance(GLSPActionDispatcher)); + container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(sandbox.createStubInstance(FeedbackActionDispatcher)); + container.bind(TYPES.ITypeHintProvider).toConstantValue(typeHintProviderMock); + bindOrRebind(container, TYPES.Action).toConstantValue(ApplyTypeHintsAction.create()); + const command = container.resolve(ApplyTypeHintsCommand); + + beforeEach(() => { + sandbox.reset(); + }); + + describe('test hints to model feature translation (after command execution)`', () => { + describe('ShapeTypeHint', () => { + const allEnabledHint: ShapeTypeHint = { + elementTypeId: 'node', + deletable: true, + reparentable: true, + repositionable: true, + resizable: true, + containableElementTypeIds: [] + }; + const allDisabledHint: ShapeTypeHint = { + elementTypeId: 'node', + deletable: false, + reparentable: false, + repositionable: false, + resizable: false, + containableElementTypeIds: [] + }; + it('should not modify feature set of model element with no applicable type hint', () => { + typeHintProviderMock.getShapeTypeHint.returns(undefined); + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0]; + expect(SNode.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ + ...(element.features as Set) + ]); + }); + it('should add all enabled (`true`) features, derived from the applied type hint, to the model', () => { + typeHintProviderMock.getShapeTypeHint.returns(allEnabledHint); + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0]; + expect(isDeletable(element), 'Element should have deletable feature').to.be.true; + expect(isReparentable(element), 'Element should have reparentable feature').to.be.true; + expect(isMoveable(element), 'Element should have moveable feature').to.be.true; + expect(isContainable(element), 'Element should have containable feature').to.be.true; + expect(isResizable(element), 'Element should have resizeable feature').to.be.true; + }); + it('should remove all disabled (`false`) features, derived from the applied type hint, from the model', () => { + typeHintProviderMock.getShapeTypeHint.returns(allDisabledHint); + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0]; + expect(isDeletable(element), 'Element should not have deletable feature').to.be.false; + expect(isReparentable(element), 'Element should not have reparentable feature').to.be.false; + expect(isMoveable(element), 'Element should not have moveable feature').to.be.false; + expect(isResizable(element), 'Element should not have resizeable feature').to.be.false; + }); + describe('`isConnectable` (after hint has been applied to element)', () => { + const shapeHint: Writable = { + deletable: false, + elementTypeId: 'node', + reparentable: false, + repositionable: false, + resizable: false + }; + const edgeHint: Writable = { + deletable: false, + elementTypeId: 'edge', + repositionable: false, + routable: false + }; + it('should return `true` if source/target elements are not defined in edge hint', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + typeHintProviderMock.getEdgeTypeHint.returns(edgeHint); + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0] as SNode; + const edge = createEdge(); + expect(element.canConnect(edge, 'source')).to.be.true; + expect(element.canConnect(edge, 'target')).to.be.true; + }); + it('should return `false` if element type is not in source/target elements of edge hint', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + typeHintProviderMock.getEdgeTypeHint.returns(edgeHint); + edgeHint.sourceElementTypeIds = []; + edgeHint.targetElementTypeIds = []; + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0] as SNode; + const edge = createEdge(); + expect(element.canConnect(edge, 'source')).to.be.false; + expect(element.canConnect(edge, 'target')).to.be.false; + }); + it('should return `true` if element type is in source/target elements of edge hint (exact type)', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + typeHintProviderMock.getEdgeTypeHint.returns(edgeHint); + edgeHint.sourceElementTypeIds = ['node']; + edgeHint.targetElementTypeIds = ['node']; + const result = command.execute(createCommandExecutionContext(createNode())); + const element = result.children[0] as SNode; + const edge = createEdge(); + expect(element.canConnect(edge, 'source')).to.be.true; + expect(element.canConnect(edge, 'target')).to.be.true; + }); + it('should return `true` if element super type is in source/target elements of edge hint (super type)', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + typeHintProviderMock.getEdgeTypeHint.returns(edgeHint); + edgeHint.sourceElementTypeIds = ['node']; + edgeHint.targetElementTypeIds = ['node']; + const result = command.execute(createCommandExecutionContext(createNode('node:task:automated'))); + const element = result.children[0] as SNode; + const edge = createEdge(); + expect(element.canConnect(edge, 'source')).to.be.true; + expect(element.canConnect(edge, 'target')).to.be.true; + }); + it('should fallback to class-level `canConnect` implementation if no edge hint is applicable to routable', () => { + typeHintProviderMock.getEdgeTypeHint.returns(undefined); + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + const node = createNode(); + const originalCanConnectSpy = sinon.spy(node, 'canConnect'); + const result = command.execute(createCommandExecutionContext(node)); + const element = result.children[0] as SNode; + const edge = createEdge(); + expect(element.canConnect(edge, 'source')).to.be.true; + expect(element.canConnect(edge, 'target')).to.be.true; + expect(originalCanConnectSpy.called).to.be.true; + }); + }); + describe('`isContainable` (after hint has been applied to element)', () => { + const shapeHint: Writable = { + deletable: false, + elementTypeId: 'node', + reparentable: false, + repositionable: false, + resizable: false + }; + it('should return `false` if corresponding hint has no containable elements defined', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + const result = command.execute(createCommandExecutionContext(createNode('node'))); + const element = result.children[0] as SNode & Containable; + expect(element.isContainableElement('other')).to.be.false; + }); + it('should return `true` if corresponding hint has containable element with matching type', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + shapeHint.containableElementTypeIds = ['node']; + const result = command.execute(createCommandExecutionContext(createNode('node'))); + const element = result.children[0] as SNode & Containable; + expect(element.isContainableElement('node')).to.be.true; + }); + it('should return `true` if corresponding hint as has containable element with matching super type', () => { + typeHintProviderMock.getShapeTypeHint.returns(shapeHint); + shapeHint.containableElementTypeIds = ['node']; + const result = command.execute(createCommandExecutionContext(createNode('node'))); + const element = result.children[0] as SNode & Containable; + expect(element.isContainableElement('node:task:automated')).to.be.true; + }); + }); + }); + describe('EdgeTypeHint', () => { + const allEnabledHint: EdgeTypeHint = { + elementTypeId: 'edge', + deletable: true, + repositionable: true, + routable: true + }; + const allDisabledHint: EdgeTypeHint = { + elementTypeId: 'edge', + deletable: false, + repositionable: false, + routable: false + }; + it('should not modify feature set of model element with no applicable type hint', () => { + typeHintProviderMock.getEdgeTypeHint.returns(undefined); + const result = command.execute(createCommandExecutionContext(createEdge())); + const element = result.children[0]; + expect(SEdge.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ + ...(element.features as Set) + ]); + }); + it('should add all enabled (`true`) features, derived from the applied type hint, to the model', () => { + typeHintProviderMock.getEdgeTypeHint.returns(allEnabledHint); + const result = command.execute(createCommandExecutionContext(createEdge())); + const element = result.children[0]; + expect(isDeletable(element), 'Element should have deletable feature').to.be.true; + expect(element.hasFeature(editFeature), 'Element should have edit feature').to.be.true; + expect(isReconnectable(element), 'Element should have reconnectable feature').to.be.true; + }); + it('should remove all disabled (`false`) features, derived from the applied type hint, from the model', () => { + typeHintProviderMock.getEdgeTypeHint.returns(allDisabledHint); + const result = command.execute(createCommandExecutionContext(createEdge())); + const element = result.children[0]; + expect(isDeletable(element), 'Element should not have deletable feature').to.be.false; + expect(element.hasFeature(editFeature), 'Element should not have edit feature').to.be.false; + expect(isReconnectable(element), 'Element should not have reconnectable feature').to.be.false; + }); + }); + }); +}); diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts new file mode 100644 index 0000000..2c4e5ce --- /dev/null +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -0,0 +1,248 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { + Action, + CommandExecutionContext, + Connectable, + EdgeTypeHint, + IActionHandler, + RequestTypeHintsAction, + SEdge, + SModelElement, + SModelElementSchema, + SModelRoot, + SRoutableElement, + SShapeElement, + SetTypeHintsAction, + ShapeTypeHint, + TYPES, + TypeHint, + connectableFeature, + deletableFeature, + editFeature, + isConnectable, + moveFeature +} from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { getElementTypeId } from '../../utils/smodel-util'; +import { resizeFeature } from '../change-bounds/model'; +import { reconnectFeature } from '../reconnect/model'; +import { containerFeature, isContainable, reparentFeature } from './model'; + +/** + * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server + * onto the graphical model. The action is dispatched as persistent feedback to ensure the applied type hints + * don't get lost after a server-side model update. + */ +export interface ApplyTypeHintsAction extends Action { + kind: typeof ApplyTypeHintsAction.KIND; +} + +export namespace ApplyTypeHintsAction { + export const KIND = 'applyTypeHints'; + + export function is(object: any): object is ApplyTypeHintsAction { + return Action.hasKind(object, KIND); + } + + export function create(): ApplyTypeHintsAction { + return { kind: KIND }; + } +} + +type CanConnectFn = Connectable['canConnect']; + +/** + * Command that processes the entire model and for each model element applies its + * type hints i.e. translates the type hint information into corresponding model features + * and adds/removes them from the model element. + */ +@injectable() +export class ApplyTypeHintsCommand extends FeedbackCommand { + public static KIND = ApplyTypeHintsAction.KIND; + public override readonly priority = 10; + + @inject(TYPES.ITypeHintProvider) + protected typeHintProvider: ITypeHintProvider; + + constructor(@inject(TYPES.Action) protected action: ApplyTypeHintsAction) { + super(); + } + + execute(context: CommandExecutionContext): SModelRoot { + context.root.index.all().forEach(element => { + if (element instanceof SShapeElement || element instanceof SModelRoot) { + return this.applyShapeTypeHint(element); + } + if (element instanceof SEdge) { + this.applyEdgeTypeHint(element); + } + }); + return context.root; + } + + protected applyEdgeTypeHint(element: SModelElement): void { + const hint = this.typeHintProvider.getEdgeTypeHint(element); + + if (hint && element.features instanceof Set) { + addOrRemove(element.features, deletableFeature, hint.deletable); + addOrRemove(element.features, editFeature, hint.routable); + addOrRemove(element.features, reconnectFeature, hint.repositionable); + } + } + + protected applyShapeTypeHint(element: SModelElement): void { + const hint = this.typeHintProvider.getShapeTypeHint(element); + if (hint && element.features instanceof Set) { + addOrRemove(element.features, deletableFeature, hint.deletable); + addOrRemove(element.features, moveFeature, hint.repositionable); + addOrRemove(element.features, resizeFeature, hint.resizable); + addOrRemove(element.features, reparentFeature, hint.reparentable); + + addOrRemove(element.features, containerFeature, true); + if (isContainable(element)) { + element.isContainableElement = input => this.isContainableElement(input, hint); + } + + const fallbackCanConnect = isConnectable(element) ? element.canConnect.bind(element) : undefined; + addOrRemove(element.features, connectableFeature, true); + if (isConnectable(element)) { + element.canConnect = (routable, role) => this.canConnect(routable, role, element, fallbackCanConnect); + } + } + } + + /** + * Type hints aware wrapper function for `Connectable.canConnect`. After type hints have been applied + * the `canConnect` implementation of `connectable` model elements (with a matching hint) will forward to this method. + */ + protected canConnect( + routable: SRoutableElement, + role: 'source' | 'target', + element: SModelElement, + fallbackCanConnect?: CanConnectFn + ): boolean { + const edgeHint = this.typeHintProvider.getEdgeTypeHint(routable.type); + if (!edgeHint) { + return fallbackCanConnect?.(routable, role) ?? false; + } + const validElementIds = role === 'source' ? edgeHint.sourceElementTypeIds : edgeHint.targetElementTypeIds; + // If no source/target element ids are defined in the hint all elements are considered valid + if (!validElementIds) { + return true; + } + const elementType = element.type + ':'; + return validElementIds.some(type => elementType.startsWith(type)); + } + + /** + * Type hints aware wrapper function for `Containable.isContainableElement`. After type hints have been applied + * the `isContainableElement` implementation of `containable` model elements (with a matching hint) will forward to this method. + */ + protected isContainableElement(input: SModelElement | SModelElementSchema | string, hint: ShapeTypeHint): boolean { + const elemenType = getElementTypeId(input) + ':'; + return hint.containableElementTypeIds?.some(type => elemenType.startsWith(type)) ?? false; + } +} + +function addOrRemove(features: Set, feature: symbol, add: boolean): void { + if (add && !features.has(feature)) { + features.add(feature); + } else if (!add && features.has(feature)) { + features.delete(feature); + } +} + +/** + * Provides query methods for retrieving the type hint that is applicable for a given model element. + * If there is no type hint registered for the given element type the hint of the most concrete subtype (if any) + * is returned instead. Subtypes are declared with a `:` delimiter. + * For example consider the type `node:task:manual`. Then the provider fist checks wether there is + * a type hint registered for `node:task:manual`. If not it checks wether there is one registered + * for `node:task` and finally it checks wether there is a type hint for `node`. + */ +export interface ITypeHintProvider { + /** + * Retrieve the most applicable {@link ShapeTypeHint} for the given model element. + * + * @param input The model element whose type hint should be retrieved + * @returns The most applicable hint of the given element or `undefined` if no matching hint is registered. + */ + getShapeTypeHint(input: SModelElement | SModelElementSchema | string): ShapeTypeHint | undefined; + /** + * Retrieve the most applicable {@link EdgeTypeHint} for the given model element. + * + * @param input The model element whose type hint should be retrieved + * @returns The most applicable hint of the given element or `undefined` if no matching hint is registered. + */ + getEdgeTypeHint(input: SModelElement | SModelElementSchema | string): EdgeTypeHint | undefined; +} + +@injectable() +export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDiagramStartup { + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackActionDispatcher: IFeedbackActionDispatcher; + + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + protected shapeHints: Map = new Map(); + protected edgeHints: Map = new Map(); + + handle(action: SetTypeHintsAction): void { + this.shapeHints.clear(); + this.edgeHints.clear(); + action.shapeHints.forEach(hint => this.shapeHints.set(hint.elementTypeId, hint)); + action.edgeHints.forEach(hint => this.edgeHints.set(hint.elementTypeId, hint)); + this.feedbackActionDispatcher.registerFeedback(this, [ApplyTypeHintsAction.create()]); + } + + getShapeTypeHint(input: SModelElement | SModelElementSchema | string): ShapeTypeHint | undefined { + return this.getTypeHint(input, this.shapeHints); + } + + getEdgeTypeHint(input: SModelElement | SModelElementSchema | string): EdgeTypeHint | undefined { + return this.getTypeHint(input, this.edgeHints); + } + + protected getTypeHint(input: SModelElement | SModelElementSchema | string, hints: Map): T | undefined { + const type = getElementTypeId(input); + let hint = hints.get(type); + // Check subtypes + if (hint === undefined) { + const subtypes = type.split(':'); + while (hint === undefined && subtypes.length > 0) { + subtypes.pop(); + hint = hints.get(subtypes.join(':')); + if (hint) { + // add received subtype hint to map to avoid future recomputation + hints.set(type, hint); + break; + } + } + } + return hint; + } + + async postRequestModel(): Promise { + const setTypeHintsAction = await this.actionDispatcher.request(RequestTypeHintsAction.create()); + this.handle(setTypeHintsAction); + } +} diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 0ba11f9..7d3f879 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { FeatureModule, SetTypeHintsAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; -import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; +import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hint-provider'; export const typeHintsModule = new FeatureModule((bind, unbind, isBound) => { const context = { bind, unbind, isBound }; diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts deleted file mode 100644 index bf61843..0000000 --- a/packages/client/src/features/hints/type-hints.ts +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { - Action, - CommandExecutionContext, - CommandReturn, - Connectable, - EdgeTypeHint, - FeatureSet, - IActionHandler, - ICommand, - MaybePromise, - RequestTypeHintsAction, - SEdge, - SModelElement, - SModelRoot, - SShapeElement, - SetTypeHintsAction, - ShapeTypeHint, - TYPES, - TypeHint, - connectableFeature, - deletableFeature, - editFeature, - moveFeature -} from '~glsp-sprotty'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; -import { FeedbackCommand } from '../../base/feedback/feedback-command'; -import { IDiagramStartup } from '../../base/model/diagram-loader'; -import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; -import { resizeFeature } from '../change-bounds/model'; -import { reconnectFeature } from '../reconnect/model'; -import { Containable, containerFeature, reparentFeature } from './model'; - -/** - * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server - * onto the graphical model. The action is dispatched as persistent feedback to ensure the applied type hints - * don't get lost after a server-side model update. - */ -export interface ApplyTypeHintsAction extends Action { - kind: typeof ApplyTypeHintsAction.KIND; -} - -export namespace ApplyTypeHintsAction { - export const KIND = 'applyTypeHints'; - - export function is(object: any): object is ApplyTypeHintsAction { - return Action.hasKind(object, KIND); - } - - export function create(): ApplyTypeHintsAction { - return { kind: KIND }; - } -} - -@injectable() -export class ApplyTypeHintsCommand extends FeedbackCommand { - public static KIND = ApplyTypeHintsAction.KIND; - public override readonly priority = 10; - - @inject(TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; - - constructor(@inject(TYPES.Action) protected action: Action) { - super(); - } - - execute(context: CommandExecutionContext): CommandReturn { - context.root.index.all().forEach(element => { - if (element instanceof SShapeElement || element instanceof SModelRoot) { - this.applyShapeTypeHint(element); - } else if (element instanceof SEdge) { - return this.applyEdgeTypeHint(element); - } - }); - return context.root; - } - - protected applyEdgeTypeHint(element: SModelElement): void { - const hint = this.typeHintProvider.getEdgeTypeHint(element); - if (hint && isModifiableFeatureSet(element.features)) { - addOrRemove(element.features, deletableFeature, hint.deletable); - addOrRemove(element.features, editFeature, hint.routable); - addOrRemove(element.features, reconnectFeature, hint.repositionable); - } - } - - protected applyShapeTypeHint(element: SModelElement): void { - const hint = this.typeHintProvider.getShapeTypeHint(element); - if (hint && isModifiableFeatureSet(element.features)) { - addOrRemove(element.features, deletableFeature, hint.deletable); - addOrRemove(element.features, moveFeature, hint.repositionable); - addOrRemove(element.features, resizeFeature, hint.resizable); - addOrRemove(element.features, reparentFeature, hint.reparentable); - - addOrRemove(element.features, containerFeature, true); - const containable = createContainable(hint); - Object.assign(element, containable); - - addOrRemove(element.features, connectableFeature, true); - const validSourceEdges = this.typeHintProvider.getValidEdgeElementTypes(element, 'source'); - const validTargetEdges = this.typeHintProvider.getValidEdgeElementTypes(element, 'target'); - const connectable = createConnectable(validSourceEdges, validTargetEdges); - Object.assign(element, connectable); - } - } -} - -function createConnectable(validSourceEdges: string[], validTargetEdges: string[]): Connectable { - return { - canConnect: (routable, role) => - role === 'source' ? validSourceEdges.includes(routable.type) : validTargetEdges.includes(routable.type) - }; -} - -function createContainable(hint: ShapeTypeHint): Containable { - return { - isContainableElement: element => - hint.containableElementTypeIds ? hint.containableElementTypeIds.includes(getElementTypeId(element)) : false - }; -} - -function addOrRemove(features: Set, feature: symbol, add: boolean): void { - if (add && !features.has(feature)) { - features.add(feature); - } else if (!add && features.has(feature)) { - features.delete(feature); - } -} - -function isModifiableFeatureSet(featureSet?: FeatureSet): featureSet is FeatureSet & Set { - return featureSet !== undefined && featureSet instanceof Set; -} - -export interface ITypeHintProvider { - getShapeTypeHint(input: SModelElement | SModelElement | string): ShapeTypeHint | undefined; - getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined; - getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: 'source' | 'target'): string[]; -} - -@injectable() -export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDiagramStartup { - @inject(TYPES.IFeedbackActionDispatcher) - protected feedbackActionDispatcher: IFeedbackActionDispatcher; - - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; - - protected shapeHints: Map = new Map(); - protected edgeHints: Map = new Map(); - - handle(action: Action): ICommand | Action | void { - if (SetTypeHintsAction.is(action)) { - action.shapeHints.forEach(hint => this.shapeHints.set(hint.elementTypeId, hint)); - action.edgeHints.forEach(hint => this.edgeHints.set(hint.elementTypeId, hint)); - this.feedbackActionDispatcher.registerFeedback(this, [ApplyTypeHintsAction.create()]); - } - } - - getValidEdgeElementTypes(input: SModelElement | SModelElement | string, role: 'source' | 'target'): string[] { - const elementTypeId = getElementTypeId(input); - if (role === 'source') { - return Array.from( - Array.from(this.edgeHints.values()) - .filter(hint => - hint.sourceElementTypeIds.some(sourceElementTypeId => hasCompatibleType(elementTypeId, sourceElementTypeId)) - ) - .map(hint => hint.elementTypeId) - ); - } else { - return Array.from( - Array.from(this.edgeHints.values()) - .filter(hint => - hint.targetElementTypeIds.some(targetElementTypeId => hasCompatibleType(elementTypeId, targetElementTypeId)) - ) - .map(hint => hint.elementTypeId) - ); - } - } - - getShapeTypeHint(input: SModelElement | SModelElement | string): ShapeTypeHint | undefined { - return getTypeHint(input, this.shapeHints); - } - - getEdgeTypeHint(input: SModelElement | SModelElement | string): EdgeTypeHint | undefined { - return getTypeHint(input, this.edgeHints); - } - - postRequestModel(): MaybePromise { - this.actionDispatcher.dispatch(RequestTypeHintsAction.create()); - } -} - -function getTypeHint(input: SModelElement | SModelElement | string, hints: Map): T | undefined { - const type = getElementTypeId(input); - let hint = hints.get(type); - // Check subtypes - if (hint === undefined) { - const subtypes = type.split(':'); - while (hint === undefined && subtypes.length > 0) { - subtypes.pop(); - hint = hints.get(subtypes.join(':')); - } - } - return hint; -} diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index fe23c1a..90b5a90 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -14,7 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, Disposable, DisposableCollection, IActionDispatcher, IActionHandler, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; @@ -27,7 +28,7 @@ import { GLSPMouseTool } from '../../base/view/mouse-tool'; @injectable() export abstract class BaseEditTool implements Tool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; - @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; @inject(GLSPKeyTool) protected keyTool: GLSPKeyTool; @inject(EditorContextService) protected readonly editorContext: EditorContextService; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 4aca5b6..5aa5f54 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -18,17 +18,20 @@ import { Action, AnchorComputerRegistry, CreateEdgeOperation, + RequestCheckEdgeAction, SEdge, SModelElement, + TYPES, TriggerEdgeCreationAction, findParentByFeature, isConnectable, isCtrlOrCmd } from '~glsp-sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; - import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { ITypeHintProvider } from '../../hints/type-hint-provider'; import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedback'; @@ -42,6 +45,8 @@ export class EdgeCreationTool extends BaseCreationTool { + if (this.pendingDynamicCheck) { + this.allowedTarget = result.isValid; + this.actionDispatcher.dispatch(this.updateEdgeFeedback()); + this.pendingDynamicCheck = false; + } + }) + .catch(err => console.error('Dynamic edge check failed with: ', err)); + // Temporarily mark the target as invalid while we wait for the server response, + // so a fast-clicking user doesn't get a chance to create the edge in the meantime. + return false; } - protected isAllowedTarget(element: SModelElement | undefined): boolean { - return element !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, 'target'); + protected isDynamic(edgeTypeId: string): boolean { + const typeHint = this.typeHintProvider.getEdgeTypeHint(edgeTypeId); + return typeHint?.dynamic ?? false; } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 004d5dc..6a724e4 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -71,7 +71,7 @@ export * from './features/copy-paste/copy-paste-handler'; export * from './features/decoration/decoration-placer'; export * from './features/export/glsp-svg-exporter'; export * from './features/hints/model'; -export * from './features/hints/type-hints'; +export * from './features/hints/type-hint-provider'; export * from './features/hover/hover'; export * from './features/label-edit/edit-label-tool'; export * from './features/label-edit/edit-label-validator'; diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index 5dbe29f..5e9101d 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -306,15 +306,6 @@ export function calcRoute( return calculatedRoute; } -/** - * Checks if the model is compatible with the passed type string. - * (either has the same type or a subtype of this type) - */ -export function hasCompatibleType(input: SModelElement | SModelElementSchema | string, type: string): boolean { - const inputType = getElementTypeId(input); - return inputType === type ? true : inputType.split(':').includes(type); -} - /** * Convenience function to retrieve the model element type from a given input. The input * can either be a {@link SModelElement}, {@link SModelElementSchema} or a string. @@ -323,8 +314,8 @@ export function hasCompatibleType(input: SModelElement | SModelElementSchema | s */ export function getElementTypeId(input: SModelElement | SModelElementSchema | string): string { if (typeof input === 'string') { - return input as string; + return input; } else { - return (input as any)['type'] as string; + return input.type; } } diff --git a/packages/protocol/src/action-protocol/element-type-hints.spec.ts b/packages/protocol/src/action-protocol/element-type-hints.spec.ts index e6d6f37..a5752b2 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.spec.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { RequestTypeHintsAction, SetTypeHintsAction } from './element-type-hints'; +import { CheckEdgeResultAction, RequestCheckEdgeAction, RequestTypeHintsAction, SetTypeHintsAction } from './element-type-hints'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. @@ -136,4 +136,131 @@ describe('Element type hints actions', () => { }); }); }); + describe('RequestCheckEdgeAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestCheckEdgeAction = { + kind: 'requestCheckEdge', + edgeType: 'edge', + sourceElementId: 'source', + requestId: '' + }; + expect(RequestCheckEdgeAction.is(action)).to.be.true; + }); + it('should return true for an object having the correct type and a value for all required interface & optional properties', () => { + const action: RequestCheckEdgeAction = { + kind: 'requestCheckEdge', + edgeType: 'edge', + sourceElementId: 'source', + targetElementId: 'target', + requestId: '' + }; + expect(RequestCheckEdgeAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestCheckEdgeAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestCheckEdgeAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestCheckEdgeAction = { + kind: 'requestCheckEdge', + edgeType: 'edge', + sourceElementId: 'source', + requestId: '', + targetElementId: undefined + }; + + expect(RequestCheckEdgeAction.create({ edgeType: 'edge', sourceElement: 'source' })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestCheckEdgeAction = { + kind: 'requestCheckEdge', + edgeType: 'edge', + sourceElementId: 'source', + targetElementId: 'target', + requestId: 'myRequest' + }; + + expect( + RequestCheckEdgeAction.create({ + edgeType: 'edge', + sourceElement: 'source', + targetElement: 'target', + requestId: 'myRequest' + }) + ).to.deep.equals(expected); + }); + }); + }); + describe('CheckEdgeResultAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: CheckEdgeResultAction = { + isValid: true, + kind: 'checkEdgeTargetResult', + edgeType: 'edge', + sourceElementId: 'source', + responseId: '' + }; + expect(CheckEdgeResultAction.is(action)).to.be.true; + }); + it('should return true for an object having the correct type and a value for all required interface & optional properties', () => { + const action: CheckEdgeResultAction = { + isValid: true, + kind: 'checkEdgeTargetResult', + edgeType: 'edge', + sourceElementId: 'source', + targetElementId: 'target', + responseId: 'myResponse' + }; + expect(CheckEdgeResultAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CheckEdgeResultAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CheckEdgeResultAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: CheckEdgeResultAction = { + isValid: true, + kind: 'checkEdgeTargetResult', + edgeType: 'edge', + sourceElementId: 'source', + responseId: '' + }; + expect(CheckEdgeResultAction.create({ edgeType: 'edge', isValid: true, sourceElementId: 'source' })).to.deep.equals( + expected + ); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: CheckEdgeResultAction = { + isValid: true, + kind: 'checkEdgeTargetResult', + edgeType: 'edge', + sourceElementId: 'source', + targetElementId: 'target', + responseId: 'myResponse' + }; + + expect( + CheckEdgeResultAction.create({ + edgeType: 'edge', + isValid: true, + sourceElementId: 'source', + targetElementId: 'target', + responseId: 'myResponse' + }) + ).to.deep.equals(expected); + }); + }); + }); }); diff --git a/packages/protocol/src/action-protocol/element-type-hints.ts b/packages/protocol/src/action-protocol/element-type-hints.ts index a0bbebe..6954679 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.ts @@ -14,7 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { hasArrayProp } from '../utils/type-util'; +import { SModelElement } from 'sprotty-protocol'; +import { SModelElementSchema } from '.'; +import { hasArrayProp, hasBooleanProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; /** @@ -63,25 +65,37 @@ export interface ShapeTypeHint extends TypeHint { */ export interface EdgeTypeHint extends TypeHint { /** - * Specifies whether the routing of this element can be changed. + * Specifies whether the routing points of the edge can be changed + * i.e. edited by the user. */ readonly routable: boolean; /** - * Allowed source element types for this edge type + * Allowed source element types for this edge type. + * If not defined any element can be used as source element for this edge. */ - readonly sourceElementTypeIds: string[]; + readonly sourceElementTypeIds?: string[]; /** - * Allowed targe element types for this edge type + * Allowed target element types for this edge type + * If not defined any element can be used as target element for this edge. */ - readonly targetElementTypeIds: string[]; + readonly targetElementTypeIds?: string[]; + + /** + * Indicates whether this type hint is dynamic or not. Dynamic edge type hints + * require an additional runtime check before creating an edge, when checking + * source and target element types is not sufficient. + * + * @see {@link RequestCheckEdgeAction} + */ + readonly dynamic?: boolean; } /** * Sent from the client to the server in order to request hints on whether certain modifications are allowed for a specific element type. * The `RequestTypeHintsAction` is optional, but should usually be among the first messages sent from the client to the server after - * receiving the model via RequestModelAction. The response is a {@link SetTypeHintsAction}. + * receiving the model via `RequestModelAction`. The response is a {@link SetTypeHintsAction}. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks * and creating new `RequestTypeHintsActions`. */ @@ -133,3 +147,119 @@ export namespace SetTypeHintsAction { }; } } + +/** + * Sent from the client to the server to check wether the provided edge context information is valid i.e. + * creation of an edge with the given edge type and source/target element is allowed by the server. + * Typically this action is dispatched by edge creation tools in the creation phase of an edge that's associated + * with a dynamic {@link EdgeTypeHint}. + */ +export interface RequestCheckEdgeAction extends RequestAction { + kind: typeof RequestCheckEdgeAction.KIND; + + /** + * The element type of the edge being created. + */ + edgeType: string; + + /** + * The ID of the edge source element. + */ + sourceElementId: string; + + /** + * The ID of the edge target element to check. + */ + targetElementId?: string; +} + +export namespace RequestCheckEdgeAction { + export const KIND = 'requestCheckEdge'; + + export function is(object: unknown): object is RequestCheckEdgeAction { + return ( + Action.hasKind(object, KIND) && + hasStringProp(object, 'edgeType') && + hasStringProp(object, 'sourceElementId') && + hasStringProp(object, 'targetElementId', true) + ); + } + + export function create(options: { + sourceElement: SModelElement | SModelElementSchema | string; + targetElement?: SModelElement | SModelElementSchema | string; + edgeType: string; + requestId?: string; + }): RequestCheckEdgeAction { + return { + kind: KIND, + edgeType: options.edgeType, + sourceElementId: getElementTypeId(options.sourceElement), + targetElementId: options.targetElement ? getElementTypeId(options.targetElement) : undefined, + requestId: options.requestId ?? '' + }; + } +} + +function getElementTypeId(element: SModelElement | string): string { + if (typeof element === 'string') { + return element; + } + return element.id; +} + +/** + * Send from the server to the client as a response for a {@link RequestCheckEdgeAction}. It provides + * a boolean indicating whether the edge context information provided by the corresponding request action is valid + * i.e. creation of an edge with the given edge type and source/target element is allowed. + */ +export interface CheckEdgeResultAction extends ResponseAction { + kind: typeof CheckEdgeResultAction.KIND; + + /** + * true if the selected element is a valid target for this edge, + * false otherwise. + */ + isValid: boolean; + /** + * The element type of the edge that has been checked. + */ + edgeType: string; + + /** + * The ID of the source element of the edge that has been checked. + */ + sourceElementId: string; + /** + * The ID of the target element of the edge that has been checked. + */ + targetElementId?: string; +} + +export namespace CheckEdgeResultAction { + export const KIND = 'checkEdgeTargetResult'; + + export function is(object: unknown): object is CheckEdgeResultAction { + return ( + Action.hasKind(object, KIND) && + hasBooleanProp(object, 'isValid') && + hasStringProp(object, 'edgeType') && + hasStringProp(object, 'sourceElementId') && + hasStringProp(object, 'targetElementId', true) + ); + } + + export function create(options: { + isValid: boolean; + edgeType: string; + sourceElementId: string; + targetElementId?: string; + responseId?: string; + }): CheckEdgeResultAction { + return { + kind: KIND, + responseId: '', + ...options + }; + } +} From 9999ba423d4b0838841edc749108ac2447e1c18d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Sep 2023 13:16:57 -0700 Subject: [PATCH 260/566] GLSP-891: Json operation handler API (#59) - Provides generic,reusable operation handler implementations for JSON-based source models - Refactor `RecordingCommand` API - Refactor `CreationOperationHandler` interfaces to be more strict (Previously it was possible to declare a handler with mixed (nodes/edges) types) Also: Filter out non-critical warning in webpack config Fixes https://github.com/eclipse-glsp/glsp/issues/891 --- examples/workflow-server/webpack.config.js | 2 +- .../common/command/recording-command.spec.ts | 20 ++- .../src/common/command/recording-command.ts | 42 ++--- .../src/common/features/model/model-state.ts | 3 + .../model/model-submission-handler.ts | 2 +- .../gmodel-create-edge-operation-handler.ts | 6 +- .../gmodel-create-node-operation-handler.ts | 6 +- packages/server/src/common/index.ts | 1 + .../operations/compound-operation-handler.ts | 7 +- .../operations/create-operation-handler.ts | 27 +++- .../operations/json-operation-handler.ts | 147 ++++++++++++++++++ .../operations/operation-handler-registry.ts | 13 +- .../common/operations/operation-handler.ts | 4 +- .../server/src/common/utils/registry.spec.ts | 4 +- packages/server/src/common/utils/registry.ts | 11 +- 15 files changed, 232 insertions(+), 63 deletions(-) create mode 100644 packages/server/src/common/operations/json-operation-handler.ts diff --git a/examples/workflow-server/webpack.config.js b/examples/workflow-server/webpack.config.js index 8a0a03e..1e242b1 100644 --- a/examples/workflow-server/webpack.config.js +++ b/examples/workflow-server/webpack.config.js @@ -57,6 +57,6 @@ module.exports = env => { } ] }, - ignoreWarnings: [/Failed to parse source map/] + ignoreWarnings: [/Failed to parse source map/, /Can't resolve .* in '.*ws\/lib'/] }; }; diff --git a/packages/server/src/common/command/recording-command.spec.ts b/packages/server/src/common/command/recording-command.spec.ts index 99999bb..27b5a81 100644 --- a/packages/server/src/common/command/recording-command.spec.ts +++ b/packages/server/src/common/command/recording-command.spec.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AnyObject } from '@eclipse-glsp/protocol'; +import { AnyObject, MaybePromise } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; -import { RecordingCommand } from './recording-command'; +import { AbstractRecordingCommand } from './recording-command'; interface TestModel { string: string; @@ -27,6 +27,16 @@ interface TestModel { let jsonObject: TestModel; +class TestRecordingCommand extends AbstractRecordingCommand { + constructor(protected jsonObject: JsonObject, protected doExecute: () => MaybePromise) { + super(); + } + + protected getJsonObject(): MaybePromise { + return this.jsonObject; + } +} + describe('RecordingCommand', () => { let beforeState: TestModel; @@ -41,14 +51,14 @@ describe('RecordingCommand', () => { it('should be undoable after execution', async () => { // eslint-disable-next-line @typescript-eslint/no-empty-function - const command = new RecordingCommand(jsonObject, () => {}); + const command = new TestRecordingCommand(jsonObject, () => {}); expect(command.canUndo()).to.be.false; await command.execute(); expect(command.canUndo()).to.be.true; }); it('should restore the pre execution state when undo is called', async () => { - const command = new RecordingCommand(jsonObject, () => { + const command = new TestRecordingCommand(jsonObject, () => { jsonObject.string = 'bar'; jsonObject.flag = false; jsonObject.maybe = { hello: 'world' }; @@ -60,7 +70,7 @@ describe('RecordingCommand', () => { }); it('should restore the post execution state when redo is called', async () => { - const command = new RecordingCommand(jsonObject, () => { + const command = new TestRecordingCommand(jsonObject, () => { jsonObject.string = 'bar'; jsonObject.flag = false; jsonObject.maybe = { hello: 'world' }; diff --git a/packages/server/src/common/command/recording-command.ts b/packages/server/src/common/command/recording-command.ts index d23c062..d3ac655 100644 --- a/packages/server/src/common/command/recording-command.ts +++ b/packages/server/src/common/command/recording-command.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { GModelRootSchema } from '@eclipse-glsp/graph'; -import { AnyObject, MaybePromise } from '@eclipse-glsp/protocol'; +import { AnyObject, MaybePromise, SModelRootSchema } from '@eclipse-glsp/protocol'; import * as jsonPatch from 'fast-json-patch'; import { GModelSerializer } from '../features/model/gmodel-serializer'; import { ModelState } from '../features/model/model-state'; @@ -34,6 +34,7 @@ export abstract class AbstractRecordingCommand imp const afterState = await this.getJsonObject(); this.undoPatch = jsonPatch.compare(afterState, beforeState); this.redoPatch = jsonPatch.compare(beforeState, afterState); + this.postChange?.(afterState); } /** @@ -48,8 +49,8 @@ export abstract class AbstractRecordingCommand imp */ protected abstract doExecute(): MaybePromise; - protected applyPatch(object: JsonObject, patch: jsonPatch.Operation[]): MaybePromise { - jsonPatch.applyPatch(object, patch, false, true); + protected applyPatch(object: JsonObject, patch: jsonPatch.Operation[]): jsonPatch.PatchResult { + return jsonPatch.applyPatch(object, patch, false, true); } /** @@ -64,33 +65,28 @@ export abstract class AbstractRecordingCommand imp async undo(): Promise { if (this.undoPatch) { - return this.applyPatch(await this.getJsonObject(), this.undoPatch); + const result = this.applyPatch(await this.getJsonObject(), this.undoPatch); + this.postChange?.(result.newDocument); } } async redo(): Promise { if (this.redoPatch) { - return this.applyPatch(await this.getJsonObject(), this.redoPatch); + const result = this.applyPatch(await this.getJsonObject(), this.redoPatch); + this.postChange?.(result.newDocument); } } canUndo(): boolean { return !!this.undoPatch && !!this.redoPatch; } -} - -/** - * Simple base implementation of {@link AbstractRecordingCommand} that records the changes made to the given JSON object during - * the the given `doExecute` function. - */ -export class RecordingCommand extends AbstractRecordingCommand { - constructor(protected jsonObject: JsonObject, protected doExecute: () => MaybePromise) { - super(); - } - protected getJsonObject(): MaybePromise { - return this.jsonObject; - } + /** + * Optional hook that (if implemented) will be + * executed after every command-driven action that changed + * the underlying model i.e. command execute, undo and redo. + */ + protected postChange?(newModel: JsonObject): MaybePromise; } /** @@ -102,18 +98,12 @@ export class GModelRecordingCommand extends AbstractRecordingCommand { - await super.execute(); - this.modelState.index.indexRoot(this.modelState.root); - } - protected getJsonObject(): MaybePromise { return this.serializer.createSchema(this.modelState.root); } - protected override async applyPatch(rootSchema: GModelRootSchema, patch: jsonPatch.Operation[]): Promise { - await super.applyPatch(rootSchema, patch); - const newRoot = this.serializer.createRoot(rootSchema); + protected override postChange(newModel: SModelRootSchema): MaybePromise { + const newRoot = this.serializer.createRoot(newModel); this.modelState.updateRoot(newRoot); } } diff --git a/packages/server/src/common/features/model/model-state.ts b/packages/server/src/common/features/model/model-state.ts index a8cdfa9..c35b9ea 100644 --- a/packages/server/src/common/features/model/model-state.ts +++ b/packages/server/src/common/features/model/model-state.ts @@ -92,6 +92,9 @@ export class DefaultModelState implements ModelState { } updateRoot(newRoot: GModelRoot): void { + if (!newRoot.revision && this.root) { + newRoot.revision = this.root.revision; + } this.root = newRoot; this.index.indexRoot(newRoot); } diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index bef3144..2601ab2 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -110,9 +110,9 @@ export class ModelSubmissionHandler { const revision = this.requestModelAction ? 0 : this.modelState.root.revision! + 1; this.modelState.root.revision = revision; - const root = this.serializeGModel(); if (this.diagramConfiguration.needsClientLayout) { + const root = this.serializeGModel(); return [RequestBoundsAction.create(root), SetDirtyStateAction.create(this.commandStack.isDirty, { reason })]; } return this.submitModelDirectly(reason); diff --git a/packages/server/src/common/gmodel/gmodel-create-edge-operation-handler.ts b/packages/server/src/common/gmodel/gmodel-create-edge-operation-handler.ts index e686b81..35add84 100644 --- a/packages/server/src/common/gmodel/gmodel-create-edge-operation-handler.ts +++ b/packages/server/src/common/gmodel/gmodel-create-edge-operation-handler.ts @@ -19,7 +19,7 @@ import { GNode } from '@eclipse-glsp/graph/lib/gnode'; import { CreateEdgeOperation, MaybePromise, TriggerEdgeCreationAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { Command } from '../command/command'; -import { CreateOperationHandler, CreateOperationKind } from '../operations/create-operation-handler'; +import { CreateEdgeOperationHandler } from '../operations/create-operation-handler'; import { GModelOperationHandler } from './gmodel-operation-handler'; /** @@ -28,8 +28,8 @@ import { GModelOperationHandler } from './gmodel-operation-handler'; * (i.e. all operation handlers directly modify the graphical model). */ @injectable() -export abstract class GModelCreateEdgeOperationHandler extends GModelOperationHandler implements CreateOperationHandler { - override readonly operationType: CreateOperationKind = CreateEdgeOperation.KIND; +export abstract class GModelCreateEdgeOperationHandler extends GModelOperationHandler implements CreateEdgeOperationHandler { + override readonly operationType = CreateEdgeOperation.KIND; abstract override label: string; abstract elementTypeIds: string[]; diff --git a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts index 112bec4..6c87dbb 100644 --- a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts +++ b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts @@ -19,7 +19,7 @@ import { CreateNodeOperation, MaybePromise, Point, SelectAction, TriggerNodeCrea import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { Command } from '../command/command'; -import { CreateOperationHandler, CreateOperationKind } from '../operations/create-operation-handler'; +import { CreateNodeOperationHandler } from '../operations/create-operation-handler'; import { getRelativeLocation } from '../utils/layout-util'; import { GModelOperationHandler } from './gmodel-operation-handler'; @@ -29,7 +29,7 @@ import { GModelOperationHandler } from './gmodel-operation-handler'; * (i.e. all operation handlers directly modify the graphical model). */ @injectable() -export abstract class GModelCreateNodeOperationHandler extends GModelOperationHandler implements CreateOperationHandler { +export abstract class GModelCreateNodeOperationHandler extends GModelOperationHandler implements CreateNodeOperationHandler { @inject(ActionDispatcher) protected actionDispatcher: ActionDispatcher; @@ -37,7 +37,7 @@ export abstract class GModelCreateNodeOperationHandler extends GModelOperationHa abstract override label: string; - override operationType: CreateOperationKind = CreateNodeOperation.KIND; + override readonly operationType = CreateNodeOperation.KIND; override createCommand(operation: CreateNodeOperation): MaybePromise { return this.commandOf(() => this.executeCreation(operation)); diff --git a/packages/server/src/common/index.ts b/packages/server/src/common/index.ts index 8cbe4e9..4c60009 100644 --- a/packages/server/src/common/index.ts +++ b/packages/server/src/common/index.ts @@ -71,6 +71,7 @@ export * from './gmodel/index'; export * from './launch/glsp-server-launcher'; export * from './operations/compound-operation-handler'; export * from './operations/create-operation-handler'; +export * from './operations/json-operation-handler'; export * from './operations/operation-action-handler'; export * from './operations/operation-handler'; export * from './operations/operation-handler-registry'; diff --git a/packages/server/src/common/operations/compound-operation-handler.ts b/packages/server/src/common/operations/compound-operation-handler.ts index 4cfee12..20aae35 100644 --- a/packages/server/src/common/operations/compound-operation-handler.ts +++ b/packages/server/src/common/operations/compound-operation-handler.ts @@ -19,6 +19,11 @@ import { Command, CompoundCommand } from '../command/command'; import { OperationHandler } from './operation-handler'; import { OperationHandlerRegistry } from './operation-handler-registry'; +/** + * Generic {@link OperationHandler} from {@link CompoundOperations}. + * Retrieves the corresponding execution commands for the list of (sub) operations + * and constructs a {@link CompoundCommand} for them. + */ @injectable() export class CompoundOperationHandler extends OperationHandler { @inject(OperationHandlerRegistry) @@ -27,7 +32,7 @@ export class CompoundOperationHandler extends OperationHandler { operationType = CompoundOperation.KIND; async createCommand(operation: CompoundOperation): Promise { - const maybeCommands = operation.operationList.map(op => this.operationHandlerRegistry.getExecutableCommand(op)); + const maybeCommands = operation.operationList.map(op => this.operationHandlerRegistry.getOperationHandler(op)?.execute(op)); const commands: Command[] = []; for await (const command of maybeCommands) { diff --git a/packages/server/src/common/operations/create-operation-handler.ts b/packages/server/src/common/operations/create-operation-handler.ts index 070f198..0e2b3f2 100644 --- a/packages/server/src/common/operations/create-operation-handler.ts +++ b/packages/server/src/common/operations/create-operation-handler.ts @@ -19,21 +19,37 @@ import { hasArrayProp, hasFunctionProp, hasStringProp, + MaybePromise, TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; +import { Command } from '../command/command'; import { OperationHandler } from './operation-handler'; /** - * A special {@link OperationHandler} that is responsible for the handling of {@link CreateOperation}s. Depending on its + * A special {@link OperationHandler} that is responsible for the handling of (a subset of) {@link CreateEdgeOperation}s. Depending on its * operation type the triggered actions are {@link TriggerNodeCreationAction} or {@link TriggerEdgeCreationAction}s. */ -export interface CreateOperationHandler extends OperationHandler { +export interface CreateEdgeOperationHandler extends OperationHandler { + label: string; + elementTypeIds: string[]; + operationType: typeof CreateEdgeOperation.KIND; + getTriggerActions(): TriggerEdgeCreationAction[]; + createCommand(operation: CreateEdgeOperation): MaybePromise; +} + +export interface CreateNodeOperationHandler extends OperationHandler { readonly label: string; elementTypeIds: string[]; - operationType: CreateOperationKind; - getTriggerActions(): (TriggerEdgeCreationAction | TriggerNodeCreationAction)[]; + operationType: typeof CreateNodeOperation.KIND; + getTriggerActions(): TriggerNodeCreationAction[]; + createCommand(operation: CreateNodeOperation): MaybePromise; } +/** + * A special {@link OperationHandler} that is responsible for the handling of a node or edge creation operation. Depending on its + * operation type the triggered actions are {@link TriggerNodeCreationAction} or {@link TriggerEdgeCreationAction}s. + */ +export type CreateOperationHandler = CreateNodeOperationHandler | CreateEdgeOperationHandler; export type CreateOperationKind = typeof CreateNodeOperation.KIND | typeof CreateEdgeOperation.KIND; @@ -41,9 +57,10 @@ export namespace CreateOperationHandler { export function is(object: unknown): object is CreateOperationHandler { return ( object instanceof OperationHandler && + hasStringProp(object, 'operationType') && hasStringProp(object, 'label') && hasArrayProp(object, 'elementTypeIds') && - hasFunctionProp(object, 'getTriggerActions') + hasFunctionProp(object, 'getTriggerActions', true) ); } } diff --git a/packages/server/src/common/operations/json-operation-handler.ts b/packages/server/src/common/operations/json-operation-handler.ts new file mode 100644 index 0000000..0bd5079 --- /dev/null +++ b/packages/server/src/common/operations/json-operation-handler.ts @@ -0,0 +1,147 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph'; +import { + AnyObject, + CreateEdgeOperation, + CreateNodeOperation, + MaybePromise, + Point, + TriggerEdgeCreationAction, + TriggerNodeCreationAction, + hasFunctionProp, + hasObjectProp +} from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { Command } from '../command/command'; +import { AbstractRecordingCommand } from '../command/recording-command'; +import { ModelState } from '../features/model/model-state'; +import { getRelativeLocation } from '../utils/layout-util'; +import { CreateEdgeOperationHandler, CreateNodeOperationHandler } from './create-operation-handler'; +import { OperationHandler } from './operation-handler'; + +/** + * Helper interface for {@link ModelState} implementations of diagram languages that use a (serializable) JSON-based source model. + * Has to be implemented in order to reuse the {@link JsonOperationHandler} API. + */ +export interface JsonModelState extends ModelState { + readonly sourceModel: JsonObject; + updateSourceModel(sourceModel: JsonObject): void; +} + +export namespace JsonModelState { + export function is(modelState: ModelState): modelState is JsonModelState { + return hasObjectProp(modelState, 'sourceModel') && hasFunctionProp(modelState, 'updateSourceModel'); + } +} + +/** + * Simple base implementation of {@link AbstractRecordingCommand} that allows recording of changes made + * to the `sourceModel` of the given {@link JsonModelState} during the given `doExecute` function + */ +export class JsonRecordingCommand extends AbstractRecordingCommand { + constructor(protected modelState: JsonModelState, protected doExecute: () => MaybePromise) { + super(); + } + + protected override getJsonObject(): MaybePromise { + return this.modelState.sourceModel; + } + + protected override postChange(newModel: JsonObject): MaybePromise { + this.modelState.updateSourceModel(newModel); + } +} + +/** + * Reusable {@link OperationHandler} base implementation for diagram languages that use a (serializable) JSON-based source model. + * To use this class (or its subclasses) the injected {@link ModelState} has to implement the {@link JsonModelState} interface. + * + */ +@injectable() +export abstract class JsonOperationHandler extends OperationHandler { + protected commandOf(runnable: () => void): Command { + if (!JsonModelState.is(this.modelState)) { + throw new Error('Cannot create command. The underlying model state does not implement the `JsonModelState` interface'); + } + return new JsonRecordingCommand(this.modelState, runnable); + } +} + +/** + * Reusable {@link CreateNodeOperationHandler} base implementation for diagram languages that use a (serializable) JSON-based source model. + */ +@injectable() +export abstract class JsonCreateNodeOperationHandler extends JsonOperationHandler implements CreateNodeOperationHandler { + abstract elementTypeIds: string[]; + abstract override label: string; + override readonly operationType = CreateNodeOperation.KIND; + + abstract override createCommand(operation: CreateNodeOperation): MaybePromise; + + getTriggerActions(): TriggerNodeCreationAction[] { + return this.elementTypeIds.map(typeId => TriggerNodeCreationAction.create(typeId)); + } + + /** + * Return the GModelElement that will contain the newly created node. It is usually + * the target element ({@link CreateNodeOperation.containerId}), but could also + * be e.g. an intermediate compartment, or even a different Node. + * + * @param operation + * @return the GModelElement that will contain the newly created node. + */ + getContainer(operation: CreateNodeOperation): GModelElement | undefined { + const index = this.modelState.index; + return operation.containerId ? index.get(operation.containerId) : undefined; + } + + getLocation(operation: CreateNodeOperation): Point | undefined { + return operation.location; + } + + /** + * Retrieves the diagram absolute location and the target container from the given {@link CreateNodeOperation} + * and converts the absolute location to coordinates relative to the given container. + * Relative coordinates can only be retrieved if the given container element is part of + * a hierarchy of {@link GBoundsAware} elements. This means each (recursive) parent element need to + * implement {@link GBoundsAware}. If that is not the case this method returns `undefined`. + * @param absoluteLocation The diagram absolute position. + * @param container The container element. + * @returns The relative position or `undefined`. + */ + getRelativeLocation(operation: CreateNodeOperation): Point | undefined { + const container = this.getContainer(operation) ?? this.modelState.root; + const absoluteLocation = this.getLocation(operation) ?? Point.ORIGIN; + return getRelativeLocation(absoluteLocation, container); + } +} + +/** + * Reusable {@link CreateEdgeOperationHandler} base implementation for diagram languages that use a (serializable) JSON-based source model. + */ +@injectable() +export abstract class JsonCreateEdgeOperationHandler extends JsonOperationHandler implements CreateEdgeOperationHandler { + abstract elementTypeIds: string[]; + abstract override label: string; + override readonly operationType = CreateEdgeOperation.KIND; + + abstract override createCommand(operation: CreateEdgeOperation): MaybePromise; + + getTriggerActions(): TriggerEdgeCreationAction[] { + return this.elementTypeIds.map(typeId => TriggerEdgeCreationAction.create(typeId)); + } +} diff --git a/packages/server/src/common/operations/operation-handler-registry.ts b/packages/server/src/common/operations/operation-handler-registry.ts index d108ec6..a1d33cb 100644 --- a/packages/server/src/common/operations/operation-handler-registry.ts +++ b/packages/server/src/common/operations/operation-handler-registry.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, CreateOperation, MaybePromise, Operation } from '@eclipse-glsp/protocol'; +import { Args, CreateOperation, Operation } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { Command } from '../command/command'; import { ClientSessionInitializer } from '../session/client-session-initializer'; import { Registry } from '../utils/registry'; import { CreateOperationHandler } from './create-operation-handler'; @@ -35,16 +34,6 @@ export class OperationHandlerRegistry extends Registry getOperationHandler(operation: Operation): OperationHandler | undefined { return CreateOperation.is(operation) ? this.get(`${operation.kind}_${operation.elementTypeId}`) : this.get(operation.kind); } - - /** - * Returns the matching command for the given operation. - * - * @param operation operation - * @return the matching command for the given operation - */ - getExecutableCommand(operation: Operation): MaybePromise { - return this.getOperationHandler(operation)?.createCommand(operation); - } } @injectable() diff --git a/packages/server/src/common/operations/operation-handler.ts b/packages/server/src/common/operations/operation-handler.ts index 5a7fc70..de484e6 100644 --- a/packages/server/src/common/operations/operation-handler.ts +++ b/packages/server/src/common/operations/operation-handler.ts @@ -37,7 +37,7 @@ export abstract class OperationHandler { * * @returns the operation type that can be handled. */ - abstract operationType: string; + abstract readonly operationType: string; readonly label?: string; @@ -46,7 +46,7 @@ export abstract class OperationHandler { * is performed on the model(s). * * @param operation The operation to process. - * @return The created command to be executed on the command stack or `undefined` nothing should be done. + * @return The created command to be executed on the command stack or `undefined` if nothing should be done. */ abstract createCommand(operation: Operation): MaybePromise; diff --git a/packages/server/src/common/utils/registry.spec.ts b/packages/server/src/common/utils/registry.spec.ts index b78c0fa..ba35333 100644 --- a/packages/server/src/common/utils/registry.spec.ts +++ b/packages/server/src/common/utils/registry.spec.ts @@ -13,14 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { MultiRegistry, Registry } from './registry'; import { expect } from 'chai'; +import { NullLogger } from './logger'; +import { MultiRegistry, Registry } from './registry'; describe('Test Registry', () => { let registry: Registry; beforeEach(() => { registry = new Registry(); + registry['logger'] = new NullLogger(); }); it('register - with new key-value pair', () => { diff --git a/packages/server/src/common/utils/registry.ts b/packages/server/src/common/utils/registry.ts index 6da653d..bf5b5a9 100644 --- a/packages/server/src/common/utils/registry.ts +++ b/packages/server/src/common/utils/registry.ts @@ -14,7 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { remove } from '@eclipse-glsp/protocol/lib/utils/array-util'; -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; +import { Logger } from './logger'; /** * A registry manages a set of key-value pairs and provides query functionality. In GLSP this is typically used to @@ -27,6 +28,9 @@ import { injectable } from 'inversify'; @injectable() export class Registry { + @inject(Logger) + protected logger: Logger; + protected elements: Map = new Map(); /** * Registers a new key-value pair. @@ -36,11 +40,12 @@ export class Registry { * @returns `true` if the pair was registered successfully, `false` if another pair with the same key is already * registered. */ - register(key: K, instance: V): boolean { + register(key: K, value: V): boolean { if (!this.hasKey(key)) { - this.elements.set(key, instance); + this.elements.set(key, value); return true; } + this.logger.warn('Could not register key-value pair. Key is already registered', { key, value }); return false; } From 02db0f5cd8f6a94a9092b2fd45a762d5d8bb105e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 27 Sep 2023 09:52:26 -0700 Subject: [PATCH 261/566] =?UTF-8?q?GLSP-211:=20Revise=20TypeHints=20and=20?= =?UTF-8?q?server=20side=20feedback=20for=20creation=20acti=E2=80=A6=20(#6?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GLSP-211: Revise TypeHints and server side feedback for creation actions (#210) - Add action handler for `RequestEdgeCheckAction` and `EdgeCreationChecker` API - Add a optional `EdgeCreationchecker` component that can be implemented by adopters to provide dynamic typehints - Adapt workflow example to use dynamic edge hints for weighted edges Also: Update eslint config to show warnings if we import from internal index instead of the relative path to the file Fixes eclipse-glsp/glsp/issues/211 --- .../common/workflow-diagram-configuration.ts | 19 ++---- .../src/common/workflow-diagram-module.ts | 6 ++ .../common/workflow-edge-creation-checker.ts | 31 ++++++++++ .../server/src/common/di/diagram-module.ts | 21 +++++-- .../type-hints/edge-creation-checker.ts | 44 +++++++++++++ .../request-check-edge-action-handler.ts | 62 +++++++++++++++++++ .../request-type-hints-action-handler.spec.ts | 4 +- .../request-type-hints-action-handler.ts | 5 +- packages/server/src/common/index.ts | 3 +- 9 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 examples/workflow-server/src/common/workflow-edge-creation-checker.ts create mode 100644 packages/server/src/common/features/type-hints/edge-creation-checker.ts create mode 100644 packages/server/src/common/features/type-hints/request-check-edge-action-handler.ts rename packages/server/src/common/{diagram => features/type-hints}/request-type-hints-action-handler.spec.ts (95%) rename packages/server/src/common/{diagram => features/type-hints}/request-type-hints-action-handler.ts (89%) diff --git a/examples/workflow-server/src/common/workflow-diagram-configuration.ts b/examples/workflow-server/src/common/workflow-diagram-configuration.ts index dabf6b5..784c9bf 100644 --- a/examples/workflow-server/src/common/workflow-diagram-configuration.ts +++ b/examples/workflow-server/src/common/workflow-diagram-configuration.ts @@ -18,10 +18,10 @@ import { DiagramConfiguration, GCompartment, GEdge, - getDefaultMapping, GLabel, GModelElementConstructor, - ServerLayoutKind + ServerLayoutKind, + getDefaultMapping } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; import { ActivityNode, Category, TaskNode } from './graph-extension'; @@ -58,15 +58,7 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration { deletable: true, resizable: true, reparentable: true, - containableElementTypeIds: [ - types.DECISION_NODE, - types.MERGE_NODE, - types.FORK_NODE, - types.JOIN_NODE, - types.AUTOMATED_TASK, - types.MANUAL_TASK, - types.CATEGORY - ] + containableElementTypeIds: [types.TASK, types.ACTIVITY_NODE, types.CATEGORY] } ]; } @@ -79,8 +71,9 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration { repositionable: true, deletable: true, routable: true, - sourceElementTypeIds: [types.DECISION_NODE], - targetElementTypeIds: [types.MANUAL_TASK, types.AUTOMATED_TASK, types.FORK_NODE, types.JOIN_NODE] + dynamic: true, + sourceElementTypeIds: [types.ACTIVITY_NODE], + targetElementTypeIds: [types.TASK, types.ACTIVITY_NODE] } ]; } diff --git a/examples/workflow-server/src/common/workflow-diagram-module.ts b/examples/workflow-server/src/common/workflow-diagram-module.ts index e131173..8cfdc82 100644 --- a/examples/workflow-server/src/common/workflow-diagram-module.ts +++ b/examples/workflow-server/src/common/workflow-diagram-module.ts @@ -20,6 +20,7 @@ import { ContextEditValidator, ContextMenuItemProvider, DiagramConfiguration, + EdgeCreationChecker, GLSPServer, GModelDiagramModule, InstanceMultiBinding, @@ -55,6 +56,7 @@ import { EditTaskOperationHandler } from './taskedit/edit-task-operation-handler import { TaskEditContextActionProvider } from './taskedit/task-edit-context-provider'; import { TaskEditValidator } from './taskedit/task-edit-validator'; import { WorkflowDiagramConfiguration } from './workflow-diagram-configuration'; +import { WorkflowEdgeCreationChecker } from './workflow-edge-creation-checker'; import { WorkflowGLSPServer } from './workflow-glsp-server'; import { WorkflowPopupFactory } from './workflow-popup-factory'; @@ -133,4 +135,8 @@ export class WorkflowDiagramModule extends GModelDiagramModule { super.configureContextEditValidators(binding); binding.add(TaskEditValidator); } + + protected override bindEdgeCreationChecker(): BindingTarget | undefined { + return WorkflowEdgeCreationChecker; + } } diff --git a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts new file mode 100644 index 0000000..32080cd --- /dev/null +++ b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { EdgeCreationChecker, GModelElement } from '@eclipse-glsp/server/'; +import { injectable } from 'inversify'; +import { TaskNode } from './graph-extension'; +import { ModelTypes } from './util/model-types'; + +@injectable() +export class WorkflowEdgeCreationChecker implements EdgeCreationChecker { + isValidSource(edgeType: string, sourceElement: GModelElement): boolean { + return edgeType !== ModelTypes.WEIGHTED_EDGE || sourceElement.type === ModelTypes.DECISION_NODE; + } + isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean { + return ( + targetElement instanceof TaskNode || targetElement.type === ModelTypes.FORK_NODE || targetElement.type === ModelTypes.JOIN_NODE + ); + } +} diff --git a/packages/server/src/common/di/diagram-module.ts b/packages/server/src/common/di/diagram-module.ts index b85d224..6f2937e 100644 --- a/packages/server/src/common/di/diagram-module.ts +++ b/packages/server/src/common/di/diagram-module.ts @@ -21,7 +21,6 @@ import { ClientActionForwarder } from '../actions/client-action-handler'; import { CommandStack, DefaultCommandStack } from '../command/command-stack'; import { UndoRedoActionHandler } from '../command/undo-redo-action-handler'; import { DiagramConfiguration } from '../diagram/diagram-configuration'; -import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler'; import { CommandPaletteActionProvider } from '../features/contextactions/command-palette-action-provider'; import { ContextActionsProvider } from '../features/contextactions/context-actions-provider'; import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry'; @@ -57,6 +56,9 @@ import { ResolveNavigationTargetsActionHandler } from '../features/navigation/re import { PopupModelFactory } from '../features/popup/popup-model-factory'; import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; import { DefaultProgressService, ProgressService } from '../features/progress/progress-service'; +import { EdgeCreationChecker } from '../features/type-hints/edge-creation-checker'; +import { RequestCheckEdgeActionHandler } from '../features/type-hints/request-check-edge-action-handler'; +import { RequestTypeHintsActionHandler } from '../features/type-hints/request-type-hints-action-handler'; import { ModelValidator } from '../features/validation/model-validator'; import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; import { CompoundOperationHandler } from '../operations/compound-operation-handler'; @@ -121,6 +123,7 @@ import { * - {@link ClientSessionInitializer} as {@link ClassMultiBinding} * - {@link PopupModelFactory} as optional binding * - {@link LayoutEngine} as optional binding + * - {@link EdgeCreationChecker} as optional binding */ export abstract class DiagramModule extends GLSPModule { @@ -172,7 +175,7 @@ export abstract class DiagramModule extends GLSPModule { applyBindingTarget(context, CommandStack, this.bindCommandStack()).inSingletonScope(); // Navigation - applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver()); + applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver())?.inSingletonScope(); this.configureMultiBinding(new MultiBinding(NavigationTargetProviders), binding => this.configureNavigationTargetProviders(binding) ); @@ -189,8 +192,9 @@ export abstract class DiagramModule extends GLSPModule { this.configureClientSessionInitializers(binding) ); applyBindingTarget(context, ProgressService, this.bindProgressService()).inSingletonScope(); - applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory()); - applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine?.()); + applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory())?.inSingletonScope(); + applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine())?.inSingletonScope(); + applyOptionalBindingTarget(context, EdgeCreationChecker, this.bindEdgeCreationChecker())?.inSingletonScope(); } configureClientSessionInitializers(binding: MultiBinding): void { @@ -203,6 +207,7 @@ export abstract class DiagramModule extends GLSPModule { binding.add(RequestContextActionsHandler); binding.add(RequestTypeHintsActionHandler); binding.add(OperationActionHandler); + binding.add(RequestCheckEdgeActionHandler); binding.add(RequestMarkersHandler); binding.add(RequestPopupModelActionHandler); binding.add(RequestEditValidationHandler); @@ -331,6 +336,7 @@ export abstract class DiagramModule extends GLSPModule { protected bindModelValidator(): BindingTarget | undefined { return undefined; } + protected bindLabelEditValidator(): BindingTarget | undefined { return undefined; } @@ -338,9 +344,11 @@ export abstract class DiagramModule extends GLSPModule { protected bindToolPaletteItemProvider(): BindingTarget | undefined { return DefaultToolPaletteItemProvider; } + protected bindCommandPaletteActionProvider(): BindingTarget | undefined { return undefined; } + protected bindContextMenuItemProvider(): BindingTarget | undefined { return undefined; } @@ -352,7 +360,12 @@ export abstract class DiagramModule extends GLSPModule { protected bindPopupModelFactory(): BindingTarget | undefined { return undefined; } + protected bindLayoutEngine(): BindingTarget | undefined { return undefined; } + + protected bindEdgeCreationChecker(): BindingTarget | undefined { + return undefined; + } } diff --git a/packages/server/src/common/features/type-hints/edge-creation-checker.ts b/packages/server/src/common/features/type-hints/edge-creation-checker.ts new file mode 100644 index 0000000..5baa715 --- /dev/null +++ b/packages/server/src/common/features/type-hints/edge-creation-checker.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement } from '@eclipse-glsp/graph'; + +/** + * Optional service used to check the validity of an edge being created. Used in combination with `dynamic` + * `EdgeTypeHints`. + * A dynamic edge type hint is used for cases where a plain list of allowed source and target element ids is not enough + * to determine wether an edge being created is valid. In this cases the client will query the server to determine wether the edge + * is valid. The `EdgeCreationChecker` then checks the given edge information and returns wether the edge being created is + * valid. + */ +export interface EdgeCreationChecker { + /** + * Checks wether the given source element for an edge being created is valid i.e. if the + * given source is and allowed source element for the given edge type. + * + * @returns `true` if the edge source is valid, `false` otherwise + */ + isValidSource(edgeType: string, sourceElement: GModelElement): boolean; + + /** + * Checks wether the given information for an edge being created is valid i.e. if the + * given target is an allowed target for the given source and edge type. + * + * @return `true` if the edge target is valid, `false` otherwise + */ + isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean; +} +export const EdgeCreationChecker = Symbol('EdgeCreationChecker'); diff --git a/packages/server/src/common/features/type-hints/request-check-edge-action-handler.ts b/packages/server/src/common/features/type-hints/request-check-edge-action-handler.ts new file mode 100644 index 0000000..ba64c25 --- /dev/null +++ b/packages/server/src/common/features/type-hints/request-check-edge-action-handler.ts @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, CheckEdgeResultAction, MaybePromise, RequestCheckEdgeAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ActionHandler } from '../../actions/action-handler'; +import { DiagramConfiguration } from '../../diagram/diagram-configuration'; +import { GLSPServerError, getOrThrow } from '../../utils/glsp-server-error'; +import { ModelState } from '../model/model-state'; +import { EdgeCreationChecker } from './edge-creation-checker'; + +@injectable() +export class RequestCheckEdgeActionHandler implements ActionHandler { + @inject(ModelState) + protected modelState: ModelState; + + @inject(DiagramConfiguration) + protected diagramConfiguration: DiagramConfiguration; + + @inject(EdgeCreationChecker) + @optional() + protected edgeCreationChecker?: EdgeCreationChecker; + + readonly actionKinds: string[] = [RequestCheckEdgeAction.KIND]; + + execute(action: RequestCheckEdgeAction): MaybePromise { + const hasDynamicHint = this.diagramConfiguration.edgeTypeHints.some(hint => hint.elementTypeId === action.edgeType && hint.dynamic); + const { edgeType, sourceElementId, targetElementId } = action; + const isValid = this.edgeCreationChecker && hasDynamicHint ? this.validate(action) : true; + + return [CheckEdgeResultAction.create({ edgeType, isValid, sourceElementId, targetElementId })]; + } + + protected validate(action: RequestCheckEdgeAction): boolean { + const sourceElement = getOrThrow( + this.modelState.index.get(action.sourceElementId), + 'Invalid `RequestCheckEdgeTargetAction`!. Could not find a source element with id: ' + action.sourceElementId + ); + const targetElement = action.targetElementId ? this.modelState.index.get(action.targetElementId) : undefined; + + if (action.targetElementId && !targetElement) { + throw new GLSPServerError( + 'Invalid `RequestCheckEdgeTargetAction`! Could not find a target element with id: ' + action.targetElementId + ); + } + return targetElement + ? this.edgeCreationChecker!.isValidTarget(action.edgeType, sourceElement, targetElement) + : this.edgeCreationChecker!.isValidSource(action.edgeType, sourceElement); + } +} diff --git a/packages/server/src/common/diagram/request-type-hints-action-handler.spec.ts b/packages/server/src/common/features/type-hints/request-type-hints-action-handler.spec.ts similarity index 95% rename from packages/server/src/common/diagram/request-type-hints-action-handler.spec.ts rename to packages/server/src/common/features/type-hints/request-type-hints-action-handler.spec.ts index 7d70f90..02289cf 100644 --- a/packages/server/src/common/diagram/request-type-hints-action-handler.spec.ts +++ b/packages/server/src/common/features/type-hints/request-type-hints-action-handler.spec.ts @@ -16,8 +16,8 @@ import { EdgeTypeHint, RequestTypeHintsAction, SetTypeHintsAction, ShapeTypeHint } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; -import * as mock from '../test/mock-util'; -import { DiagramConfiguration } from './diagram-configuration'; +import { DiagramConfiguration } from '../../diagram/diagram-configuration'; +import * as mock from '../../test/mock-util'; import { RequestTypeHintsActionHandler } from './request-type-hints-action-handler'; describe('test RequestTypeHintsActionHandler', () => { diff --git a/packages/server/src/common/diagram/request-type-hints-action-handler.ts b/packages/server/src/common/features/type-hints/request-type-hints-action-handler.ts similarity index 89% rename from packages/server/src/common/diagram/request-type-hints-action-handler.ts rename to packages/server/src/common/features/type-hints/request-type-hints-action-handler.ts index 7bbaf94..c5db3ee 100644 --- a/packages/server/src/common/diagram/request-type-hints-action-handler.ts +++ b/packages/server/src/common/features/type-hints/request-type-hints-action-handler.ts @@ -15,13 +15,12 @@ ********************************************************************************/ import { Action, MaybePromise, RequestTypeHintsAction, SetTypeHintsAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ActionHandler } from '../actions/action-handler'; -import { DiagramConfiguration } from './diagram-configuration'; +import { ActionHandler } from '../../actions/action-handler'; +import { DiagramConfiguration } from '../../diagram/diagram-configuration'; @injectable() export class RequestTypeHintsActionHandler implements ActionHandler { @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; - static KINDS = [RequestTypeHintsAction.KIND]; execute(action: RequestTypeHintsAction): MaybePromise { return [ diff --git a/packages/server/src/common/index.ts b/packages/server/src/common/index.ts index 4c60009..a2e84b4 100644 --- a/packages/server/src/common/index.ts +++ b/packages/server/src/common/index.ts @@ -33,7 +33,6 @@ export * from './di/multi-binding'; export * from './di/server-module'; export * from './di/service-identifiers'; export * from './diagram/diagram-configuration'; -export * from './diagram/request-type-hints-action-handler'; export * from './features/clipboard/request-clipboard-data-action-handler'; export * from './features/contextactions/command-palette-action-provider'; export * from './features/contextactions/context-actions-provider'; @@ -65,6 +64,8 @@ export * from './features/navigation/request-navigation-targets-action-handler'; export * from './features/navigation/resolve-navigation-targets-action-handler'; export * from './features/popup/popup-model-factory'; export * from './features/popup/request-popup-model-action-handler'; +export * from './features/type-hints/edge-creation-checker'; +export * from './features/type-hints/request-type-hints-action-handler'; export * from './features/validation/model-validator'; export * from './features/validation/request-markers-handler'; export * from './gmodel/index'; From 1cdad0623c070994b7b230e3040d3728cc776c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aylin=20Sario=C4=9Flu?= <61785275+aylin-sarioglu@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:31:37 +0200 Subject: [PATCH 262/566] GLSP-1029: Introducing accessible tool palette, node and edge creation via keyboard (#279) - Introduce tool and handler for Grid and Pointer for node creation - Introduce tool and handler for edge creation with autocomplete feature - Introduce tool and handler zooming via grid Part of eclipse-glsp/glsp#1029 --- examples/workflow-standalone/src/di.config.ts | 12 +- packages/client/css/glsp-sprotty.css | 2 +- packages/client/css/keyboard-tool-palette.css | 66 +++ packages/client/css/keyboard.css | 50 ++ .../src/features/accessibility/README.md | 76 ++- .../accessibility/accessibility-module.ts | 5 + .../src/features/accessibility/actions.ts | 33 ++ .../accessibility/edge-autocomplete/action.ts | 34 ++ .../edge-autocomplete-context.ts | 24 + .../edge-autocomplete-palette.ts | 147 +++++ .../edge-autocomplete-tool.ts | 56 ++ .../accessibility/global-keylistener-tool.ts | 98 ++++ .../accessibility/keyboard-grid/action.ts | 93 ++++ .../accessibility/keyboard-grid/constants.ts | 24 + .../keyboard-grid-search-palette.ts | 64 +++ .../keyboard-grid/keyboard-grid.ts | 192 +++++++ .../keyboard-grid/keyboard-node-grid.ts | 63 +++ .../accessibility/keyboard-pointer/actions.ts | 34 ++ .../keyboard-pointer/constants.ts | 22 + .../keyboard-pointer-listener.ts | 125 +++++ .../keyboard-pointer-module.ts | 63 +++ .../keyboard-pointer-position.ts | 78 +++ .../keyboard-pointer/keyboard-pointer.ts | 120 +++++ .../keyboard-tool-palette-module.ts | 35 ++ .../keyboard-tool-palette.ts | 505 ++++++++++++++++++ .../accessibility/search/search-palette.ts | 24 +- .../accessibility/toast/messages.json | 4 +- .../view-key-tools/view-key-tools-module.ts | 7 +- .../view-key-tools/zoom-key-tool.ts | 90 +++- .../base-autocomplete-palette.ts | 2 +- .../src/features/tool-palette/tool-palette.ts | 2 +- .../tools/node-creation/node-creation-tool.ts | 4 + .../src/features/viewport/viewport-handler.ts | 67 +++ .../src/features/viewport/viewport-modules.ts | 6 + packages/client/src/utils/viewpoint-util.ts | 7 +- 35 files changed, 2213 insertions(+), 21 deletions(-) create mode 100644 packages/client/css/keyboard-tool-palette.css create mode 100644 packages/client/css/keyboard.css create mode 100644 packages/client/src/features/accessibility/actions.ts create mode 100644 packages/client/src/features/accessibility/edge-autocomplete/action.ts create mode 100644 packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts create mode 100644 packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts create mode 100644 packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts create mode 100644 packages/client/src/features/accessibility/global-keylistener-tool.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/action.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/constants.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/actions.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/constants.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts create mode 100644 packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts create mode 100644 packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts create mode 100644 packages/client/src/features/viewport/viewport-handler.ts diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 2843cbe..2e1c8ff 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -22,12 +22,20 @@ import { IDiagramOptions, LogLevel, STANDALONE_MODULE_CONFIG, - TYPES + TYPES, + toolPaletteModule } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import '../css/diagram.css'; export default function createContainer(options: IDiagramOptions): Container { - const container = createWorkflowDiagramContainer(createDiagramOptionsModule(options), accessibilityModule, STANDALONE_MODULE_CONFIG); + const container = createWorkflowDiagramContainer( + createDiagramOptionsModule(options), + { + add: accessibilityModule, + remove: toolPaletteModule + }, + STANDALONE_MODULE_CONFIG + ); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index b08cae5..8bbd826 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -264,7 +264,7 @@ } .search-hidden { - opacity: 0.2; + opacity: 0.4; } .search-highlighted .sprotty-node, diff --git a/packages/client/css/keyboard-tool-palette.css b/packages/client/css/keyboard-tool-palette.css new file mode 100644 index 0000000..28bf3a3 --- /dev/null +++ b/packages/client/css/keyboard-tool-palette.css @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.accessibility-tool-palette.tool-palette { + top: 48px; + width: 240px; +} + +.accessibility-tool-palette .header-tools i { + margin-right: 0.5em; + position: relative; +} + +.accessibility-tool-palette .tool-button .key-shortcut, +.accessibility-tool-palette .header-tools .key-shortcut { + display: none; + position: absolute; + + border-radius: 50%; + width: 0.5rem; + height: 0.5rem; + padding: 0.5rem; + + background: #fff; + border: 2px solid #666; + color: black; + text-align: center; + + font: bold 0.8rem Arial, sans-serif; + line-height: 0.5rem; +} + +.accessibility-tool-palette .tool-button .key-shortcut { + left: -2rem; +} + +.accessibility-tool-palette .header-tools .key-shortcut { + top: -2.5rem; + right: -0.3rem; +} + +.accessibility-show-shortcuts:focus-within .header-tools .key-shortcut, +.accessibility-show-shortcuts:focus-within .tool-button .key-shortcut { + display: block; +} + +.accessibility-tool-palette.collapsible-palette { + overflow: visible; +} + +.accessibility-tool-palette.collapsible-palette + .minimize-palette-button { + top: 50px; +} diff --git a/packages/client/css/keyboard.css b/packages/client/css/keyboard.css new file mode 100644 index 0000000..1d4db6c --- /dev/null +++ b/packages/client/css/keyboard.css @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.grid-container { + display: grid; + position: absolute; + top: 0.5rem; + bottom: 0.5rem; + left: 0.5rem; + right: 0.5rem; + grid-template-columns: auto auto auto; +} + +.grid-container:focus { + opacity: 1 !important; +} + +.grid-item { + border: 1px solid rgba(0, 0, 0, 0.6); + padding: 1rem; + /* Fix overlapping borders */ + margin: 0 -1px -1px 0; +} + +.grid-item-number { + border-radius: 50%; + width: 1.5rem; + height: 1.5rem; + padding: 0.5rem; + + background: #fff; + border: 2px solid #666; + color: black; + text-align: center; + + font: bold 1.3rem Arial, sans-serif; +} diff --git a/packages/client/src/features/accessibility/README.md b/packages/client/src/features/accessibility/README.md index f403e87..a61952f 100644 --- a/packages/client/src/features/accessibility/README.md +++ b/packages/client/src/features/accessibility/README.md @@ -11,15 +11,63 @@ To activate and use only specific accessibility features, the respective module Below is a list of the keyboard only accessibility features:
-| Feature | Description | Shortcut | Module | -| ------- | :----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | :-------------------: | -| Search | Search for elements or edges using labels, types, etc.
with respective diagram highlighting | CTRL+F | `searchPaletteModule` | -| Move | Move elements, edges or viewport into the directions | \|\| \|\| \|\| | `moveZoomModule` | -| Zoom | Zoom in or out gradually to elements or viewport or reset to default zoom level. | + \|\| - \|\| CTRL+0 | `moveZoomModule` | -| Resize | Increase or decrease element sizes gradually or reset to default element size. | ALT+A then + \|\| - \|\| CTRL+0 | `resizeKeyModule` | +| Feature | Description | Shortcut | Module | +| -------------------------- | :----------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | :---------------------------: | +| Tool Palette | Accessible tool palette | ALT+P | `keyboardToolPaletteModule` | +| Graph | Focus on the graph | ALT+G | `-` | +| Grid & Pointer | Used for positioning on the canvas for creating new elements | activated through selected _Tool Palette_ entry | `keyboardControlModule` | +| Search | Search for elements or edges using labels, types, etc.
with respective diagram highlighting | CTRL+F | `searchPaletteModule` | +| Move | Move elements, edges or viewport into the directions | \|\| \|\| \|\| | `moveZoomModule` | +| Zoom | Zoom in or out gradually to elements or viewport or reset to default zoom level. | + \|\| - \|\| CTRL+0 | `moveZoomModule` | +| Zoom via Grid | Zoom in gradually according to the direction of the selected grid box. | CTRL++ then + \|\| - \|\| CTRL+0 | `moveZoomModule` | +| Resize | Increase or decrease element sizes gradually or reset to default element size. | ALT+A then + \|\| - \|\| CTRL+0 | `resizeKeyModule` | +| Element Navigation | Navigate through the elements of the diagram (**Default** or **Position-based** Navigation). | N or ALT+N then use arrow keys to iterate | `glspElementNavigationModule` | +| Help | Display list of existing keyboard shortcuts | ALT+H | `glspShortcutHelpModule` | +| Focus Tracker | Display the current focused element on the canvas | no key needed | `glspFocusTrackerModule` | +| User Notifications (Toast) | Displaying user notifications | no key needed | `glspToastModule` | ## Usage +### CRUD Modeling Operations + +#### Focus on the Graph + +- ALT+G: Use to set the focus on the graph. + +#### Tool Palette + +The shortcut ALT + P sets the focus on the tool palette. Afterward, the characters a - z select an element or 1 - 5 for the header menu options. + +#### Grid + Pointer + +After selecting a node in the tool palette, the grid gets visible. The grid is for positioning the _pointer_ in the screen. + +The following shortcuts are usable: + +- 1- 9: Position the pointer in the grid +- Use arrow keys ( || || || ) to move the pointer to a direction +- ENTER: Create the node +- CTRL + ENTER: Create multiple nodes + +#### Create Nodes + +1. ALT + P: Focus the tool palette +2. a - z: Select a node +3. 1 - 9: Position the pointer in a cell +4. Use arrow keys ( || || || ) to move the pointer to the correct position +5. Create the node by using either + - ENTER: Create the node und finishes the operation + - CTRL + ENTER: Create multiple nodes + +#### Create Edges + +1. ALT + P: Focus the tool palette +2. a - z: Select an edge +3. Type in either **type** or **name** of node for **source** +4. ENTER: Make selection +5. Type in either **type** or **name** of node for **target** +6. ENTER: Make selection + #### Search The search palette allows to search labelled elements or edges that have a labelled node as source or target. The result set will be highlighted accordingly, to also be able to visually distinguish between the searched and the remaining parts of the given diagram. @@ -52,7 +100,17 @@ viewport. 2. Use + or - to zoom in or out gradually. 3. CTRL+0: Set the zoom level to default. -### Resize element +#### Zoom via Grid + +This zoom functionality via Grid can be used to gradually adapt the zoom level according to the direction of the selected _Grid Box_. + +#### Steps + +1. CTRL++: Display the grid. +2. 1- 9: Position the pointer in a cell to zoom. +3. Repeat **2.)** to reach the desired zoom level. + +#### Resize element The resize functionality helps to set the size of the nodes, by either increasing or decreasing the size of the nodes’ shape gradually. @@ -66,11 +124,13 @@ The resize functionality helps to set the size of the nodes, by either increasin ### Model Navigation +For navigating through the elements of the diagram two navigation algorithms are provided. + #### Default Navigation (following directions of relations) 1. Select element as starting point. 2. N: Activate default navigation. -3. Use arrow keys to iterate through model according to the directions of the given relations. +3. Use arrow keys ( || || || ) to iterate through model according to the directions of the given relations. #### Position-based Navigation (following x and y coordinates) diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index b5101b9..c60c264 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -20,9 +20,12 @@ import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; +import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; +import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; import { configureToastTool } from './toast/toast-module'; + /** * Enables the accessibility tools for a keyboard-only-usage */ @@ -33,6 +36,8 @@ export const accessibilityModule = new FeatureModule((bind, unbind, isBound, reb configureMoveZoom(context); configureSearchPaletteModule(context); configureShortcutHelpTool(context); + configureKeyboardToolPaletteTool(context); + configureKeyboardControlTools(context); configureElementNavigationTool(context); configureFocusTrackerTool(context); configureToastTool(context); diff --git a/packages/client/src/features/accessibility/actions.ts b/packages/client/src/features/accessibility/actions.ts new file mode 100644 index 0000000..50796ea --- /dev/null +++ b/packages/client/src/features/accessibility/actions.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, hasStringProp } from '~glsp-sprotty'; + +export interface FocusDomAction extends Action { + kind: typeof FocusDomAction.KIND; + id: string; +} + +export namespace FocusDomAction { + export const KIND = 'focusDomAction'; + + export function is(object: any): object is FocusDomAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'id'); + } + + export function create(id: string): FocusDomAction { + return { kind: KIND, id }; + } +} diff --git a/packages/client/src/features/accessibility/edge-autocomplete/action.ts b/packages/client/src/features/accessibility/edge-autocomplete/action.ts new file mode 100644 index 0000000..11cbfb9 --- /dev/null +++ b/packages/client/src/features/accessibility/edge-autocomplete/action.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, hasObjectProp, hasStringProp } from '~glsp-sprotty'; + +export interface SetEdgeTargetSelectionAction extends Action { + kind: typeof SetEdgeTargetSelectionAction.KIND; + elementId: string; + context: string; +} + +export namespace SetEdgeTargetSelectionAction { + export const KIND = 'setEdgeTargetSelectionAction'; + + export function is(object: any): object is SetEdgeTargetSelectionAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'elementId') && hasObjectProp(object, 'context'); + } + + export function create(elementId: string, context: string): SetEdgeTargetSelectionAction { + return { kind: KIND, elementId, context }; + } +} diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts new file mode 100644 index 0000000..f03131e --- /dev/null +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { TriggerEdgeCreationAction } from '~glsp-sprotty'; + +export interface EdgeAutocompleteContext { + role: 'source' | 'target'; + trigger: TriggerEdgeCreationAction; + sourceId?: string; + targetId?: string; +} diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts new file mode 100644 index 0000000..0c68b4a --- /dev/null +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts @@ -0,0 +1,147 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { + codiconCSSString, + isConnectable, + SEdge, + SModelElement, + SModelRoot, + LabeledAction, + name, + SetUIExtensionVisibilityAction, + IActionHandler, + Action, + CreateEdgeOperation, + TriggerEdgeCreationAction +} from '~glsp-sprotty'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { EdgeAutocompleteContext } from './edge-autocomplete-context'; +import { SearchAutocompletePalette } from '../search/search-palette'; +import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; +import { SetEdgeTargetSelectionAction } from './action'; +import { CloseReason, toActionArray } from '../../../base/auto-complete/auto-complete-widget'; + +export namespace EdgeAutocompletePaletteMetadata { + export const ID = 'edge-autocomplete-palette'; +} + +@injectable() +export class EdgeAutocompletePalette extends SearchAutocompletePalette implements IActionHandler { + protected context?: EdgeAutocompleteContext; + + protected readonly targetSuggestionProvider = new PossibleEdgeTargetAutocompleteSuggestionProvider(); + + override id(): string { + return EdgeAutocompletePaletteMetadata.ID; + } + + handle(action: Action): Action | void { + if (TriggerEdgeCreationAction.is(action)) { + this.context = { + trigger: action, + role: 'source' + }; + this.targetSuggestionProvider.setContext(action, this.context); + } + } + + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { + super.onBeforeShow(containerElement, root, ...contextElementIds); + + this.autocompleteWidget.inputField.placeholder = `Search for ${this.context?.role} elements`; + } + + protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + return [this.targetSuggestionProvider]; + } + + protected reload(): void { + const context = this.context; + this.hide(); + this.context = context; + this.actionDispatcher.dispatch( + SetUIExtensionVisibilityAction.create({ + extensionId: EdgeAutocompletePaletteMetadata.ID, + visible: true + }) + ); + } + + protected override executeSuggestion(input: LabeledAction | Action[] | Action): void { + const action = toActionArray(input)[0] as SetEdgeTargetSelectionAction; + + if (this.context?.role === 'source') { + this.context.sourceId = action.elementId; + this.context.role = 'target'; + this.reload(); + } else if (this.context?.role === 'target') { + this.context.targetId = action.elementId; + } + if (this.context?.sourceId !== undefined && this.context?.targetId !== undefined) { + this.actionDispatcher.dispatchAll([ + CreateEdgeOperation.create({ + elementTypeId: this.context.trigger.elementTypeId, + sourceElementId: this.context.sourceId, + targetElementId: this.context.targetId, + args: this.context.trigger.args + }), + EnableDefaultToolsAction.create() + ]); + this.hide(); + } + } + + protected override autocompleteHide(reason: CloseReason): void { + if (reason !== 'submission') { + this.hide(); + } + } +} + +@injectable() +export class PossibleEdgeTargetAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { + protected proxyEdge?: SEdge; + protected context?: EdgeAutocompleteContext; + + setContext(triggerAction: TriggerEdgeCreationAction, edgeAutocompleteContext: EdgeAutocompleteContext): void { + this.proxyEdge = new SEdge(); + this.proxyEdge.type = triggerAction.elementTypeId; + this.context = edgeAutocompleteContext; + } + + isAllowedSource(element: SModelElement | undefined, role: 'source' | 'target'): boolean { + return element !== undefined && this.proxyEdge !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, role); + } + + async retrieveSuggestions(root: Readonly, text: string): Promise { + const context = this.context; + if (this.context === undefined) { + return []; + } + + const nodes = toArray(root.index.all().filter(element => this.isAllowedSource(element, context!.role))) as SEdge[]; + return nodes.map(node => ({ + element: node, + action: { + label: `[${node.type}] ${name(node) ?? ''}`, + actions: [SetEdgeTargetSelectionAction.create(node.id, context!.role)], + icon: codiconCSSString('arrow-both') + } + })); + } +} diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts new file mode 100644 index 0000000..80f6a70 --- /dev/null +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { KeyTool, KeyListener, SetUIExtensionVisibilityAction, SModelElement, Action } from '~glsp-sprotty'; +import { Tool } from '../../../base/tool-manager/tool'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { EdgeAutocompletePaletteMetadata } from './edge-autocomplete-palette'; + +@injectable() +export class EdgeAutocompletePaletteTool implements Tool { + static readonly ID = 'glsp.edge-autocomplete-palette-tool'; + + protected readonly keyListener = new EdgeAutocompletePaletteKeyListener(); + + @inject(KeyTool) protected keyTool: KeyTool; + + get id(): string { + return EdgeAutocompletePaletteTool.ID; + } + + enable(): void { + this.keyTool.register(this.keyListener); + } + + disable(): void { + this.keyTool.deregister(this.keyListener); + } +} + +export class EdgeAutocompletePaletteKeyListener extends KeyListener { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'Escape')) { + return [ + SetUIExtensionVisibilityAction.create({ + extensionId: EdgeAutocompletePaletteMetadata.ID, + visible: false, + contextElementsId: [] + }) + ]; + } + return []; + } +} diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts new file mode 100644 index 0000000..50f1d7b --- /dev/null +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { SetUIExtensionVisibilityAction, Action } from '~glsp-sprotty'; +import { Tool } from '../../base/tool-manager/tool'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { ToolPalette } from '../tool-palette/tool-palette'; +import { FocusDomAction } from './actions'; +import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; +import { KeyboardPointerMetadata } from './keyboard-pointer/constants'; +import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from './key-shortcut/accessible-key-shortcut'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { KeyboardToolPalette } from './keyboard-tool-palette/keyboard-tool-palette'; + +@injectable() +export class GlobalKeyListenerTool implements Tool, AccessibleKeyShortcutProvider { + static ID = 'glsp.global-key-listener'; + + isEditTool = false; + protected alreadyRegistered = false; + + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + get id(): string { + return GlobalKeyListenerTool.ID; + } + + enable(): void { + if (!this.alreadyRegistered) { + this.alreadyRegistered = true; + document.addEventListener('keyup', this.trigger.bind(this)); + this.registerShortcutKey(); + } + } + + disable(): void { + // It is not possible to remove the handlers after registration + // The handlers need to be available all the time to work correctly + } + + registerShortcutKey(): void { + this.actionDispatcher.onceModelInitialized().then(() => { + this.actionDispatcher.dispatchAll([ + SetAccessibleKeyShortcutAction.create({ + token: KeyboardToolPalette.name, + keys: [{ shortcuts: ['ALT', 'P'], description: 'Focus on tool palette', group: 'Tool-Palette', position: 0 }] + }), + SetAccessibleKeyShortcutAction.create({ + token: 'Graph', + keys: [{ shortcuts: ['ALT', 'G'], description: 'Focus on graph', group: 'Graph', position: 0 }] + }) + ]); + }); + } + + trigger(event: KeyboardEvent): void { + this.actionDispatcher.dispatchAll(this.handleKeyEvent(event)); + } + + protected handleKeyEvent(event: KeyboardEvent): Action[] { + if (this.matchesSetFocusOnToolPalette(event)) { + return [FocusDomAction.create(ToolPalette.ID)]; + } else if (this.matchesSetFocusOnDiagram(event)) { + return [FocusDomAction.create('graph')]; + } else if (this.matchesReleaseFocusFromToolPalette(event)) { + return [ + SetUIExtensionVisibilityAction.create({ extensionId: KeyboardPointerMetadata.ID, visible: false, contextElementsId: [] }), + SetUIExtensionVisibilityAction.create({ extensionId: KeyboardGridMetadata.ID, visible: false, contextElementsId: [] }), + SetUIExtensionVisibilityAction.create({ extensionId: KeyboardNodeGridMetadata.ID, visible: false, contextElementsId: [] }) + ]; + } + return []; + } + + protected matchesSetFocusOnToolPalette(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyP', 'alt'); + } + protected matchesSetFocusOnDiagram(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyG', 'alt'); + } + protected matchesReleaseFocusFromToolPalette(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/action.ts b/packages/client/src/features/accessibility/keyboard-grid/action.ts new file mode 100644 index 0000000..364d804 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/action.ts @@ -0,0 +1,93 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, hasObjectProp, Point } from '~glsp-sprotty'; + +export interface EnableKeyboardGridAction extends Action { + kind: typeof EnableKeyboardGridAction.KIND; + options: EnableKeyboardGridAction.Options; +} + +export namespace EnableKeyboardGridAction { + export const KIND = 'enableKeyboardGrid'; + + export interface Options { + originId: string; + triggerActions: Action[]; + } + + export function is(object: any): object is EnableKeyboardGridAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'options'); + } + + export function create(options: Options): EnableKeyboardGridAction { + return { + kind: KIND, + options + }; + } +} + +export interface KeyboardGridCellSelectedAction extends Action { + kind: typeof KeyboardGridCellSelectedAction.KIND; + options: KeyboardGridCellSelectedAction.Options; +} + +export namespace KeyboardGridCellSelectedAction { + export const KIND = 'keyboardGridCellSelectedAction'; + + export interface Options { + originId: string; + cellId: string; + centerCellPosition: Point; + } + + export function is(object: any): object is KeyboardGridCellSelectedAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'options'); + } + + export function create(options: Options): KeyboardGridCellSelectedAction { + return { + kind: KIND, + options + }; + } +} + +export interface KeyboardGridKeyboardEventAction extends Action { + kind: typeof KeyboardGridKeyboardEventAction.KIND; + options: KeyboardGridKeyboardEventAction.Options; +} + +export namespace KeyboardGridKeyboardEventAction { + export const KIND = 'keyboardGridKeyboardEvent'; + + export interface Options { + originId: string; + event: KeyboardEvent; + } + + export function is(object: any): object is KeyboardGridKeyboardEventAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'options'); + } + + export function create(options: Options): KeyboardGridKeyboardEventAction { + return { + kind: KIND, + options + }; + } +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/constants.ts b/packages/client/src/features/accessibility/keyboard-grid/constants.ts new file mode 100644 index 0000000..7d6aec1 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/constants.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export namespace KeyboardGridMetadata { + export const ID = 'keyboard-grid'; + export const TAB_INDEX = 15; +} + +export namespace KeyboardNodeGridMetadata { + export const ID = 'keyboard-node-grid'; +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts new file mode 100644 index 0000000..cd383fb --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import '../../../../css/keyboard.css'; + +import { injectable } from 'inversify'; +import { SEdge, SetUIExtensionVisibilityAction, SModelElement, SModelRoot, Action } from '~glsp-sprotty'; +import { KeyboardGridMetadata } from './constants'; +import { + RevealEdgeElementAutocompleteSuggestionProvider, + RevealNamedElementAutocompleteSuggestionProvider, + SearchAutocompletePalette +} from '../search/search-palette'; +import { IAutocompleteSuggestionProvider } from '../../../features/autocomplete-palette/autocomplete-suggestion-providers'; + +export namespace GridSearchPaletteMetadata { + export const ID = 'grid-search-palette'; +} + +@injectable() +export class GridSearchPalette extends SearchAutocompletePalette { + override id(): string { + return GridSearchPaletteMetadata.ID; + } + + protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + return [new GridRevealNamedElementSuggestionProvider(), new GridRevealEdgeSuggestionProvider()]; + } +} + +export class GridRevealEdgeSuggestionProvider extends RevealEdgeElementAutocompleteSuggestionProvider { + protected override getActions(edge: SEdge): Action[] { + return [ + ...super.getActions(edge), + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardGridMetadata.ID, + visible: true + }) + ]; + } +} +export class GridRevealNamedElementSuggestionProvider extends RevealNamedElementAutocompleteSuggestionProvider { + protected override getActions(nameable: SModelElement): Action[] { + return [ + ...super.getActions(nameable), + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardGridMetadata.ID, + visible: true + }) + ]; + } +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts new file mode 100644 index 0000000..897b589 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -0,0 +1,192 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import '../../../../css/keyboard.css'; + +import { inject, injectable } from 'inversify'; +import { + AbstractUIExtension, + ActionDispatcher, + IActionHandler, + SetUIExtensionVisibilityAction, + SModelRoot, + TYPES, + Action, + ICommand, + Point +} from '~glsp-sprotty'; +import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { KeyboardGridMetadata } from './constants'; +import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from './action'; + +@injectable() +export class KeyboardGrid extends AbstractUIExtension implements IActionHandler { + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: ActionDispatcher; + + protected triggerActions: Action[] = []; + protected originId: string; + + id(): string { + return KeyboardGridMetadata.ID; + } + + containerClass(): string { + return KeyboardGridMetadata.ID; + } + handle(action: Action): void | Action | ICommand { + if (EnableKeyboardGridAction.is(action)) { + this.triggerActions = action.options.triggerActions; + this.originId = action.options.originId; + this.actionDispatcher.dispatch( + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardGridMetadata.ID, + visible: true + }) + ); + } + } + + protected initializeContents(containerElement: HTMLElement): void { + containerElement.tabIndex = KeyboardGridMetadata.TAB_INDEX; + containerElement.classList.add('grid-container'); + + for (let i = 1; i <= 9; i++) { + const gridNumber = document.createElement('div'); + const gridItem = document.createElement('div'); + + gridItem.classList.add('grid-item'); + gridItem.id = `keyboard-grid-item-${i}`; + + gridNumber.classList.add('grid-item-number'); + gridNumber.innerHTML = i.toString(); + + gridItem.appendChild(gridNumber); + containerElement.appendChild(gridItem); + } + + this.containerElement.onkeydown = ev => { + this.onKeyDown(ev); + }; + } + + protected onKeyDown(event: KeyboardEvent): void { + this.activateCellIfDigitEvent(event); + this.hideIfEscapeEvent(event); + + this.actionDispatcher.dispatch( + KeyboardGridKeyboardEventAction.create({ + originId: this.originId, + event + }) + ); + } + + protected override setContainerVisible(visible: boolean): void { + if (this.containerElement) { + if (visible) { + this.containerElement.style.visibility = 'visible'; + this.containerElement.style.opacity = '0.7'; + } else { + this.containerElement.style.visibility = 'hidden'; + this.containerElement.style.opacity = '0'; + } + } + } + + override show(root: Readonly, ...contextElementIds: string[]): void { + super.show(root, ...contextElementIds); + this.containerElement.focus(); + } + + protected hideIfEscapeEvent(event: KeyboardEvent): any { + if (this.matchesDeactivateGrid(event)) { + this.hide(); + } + } + + protected activateCellIfDigitEvent(event: KeyboardEvent): any { + let index: number | undefined = undefined; + + for (let i = 1; i <= 9; i++) { + if (this.matchesGridBoxAtIndex(event, i)) { + index = i; + break; + } + } + + if (index !== undefined) { + const position = this.centerPositionOfCell(index); + + this.dispatchActionsForCell(index, position); + } + } + + protected dispatchActionsForCell(index: number, cellCenter: Point): void { + this.actionDispatcher.dispatchAll([ + ...this.triggerActions, + KeyboardGridCellSelectedAction.create({ + originId: this.originId, + cellId: index.toString(), + centerCellPosition: cellCenter + }) + ]); + } + protected centerPositionOfCell(index: number): Point { + let x = 0; + let y = 0; + + const activeGridCell = document.getElementById(`keyboard-grid-item-${index}`); + // eslint-disable-next-line no-null/no-null + if (activeGridCell !== null) { + const positions = this.getCenterOfCell(activeGridCell); + x = positions[0]; + y = positions[1]; + } + + return { + x, + y + }; + } + protected matchesDeactivateGrid(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } + protected matchesGridBoxAtIndex(event: KeyboardEvent, index: number): boolean { + return matchesKeystroke(event, ('Digit' + index) as KeyCode) || matchesKeystroke(event, ('Numpad' + index) as KeyCode); + } + // https://www.delftstack.com/howto/javascript/get-position-of-element-in-javascript/ + private getOffset(el: any): { top: number; left: number } { + let _x = 0; + let _y = 0; + while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) { + _x += el.offsetLeft - el.scrollLeft; + _y += el.offsetTop - el.scrollTop; + el = el.offsetParent; + } + return { top: _y, left: _x }; + } + + private getCenterOfCell(cell: HTMLElement): number[] { + const cellLeft = this.getOffset(cell).left; + const cellTop = this.getOffset(cell).top; + const cellWidth = cell.offsetWidth; + const cellHeight = cell.offsetHeight; + + const newCellWidth = cellWidth / 2; + const newCellHeight = cellHeight / 2; + + return [cellLeft + newCellWidth, cellTop + newCellHeight]; + } +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts new file mode 100644 index 0000000..568bded --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import '../../../../css/keyboard.css'; + +import { inject, injectable } from 'inversify'; +import { ICommand, SetUIExtensionVisibilityAction, Action } from '~glsp-sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { KeyboardPointer } from '../keyboard-pointer/keyboard-pointer'; +import { KeyboardPointerMetadata } from '../keyboard-pointer/constants'; +import { GridSearchPaletteMetadata } from './keyboard-grid-search-palette'; +import { KeyboardGrid } from './keyboard-grid'; +import { KeyboardNodeGridMetadata } from './constants'; + +@injectable() +export class KeyboardNodeGrid extends KeyboardGrid { + @inject(KeyboardPointer) protected readonly keyboardPointer: KeyboardPointer; + + protected override triggerActions = [SetUIExtensionVisibilityAction.create({ extensionId: KeyboardPointerMetadata.ID, visible: true })]; + protected override originId = KeyboardPointerMetadata.ID; + + override id(): string { + return KeyboardNodeGridMetadata.ID; + } + + override handle(action: Action): void | Action | ICommand { + // Do nothing + } + + protected override onKeyDown(event: KeyboardEvent): void { + super.onKeyDown(event); + this.showSearchOnEvent(event); + + if (this.keyboardPointer.isVisible) { + this.keyboardPointer.getKeyListener.keyDown(event); + } + } + + protected showSearchOnEvent(event: KeyboardEvent): void { + if (matchesKeystroke(event, 'KeyF', 'ctrl')) { + event.preventDefault(); + this.actionDispatcher.dispatch( + SetUIExtensionVisibilityAction.create({ + extensionId: GridSearchPaletteMetadata.ID, + visible: true + }) + ); + this.hide(); + } + } +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/actions.ts b/packages/client/src/features/accessibility/keyboard-pointer/actions.ts new file mode 100644 index 0000000..d1c1637 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/actions.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, hasNumberProp } from '~glsp-sprotty'; + +export interface SetKeyboardPointerRenderPositionAction extends Action { + kind: typeof SetKeyboardPointerRenderPositionAction.KIND; + x: number; + y: number; +} + +export namespace SetKeyboardPointerRenderPositionAction { + export const KIND = 'setKeyboardPointerRenderPositionAction'; + + export function is(object: any): object is SetKeyboardPointerRenderPositionAction { + return Action.hasKind(object, KIND) && hasNumberProp(object, 'x') && hasNumberProp(object, 'y'); + } + + export function create(x: number, y: number): SetKeyboardPointerRenderPositionAction { + return { kind: KIND, x, y }; + } +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/constants.ts b/packages/client/src/features/accessibility/keyboard-pointer/constants.ts new file mode 100644 index 0000000..a98001c --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/constants.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export namespace KeyboardPointerMetadata { + export const ID = 'keyboard-pointer'; + export const CRICLE_HEIGHT = 16; + export const CIRCLE_WIDTH = 16; + export const TAB_INDEX = 10; +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts new file mode 100644 index 0000000..e851b0e --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts @@ -0,0 +1,125 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { IActionDispatcher, SetUIExtensionVisibilityAction, CreateNodeOperation } from '~glsp-sprotty'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; +import { KeyboardPointerMetadata } from './constants'; +import { KeyboardPointer } from './keyboard-pointer'; +import { KeyboardPointerPosition } from './keyboard-pointer-position'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; + +/** + * Keyboard listener for the pointer with the necessary logic to handle keyboard events + */ +export class KeyboardPointerKeyboardListener { + protected get position(): KeyboardPointerPosition { + return this.keyboardPointer.getPosition; + } + + constructor(protected readonly keyboardPointer: KeyboardPointer, protected readonly actionDispatcher: IActionDispatcher) {} + + keyDown(event: KeyboardEvent): void { + this.moveIfArrows(event); + this.createIfEnterEvent(event); + this.hideIfEscapeEvent(event); + } + + protected moveIfArrows(event: KeyboardEvent): any { + if (this.matchesMovePointerDown(event)) { + this.position.renderPosition = this.position.calcRelativeRenderPosition(0, 10); + this.keyboardPointer.render(); + } else if (this.matchesMovePointerUp(event)) { + this.position.renderPosition = this.position.calcRelativeRenderPosition(0, -10); + this.keyboardPointer.render(); + } else if (this.matchesMovePointerRight(event)) { + this.position.renderPosition = this.position.calcRelativeRenderPosition(10, 0); + this.keyboardPointer.render(); + } else if (this.matchesMovePointerLeft(event)) { + this.position.renderPosition = this.position.calcRelativeRenderPosition(-10, 0); + this.keyboardPointer.render(); + } + } + + protected createIfEnterEvent(event: KeyboardEvent): any { + const elementTypeId = this.keyboardPointer.triggerAction.elementTypeId; + + const { container, status } = this.position.containableParentAtDiagramPosition(elementTypeId); + + if (container !== undefined && status === CursorCSS.NODE_CREATION) { + if (this.matchesConfirmPointerPosition(event)) { + // close everything and return to default + + const containerId = container.id; + const location = this.position.diagramPosition; + + this.actionDispatcher.dispatchAll([ + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardPointerMetadata.ID, + visible: false, + contextElementsId: [] + }), + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardNodeGridMetadata.ID, + visible: false, + contextElementsId: [] + }), + CreateNodeOperation.create(elementTypeId, { location, containerId, args: this.keyboardPointer.triggerAction.args }), + EnableDefaultToolsAction.create() + ]); + } else if (this.matchesConfirmPointerPositionAndStayInMode(event)) { + // stay in this mode, selected palette option stays, grid and keyboard mouse are displayed + + const containerId = container.id; + const location = this.position.diagramPosition; + + this.actionDispatcher.dispatch( + CreateNodeOperation.create(elementTypeId, { location, containerId, args: this.keyboardPointer.triggerAction.args }) + ); + } + } + } + + protected hideIfEscapeEvent(event: KeyboardEvent): any { + if (this.matchesDeactivatePointer(event)) { + this.keyboardPointer.hide(); + } + } + + protected matchesDeactivatePointer(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Escape'); + } + protected matchesConfirmPointerPosition(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Enter'); + } + protected matchesConfirmPointerPositionAndStayInMode(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Enter', 'ctrl'); + } + + protected matchesMovePointerLeft(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowLeft'); + } + protected matchesMovePointerRight(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowRight'); + } + protected matchesMovePointerUp(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowUp'); + } + protected matchesMovePointerDown(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowDown'); + } +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts new file mode 100644 index 0000000..d249015 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + TYPES, + bindAsService, + configureActionHandler, + TriggerNodeCreationAction, + FeatureModule, + BindingContext, + TriggerEdgeCreationAction +} from '~glsp-sprotty'; +import { GlobalKeyListenerTool } from '../global-keylistener-tool'; +import { KeyboardPointer } from './keyboard-pointer'; +import { KeyboardGrid } from '../keyboard-grid/keyboard-grid'; +import { SetKeyboardPointerRenderPositionAction } from './actions'; +import { EdgeAutocompletePalette } from '../edge-autocomplete/edge-autocomplete-palette'; +import { EdgeAutocompletePaletteTool } from '../edge-autocomplete/edge-autocomplete-tool'; +import { SetEdgeTargetSelectionAction } from '../edge-autocomplete/action'; +import { GridSearchPalette } from '../keyboard-grid/keyboard-grid-search-palette'; +import { KeyboardNodeGrid } from '../keyboard-grid/keyboard-node-grid'; +import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; + +/** + * Handles the pointer used via grid to position new elements. + */ +export const keyboardControlModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureKeyboardControlTools(context); +}); + +export function configureKeyboardControlTools(context: BindingContext): void { + bindAsService(context, TYPES.IDefaultTool, GlobalKeyListenerTool); + bindAsService(context, TYPES.IUIExtension, KeyboardPointer); + bindAsService(context, TYPES.IUIExtension, KeyboardGrid); + bindAsService(context, TYPES.IUIExtension, KeyboardNodeGrid); + + configureActionHandler(context, TriggerNodeCreationAction.KIND, KeyboardPointer); + configureActionHandler(context, SetKeyboardPointerRenderPositionAction.KIND, KeyboardPointer); + + bindAsService(context, TYPES.IUIExtension, EdgeAutocompletePalette); + bindAsService(context, TYPES.IDefaultTool, EdgeAutocompletePaletteTool); + + configureActionHandler(context, EnableKeyboardGridAction.KIND, KeyboardGrid); + configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, KeyboardPointer); + + configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeAutocompletePalette); + configureActionHandler(context, SetEdgeTargetSelectionAction.KIND, EdgeAutocompletePalette); + + bindAsService(context, TYPES.IUIExtension, GridSearchPalette); +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts new file mode 100644 index 0000000..bff421a --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { findChildrenAtPosition, findParentByFeature, SModelElement, Point } from '~glsp-sprotty'; +import { Containable, isContainable } from '../../hints/model'; +import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; +import { KeyboardPointerMetadata } from './constants'; +import { KeyboardPointer } from './keyboard-pointer'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; + +export class KeyboardPointerPosition { + public renderPosition: Point = { x: 20, y: 20 }; + + constructor(protected readonly keyboardPointer: KeyboardPointer) {} + + get centerizedRenderPosition(): Point { + return { + x: this.renderPosition.x + KeyboardPointerMetadata.CIRCLE_WIDTH / 2, + y: this.renderPosition.y + KeyboardPointerMetadata.CRICLE_HEIGHT / 2 + }; + } + + get diagramPosition(): Point { + return getAbsolutePositionByPoint(this.keyboardPointer.editorContextService.modelRoot, this.centerizedRenderPosition); + } + + childrenAtDiagramPosition(): SModelElement[] { + const position = this.diagramPosition; + + return [ + this.keyboardPointer.editorContextService.modelRoot, + ...findChildrenAtPosition(this.keyboardPointer.editorContextService.modelRoot, position) + ]; + } + + containableParentAtDiagramPosition(elementTypeId: string): { + container: (SModelElement & Containable) | undefined; + status: CursorCSS; + } { + const children = this.childrenAtDiagramPosition(); + + return this.containableParentOf(children.reverse()[0], elementTypeId); + } + + calcRelativeRenderPosition(x: number, y: number): Point { + return { + x: this.renderPosition.x + x, + y: this.renderPosition.y + y + }; + } + + private containableParentOf( + target: SModelElement, + elementTypeId: string + ): { container: (SModelElement & Containable) | undefined; status: CursorCSS } { + const container = findParentByFeature(target, isContainable); + return { + container, + status: this.isCreationAllowed(container, elementTypeId) ? CursorCSS.NODE_CREATION : CursorCSS.OPERATION_NOT_ALLOWED + }; + } + + private isCreationAllowed(container: (SModelElement & Containable) | undefined, elementTypeId: string): boolean | undefined { + return container && container.isContainableElement(elementTypeId); + } +} diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts new file mode 100644 index 0000000..2af0f5c --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { + AbstractUIExtension, + IActionDispatcher, + IActionHandler, + SModelRoot, + TYPES, + Action, + TriggerNodeCreationAction +} from '~glsp-sprotty'; +import { SetKeyboardPointerRenderPositionAction } from './actions'; +import { KeyboardPointerMetadata } from './constants'; +import { KeyboardPointerKeyboardListener } from './keyboard-pointer-listener'; +import { KeyboardPointerPosition } from './keyboard-pointer-position'; +import { KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; + +@injectable() +export class KeyboardPointer extends AbstractUIExtension implements IActionHandler { + protected _triggerAction: TriggerNodeCreationAction = { + elementTypeId: 'task:automated', + kind: 'triggerNodeCreation' + }; + + protected position: KeyboardPointerPosition = new KeyboardPointerPosition(this); + protected keyListener: KeyboardPointerKeyboardListener; + @inject(EditorContextService) + public editorContextService: EditorContextService; + + constructor(@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher) { + super(); + + this.keyListener = new KeyboardPointerKeyboardListener(this, actionDispatcher); + } + + get triggerAction(): TriggerNodeCreationAction { + return this._triggerAction; + } + + get isVisible(): boolean { + return this.containerElement?.style.visibility === 'visible'; + } + + get getPosition(): KeyboardPointerPosition { + return this.position; + } + get getKeyListener(): KeyboardPointerKeyboardListener { + return this.keyListener; + } + + id(): string { + return KeyboardPointerMetadata.ID; + } + + containerClass(): string { + return KeyboardPointerMetadata.ID; + } + + protected initializeContents(containerElement: HTMLElement): void { + containerElement.style.position = 'absolute'; + containerElement.style.height = `${KeyboardPointerMetadata.CRICLE_HEIGHT}px`; + containerElement.style.width = `${KeyboardPointerMetadata.CIRCLE_WIDTH}px`; + containerElement.style.borderRadius = '100%'; + } + + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...selectedElementIds: string[]): void { + this.render(); + } + + handle(action: Action): Action | void { + if (TriggerNodeCreationAction.is(action)) { + this._triggerAction = action; + } else if (SetKeyboardPointerRenderPositionAction.is(action)) { + this.position.renderPosition = { x: action.x, y: action.y }; + this.render(); + } else if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === KeyboardPointerMetadata.ID) { + this.position.renderPosition = action.options.centerCellPosition; + this.render(); + } + } + + render(): void { + if (this.containerElement !== undefined) { + const { x, y } = this.position.renderPosition; + this.containerElement.style.left = `${x}px`; + this.containerElement.style.top = `${y}px`; + + const { status } = this.position.containableParentAtDiagramPosition(this._triggerAction.elementTypeId); + + this.containerElement.style.borderStyle = 'solid'; + this.containerElement.style.borderWidth = 'thick'; + switch (status) { + case CursorCSS.NODE_CREATION: { + this.containerElement.style.borderColor = 'green'; + break; + } + case CursorCSS.OPERATION_NOT_ALLOWED: { + this.containerElement.style.borderColor = 'red'; + break; + } + } + } + } +} diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts new file mode 100644 index 0000000..18535e4 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import '../../../../css/keyboard-tool-palette.css'; + +import { configureActionHandler, TYPES, bindAsService, BindingContext, FeatureModule } from '~glsp-sprotty'; +import { EnableToolPaletteAction } from '../../tool-palette/tool-palette'; +import { KeyboardToolPalette } from './keyboard-tool-palette'; +import { FocusDomAction } from '../actions'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; + +export const keyboardToolPaletteModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureKeyboardToolPaletteTool(context); +}); + +export function configureKeyboardToolPaletteTool(context: BindingContext): void { + bindAsService(context, TYPES.IUIExtension, KeyboardToolPalette); + context.bind(TYPES.IDiagramStartup).toService(KeyboardToolPalette); + configureActionHandler(context, EnableDefaultToolsAction.KIND, KeyboardToolPalette); + configureActionHandler(context, FocusDomAction.KIND, KeyboardToolPalette); + configureActionHandler(context, EnableToolPaletteAction.KIND, KeyboardToolPalette); +} diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts new file mode 100644 index 0000000..cb900a0 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -0,0 +1,505 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import { + ICommand, + SetUIExtensionVisibilityAction, + Action, + PaletteItem, + RequestContextActions, + RequestMarkersAction, + SetContextActions, + TriggerNodeCreationAction +} from '~glsp-sprotty'; +import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; +import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; +import { + createIcon, + changeCodiconClass, + createToolGroup, + ToolPalette, + compare, + EnableToolPaletteAction +} from '../../tool-palette/tool-palette'; +import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; +import { FocusDomAction } from '../actions'; +import { EdgeAutocompletePaletteMetadata } from '../edge-autocomplete/edge-autocomplete-palette'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; +import { ShowToastMessageAction } from '../toast/toast-handler'; +import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; +import * as messages from '../toast/messages.json'; + +const SEARCH_ICON_ID = 'search'; +const PALETTE_ICON_ID = 'symbol-color'; +const CHEVRON_DOWN_ICON_ID = 'chevron-right'; +const PALETTE_HEIGHT = '500px'; +const SELECTION_TOOL_KEY: KeyCode[] = ['Digit1', 'Numpad1']; +const DELETION_TOOL_KEY: KeyCode[] = ['Digit2', 'Numpad2']; +const MARQUEE_TOOL_KEY: KeyCode[] = ['Digit3', 'Numpad3']; +const VALIDATION_TOOL_KEY: KeyCode[] = ['Digit4', 'Numpad4']; +const SEARCH_TOOL_KEY: KeyCode[] = ['Digit5', 'Numpad5']; +const SHOW_SHORTCUTS_CLASS = 'accessibility-show-shortcuts'; + +const AVAILABLE_KEYS: KeyCode[] = [ + 'KeyA', + 'KeyB', + 'KeyC', + 'KeyD', + 'KeyE', + 'KeyF', + 'KeyG', + 'KeyH', + 'KeyI', + 'KeyJ', + 'KeyK', + 'KeyL', + 'KeyM', + 'KeyN', + 'KeyO', + 'KeyP', + 'KeyQ', + 'KeyR', + 'KeyS', + 'KeyT', + 'KeyU', + 'KeyV', + 'KeyX', + 'KeyY', + 'KeyZ' +]; + +const HEADER_TOOL_KEYS: KeyCode[][] = [SELECTION_TOOL_KEY, DELETION_TOOL_KEY, MARQUEE_TOOL_KEY, VALIDATION_TOOL_KEY, SEARCH_TOOL_KEY]; + +@injectable() +export class KeyboardToolPalette extends ToolPalette { + protected deleteToolButton: HTMLElement; + protected marqueeToolButton: HTMLElement; + protected validateToolButton: HTMLElement; + protected searchToolButton: HTMLElement; + + protected keyboardIndexButtonMapping = new Map(); + protected headerToolsButtonMapping = new Map(); + + protected get interactablePaletteItems(): PaletteItem[] { + return this.paletteItems + .sort(compare) + .map(item => item.children?.sort(compare) ?? [item]) + .reduce((acc, val) => acc.concat(val), []); + } + + protected override initializeContents(_containerElement: HTMLElement): void { + this.containerElement.setAttribute('aria-label', 'Tool Palette'); + this.containerElement.tabIndex = 20; + this.containerElement.classList.add('accessibility-tool-palette'); + this.createHeader(); + this.createBody(); + this.lastActiveButton = this.defaultToolsButton; + + this.containerElement.onkeyup = ev => { + this.clearToolOnEscape(ev); + if (this.isShortcutsVisible()) { + this.selectItemOnCharacter(ev); + this.triggerHeaderToolsByKey(ev); + } + }; + } + + override handle(action: Action): ICommand | Action | void { + if (action.kind === EnableToolPaletteAction.KIND) { + const requestAction = RequestContextActions.create({ + contextId: ToolPalette.ID, + editorContext: { + selectedElementIds: [] + } + }); + this.actionDispatcher.requestUntil(requestAction).then(response => { + if (SetContextActions.is(response)) { + this.paletteItems = response.actions.map(e => e as PaletteItem); + this.actionDispatcher.dispatchAll([ + SetUIExtensionVisibilityAction.create({ extensionId: ToolPalette.ID, visible: !this.editorContext.isReadonly }) + ]); + } + }); + } else if (action.kind === EnableDefaultToolsAction.KIND) { + this.changeActiveButton(); + this.restoreFocus(); + } else if (FocusDomAction.is(action) && action.id === ToolPalette.ID) { + if (this.containerElement.contains(document.activeElement)) { + this.toggleShortcutVisibility(); + } else { + this.showShortcuts(); + } + this.containerElement.focus(); + } + } + + protected override addMinimizePaletteButton(): void { + const baseDiv = document.getElementById(this.options.baseDiv); + const minPaletteDiv = document.createElement('div'); + minPaletteDiv.classList.add('minimize-palette-button'); + this.containerElement.classList.add('collapsible-palette'); + if (baseDiv) { + const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); + const minimizeIcon = createIcon(CHEVRON_DOWN_ICON_ID); + this.updateMinimizePaletteButtonTooltip(minPaletteDiv); + minimizeIcon.onclick = _event => { + if (this.isPaletteMaximized()) { + this.containerElement.style.overflow = 'hidden'; + this.containerElement.style.maxHeight = '0px'; + } else { + this.containerElement.style.overflow = 'visible'; + this.containerElement.style.maxHeight = PALETTE_HEIGHT; + } + this.updateMinimizePaletteButtonTooltip(minPaletteDiv); + changeCodiconClass(minimizeIcon, PALETTE_ICON_ID); + changeCodiconClass(minimizeIcon, CHEVRON_DOWN_ICON_ID); + }; + insertedDiv.appendChild(minimizeIcon); + } + } + + protected override createBody(): void { + const bodyDiv = document.createElement('div'); + bodyDiv.classList.add('palette-body'); + const tabIndex = 21; + let toolButtonCounter = 0; + + this.keyboardIndexButtonMapping.clear(); + this.paletteItems.sort(compare).forEach(item => { + if (item.children) { + const group = createToolGroup(item); + item.children.sort(compare).forEach(child => { + const button = this.createKeyboardToolButton(child, tabIndex, toolButtonCounter); + group.appendChild(button); + this.keyboardIndexButtonMapping.set(toolButtonCounter, button); + toolButtonCounter++; + }); + bodyDiv.appendChild(group); + } else { + const button = this.createKeyboardToolButton(item, tabIndex, toolButtonCounter); + bodyDiv.appendChild(button); + this.keyboardIndexButtonMapping.set(toolButtonCounter, button); + toolButtonCounter++; + } + }); + + if (this.paletteItems.length === 0) { + const noResultsDiv = document.createElement('div'); + noResultsDiv.innerText = 'No results found.'; + noResultsDiv.classList.add('tool-button'); + bodyDiv.appendChild(noResultsDiv); + } + // Remove existing body to refresh filtered entries + if (this.bodyDiv) { + this.containerElement.removeChild(this.bodyDiv); + } + this.containerElement.appendChild(bodyDiv); + this.bodyDiv = bodyDiv; + } + + protected override createHeaderTools(): HTMLElement { + this.headerToolsButtonMapping.clear(); + + const headerTools = document.createElement('div'); + headerTools.classList.add('header-tools'); + + this.defaultToolsButton = this.createDefaultToolButton(); + this.headerToolsButtonMapping.set(0, this.defaultToolsButton); + headerTools.appendChild(this.defaultToolsButton); + + this.deleteToolButton = this.createMouseDeleteToolButton(); + this.headerToolsButtonMapping.set(1, this.deleteToolButton); + headerTools.appendChild(this.deleteToolButton); + + this.marqueeToolButton = this.createMarqueeToolButton(); + this.headerToolsButtonMapping.set(2, this.marqueeToolButton); + headerTools.appendChild(this.marqueeToolButton); + + this.validateToolButton = this.createValidateButton(); + this.headerToolsButtonMapping.set(3, this.validateToolButton); + headerTools.appendChild(this.validateToolButton); + + // Create button for Search + this.searchToolButton = this.createSearchButton(); + this.headerToolsButtonMapping.set(4, this.searchToolButton); + headerTools.appendChild(this.searchToolButton); + + return headerTools; + } + + protected override createDefaultToolButton(): HTMLElement { + const button = createIcon('inspect'); + button.id = 'btn_default_tools'; + button.title = 'Enable selection tool'; + button.onclick = this.onClickStaticToolButton(button); + button.appendChild(this.createKeyboardShotcut(SELECTION_TOOL_KEY[0])); + + return button; + } + + protected override createMouseDeleteToolButton(): HTMLElement { + const deleteToolButton = createIcon('chrome-close'); + deleteToolButton.title = 'Enable deletion tool'; + deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); + deleteToolButton.appendChild(this.createKeyboardShotcut(DELETION_TOOL_KEY[0])); + + return deleteToolButton; + } + + protected override createMarqueeToolButton(): HTMLElement { + const marqueeToolButton = createIcon('screen-full'); + marqueeToolButton.title = 'Enable marquee tool'; + const toastMessageAction = ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.tool_palette.marqueeTool + }); + marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID, toastMessageAction); + marqueeToolButton.appendChild(this.createKeyboardShotcut(MARQUEE_TOOL_KEY[0])); + + return marqueeToolButton; + } + + protected override createValidateButton(): HTMLElement { + const validateToolButton = createIcon('pass'); + validateToolButton.title = 'Validate model'; + validateToolButton.onclick = _event => { + const modelIds: string[] = [this.modelRootId]; + this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds)); + }; + validateToolButton.appendChild(this.createKeyboardShotcut(VALIDATION_TOOL_KEY[0])); + + return validateToolButton; + } + + protected override onClickStaticToolButton(button: HTMLElement, toolId?: string, action?: Action) { + return (_ev: MouseEvent) => { + if (!this.editorContext.isReadonly) { + const defaultAction = toolId ? EnableToolsAction.create([toolId]) : EnableDefaultToolsAction.create(); + if (action) { + this.actionDispatcher.dispatchAll([defaultAction, action]); + } else { + this.actionDispatcher.dispatchAll([defaultAction]); + } + this.changeActiveButton(button); + button.focus(); + } + }; + } + protected override createSearchButton(): HTMLElement { + const searchIcon = createIcon(SEARCH_ICON_ID); + searchIcon.onclick = _ev => { + const searchField = document.getElementById(this.containerElement.id + '_search_field'); + if (searchField) { + if (searchField.style.display === 'none') { + searchField.style.display = ''; + searchField.focus(); + } else { + searchField.style.display = 'none'; + } + } + }; + searchIcon.classList.add('search-icon'); + searchIcon.title = 'Filter palette entries'; + searchIcon.appendChild(this.createKeyboardShotcut(SEARCH_TOOL_KEY[0])); + + return searchIcon; + } + + protected override createHeaderSearchField(): HTMLInputElement { + const searchField = document.createElement('input'); + searchField.classList.add('search-input'); + searchField.tabIndex = 21; + searchField.id = this.containerElement.id + '_search_field'; + searchField.type = 'text'; + searchField.placeholder = ' Search...'; + searchField.style.display = 'none'; + searchField.onkeyup = ev => { + this.requestFilterUpdate(this.searchField.value); + ev.stopPropagation(); + + if (searchField.value === '') { + this.focusToolPaletteOnEscape(ev); + } else { + this.clearOnEscape(ev); + } + }; + + return searchField; + } + + protected focusToolPaletteOnEscape(event: KeyboardEvent): void { + if (matchesKeystroke(event, 'Escape')) { + this.containerElement.focus(); + } + } + + protected createKeyboardShotcut(keyShortcut: KeyCode): HTMLElement { + const hint = document.createElement('div'); + hint.classList.add('key-shortcut'); + let keyShortcutValue = keyShortcut.toString(); + + if (keyShortcut.includes('Key')) { + keyShortcutValue = keyShortcut.toString().substring(3); + } else if (keyShortcut.includes('Digit')) { + keyShortcutValue = keyShortcut.toString().substring(5); + } + hint.innerHTML = keyShortcutValue; + return hint; + } + + protected createKeyboardToolButton(item: PaletteItem, tabIndex: number, buttonIndex: number): HTMLElement { + const button = document.createElement('div'); + // add keyboard index + if (buttonIndex < AVAILABLE_KEYS.length) { + button.appendChild(this.createKeyboardShotcut(AVAILABLE_KEYS[buttonIndex])); + } + button.tabIndex = tabIndex; + button.classList.add('tool-button'); + if (item.icon) { + button.appendChild(createIcon(item.icon)); + } + button.insertAdjacentText('beforeend', item.label); + button.onclick = this.onClickCreateToolButton(button, item); + + button.onkeydown = ev => { + this.clickToolOnEnter(ev, button, item); + this.clearToolOnEscape(ev); + + if (matchesKeystroke(ev, 'ArrowDown')) { + if (buttonIndex + 1 > this.keyboardIndexButtonMapping.size - 1) { + this.selectItemViaArrowKey(this.keyboardIndexButtonMapping.get(0)); + } else { + this.selectItemViaArrowKey(this.keyboardIndexButtonMapping.get(buttonIndex + 1)); + } + } else if (matchesKeystroke(ev, 'ArrowUp')) { + if (buttonIndex - 1 < 0) { + this.selectItemViaArrowKey(this.keyboardIndexButtonMapping.get(this.keyboardIndexButtonMapping.size - 1)); + } else { + this.selectItemViaArrowKey(this.keyboardIndexButtonMapping.get(buttonIndex - 1)); + } + } + }; + + return button; + } + + protected clickToolOnEnter(event: KeyboardEvent, button: HTMLElement, item: PaletteItem): void { + if (matchesKeystroke(event, 'Enter')) { + if (!this.editorContext.isReadonly) { + this.actionDispatcher.dispatchAll(item.actions); + this.changeActiveButton(button); + this.selectItemOnCharacter(event); + } + } + } + + protected selectItemOnCharacter(event: KeyboardEvent): void { + let index: number | undefined = undefined; + const items = this.interactablePaletteItems; + + const itemsCount = items.length < AVAILABLE_KEYS.length ? items.length : AVAILABLE_KEYS.length; + + for (let i = 0; i < itemsCount; i++) { + const keycode = AVAILABLE_KEYS[i]; + if (matchesKeystroke(event, keycode)) { + index = i; + break; + } + } + + if (index !== undefined) { + if (items[index].actions.some(a => a.kind === TriggerNodeCreationAction.KIND)) { + this.actionDispatcher.dispatchAll([ + ...items[index].actions, + SetUIExtensionVisibilityAction.create({ + extensionId: KeyboardNodeGridMetadata.ID, + visible: true, + contextElementsId: [] + }) + ]); + } else { + this.actionDispatcher.dispatchAll([ + ...items[index].actions, + SetUIExtensionVisibilityAction.create({ + extensionId: EdgeAutocompletePaletteMetadata.ID, + visible: true, + contextElementsId: [] + }) + ]); + } + this.changeActiveButton(this.keyboardIndexButtonMapping.get(index)); + this.keyboardIndexButtonMapping.get(index)?.focus(); + } + } + + protected triggerHeaderToolsByKey(event: KeyboardEvent): void { + let index: number | undefined = undefined; + + for (let i = 0; i < HEADER_TOOL_KEYS.length; i++) { + for (let j = 0; j < HEADER_TOOL_KEYS[i].length; j++) { + const keycode = HEADER_TOOL_KEYS[i][j]; + + if (matchesKeystroke(event, keycode)) { + event.stopPropagation(); + event.preventDefault(); + index = i; + break; + } + } + } + + if (index !== undefined) { + this.headerToolsButtonMapping.get(index)?.click(); + } + } + + protected selectItemViaArrowKey(currentButton: HTMLElement | undefined): void { + if (currentButton !== undefined) { + this.changeActiveButton(currentButton); + currentButton?.focus(); + } + } + + protected override clearToolOnEscape(event: KeyboardEvent): void { + if (matchesKeystroke(event, 'Escape')) { + if (event.target instanceof HTMLElement) { + event.target.blur(); + } + this.actionDispatcher.dispatch(EnableDefaultToolsAction.create()); + } + } + + protected toggleShortcutVisibility(): void { + if (this.isShortcutsVisible()) { + this.hideShortcuts(); + } else { + this.showShortcuts(); + } + } + + protected isShortcutsVisible(): boolean { + return this.containerElement.classList.contains(SHOW_SHORTCUTS_CLASS); + } + + protected showShortcuts(): void { + this.containerElement.classList.add(SHOW_SHORTCUTS_CLASS); + } + + protected hideShortcuts(): void { + this.containerElement.classList.remove(SHOW_SHORTCUTS_CLASS); + } +} diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index daca465..b9078c7 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -57,6 +57,23 @@ export class RevealNamedElementAutocompleteSuggestionProvider implements IAutoco return [SelectAction.create({ selectedElementsIDs: [nameable.id] }), CenterAction.create([nameable.id], { retainZoom: true })]; } } +export class RevealNodesWithoutNameAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { + async retrieveSuggestions(root: Readonly, text: string): Promise { + const nodes = toArray(root.index.all().filter(element => !isNameable(element) && element instanceof SNode)); + return nodes.map(node => ({ + element: node, + action: { + label: `[${node.type}]`, + actions: this.getActions(node), + icon: codiconCSSString('symbol-namespace') + } + })); + } + + protected getActions(nameable: SModelElement): Action[] { + return [SelectAction.create({ selectedElementsIDs: [nameable.id] }), CenterAction.create([nameable.id], { retainZoom: true })]; + } +} export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { async retrieveSuggestions(root: Readonly, text: string): Promise { @@ -70,6 +87,7 @@ export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocom } })); } + protected getActions(edge: SEdge): Action[] { return [SelectAction.create({ selectedElementsIDs: [edge.id] }), CenterAction.create([edge.sourceId, edge.targetId])]; } @@ -107,7 +125,11 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { containerElement.setAttribute('aria-label', 'Search Field'); } protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { - return [new RevealNamedElementAutocompleteSuggestionProvider(), new RevealEdgeElementAutocompleteSuggestionProvider()]; + return [ + new RevealNamedElementAutocompleteSuggestionProvider(), + new RevealEdgeElementAutocompleteSuggestionProvider(), + new RevealNodesWithoutNameAutocompleteSuggestionProvider() + ]; } protected async retrieveSuggestions(root: Readonly, input: string): Promise { const providers = this.getSuggestionProviders(root, input); diff --git a/packages/client/src/features/accessibility/toast/messages.json b/packages/client/src/features/accessibility/toast/messages.json index 27afcfa..8a14a5e 100644 --- a/packages/client/src/features/accessibility/toast/messages.json +++ b/packages/client/src/features/accessibility/toast/messages.json @@ -12,5 +12,7 @@ "resize": { "resize_mode_activated": "Resize On: Use plus(+) and minus(-) to resize, 'CTRL'+'0' for default size. Press 'ESC' to exit.", "resize_mode_deactivated": "Resize Off: Press 'ALT'+'A' for resize mode." - } + }, + "grid": { "zoom_in_grid": "Select a digit from 1-9 to zoom in on the respective box." }, + "tool_palette": { "marqueeTool": "Currently marquee tool is only usable with mouse." } } diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 28f3364..8bb8b7b 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -14,18 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext, FeatureModule, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; import { DeselectKeyTool } from './deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; +import { KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; export const viewKeyToolsModule = new FeatureModule((bind, _unbind, isBound, rebind) => { const context = { bind, isBound, rebind }; configureViewKeyTools(context); }); -export function configureViewKeyTools(context: Pick): void { +export function configureViewKeyTools(context: Pick): void { bindAsService(context, TYPES.IDefaultTool, MovementKeyTool); bindAsService(context, TYPES.IDefaultTool, ZoomKeyTool); + configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, ZoomKeyTool); + configureActionHandler(context, KeyboardGridKeyboardEventAction.KIND, ZoomKeyTool); bindAsService(context, TYPES.IDefaultTool, DeselectKeyTool); } diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index eb2b3d0..ea4b3c3 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -16,12 +16,30 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, CenterAction, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { + Action, + CenterAction, + KeyListener, + KeyTool, + Point, + SModelElement, + SModelRoot, + SetViewportAction, + TYPES, + Viewport, + isViewport +} from '~glsp-sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; +import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; +import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; +import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; +import * as messages from '../toast/messages.json'; /** * Zoom viewport and elements when its focused and arrow keys are hit. @@ -37,6 +55,8 @@ export class ZoomKeyTool implements Tool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(SelectionService) selectionService: SelectionService; + @inject(EditorContextService) + protected editorContextService: EditorContextService; get id(): string { return ZoomKeyTool.ID; @@ -50,6 +70,36 @@ export class ZoomKeyTool implements Tool { disable(): void { this.keytool.deregister(this.zoomKeyListener); } + + handle(action: Action): Action | void { + if (isViewport(this.editorContextService.modelRoot)) { + let viewportAction: Action | undefined = undefined; + + if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === ZoomKeyTool.ID) { + viewportAction = this.zoomKeyListener.setNewZoomFactor( + this.editorContextService.modelRoot, + ZoomKeyListener.defaultZoomInFactor, + getAbsolutePositionByPoint(this.editorContextService.modelRoot, action.options.centerCellPosition) + ); + } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === ZoomKeyTool.ID) { + if (matchesKeystroke(action.options.event, 'Minus')) { + viewportAction = this.zoomKeyListener.setNewZoomFactor( + this.editorContextService.modelRoot, + ZoomKeyListener.defaultZoomOutFactor + ); + } else if (matchesKeystroke(action.options.event, 'Digit0', 'ctrl')) { + viewportAction = CenterAction.create([]); + } + } + + if (viewportAction) { + this.actionDispatcher.dispatchAll([ + viewportAction, + HideToastAction.create({ id: Symbol.for(ElementNavigatorKeyListener.name) }) + ]); + } + } + } } export class ZoomKeyListener extends KeyListener { @@ -75,10 +125,43 @@ export class ZoomKeyListener extends KeyListener { ); } + setNewZoomFactor(viewport: SModelElement & SModelRoot & Viewport, zoomFactor: number, point?: Point): SetViewportAction { + let newViewport: Viewport; + const newZoom = viewport.zoom * zoomFactor; + + if (point) { + newViewport = { + scroll: { + x: point.x - (0.5 * viewport.canvasBounds.width) / newZoom, + y: point.y - (0.5 * viewport.canvasBounds.height) / newZoom + }, + zoom: newZoom + }; + } else { + newViewport = { + scroll: viewport.scroll, + zoom: newZoom + }; + } + return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + } + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); - if (this.matchesZoomOutKeystroke(event)) { + if (this.matchesZoomViaGrid(event)) { + return [ + EnableKeyboardGridAction.create({ + originId: ZoomKeyTool.ID, + triggerActions: [] + }), + + ShowToastMessageAction.createWithTimeout({ + id: Symbol.for(ElementNavigatorKeyListener.name), + message: messages.grid.zoom_in_grid + }) + ]; + } else if (this.matchesZoomOutKeystroke(event)) { if (selectedElementIds.length > 0) { return [ZoomElementAction.create(selectedElementIds, ZoomKeyListener.defaultZoomOutFactor)]; } else { @@ -101,6 +184,9 @@ export class ZoomKeyListener extends KeyListener { return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); } + protected matchesZoomViaGrid(event: KeyboardEvent): boolean { + return event.key === '+' && event.ctrlKey; + } protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); } diff --git a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts index f992a7e..9f9f092 100644 --- a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts +++ b/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts @@ -45,7 +45,7 @@ export abstract class BaseAutocompletePalette extends AbstractUIExtension { } override hide(): void { - this.autocompleteWidget.dispose(); + this.autocompleteWidget?.dispose(); this.root = undefined; super.hide(); } diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index b877118..69ecc31 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -182,7 +182,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.containerElement.appendChild(headerCompartment); } - private createHeaderTools(): HTMLElement { + protected createHeaderTools(): HTMLElement { const headerTools = document.createElement('div'); headerTools.classList.add('header-tools'); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 641e222..20df9d3 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -70,6 +70,10 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { override nonDraggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; + if (this.container === undefined) { + this.mouseOver(target, event); + } + if (this.creationAllowed(this.elementTypeId)) { const containerId = this.container ? this.container.id : undefined; let location = getAbsolutePosition(target, event); diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts new file mode 100644 index 0000000..71bd41f --- /dev/null +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { IActionHandler, ViewerOptions, TYPES, Action } from '~glsp-sprotty'; +import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; +import { injectable, inject } from 'inversify'; +import { FocusDomAction } from '../accessibility/actions'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; + +/** + * Focuses the graph on different actions. + */ +@injectable() +export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { + protected readonly graphSelector = '[data-svg-metadata-type="graph"]'; + + @inject(TYPES.ViewerOptions) protected options: ViewerOptions; + + handle(action: Action): void | Action { + if (EnableDefaultToolsAction.is(action) || (FocusDomAction.is(action) && action.id === 'graph')) { + this.focusGraph(); + } + } + + async postRequestModel(): Promise { + await this.waitForElement(this.graphSelector); + this.focusGraph(); + } + + protected focusGraph(): void { + const container = document.getElementById(this.options.baseDiv)?.querySelector(this.graphSelector) as HTMLElement | null; + container?.focus(); + } + + // https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists + protected waitForElement(selector: string): Promise { + return new Promise(resolve => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)); + } + + const observer = new MutationObserver(mutations => { + if (document.querySelector(selector)) { + observer.disconnect(); + resolve(document.querySelector(selector)); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + }); + } +} diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 17d28b6..9d9eed0 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -28,7 +28,9 @@ import { } from '~glsp-sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; +import { RestoreViewportHandler } from './viewport-handler'; import { RepositionCommand } from './reposition'; +import { FocusDomAction } from '../accessibility/actions'; export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; @@ -43,6 +45,10 @@ export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); + + bindAsService(context, TYPES.IDiagramStartup, RestoreViewportHandler); + configureActionHandler(context, EnableDefaultToolsAction.KIND, RestoreViewportHandler); + configureActionHandler(context, FocusDomAction.KIND, RestoreViewportHandler); }); /** diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 5bbc3ef..8fa7a5d 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -44,8 +44,11 @@ import { BoundsAwareModelElement } from './smodel-util'; * A mouseEvent */ export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEvent): Point { - let xPos = mouseEvent.pageX; - let yPos = mouseEvent.pageY; + return getAbsolutePositionByPoint(target, { x: mouseEvent.pageX, y: mouseEvent.pageY }); +} +export function getAbsolutePositionByPoint(target: SModelElement, point: Point): Point { + let xPos = point.x; + let yPos = point.y; const canvasBounds = target.root.canvasBounds; xPos -= canvasBounds.x; yPos -= canvasBounds.y; From af1634ae885953150077fc6979d997cd87f84f2d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 5 Oct 2023 02:10:53 -0700 Subject: [PATCH 263/566] Update tsconfig (#1133) Switch back module(resolution) settings to common.js/node. This is inline with what is used in Theia/sprotty. This was originally changed to node16 because we needed an async import of `vscode-ws-jsonrpc`. We no longer use this module so we can safely switch this back. Another positive side effect is that autoimporting in VS Code (or with other Typescript LSPs) works way better with "CommonJS" resolution --- dev-packages/ts-config/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index a29cb0b..bea9bd6 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -19,8 +19,8 @@ "emitDecoratorMetadata": true, "downlevelIteration": true, "resolveJsonModule": true, - "module": "Node16", - "moduleResolution": "Node16", + "module": "CommonJS", + "moduleResolution": "Node", "target": "ES2017", "jsx": "react", "lib": ["ES2017", "dom"], From da4dccdca014e27b3b06474a92adaae9ca955df1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 9 Oct 2023 07:37:19 -0700 Subject: [PATCH 264/566] GLSP-865: Align client model namespace with server model (#291) Also export all SModel elements (and releated) as aliased/augmented classes in the `GModel` namespace. This means we now can consistently use the GModel syntax on both server and client side. Move package-internal glsp-sprotty reexport into dedicated package. The package-internal reexport required overhead with `tsc-alias` which did not play well with the test setup and ide support (auto-import). Using a dedicated package also improved DevXP in glsp-client as auto-import now longer suggests imports from sprotty or sprotty-protocol. Sprotty and GLSP use different definitions of `Action`. While they are fully compatible this kind of breaks the IDE support for API that uses `Action`. e.g. Implementing an `IActionHandler` would have always auto-imported the wrong `Action` type (from sprotty-protocol). To bypass this issue we extend the sprotty core API classes in question and reexport them instead (see action-override.ts). Also: - reexport sprotty modules that are used in glsp as `FeatureModules`. - Refactor `Argumentable` interface to a model feature and rename to `ArgsAware` and ensure that the argsFeature gets added to each element per default - Augment all actions directly provided by sprotty-protocol to provide a typeguard via namespace - Move all reusable autocomplete compontens into base package - Remove "baseUrl" from tsconfig to avoid breaking "src" auto imports in vscode Part of eclipse-glsp/glsp/issues/865 --- .../direct-task-editing/direct-task-editor.ts | 6 +- examples/workflow-glsp/src/model.ts | 14 +- .../src/workflow-diagram-module.ts | 22 +- examples/workflow-glsp/src/workflow-views.tsx | 4 +- examples/workflow-glsp/tsconfig.json | 1 - .../workflow-standalone/src/url-parameters.ts | 7 +- examples/workflow-standalone/tsconfig.json | 4 +- packages/client/.eslintrc.js | 15 +- packages/client/package.json | 16 +- .../client/src/base/action-dispatcher.spec.ts | 2 +- packages/client/src/base/action-dispatcher.ts | 2 +- .../src/base/action-handler-registry.ts | 2 +- .../base/{argumentable.ts => args-feature.ts} | 23 +- .../auto-complete/auto-complete-actions.ts | 3 +- .../auto-complete/auto-complete-widget.ts | 32 +- .../autocomplete-suggestion-providers.ts | 6 +- .../base-autocomplete-palette.ts | 21 +- .../auto-complete/validation-decorator.ts | 2 +- packages/client/src/base/command-stack.ts | 18 +- packages/client/src/base/default.module.ts | 10 +- .../src/base/drag-aware-mouse-listener.ts | 12 +- .../client/src/base/editor-context-service.ts | 10 +- .../client/src/base/feedback/css-feedback.ts | 12 +- .../feedback/feedback-action-dispatcher.ts | 6 +- .../src/base/feedback/feedback-command.ts | 2 +- .../src/base/feedback/update-model-command.ts | 8 +- .../base/focus/focus-state-change-action.ts | 2 +- .../client/src/base/focus/focus-tracker.ts | 2 +- .../client/src/base/model/diagram-loader.ts | 2 +- .../src/base/model/glsp-model-source.ts | 10 +- .../model-initialization-constraint.spec.ts | 2 +- .../model/model-initialization-constraint.ts | 10 +- .../client/src/base/model/model-registry.ts | 23 +- packages/client/src/base/ranked.ts | 2 +- .../base/selection-clearing-mouse-listener.ts | 4 +- .../client/src/base/selection-service.spec.ts | 15 +- packages/client/src/base/selection-service.ts | 46 +- .../src/base/tool-manager/tool-manager.ts | 6 +- packages/client/src/base/tool-manager/tool.ts | 2 +- packages/client/src/base/view/key-tool.ts | 2 +- packages/client/src/base/view/mouse-tool.ts | 8 +- .../client/src/base/view/view-registry.ts | 9 +- packages/client/src/default-modules.ts | 2 +- .../accessibility/accessibility-module.ts | 2 +- .../src/features/accessibility/actions.ts | 2 +- .../accessibility/edge-autocomplete/action.ts | 2 +- .../edge-autocomplete-context.ts | 2 +- .../edge-autocomplete-palette.ts | 24 +- .../edge-autocomplete-tool.ts | 4 +- .../diagram-navigation-tool.ts | 38 +- .../element-navigation-module.ts | 2 +- .../element-navigation/element-navigator.ts | 34 +- .../left-right-top-bottom-navigator.ts | 31 +- .../local-element-navigator.ts | 81 +-- .../element-navigation/position-navigator.ts | 48 +- .../focus-tracker/focus-tracker-module.ts | 2 +- .../focus-tracker/focus-tracker-tool.ts | 2 +- .../accessibility/global-keylistener-tool.ts | 2 +- .../accessible-key-shortcut-tool.ts | 4 +- .../key-shortcut/accessible-key-shortcut.ts | 4 +- .../accessibility/key-shortcut/di.config.ts | 2 +- .../accessibility/keyboard-grid/action.ts | 2 +- .../keyboard-grid-search-palette.ts | 13 +- .../keyboard-grid/keyboard-grid.ts | 6 +- .../keyboard-grid/keyboard-node-grid.ts | 2 +- .../accessibility/keyboard-pointer/actions.ts | 2 +- .../keyboard-pointer-listener.ts | 2 +- .../keyboard-pointer-module.ts | 2 +- .../keyboard-pointer-position.ts | 12 +- .../keyboard-pointer/keyboard-pointer.ts | 6 +- .../keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette.ts | 2 +- .../accessibility/move-zoom/move-handler.ts | 12 +- .../move-zoom/move-zoom-module.ts | 2 +- .../accessibility/move-zoom/zoom-handler.ts | 20 +- .../resize-key-tool/resize-key-handler.ts | 10 +- .../resize-key-tool/resize-key-module.ts | 2 +- .../resize-key-tool/resize-key-tool.ts | 4 +- .../search/search-palette-module.ts | 2 +- .../accessibility/search/search-palette.ts | 52 +- .../accessibility/search/search-tool.ts | 4 +- .../accessibility/toast/toast-handler.ts | 2 +- .../accessibility/toast/toast-module.ts | 2 +- .../accessibility/toast/toast-tool.ts | 2 +- .../view-key-tools/deselect-key-tool.ts | 10 +- .../view-key-tools/movement-key-tool.ts | 4 +- .../view-key-tools/view-key-tools-module.ts | 2 +- .../view-key-tools/zoom-key-tool.ts | 10 +- .../src/features/bounds/bounds-module.ts | 2 +- .../features/bounds/freeform-layout.spec.ts | 66 +- .../src/features/bounds/freeform-layout.ts | 14 +- .../bounds/glsp-hidden-bounds-updater.ts | 12 +- .../src/features/bounds/hbox-layout.spec.ts | 16 +- .../client/src/features/bounds/hbox-layout.ts | 26 +- .../bounds/layouter-test-util.spec.ts | 52 +- .../client/src/features/bounds/layouter.ts | 16 +- .../src/features/bounds/vbox-layout.spec.ts | 16 +- .../client/src/features/bounds/vbox-layout.ts | 26 +- .../src/features/change-bounds/model.ts | 26 +- .../change-bounds/movement-restrictor.ts | 26 +- .../src/features/change-bounds/snap.spec.ts | 10 +- .../client/src/features/change-bounds/snap.ts | 8 +- .../command-palette/command-palette-module.ts | 2 +- .../command-palette/command-palette-tool.ts | 2 +- .../server-command-palette-provider.ts | 6 +- .../context-menu/context-menu-module.ts | 2 +- .../delete-element-context-menu.ts | 4 +- .../glsp-context-menu-mouse-listener.ts | 13 +- .../server-context-menu-provider.ts | 6 +- .../copy-paste/copy-paste-context-menu.ts | 6 +- .../features/copy-paste/copy-paste-handler.ts | 2 +- .../features/copy-paste/copy-paste-modules.ts | 2 +- .../features/decoration/decoration-module.ts | 2 +- .../features/decoration/decoration-placer.ts | 6 +- .../src/features/export/export-modules.ts | 2 +- .../export/export-svg-action-handler.ts | 2 +- .../src/features/export/glsp-svg-exporter.ts | 6 +- packages/client/src/features/hints/model.ts | 18 +- .../features/hints/type-hint-provider.spec.ts | 57 +- .../src/features/hints/type-hint-provider.ts | 43 +- .../src/features/hints/type-hints-module.ts | 2 +- .../client/src/features/hover/hover-module.ts | 2 +- packages/client/src/features/hover/hover.ts | 24 +- .../features/label-edit/edit-label-tool.ts | 2 +- .../label-edit/edit-label-validator.ts | 6 +- .../features/label-edit/label-edit-module.ts | 2 +- .../layout/layout-elements-action.spec.ts | 62 +- .../features/layout/layout-elements-action.ts | 12 +- .../src/features/layout/layout-module.ts | 2 +- .../navigation/navigation-action-handler.ts | 2 +- .../features/navigation/navigation-module.ts | 2 +- .../navigation-target-resolver.spec.ts | 2 +- .../navigation/navigation-target-resolver.ts | 2 +- .../client/src/features/reconnect/model.ts | 42 +- .../routing/glsp-manhattan-edge-router.ts | 4 +- .../src/features/routing/routing-module.ts | 2 +- .../src/features/save/save-keylistener.ts | 4 +- .../client/src/features/save/save-module.ts | 2 +- .../select/select-feedback-command.ts | 8 +- .../src/features/select/select-module.ts | 2 +- .../features/select/select-mouse-listener.ts | 10 +- .../source-model-changed-action-handler.ts | 2 +- .../source-model-wacher-module.ts | 2 +- .../src/features/status/status-module.ts | 2 +- .../src/features/status/status-overlay.ts | 2 +- .../features/svg-metadata/metadata-placer.ts | 11 +- .../svg-metadata/svg-metadata-module.ts | 2 +- .../tool-palette/tool-palette-module.ts | 2 +- .../src/features/tool-palette/tool-palette.ts | 6 +- .../client/src/features/tools/base-tools.ts | 4 +- .../change-boounds-tool-module.ts | 2 +- .../change-bounds-tool-feedback.ts | 39 +- .../tools/change-bounds/change-bounds-tool.ts | 68 +- .../src/features/tools/change-bounds/view.tsx | 2 +- .../features/tools/deletion/delete-tool.ts | 8 +- .../tools/deletion/deletion-tool-module.ts | 2 +- .../edge-creation/dangling-edge-feedback.ts | 40 +- .../edge-creation/edege-creation-module.ts | 2 +- .../edge-creation-tool-feedback.ts | 12 +- .../tools/edge-creation/edge-creation-tool.ts | 18 +- .../src/features/tools/edge-creation/view.tsx | 4 +- .../tools/edge-edit/edge-edit-module.ts | 2 +- .../edge-edit/edge-edit-tool-feedback.ts | 32 +- .../tools/edge-edit/edge-edit-tool.ts | 40 +- .../marquee-selection/marquee-behavior.ts | 2 +- .../marquee-selection/marquee-mouse-tool.ts | 32 +- .../marquee-selection-module.ts | 2 +- .../marquee-tool-feedback.ts | 16 +- .../tools/marquee-selection/marquee-tool.ts | 4 +- .../features/tools/marquee-selection/model.ts | 2 +- .../features/tools/marquee-selection/view.tsx | 2 +- .../node-creation/node-creation-module.ts | 2 +- .../tools/node-creation/node-creation-tool.ts | 14 +- .../features/tools/tool-focus-loss-module.ts | 2 +- .../undo-redo/undo-redo-key-listener.ts | 4 +- .../features/undo-redo/undo-redo-module.ts | 2 +- .../src/features/validation/issue-marker.ts | 52 +- .../validation/marker-navigator.spec.ts | 99 +-- .../features/validation/marker-navigator.ts | 66 +- .../src/features/validation/validate.ts | 25 +- .../features/validation/validation-modules.ts | 8 +- .../viewport/glsp-scroll-mouse-listener.ts | 4 +- .../src/features/viewport/reposition.ts | 34 +- .../src/features/viewport/viewport-handler.ts | 2 +- .../src/features/viewport/viewport-modules.ts | 2 +- .../src/glsp-sprotty/augmented-actions.ts | 33 - packages/client/src/index.ts | 13 +- packages/client/src/{lib => }/model.ts | 36 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/argument-utils.ts | 34 +- ...model-util.spec.ts => gmodel-util.spec.ts} | 37 +- .../utils/{smodel-util.ts => gmodel-util.ts} | 96 +-- packages/client/src/utils/layout-utils.ts | 25 +- packages/client/src/utils/marker.ts | 15 +- packages/client/src/utils/viewpoint-util.ts | 20 +- packages/client/src/views/base-view-module.ts | 67 +- packages/client/src/views/compartments.tsx | 6 +- .../{glsp-edge-view.tsx => gedge-view.tsx} | 11 +- .../client/src/views/glsp-projection-view.tsx | 18 +- packages/client/src/views/index.ts | 2 +- .../client/src/views/issue-marker-view.tsx | 9 +- .../client/src/views/rounded-corner-view.tsx | 14 +- packages/client/src/views/rounded-corner.ts | 4 +- packages/client/tsconfig.json | 7 +- packages/glsp-sprotty/.eslintrc.js | 17 + packages/glsp-sprotty/LICENSE | 642 ++++++++++++++++++ packages/glsp-sprotty/README.md | 14 + packages/glsp-sprotty/package.json | 50 ++ packages/glsp-sprotty/src/action-override.ts | 124 ++++ packages/glsp-sprotty/src/feature-modules.ts | 48 ++ .../src}/index.ts | 337 +++++---- .../src}/types.ts | 2 +- packages/glsp-sprotty/tsconfig.json | 8 + packages/protocol/package.json | 4 +- .../src/action-protocol/base-protocol.spec.ts | 4 +- .../src/action-protocol/base-protocol.ts | 12 +- .../action-protocol/client-notification.ts | 10 +- .../protocol/src/action-protocol/clipboard.ts | 8 +- .../protocol/src/action-protocol/contexts.ts | 4 +- .../src/action-protocol/edge-modification.ts | 4 +- .../src/action-protocol/element-creation.ts | 10 +- .../src/action-protocol/element-hover.ts | 12 +- .../src/action-protocol/element-navigation.ts | 18 +- .../src/action-protocol/element-selection.ts | 4 +- .../action-protocol/element-text-editing.ts | 8 +- .../src/action-protocol/element-type-hints.ts | 19 +- .../src/action-protocol/element-validation.ts | 8 +- .../protocol/src/action-protocol/index.ts | 3 +- .../src/action-protocol/model-data.ts | 18 +- .../src/action-protocol/model-edit-mode.ts | 2 +- .../src/action-protocol/model-layout.ts | 14 +- .../src/action-protocol/model-saving.ts | 8 +- .../src/action-protocol/model-structure.ts | 64 -- .../action-protocol/node-modification.spec.ts | 2 +- .../src/action-protocol/node-modification.ts | 4 +- .../src/action-protocol/tool-palette.ts | 4 +- .../protocol/src/action-protocol/types.ts | 12 +- .../protocol/src/action-protocol/undo-redo.ts | 4 +- .../protocol/src/action-protocol/viewport.ts | 4 +- .../base-glsp-client.spec.ts | 2 +- .../base-glsp-client.ts | 2 +- .../src/client-server-protocol/glsp-client.ts | 7 +- .../src/client-server-protocol/glsp-server.ts | 2 +- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 12 +- .../jsonrpc/glsp-jsonrpc-client.ts | 4 +- .../jsonrpc/websocket-connection.ts | 8 +- packages/protocol/src/index.ts | 29 +- packages/protocol/src/model/model-schema.ts | 63 +- packages/protocol/src/sprotty-actions.ts | 129 ++++ packages/protocol/src/utils/array-util.ts | 10 +- .../protocol/src/utils/disposable.spec.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/event.ts | 2 +- packages/protocol/src/utils/test-util.ts | 7 +- packages/protocol/src/utils/type-util.spec.ts | 2 +- packages/protocol/src/utils/type-util.ts | 11 +- packages/protocol/tsconfig.json | 4 +- 257 files changed, 2749 insertions(+), 1761 deletions(-) rename packages/client/src/base/{argumentable.ts => args-feature.ts} (53%) rename packages/client/src/{features/autocomplete-palette => base/auto-complete}/autocomplete-suggestion-providers.ts (88%) rename packages/client/src/{features/autocomplete-palette => base/auto-complete}/base-autocomplete-palette.ts (82%) delete mode 100644 packages/client/src/glsp-sprotty/augmented-actions.ts rename packages/client/src/{lib => }/model.ts (68%) rename packages/client/src/utils/{smodel-util.spec.ts => gmodel-util.spec.ts} (91%) rename packages/client/src/utils/{smodel-util.ts => gmodel-util.ts} (79%) rename packages/client/src/views/{glsp-edge-view.tsx => gedge-view.tsx} (88%) create mode 100644 packages/glsp-sprotty/.eslintrc.js create mode 100644 packages/glsp-sprotty/LICENSE create mode 100644 packages/glsp-sprotty/README.md create mode 100644 packages/glsp-sprotty/package.json create mode 100644 packages/glsp-sprotty/src/action-override.ts create mode 100644 packages/glsp-sprotty/src/feature-modules.ts rename packages/{client/src/glsp-sprotty => glsp-sprotty/src}/index.ts (52%) rename packages/{client/src/glsp-sprotty => glsp-sprotty/src}/types.ts (97%) create mode 100644 packages/glsp-sprotty/tsconfig.json delete mode 100644 packages/protocol/src/action-protocol/model-structure.ts create mode 100644 packages/protocol/src/sprotty-actions.ts diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index e2cc398..34e8bc8 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -26,7 +26,7 @@ import { Operation, RequestContextActions, RequestEditValidationAction, - SModelRoot, + GModelRoot, SetContextActions, SetEditValidationResultAction, TYPES, @@ -137,12 +137,12 @@ export class TaskEditor extends AbstractUIExtension { this.autoSuggestion.initialize(containerElement); } - override show(root: Readonly, ...contextElementIds: string[]): void { + override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.autoSuggestion.open(root); } - protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { this.task = getTask(contextElementIds, root.index)[0]; this.autoSuggestion.inputField.value = ''; this.setPosition(containerElement); diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index a2436b7..94953ed 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -31,10 +31,10 @@ import { nameFeature, popupFeature, RectangularNode, - SChildElement, + GChildElement, selectFeature, - SModelElement, - SShapeElement, + GModelElement, + GShapeElement, WithEditableLabel, withEditLabelFeature } from '@eclipse-glsp/client'; @@ -57,7 +57,7 @@ export class TaskNode extends RectangularNode implements Nameable, WithEditableL taskType?: string; reference?: string; - get editableLabel(): (SChildElement & EditableLabel) | undefined { + get editableLabel(): (GChildElement & EditableLabel) | undefined { const label = this.children.find(element => element.type === 'label:heading'); if (label && isEditableLabel(label)) { return label; @@ -71,7 +71,7 @@ export class TaskNode extends RectangularNode implements Nameable, WithEditableL } } -export function isTaskNode(element: SModelElement): element is TaskNode { +export function isTaskNode(element: GModelElement): element is TaskNode { return element instanceof TaskNode || false; } @@ -100,7 +100,7 @@ export namespace ActivityNode { } } -export class Icon extends SShapeElement implements LayoutContainer { +export class Icon extends GShapeElement implements LayoutContainer { static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature]; layout: string; @@ -127,7 +127,7 @@ export class CategoryNode extends RectangularNode implements Nameable, WithEdita name = ''; - get editableLabel(): (SChildElement & EditableLabel) | undefined { + get editableLabel(): (GChildElement & EditableLabel) | undefined { const label = this.children.find(element => element.type === 'label:heading'); if (label && isEditableLabel(label)) { return label; diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 54c84bf..b3d1f08 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -20,17 +20,17 @@ import { DeleteElementContextMenuItemProvider, DiamondNodeView, GEdge, - GLSPGraph, + GGraph, GLSPProjectionView, GridSnapper, LogLevel, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, - SCompartment, - SCompartmentView, - SLabel, - SLabelView, + GCompartment, + GCompartmentView, + GLabel, + GLabelView, StructureCompartmentView, TYPES, bindAsService, @@ -60,10 +60,10 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, configureDefaultModelElements(context); configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); - configureModelElement(context, 'label:heading', SLabel, SLabelView, { enable: [editLabelFeature] }); - configureModelElement(context, 'comp:comp', SCompartment, SCompartmentView); - configureModelElement(context, 'comp:header', SCompartment, SCompartmentView); - configureModelElement(context, 'label:icon', SLabel, SLabelView); + configureModelElement(context, 'label:heading', GLabel, GLabelView, { enable: [editLabelFeature] }); + configureModelElement(context, 'comp:comp', GCompartment, GCompartmentView); + configureModelElement(context, 'comp:header', GCompartment, GCompartmentView); + configureModelElement(context, 'label:icon', GLabel, GLabelView); configureModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); configureModelElement(context, 'icon', Icon, IconView); @@ -71,9 +71,9 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); configureModelElement(context, 'activityNode:fork', ActivityNode, RectangularNodeView); configureModelElement(context, 'activityNode:join', ActivityNode, RectangularNodeView); - configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, GLSPProjectionView); + configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); - configureModelElement(context, 'struct', SCompartment, StructureCompartmentView); + configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); }); export function createWorkflowDiagramContainer(...containerConfiguration: ContainerConfiguration): Container { diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index f605849..ffdf448 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -20,7 +20,7 @@ import { Point, PolylineEdgeViewWithGapsOnIntersections, RenderingContext, - SEdge, + GEdge, setAttr, ShapeView, svg, @@ -35,7 +35,7 @@ const JSX = { createElement: svg }; @injectable() export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { - protected override renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { + protected override renderAdditionals(edge: GEdge, segments: Point[], context: RenderingContext): VNode[] { const additionals = super.renderAdditionals(edge, segments, context); const p1 = segments[segments.length - 2]; const p2 = segments[segments.length - 1]; diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index b24158e..b4e6fe3 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": ".", "reactNamespace": "JSX", "skipLibCheck": true, "resolveJsonModule": true diff --git a/examples/workflow-standalone/src/url-parameters.ts b/examples/workflow-standalone/src/url-parameters.ts index 45063b1..03dd9a9 100644 --- a/examples/workflow-standalone/src/url-parameters.ts +++ b/examples/workflow-standalone/src/url-parameters.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export function getParameters(): { [key: string]: string } { + +export function getParameters(): Record { let search = window.location.search.substring(1); - const result = {}; + const result: Record = {}; while (search.length > 0) { const nextParamIndex = search.indexOf('&'); let param: string; diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json index f76247e..c5b2db8 100644 --- a/examples/workflow-standalone/tsconfig.json +++ b/examples/workflow-standalone/tsconfig.json @@ -2,9 +2,7 @@ "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib", - "baseUrl": ".", - "noImplicitAny": false + "outDir": "lib" }, "include": ["src", "lib", "css", "app"] } diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 0db6b82..e721eff 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -6,29 +6,28 @@ module.exports = { 'warn', { name: 'sprotty', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' }, { name: 'sprotty/*', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' }, { name: 'sprotty-protocol', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' }, { name: 'sprotty-protocol/*', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' }, { name: '@eclipse-glsp/protocol', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' }, { name: '@eclipse-glsp/protocol/*', - message: "Please use '~glsp-sprotty' instead" + message: 'Please use @eclipse-glsp/sprotty instead' } - ], - 'import/no-unresolved': ['error', { ignore: ['~glsp-sprotty'] }] + ] } }; diff --git a/packages/client/package.json b/packages/client/package.json index c8666bd..a9e1226 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -36,7 +36,7 @@ "css" ], "scripts": { - "build": "tsc -b && tsc-alias", + "build": "tsc -b", "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", @@ -47,16 +47,14 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "1.1.0-next", - "autocompleter": "5.1.0", - "lodash": "4.17.21", - "sprotty": "~0.14.0" + "@eclipse-glsp/sprotty": "1.1.0-next", + "autocompleter": "^9.1.0", + "file-saver": "^2.0.5", + "lodash": "4.17.21" }, "devDependencies": { - "@types/file-saver": "^2.0.3", - "@types/lodash": "4.14.191", - "@vscode/codicons": "^0.0.25", - "tsc-alias": "^1.8.2" + "@types/file-saver": "^2.0.5", + "@types/lodash": "4.14.191" }, "publishConfig": { "access": "public" diff --git a/packages/client/src/base/action-dispatcher.spec.ts b/packages/client/src/base/action-dispatcher.spec.ts index 0cee9db..b12a0b2 100644 --- a/packages/client/src/base/action-dispatcher.spec.ts +++ b/packages/client/src/base/action-dispatcher.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { expect } from 'chai'; import { Container } from 'inversify'; -import { ActionHandlerRegistry, IActionHandler, RequestAction, ResponseAction, TYPES } from '~glsp-sprotty'; +import { ActionHandlerRegistry, IActionHandler, RequestAction, ResponseAction, TYPES } from '@eclipse-glsp/sprotty'; import { IDiagramOptions } from '..'; import { GLSPActionDispatcher } from './action-dispatcher'; import { defaultModule } from './default.module'; diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index cb6cdb2..74ebb45 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, ActionDispatcher, RequestAction, ResponseAction } from '~glsp-sprotty'; +import { Action, ActionDispatcher, RequestAction, ResponseAction } from '@eclipse-glsp/sprotty'; import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @injectable() diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index e4220bb..7b13032 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; -import { ActionHandlerRegistry } from '~glsp-sprotty'; +import { ActionHandlerRegistry } from '@eclipse-glsp/sprotty'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { diff --git a/packages/client/src/base/argumentable.ts b/packages/client/src/base/args-feature.ts similarity index 53% rename from packages/client/src/base/argumentable.ts rename to packages/client/src/base/args-feature.ts index e530414..680b6fc 100644 --- a/packages/client/src/base/argumentable.ts +++ b/packages/client/src/base/args-feature.ts @@ -13,12 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, SModelElement } from '~glsp-sprotty'; +import { Args, GModelElement } from '@eclipse-glsp/sprotty'; -export interface SArgumentable { - args: Args; +export const argsFeature = Symbol('argsFeature'); + +/** + * Adds an optional `args` property to a {@link GModelElement}. This allows + * to add arbitrary arguments to the element (on client or server side) without having + * to extend the model class. + * + * Feature extension interface for {@link argsFeature}. + */ +export interface ArgsAware { + args?: Args; +} + +export function isArgsAware(element: GModelElement): element is GModelElement & ArgsAware { + return element.hasFeature(argsFeature); } -export function hasArguments(element?: SModelElement): element is SModelElement & SArgumentable { - return element !== undefined && 'args' in element; +export function hasArgs(element?: GModelElement): element is GModelElement & Required { + return element !== undefined && isArgsAware(element) && element.args !== undefined; } diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts index 74fab61..7225ca8 100644 --- a/packages/client/src/base/auto-complete/auto-complete-actions.ts +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { hasStringProp, LabeledAction } from '~glsp-sprotty'; +import { hasStringProp, LabeledAction } from '@eclipse-glsp/sprotty'; +/** A special {@link LabeledAction} that has a `text` property in addition to the `label */ export interface AutoCompleteValue extends LabeledAction { readonly text: string; } diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index d164f78..40376ae 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -14,7 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; -import { Action, ILogger, LabeledAction, SModelRoot, ValidationStatus, codiconCSSClasses, matchesKeystroke, toArray } from '~glsp-sprotty'; +import { + Action, + ILogger, + LabeledAction, + GModelRoot, + ValidationStatus, + codiconCSSClasses, + matchesKeystroke, + toArray +} from '@eclipse-glsp/sprotty'; import { AutoCompleteValue } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; @@ -35,7 +44,7 @@ export interface SuggestionProvider { } export interface InputValueInitializer { - initializeValue(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): string; + initializeValue(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): string; } export interface SuggestionSubmitHandler { @@ -110,7 +119,7 @@ export class AutoCompleteWidget { inputElement.style.position = 'absolute'; inputElement.spellcheck = false; inputElement.autocapitalize = 'false'; - inputElement.autocomplete = 'false'; + inputElement.autocomplete = 'off'; inputElement.style.width = '100%'; inputElement.addEventListener('keydown', event => this.handleKeyDown(event)); inputElement.addEventListener('blur', () => { @@ -155,7 +164,7 @@ export class AutoCompleteWidget { this.validationDecorator.invalidate(); } - open(root: Readonly, ...contextElementIds: string[]): void { + open(root: Readonly, ...contextElementIds: string[]): void { this.contextActions = undefined; this.autoCompleteResult = configureAutocomplete(this.autocompleteSettings(root)); this.previousContent = this.inputElement.value; @@ -163,7 +172,7 @@ export class AutoCompleteWidget { this.inputElement.focus(); } - protected autocompleteSettings(root: Readonly): AutocompleteSettings { + protected autocompleteSettings(root: Readonly): AutocompleteSettings { return { input: this.inputElement, emptyMsg: this.autoSuggestionSettings.noSuggestionsMessage, @@ -174,13 +183,18 @@ export class AutoCompleteWidget { fetch: (text: string, update: (items: LabeledAction[]) => void) => this.updateSuggestions(update, text, root), onSelect: (item: LabeledAction) => this.onSelect(item), render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => this.renderSuggestions(item, currentValue), - customize: (input: HTMLInputElement, inputRect: DOMRect, container: HTMLDivElement, maxHeight: number) => { + customize: (input, inputRect, container, maxHeight) => { this.customizeInputElement(input, inputRect, container, maxHeight); } }; } - protected customizeInputElement(input: HTMLInputElement, inputRect: DOMRect, container: HTMLDivElement, maxHeight: number): void { + protected customizeInputElement( + input: HTMLInputElement | HTMLTextAreaElement, + inputRect: DOMRect, + container: HTMLDivElement, + maxHeight: number + ): void { // move container into our UIExtension container as this is already positioned correctly if (this.containerElement) { this.containerElement.appendChild(container); @@ -201,7 +215,7 @@ export class AutoCompleteWidget { protected updateSuggestions( update: (items: LabeledAction[]) => void, text: string, - root: Readonly, + root: Readonly, ...contextElementIds: string[] ): void { this.onLoading(); @@ -229,7 +243,7 @@ export class AutoCompleteWidget { this.containerElement.appendChild(this.loadingIndicator); } - protected doUpdateSuggestions(text: string, root: Readonly, ...contextElementIds: string[]): Promise { + protected doUpdateSuggestions(text: string, root: Readonly, ...contextElementIds: string[]): Promise { return this.suggestionProvider.provideSuggestions(text); } diff --git a/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts b/packages/client/src/base/auto-complete/autocomplete-suggestion-providers.ts similarity index 88% rename from packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts rename to packages/client/src/base/auto-complete/autocomplete-suggestion-providers.ts index a10f03b..6484f1b 100644 --- a/packages/client/src/features/autocomplete-palette/autocomplete-suggestion-providers.ts +++ b/packages/client/src/base/auto-complete/autocomplete-suggestion-providers.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LabeledAction, SModelElement, SModelRoot } from '~glsp-sprotty'; +import { GModelElement, GModelRoot, LabeledAction } from '@eclipse-glsp/sprotty'; /** * Interface for a provider that provides suggestions that can be used to fill the autocomplete @@ -25,13 +25,13 @@ export interface IAutocompleteSuggestionProvider { * @param root: The root model * @param text: The text input provided by the user. */ - retrieveSuggestions(root: Readonly, text: string): Promise; + retrieveSuggestions(root: Readonly, text: string): Promise; } /** * Interface for autocomplete suggestions. */ export interface AutocompleteSuggestion { - element: SModelElement; + element: GModelElement; action: LabeledAction; } diff --git a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts similarity index 82% rename from packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts rename to packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 9f9f092..339928d 100644 --- a/packages/client/src/features/autocomplete-palette/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -15,11 +15,16 @@ ********************************************************************************/ import { inject } from 'inversify'; -import { AbstractUIExtension, Action, LabeledAction, SModelRoot, TYPES } from '~glsp-sprotty'; +import { AbstractUIExtension, Action, LabeledAction, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import '../../../css/autocomplete-palette.css'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { AutoCompleteWidget, CloseReason, toActionArray } from '../../base/auto-complete/auto-complete-widget'; +import { GLSPActionDispatcher } from '../action-dispatcher'; +import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete-widget'; +/** + * A reusable base implementation for `UIExtensions` that want to provide autocomplete functionality + * using the {@link AutoCompleteWidget}. + * + */ export abstract class BaseAutocompletePalette extends AbstractUIExtension { protected readonly autoSuggestionSettings = { noSuggestionsMessage: 'No suggestions available', @@ -28,7 +33,7 @@ export abstract class BaseAutocompletePalette extends AbstractUIExtension { showOnFocus: true }; - protected root?: Readonly; + protected root?: Readonly; protected autocompleteWidget: AutoCompleteWidget; @inject(TYPES.IActionDispatcher) @@ -38,7 +43,7 @@ export abstract class BaseAutocompletePalette extends AbstractUIExtension { return 'autocomplete-palette'; } - override show(root: Readonly, ...contextElementIds: string[]): void { + override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.root = root; this.autocompleteWidget.open(root); @@ -67,13 +72,13 @@ export abstract class BaseAutocompletePalette extends AbstractUIExtension { this.autocompleteWidget.initialize(containerElement); } - protected abstract retrieveSuggestions(root: Readonly, input: string): Promise; + protected abstract retrieveSuggestions(root: Readonly, input: string): Promise; - protected async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { + protected async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { return; } - protected async selectedSuggestionChanged(root: Readonly, labeledAction?: LabeledAction): Promise { + protected async selectedSuggestionChanged(root: Readonly, labeledAction?: LabeledAction): Promise { return; } diff --git a/packages/client/src/base/auto-complete/validation-decorator.ts b/packages/client/src/base/auto-complete/validation-decorator.ts index 50415ec..39cbc9d 100644 --- a/packages/client/src/base/auto-complete/validation-decorator.ts +++ b/packages/client/src/base/auto-complete/validation-decorator.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ValidationStatus, codiconCSSString } from '~glsp-sprotty'; +import { ValidationStatus, codiconCSSString } from '@eclipse-glsp/sprotty'; export interface IValidationDecorator { decorateValidationResult(status: ValidationStatus): void; diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index d16c1b6..58c70df 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -21,18 +21,18 @@ import { Emitter, Event, ICommand, - SModelRoot, + GModelRoot, SetModelCommand, TYPES, UpdateModelCommand -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; /** * A hook to listen for model root changes. Will be called after a server update * has been processed */ export interface ISModelRootListener { - modelRootChanged(root: Readonly): void; + modelRootChanged(root: Readonly): void; } @injectable() @@ -53,12 +53,12 @@ export class GLSPCommandStack extends CommandStack implements Disposable { this.toDispose.dispose(); } - protected onModelRootChangedEmitter = new Emitter>(); - get onModelRootChanged(): Event> { + protected onModelRootChangedEmitter = new Emitter>(); + get onModelRootChanged(): Event> { return this.onModelRootChangedEmitter.event; } - override undo(): Promise { + override undo(): Promise { this.logger.warn( this, 'GLSPCommandStack.undo() was called. This should never happen as the GLSP server is responsible for handling undo requests' @@ -66,7 +66,7 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.currentModel; } - override redo(): Promise { + override redo(): Promise { this.logger.warn( this, 'GLSPCommandStack.redo() was called. This should never happen as the GLSP server is responsible for handling redo requests' @@ -74,7 +74,7 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.currentModel; } - override execute(command: ICommand): Promise { + override execute(command: ICommand): Promise { const result = super.execute(command); if (command instanceof SetModelCommand || command instanceof UpdateModelCommand) { result.then(root => this.notifyListeners(root)); @@ -82,7 +82,7 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return result; } - protected notifyListeners(root: Readonly): void { + protected notifyListeners(root: Readonly): void { this.onModelRootChangedEmitter.fire(root); } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index c8cd96a..32a3a69 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -29,7 +29,7 @@ import { configureActionHandler, configureCommand, sprottyDefaultModule -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; @@ -43,14 +43,14 @@ import { FocusTracker } from './focus/focus-tracker'; import { DiagramLoader } from './model/diagram-loader'; import { GLSPModelSource } from './model/glsp-model-source'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model/model-initialization-constraint'; -import { GLSPModelRegistry } from './model/model-registry'; +import { GModelRegistry } from './model/model-registry'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; import { GLSPKeyTool } from './view/key-tool'; import { GLSPMouseTool } from './view/mouse-tool'; -import { GLSPViewRegistry } from './view/view-registry'; +import { GViewRegistry } from './view/view-registry'; /** * The default module provides all of GLSP's base functionality and services. @@ -92,8 +92,8 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); // support re-registration of model elements and views - bindOrRebind(context, TYPES.SModelRegistry).to(GLSPModelRegistry).inSingletonScope(); - bindOrRebind(context, TYPES.ViewRegistry).to(GLSPViewRegistry).inSingletonScope(); + bindOrRebind(context, TYPES.SModelRegistry).to(GModelRegistry).inSingletonScope(); + bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); bind(SelectionService).toSelf().inSingletonScope(); bind(TYPES.ISModelRootListener).toService(SelectionService); diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index a5d85fb..452a995 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MouseListener, SModelElement } from '~glsp-sprotty'; +import { Action, MouseListener, GModelElement } from '@eclipse-glsp/sprotty'; /** * A mouse listener that is aware of prior mouse dragging. @@ -27,19 +27,19 @@ export class DragAwareMouseListener extends MouseListener { protected _isMouseDown = false; protected _isMouseDrag = false; - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { this._isMouseDown = true; return []; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { if (this._isMouseDown) { this._isMouseDrag = true; } return []; } - override mouseUp(element: SModelElement, event: MouseEvent): Action[] { + override mouseUp(element: GModelElement, event: MouseEvent): Action[] { this._isMouseDown = false; if (this._isMouseDrag) { this._isMouseDrag = false; @@ -49,11 +49,11 @@ export class DragAwareMouseListener extends MouseListener { return this.nonDraggingMouseUp(element, event); } - nonDraggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { return []; } - draggingMouseUp(element: SModelElement, event: MouseEvent): Action[] { + draggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { return []; } diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 629bc2c..290fd02 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -26,13 +26,13 @@ import { IActionHandler, MaybePromise, MousePositionTracker, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, SetDirtyStateAction, SetEditModeAction, TYPES, ValueChange -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from './action-dispatcher'; import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; import { SelectionService } from './selection-service'; @@ -153,11 +153,11 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this.diagramOptions.clientId; } - get modelRoot(): Readonly { + get modelRoot(): Readonly { return this.selectionService.getModelRoot(); } - get selectedElements(): Readonly[] { + get selectedElements(): Readonly[] { return this.selectionService.getSelectedElements(); } diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 0a623df..7d1cd9d 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, CommandExecutionContext, SModelElement, SModelRoot, TYPES, hasArrayProp } from '~glsp-sprotty'; -import { addCssClasses, getElements, removeCssClasses } from '../../utils/smodel-util'; +import { Action, CommandExecutionContext, GModelElement, GModelRoot, TYPES, hasArrayProp } from '@eclipse-glsp/sprotty'; +import { addCssClasses, getElements, removeCssClasses } from '../../utils/gmodel-util'; import { FeedbackCommand } from './feedback-command'; export interface ModifyCSSFeedbackAction extends Action { @@ -33,7 +33,7 @@ export namespace ModifyCSSFeedbackAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); } - export function create(options: { elements?: (string | SModelElement)[]; add?: string[]; remove?: string[] }): ModifyCSSFeedbackAction { + export function create(options: { elements?: (string | GModelElement)[]; add?: string[]; remove?: string[] }): ModifyCSSFeedbackAction { const { elements, ...rest } = options; const elementIds = elements ? elements.map(element => (typeof element === 'string' ? element : element.id)) : undefined; return { @@ -52,7 +52,7 @@ export class ModifyCssFeedbackCommand extends FeedbackCommand { super(); } - execute(context: CommandExecutionContext): SModelRoot { + execute(context: CommandExecutionContext): GModelRoot { const elements = this.action.elementIds ? getElements(context.root.index, this.action.elementIds) : [context.root]; elements.forEach(e => { @@ -93,10 +93,10 @@ export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAc return ModifyCSSFeedbackAction.create({ add, remove: Object.values(CursorCSS) }); } -export function applyCssClasses(element: SModelElement, ...add: string[]): ModifyCSSFeedbackAction { +export function applyCssClasses(element: GModelElement, ...add: string[]): ModifyCSSFeedbackAction { return ModifyCSSFeedbackAction.create({ elements: [element], add }); } -export function deleteCssClasses(element: SModelElement, ...remove: string[]): ModifyCSSFeedbackAction { +export function deleteCssClasses(element: GModelElement, ...remove: string[]): ModifyCSSFeedbackAction { return ModifyCSSFeedbackAction.create({ elements: [element], remove }); } diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index bafdcbf..708b2bb 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, Disposable, IActionDispatcher, ILogger, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, IActionDispatcher, ILogger, TYPES } from '@eclipse-glsp/sprotty'; export interface IFeedbackEmitter {} @@ -26,10 +26,10 @@ export interface IFeedbackEmitter {} * The purpose of this dispatcher is to re-establish the feedback * after the model has been updated or reset by the server, as this would * overwrite the already established feedback, in case it is drawn by - * extending the `SModelRoot`. Therefore, tools can register themselves + * extending the `GModelRoot`. Therefore, tools can register themselves * as feedback emitters with actions they want to place for showing * feedback. This dispatcher will then re-establish all feedback actions - * of the registered emitters, whenever the `SModelRoot` has been set or updated. + * of the registered emitters, whenever the `GModelRoot` has been set or updated. */ export interface IFeedbackActionDispatcher { /** diff --git a/packages/client/src/base/feedback/feedback-command.ts b/packages/client/src/base/feedback/feedback-command.ts index d3b0a30..1cd192a 100644 --- a/packages/client/src/base/feedback/feedback-command.ts +++ b/packages/client/src/base/feedback/feedback-command.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Command, CommandExecutionContext, CommandReturn } from '~glsp-sprotty'; +import { Command, CommandExecutionContext, CommandReturn } from '@eclipse-glsp/sprotty'; export abstract class FeedbackCommand extends Command { // used by the `FeedbackAwareUpdateModelCommand` diff --git a/packages/client/src/base/feedback/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts index 98743e5..18a8f05 100644 --- a/packages/client/src/base/feedback/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -23,13 +23,13 @@ import { CommandReturn, ILogger, MorphEdgesAnimation, - SModelRoot, + GModelRoot, TYPES, UpdateAnimationData, UpdateModelAction, UpdateModelCommand, toTypeGuard -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; import { FeedbackCommand } from './feedback-command'; @@ -58,7 +58,7 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } - protected override performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { + protected override performUpdate(oldRoot: GModelRoot, newRoot: GModelRoot, context: CommandExecutionContext): CommandReturn { if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { // Create a temporary context which defines the `newRoot` as `root` // This way we do not corrupt the redo/undo behavior of the super class @@ -93,7 +93,7 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { // Override the `createAnimations` implementation and remove the animation for edge morphing. Otherwise routing & reconnect // handles flicker after each server update. - protected override createAnimations(data: UpdateAnimationData, root: SModelRoot, context: CommandExecutionContext): Animation[] { + protected override createAnimations(data: UpdateAnimationData, root: GModelRoot, context: CommandExecutionContext): Animation[] { const animations = super.createAnimations(data, root, context); return animations.filter(animation => !(animation instanceof MorphEdgesAnimation)); } diff --git a/packages/client/src/base/focus/focus-state-change-action.ts b/packages/client/src/base/focus/focus-state-change-action.ts index 105c076..ec61a3b 100644 --- a/packages/client/src/base/focus/focus-state-change-action.ts +++ b/packages/client/src/base/focus/focus-state-change-action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasBooleanProp } from '~glsp-sprotty'; +import { Action, hasBooleanProp } from '@eclipse-glsp/sprotty'; /** * A `FocusStateChangedAction` is dispatched by the client whenever the diff --git a/packages/client/src/base/focus/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts index fe0e02f..67806b8 100644 --- a/packages/client/src/base/focus/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from '~glsp-sprotty'; +import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { FocusStateChangedAction } from './focus-state-change-action'; @injectable() diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index b0da8cc..002b62e 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -28,7 +28,7 @@ import { StatusAction, TYPES, hasNumberProp -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; import { GLSPModelSource } from './glsp-model-source'; diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index a0251ce..1687bef 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -22,13 +22,13 @@ import { DisposableCollection, DisposeClientSessionParameters, GLSPClient, + GModelRootSchema, ILogger, InitializeClientSessionParameters, InitializeResult, ModelSource, - SModelRootSchema, TYPES -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionHandlerRegistry } from '../action-handler-registry'; import { IDiagramOptions } from './diagram-loader'; /** @@ -73,7 +73,7 @@ export class GLSPModelSource extends ModelSource implements Disposable { protected toDispose = new DisposableCollection(); clientId: string; - protected _currentRoot: SModelRootSchema; + protected _currentRoot: GModelRootSchema; protected registry: GLSPActionHandlerRegistry; protected glspClient?: GLSPClient; @@ -178,7 +178,7 @@ export class GLSPModelSource extends ModelSource implements Disposable { return !ServerAction.is(action); } - commitModel(newRoot: SModelRootSchema): SModelRootSchema { + commitModel(newRoot: GModelRootSchema): GModelRootSchema { /* In GLSP the model update flow is server-driven. i.e. changes to the graphical model are applied * on server-side an only the server can issue a model update. * The internal/local model should never be committed back to the model source i.e. GLSP server. @@ -188,7 +188,7 @@ export class GLSPModelSource extends ModelSource implements Disposable { return newRoot; } - override get model(): SModelRootSchema { + override get model(): GModelRootSchema { return this._currentRoot; } diff --git a/packages/client/src/base/model/model-initialization-constraint.spec.ts b/packages/client/src/base/model/model-initialization-constraint.spec.ts index 712a1df..588b2d8 100644 --- a/packages/client/src/base/model/model-initialization-constraint.spec.ts +++ b/packages/client/src/base/model/model-initialization-constraint.spec.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; import { Container } from 'inversify'; import 'reflect-metadata'; import * as sinon from 'sinon'; -import { Deferred, EMPTY_ROOT, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; +import { Deferred, EMPTY_ROOT, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '@eclipse-glsp/sprotty'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model-initialization-constraint'; const sandbox = sinon.createSandbox(); const container = new Container(); diff --git a/packages/client/src/base/model/model-initialization-constraint.ts b/packages/client/src/base/model/model-initialization-constraint.ts index 9914321..19effcd 100644 --- a/packages/client/src/base/model/model-initialization-constraint.ts +++ b/packages/client/src/base/model/model-initialization-constraint.ts @@ -14,7 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, Deferred, Disposable, Emitter, InitializeCanvasBoundsAction, SetModelAction, UpdateModelAction } from '~glsp-sprotty'; +import { + Action, + Deferred, + Disposable, + Emitter, + InitializeCanvasBoundsAction, + SetModelAction, + UpdateModelAction +} from '@eclipse-glsp/sprotty'; /** * The constraint defining when the initialization of the GLSP model is completed. diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index aea3bd1..71e082e 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -13,12 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GModelElement, GModelElementConstructor, SModelRegistry } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { SModelElement, SModelRegistry } from '~glsp-sprotty'; +import { argsFeature } from '../args-feature'; +/** + * Model element classes registered here are considered automatically when constructing a model from its schema. + * Use the `configureModelElement` utility function to register a model element and its target render view in the + * corresponding factories. + */ @injectable() -export class GLSPModelRegistry extends SModelRegistry { - override register(key: string, factory: (u: void) => SModelElement): void { +export class GModelRegistry extends SModelRegistry { + /* Overwrite the `register` method to only log an info message (instead of thrown an error) if + an existing registration is overwritten */ + override register(key: string, factory: (u: void) => GModelElement): void { if (key === undefined) { throw new Error('Key is undefined'); } @@ -28,4 +36,13 @@ export class GLSPModelRegistry extends SModelRegistry { } this.elements.set(key, factory); } + + protected override getDefaultFeatures(constr: GModelElementConstructor): readonly symbol[] | undefined { + // Add the argsFeature per default to all model elements + const features = [...(super.getDefaultFeatures(constr) ?? [])]; + if (!features.includes(argsFeature)) { + features.push(argsFeature); + } + return features; + } } diff --git a/packages/client/src/base/ranked.ts b/packages/client/src/base/ranked.ts index d70b3e9..f1914fb 100644 --- a/packages/client/src/base/ranked.ts +++ b/packages/client/src/base/ranked.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AnyObject, hasNumberProp } from '~glsp-sprotty'; +import { AnyObject, hasNumberProp } from '@eclipse-glsp/sprotty'; /** * A common interface for services/listeners that should be diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts index e57340f..11fd497 100644 --- a/packages/client/src/base/selection-clearing-mouse-listener.ts +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { MouseListener, SModelElement } from '~glsp-sprotty'; +import { MouseListener, GModelElement } from '@eclipse-glsp/sprotty'; /** * A mouse listener that clears the document selection on click @@ -30,7 +30,7 @@ import { MouseListener, SModelElement } from '~glsp-sprotty'; */ @injectable() export class SelectionClearingMouseListener extends MouseListener { - override mouseDown(_target: SModelElement, event: MouseEvent): never[] { + override mouseDown(_target: GModelElement, event: MouseEvent): never[] { const selection = document.getSelection(); // eslint-disable-next-line no-null/no-null if (selection === null) { diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index e621e59..cabde1f 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -16,7 +16,7 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { Action, Disposable, SGraphFactory, SModelRoot, SNode, TYPES, initializeContainer } from '~glsp-sprotty'; +import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; import { defaultModule } from './default.module'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @@ -47,7 +47,7 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { } class MockSelectionListener implements ISelectionListener { - selectionChanged(root: Readonly, selectedElements: string[], deselectedElements: string[] | undefined): void { + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements: string[] | undefined): void { // no.op } } @@ -55,14 +55,13 @@ class MockSelectionListener implements ISelectionListener { function createContainer(): Container { const container = initializeContainer(new Container(), defaultModule); // eslint-disable-next-line deprecation/deprecation - container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); container.rebind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); return container; } describe('SelectionService', () => { // eslint-disable-next-line deprecation/deprecation - let root: SModelRoot; + let root: GModelRoot; let selectionService: SelectionService; let feedbackDispatcher: MockFeedbackActionDispatcher; @@ -240,12 +239,12 @@ describe('SelectionService', () => { }); }); - function createRoot(...nodes: string[]): SModelRoot { - const model = new SModelRoot(); + function createRoot(...nodes: string[]): GModelRoot { + const model = new GModelRoot(); model.id = 'selection-service-spec'; model.type = 'graph'; nodes.forEach(id => { - const node = new SNode(); + const node = new GNode(); node.type = 'node:circle'; node.id = id; model.add(node); @@ -285,7 +284,7 @@ describe('SelectionService', () => { function assertListener( listener: sinon.SinonStubbedInstance, - expectedRoot: SModelRoot | undefined, + expectedRoot: GModelRoot | undefined, expectedSelection: string[], expectedDeselection: string[], expectedCalled: number diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index fdfe06b..231d5e6 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -22,10 +22,10 @@ import { DisposableCollection, Emitter, Event, + GChildElement, ILogger, - SChildElement, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, SelectAction, SelectAllAction, Selectable, @@ -35,24 +35,24 @@ import { hasArrayProp, isSelectable, pluck -} from '~glsp-sprotty'; -import { getElements, getMatchingElements } from '../utils/smodel-util'; +} from '@eclipse-glsp/sprotty'; +import { getElements, getMatchingElements } from '../utils/gmodel-util'; import { ISModelRootListener } from './command-stack'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; export interface ISelectionListener { - selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; } export interface SelectionChange { - root: Readonly; + root: Readonly; selectedElements: string[]; deselectedElements: string[]; } @injectable() export class SelectionService implements ISModelRootListener, Disposable { - protected root: Readonly; + protected root: Readonly; protected selectedElementIDs: Set = new Set(); @inject(TYPES.IFeedbackActionDispatcher) @@ -85,11 +85,11 @@ export class SelectionService implements ISModelRootListener, Disposable { return this.onSelectionChangedEmitter.event; } - modelRootChanged(root: Readonly): void { + modelRootChanged(root: Readonly): void { this.updateSelection(root, [], []); } - updateSelection(root: Readonly, select: string[], deselect: string[]): void { + updateSelection(root: Readonly, select: string[], deselect: string[]): void { if (root === undefined && select.length === 0 && deselect.length === 0) { return; } @@ -148,7 +148,7 @@ export class SelectionService implements ISModelRootListener, Disposable { this.feedbackDispatcher.registerFeedback(this, actions); } - notifyListeners(root: SModelRoot, selectedElementIDs: Set, deselectedElementIDs: Set): void { + notifyListeners(root: GModelRoot, selectedElementIDs: Set, deselectedElementIDs: Set): void { this.onSelectionChangedEmitter.fire({ root, selectedElements: Array.from(selectedElementIDs), @@ -156,11 +156,11 @@ export class SelectionService implements ISModelRootListener, Disposable { }); } - getModelRoot(): Readonly { + getModelRoot(): Readonly { return this.root; } - getSelectedElements(): Readonly[] { + getSelectedElements(): Readonly[] { return !this.root ? [] : getElements(this.root.index, Array.from(this.selectedElementIDs), isSelectable); } @@ -194,16 +194,16 @@ export class SelectionService implements ISModelRootListener, Disposable { export class SelectCommand extends Command { static readonly KIND = SprottySelectCommand.KIND; - protected selected: SModelElement[] = []; - protected deselected: SModelElement[] = []; + protected selected: GModelElement[] = []; + protected deselected: GModelElement[] = []; constructor(@inject(TYPES.Action) public action: SelectAction, @inject(SelectionService) public selectionService: SelectionService) { super(); } - execute(context: CommandExecutionContext): SModelRoot { + execute(context: CommandExecutionContext): GModelRoot { const model = context.root; - const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element); + const selectionGuard = (element: any): element is GModelElement => element instanceof GChildElement && isSelectable(element); const selectedElements = getElements(model.index, this.action.selectedElementsIDs, selectionGuard); const deselectedElements = this.action.deselectAll ? this.selectionService.getSelectedElements() @@ -214,12 +214,12 @@ export class SelectCommand extends Command { } // Basically no-op since client-side undo is not supported in GLSP. - undo(context: CommandExecutionContext): SModelRoot { + undo(context: CommandExecutionContext): GModelRoot { return context.root; } // Basically no-op since client-side redo is not supported in GLSP. - redo(context: CommandExecutionContext): SModelRoot { + redo(context: CommandExecutionContext): GModelRoot { return context.root; } } @@ -238,9 +238,9 @@ export class SelectAllCommand extends Command { super(); } - execute(context: CommandExecutionContext): SModelRoot { + execute(context: CommandExecutionContext): GModelRoot { const model = context.root; - const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element); + const selectionGuard = (element: any): element is GModelElement => element instanceof GChildElement && isSelectable(element); const selectables = getMatchingElements(model.index, selectionGuard); const selectableIds = pluck(selectables, 'id'); @@ -254,12 +254,12 @@ export class SelectAllCommand extends Command { } // Basically no-op since client-side undo is not supported in GLSP. - undo(context: CommandExecutionContext): SModelRoot { + undo(context: CommandExecutionContext): GModelRoot { return context.root; } // Basically no-op since client-side redo is not supported in GLSP. - redo(context: CommandExecutionContext): SModelRoot { + redo(context: CommandExecutionContext): GModelRoot { return context.root; } } diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts index 7de653d..4f7a6e9 100644 --- a/packages/client/src/base/tool-manager/tool-manager.ts +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -21,11 +21,11 @@ import { IActionHandler, ICommand, KeyListener, - SModelElement, + GModelElement, TYPES, distinctAdd, matchesKeystroke -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from './tool'; /** @@ -174,7 +174,7 @@ export class ToolManagerActionHandler implements IActionHandler { @injectable() export class DefaultToolsEnablingKeyListener extends KeyListener { - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Escape')) { return [EnableDefaultToolsAction.create()]; } diff --git a/packages/client/src/base/tool-manager/tool.ts b/packages/client/src/base/tool-manager/tool.ts index 0331bdf..220e10f 100644 --- a/packages/client/src/base/tool-manager/tool.ts +++ b/packages/client/src/base/tool-manager/tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, AnyObject, hasArrayProp, hasBooleanProp, hasFunctionProp, hasStringProp } from '~glsp-sprotty'; +import { Action, AnyObject, hasArrayProp, hasBooleanProp, hasFunctionProp, hasStringProp } from '@eclipse-glsp/sprotty'; /** * Action to enable the tools of the specified `toolIds`. diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts index 2255e55..9f29998 100644 --- a/packages/client/src/base/view/key-tool.ts +++ b/packages/client/src/base/view/key-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Disposable, KeyListener, KeyTool } from '~glsp-sprotty'; +import { Disposable, KeyListener, KeyTool } from '@eclipse-glsp/sprotty'; @injectable() export class GLSPKeyTool extends KeyTool { diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index ed498d4..d7e595c 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, multiInject, optional } from 'inversify'; -import { Action, Disposable, MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, MouseListener, MouseTool, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { Ranked } from '../ranked'; /** @@ -47,7 +47,7 @@ export class GLSPMouseTool extends MouseTool { this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener)); } - protected override handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { + protected override handleEvent(methodName: K, model: GModelRoot, event: MouseEvent): void { this.focusOnMouseEvent(methodName, model); const element = this.getTargetElement(model, event); if (!element) { @@ -57,7 +57,7 @@ export class GLSPMouseTool extends MouseTool { } protected async notifyListenersByRank( - element: SModelElement, + element: GModelElement, methodName: K, event: MouseEvent ): Promise { @@ -69,7 +69,7 @@ export class GLSPMouseTool extends MouseTool { protected async dispatchActions( mouseListeners: MouseListener[], methodName: K, - element: SModelElement, + element: GModelElement, event: MouseEvent ): Promise { const actions = mouseListeners.map(listener => listener[methodName](element, event as WheelEvent)).reduce((a, b) => a.concat(b)); diff --git a/packages/client/src/base/view/view-registry.ts b/packages/client/src/base/view/view-registry.ts index 72fb64d..6ef9160 100644 --- a/packages/client/src/base/view/view-registry.ts +++ b/packages/client/src/base/view/view-registry.ts @@ -13,11 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { IView, ViewRegistry } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { IView, ViewRegistry } from '~glsp-sprotty'; +/** + * Allows to look up the IView for a given GModelElement based on its type. + */ @injectable() -export class GLSPViewRegistry extends ViewRegistry { +export class GViewRegistry extends ViewRegistry { + /* Overwrite the `register` method to only log an info message (instead of thrown an error) if + an existing registration is overwritten */ override register(key: string, instance: IView): void { if (key === undefined) { throw new Error('Key is undefined'); diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index f563567..1220590 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -31,7 +31,7 @@ import { modelSourceModule, resolveContainerConfiguration, zorderModule -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { defaultModule } from './base/default.module'; import { IDiagramOptions } from './base/model/diagram-loader'; import { boundsModule } from './features/bounds/bounds-module'; diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index c60c264..1a5b68d 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule } from '~glsp-sprotty'; +import { FeatureModule } from '@eclipse-glsp/sprotty'; import { configureShortcutHelpTool } from './key-shortcut/di.config'; import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; diff --git a/packages/client/src/features/accessibility/actions.ts b/packages/client/src/features/accessibility/actions.ts index 50796ea..f8825bc 100644 --- a/packages/client/src/features/accessibility/actions.ts +++ b/packages/client/src/features/accessibility/actions.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasStringProp } from '~glsp-sprotty'; +import { Action, hasStringProp } from '@eclipse-glsp/sprotty'; export interface FocusDomAction extends Action { kind: typeof FocusDomAction.KIND; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/action.ts b/packages/client/src/features/accessibility/edge-autocomplete/action.ts index 11cbfb9..92660c7 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/action.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/action.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasObjectProp, hasStringProp } from '~glsp-sprotty'; +import { Action, hasObjectProp, hasStringProp } from '@eclipse-glsp/sprotty'; export interface SetEdgeTargetSelectionAction extends Action { kind: typeof SetEdgeTargetSelectionAction.KIND; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts index f03131e..3ff5651 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-context.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { TriggerEdgeCreationAction } from '~glsp-sprotty'; +import { TriggerEdgeCreationAction } from '@eclipse-glsp/sprotty'; export interface EdgeAutocompleteContext { role: 'source' | 'target'; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts index 0c68b4a..066c57c 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts @@ -17,9 +17,8 @@ import { injectable } from 'inversify'; import { codiconCSSString, isConnectable, - SEdge, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, LabeledAction, name, SetUIExtensionVisibilityAction, @@ -27,14 +26,15 @@ import { Action, CreateEdgeOperation, TriggerEdgeCreationAction -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { toArray } from 'sprotty/lib/utils/iterable'; import { EdgeAutocompleteContext } from './edge-autocomplete-context'; import { SearchAutocompletePalette } from '../search/search-palette'; -import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; import { SetEdgeTargetSelectionAction } from './action'; import { CloseReason, toActionArray } from '../../../base/auto-complete/auto-complete-widget'; +import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; +import { GEdge } from '../../../model'; export namespace EdgeAutocompletePaletteMetadata { export const ID = 'edge-autocomplete-palette'; @@ -60,13 +60,13 @@ export class EdgeAutocompletePalette extends SearchAutocompletePalette implement } } - protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { super.onBeforeShow(containerElement, root, ...contextElementIds); this.autocompleteWidget.inputField.placeholder = `Search for ${this.context?.role} elements`; } - protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { return [this.targetSuggestionProvider]; } @@ -115,26 +115,26 @@ export class EdgeAutocompletePalette extends SearchAutocompletePalette implement @injectable() export class PossibleEdgeTargetAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { - protected proxyEdge?: SEdge; + protected proxyEdge?: GEdge; protected context?: EdgeAutocompleteContext; setContext(triggerAction: TriggerEdgeCreationAction, edgeAutocompleteContext: EdgeAutocompleteContext): void { - this.proxyEdge = new SEdge(); + this.proxyEdge = new GEdge(); this.proxyEdge.type = triggerAction.elementTypeId; this.context = edgeAutocompleteContext; } - isAllowedSource(element: SModelElement | undefined, role: 'source' | 'target'): boolean { + isAllowedSource(element: GModelElement | undefined, role: 'source' | 'target'): boolean { return element !== undefined && this.proxyEdge !== undefined && isConnectable(element) && element.canConnect(this.proxyEdge, role); } - async retrieveSuggestions(root: Readonly, text: string): Promise { + async retrieveSuggestions(root: Readonly, text: string): Promise { const context = this.context; if (this.context === undefined) { return []; } - const nodes = toArray(root.index.all().filter(element => this.isAllowedSource(element, context!.role))) as SEdge[]; + const nodes = toArray(root.index.all().filter(element => this.isAllowedSource(element, context!.role))) as GEdge[]; return nodes.map(node => ({ element: node, action: { diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts index 80f6a70..0aca80e 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { KeyTool, KeyListener, SetUIExtensionVisibilityAction, SModelElement, Action } from '~glsp-sprotty'; +import { KeyTool, KeyListener, SetUIExtensionVisibilityAction, GModelElement, Action } from '@eclipse-glsp/sprotty'; import { Tool } from '../../../base/tool-manager/tool'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { EdgeAutocompletePaletteMetadata } from './edge-autocomplete-palette'; @@ -41,7 +41,7 @@ export class EdgeAutocompletePaletteTool implements Tool { } export class EdgeAutocompletePaletteKeyListener extends KeyListener { - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Escape')) { return [ SetUIExtensionVisibilityAction.create({ diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 0edab93..312083a 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -14,16 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, + GModelElement, + GModelRoot, KeyListener, KeyTool, - SEdge, - SModelElement, - SModelRoot, SelectAction, Selectable, TYPES, @@ -31,10 +27,13 @@ import { isBoundsAware, isSelectable, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; -import { SelectableBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware } from '../../../utils/gmodel-util'; import { RepositionAction } from '../../viewport/reposition'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { AccessibleKeyShortcutTool } from '../key-shortcut/accessible-key-shortcut-tool'; @@ -42,6 +41,7 @@ import { SearchAutocompletePaletteTool } from '../search/search-tool'; import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigator } from './element-navigator'; +import { GEdge } from '../../../model'; @injectable() export class ElementNavigatorTool implements Tool { @@ -108,7 +108,7 @@ export class ElementNavigatorKeyListener extends KeyListener { ); } - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { this.resetOnEscape(event, element); if (this.getSelectedElements(element.root).length > 0) { @@ -124,7 +124,7 @@ export class ElementNavigatorKeyListener extends KeyListener { return []; } - protected resetOnEscape(event: KeyboardEvent, element: SModelElement): void { + protected resetOnEscape(event: KeyboardEvent, element: GModelElement): void { if (this.mode !== NavigationMode.NONE && this.matchesDeactivateNavigationMode(event)) { this.navigator?.clean?.(element.root); this.clean(); @@ -151,7 +151,7 @@ export class ElementNavigatorKeyListener extends KeyListener { } } - protected triggerPositionNavigationOnEvent(event: KeyboardEvent, element: SModelElement): boolean { + protected triggerPositionNavigationOnEvent(event: KeyboardEvent, element: GModelElement): boolean { if (this.matchesActivatePositionNavigation(event)) { if (this.mode !== NavigationMode.POSITION) { this.clean(); @@ -174,7 +174,7 @@ export class ElementNavigatorKeyListener extends KeyListener { return false; } - protected resetPositionNavigationOnEvent(event: KeyboardEvent, element: SModelElement): void { + protected resetPositionNavigationOnEvent(event: KeyboardEvent, element: GModelElement): void { if (this.mode === NavigationMode.POSITION && this.matchesActivatePositionNavigation(event)) { this.navigator?.clean?.(element.root); this.clean(); @@ -188,7 +188,7 @@ export class ElementNavigatorKeyListener extends KeyListener { ]); } } - protected triggerDefaultNavigationOnEvent(event: KeyboardEvent, element: SModelElement): boolean { + protected triggerDefaultNavigationOnEvent(event: KeyboardEvent, element: GModelElement): boolean { if (this.matchesActivateDefaultNavigation(event)) { if (this.mode !== NavigationMode.DEFAULT) { this.clean(); @@ -211,7 +211,7 @@ export class ElementNavigatorKeyListener extends KeyListener { return false; } - protected resetDefaultNavigationOnEvent(event: KeyboardEvent, element: SModelElement): void { + protected resetDefaultNavigationOnEvent(event: KeyboardEvent, element: GModelElement): void { if (this.mode === NavigationMode.DEFAULT && this.matchesActivateDefaultNavigation(event)) { this.navigator?.clean?.(element.root); this.clean(); @@ -226,7 +226,7 @@ export class ElementNavigatorKeyListener extends KeyListener { } } - protected navigate(element: SModelElement, event: KeyboardEvent): Action[] { + protected navigate(element: GModelElement, event: KeyboardEvent): Action[] { const selected = this.getSelectedElements(element.root); const current = selected.length > 0 ? selected[0] : undefined; @@ -241,7 +241,7 @@ export class ElementNavigatorKeyListener extends KeyListener { const selectableTarget = target ? findParentByFeature(target, isSelectable) : undefined; if (selectableTarget) { - if (!(current instanceof SEdge)) { + if (!(current instanceof GEdge)) { this.previousNode = current; } const deselectedElementsIDs = selected.map(e => e.id).filter(id => id !== selectableTarget.id); @@ -260,7 +260,7 @@ export class ElementNavigatorKeyListener extends KeyListener { event: KeyboardEvent, navigator: ElementNavigator, current: SelectableBoundsAware - ): SModelElement | undefined { + ): GModelElement | undefined { if (this.matchesNavigatePrevious(event)) { return navigator.previous(current.root, current); } else if (this.matchesNavigateNext(event)) { @@ -279,8 +279,8 @@ export class ElementNavigatorKeyListener extends KeyListener { this.navigator = undefined; } - protected getSelectedElements(root: SModelRoot): (SModelElement & Selectable)[] { - return toArray(root.index.all().filter(e => isSelected(e))) as (SModelElement & Selectable)[]; + protected getSelectedElements(root: GModelRoot): (GModelElement & Selectable)[] { + return toArray(root.index.all().filter(e => isSelected(e))) as (GModelElement & Selectable)[]; } protected matchesDeactivateNavigationMode(event: KeyboardEvent): boolean { diff --git a/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts index a0f53e2..3017777 100644 --- a/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts +++ b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { BindingContext, TYPES, bindAsService } from '~glsp-sprotty'; +import { BindingContext, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { PositionNavigator } from './position-navigator'; import { LocalElementNavigator } from './local-element-navigator'; import { ElementNavigatorTool } from './diagram-navigation-tool'; diff --git a/packages/client/src/features/accessibility/element-navigation/element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/element-navigator.ts index 107e110..c709c06 100644 --- a/packages/client/src/features/accessibility/element-navigation/element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/element-navigator.ts @@ -14,44 +14,44 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelRoot } from '~glsp-sprotty'; -import { SelectableBoundsAware } from '../../../utils/smodel-util'; +import { GModelElement, GModelRoot } from '@eclipse-glsp/sprotty'; +import { SelectableBoundsAware } from '../../../utils/gmodel-util'; export interface ElementNavigator { previous( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined; + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined; next( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined; + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined; up?( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined; + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined; down?( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined; + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined; process?( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, target: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean + predicate?: (element: GModelElement) => boolean ): void; - clean?(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void; + clean?(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void; } diff --git a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts index dd39454..a022625 100644 --- a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts @@ -14,22 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { EdgeRouterRegistry, findParentByFeature, SEdge, SModelElement, SModelRoot, Point } from '~glsp-sprotty'; +import { EdgeRouterRegistry, findParentByFeature, GModelElement, GModelRoot, Point } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { toArray } from 'sprotty/lib/utils/iterable'; -import { calcElementAndRoute, isRoutable, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util'; +import { calcElementAndRoute, isRoutable, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/gmodel-util'; import { ElementNavigator } from './element-navigator'; -import { inject, injectable, optional } from 'inversify'; +import { GEdge } from '../../../model'; @injectable() export class LeftToRightTopToBottomElementNavigator implements ElementNavigator { @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; previous( - root: Readonly, + root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement | undefined { + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement | undefined { const elements = this.getElements(root, predicate); if (current === undefined) { @@ -39,11 +40,11 @@ export class LeftToRightTopToBottomElementNavigator implements ElementNavigator } next( - root: Readonly, + root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement | undefined { + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement | undefined { const elements = this.getElements(root, predicate); if (current === undefined) { return elements.length > 0 ? elements[0] : undefined; @@ -51,12 +52,12 @@ export class LeftToRightTopToBottomElementNavigator implements ElementNavigator return elements[this.getNextIndex(current, elements) % elements.length]; } - protected getElements(root: Readonly, predicate: (element: SModelElement) => boolean): SModelElement[] { + protected getElements(root: Readonly, predicate: (element: GModelElement) => boolean): GModelElement[] { const elements = toArray(root.index.all().filter(e => isSelectableAndBoundsAware(e))) as SelectableBoundsAware[]; return elements.sort((a, b) => this.compare(a, b)).filter(predicate); } - protected getNextIndex(current: SelectableBoundsAware, elements: SModelElement[]): number { + protected getNextIndex(current: SelectableBoundsAware, elements: GModelElement[]): number { for (let index = 0; index < elements.length; index++) { if (this.compare(elements[index], current) > 0) { return index; @@ -66,7 +67,7 @@ export class LeftToRightTopToBottomElementNavigator implements ElementNavigator return 0; } - protected getPreviousIndex(current: SelectableBoundsAware, elements: SModelElement[]): number { + protected getPreviousIndex(current: SelectableBoundsAware, elements: GModelElement[]): number { for (let index = elements.length - 1; index >= 0; index--) { if (this.compare(elements[index], current) < 0) { return index; @@ -76,15 +77,15 @@ export class LeftToRightTopToBottomElementNavigator implements ElementNavigator return elements.length - 1; } - protected compare(one: SModelElement, other: SModelElement): number { + protected compare(one: GModelElement, other: GModelElement): number { let positionOne: Point | undefined = undefined; let positionOther: Point | undefined = undefined; - if (one instanceof SEdge && isRoutable(one)) { + if (one instanceof GEdge && isRoutable(one)) { positionOne = calcElementAndRoute(one, this.edgeRouterRegistry).newRoutingPoints?.[0]; } - if (other instanceof SEdge && isRoutable(other)) { + if (other instanceof GEdge && isRoutable(other)) { positionOther = calcElementAndRoute(other, this.edgeRouterRegistry).newRoutingPoints?.[0]; } diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index a0ca670..b5f6e7e 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -13,12 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BoundsAware, EdgeRouterRegistry, SConnectableElement, SEdge, SModelElement, SModelRoot, TYPES } from '~glsp-sprotty'; -import { ElementNavigator } from './element-navigator'; +import { BoundsAware, EdgeRouterRegistry, GConnectableElement, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; -import { SelectableBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware } from '../../../utils/gmodel-util'; +import { ElementNavigator } from './element-navigator'; +import { GEdge } from '../../../model'; @injectable() export class LocalElementNavigator implements ElementNavigator { @@ -27,54 +28,54 @@ export class LocalElementNavigator implements ElementNavigator { @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; previous( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getPreviousElement(current, predicate); } next( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getNextElement(current, predicate); } up( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getIterable(current, previousCurrent, predicate); } down( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getIterable(current, previousCurrent, predicate); } process( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, target: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean + predicate?: (element: GModelElement) => boolean ): void { - let elements: SModelElement[] = []; + let elements: GModelElement[] = []; // Mark only edges - if (target instanceof SEdge) { + if (target instanceof GEdge) { // If current is a edge, we have to check the source and target - if (current instanceof SEdge) { + if (current instanceof GEdge) { elements = this.getIterables(target, current.source === target.source ? current.source : current.target, predicate); } else { // Otherwise take the current as it is @@ -84,18 +85,18 @@ export class LocalElementNavigator implements ElementNavigator { elements.filter(e => e.id !== target.id).forEach(e => this.actionDispatcher.dispatch(applyCssClasses(e, this.navigableElementCSS))); } - clean(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void { + clean(root: Readonly, current?: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware): void { root.index.all().forEach(e => this.actionDispatcher.dispatch(deleteCssClasses(e, this.navigableElementCSS))); } protected getIterables( current: SelectableBoundsAware, - previousCurrent?: SModelElement & BoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement[] { - const elements: SModelElement[] = []; + previousCurrent?: GModelElement & BoundsAware, + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement[] { + const elements: GModelElement[] = []; - if (current instanceof SEdge) { + if (current instanceof GEdge) { if (current.target === previousCurrent) { current.target?.incomingEdges.forEach(e => elements.push(e)); } else { @@ -109,20 +110,20 @@ export class LocalElementNavigator implements ElementNavigator { protected getIterable( current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement | undefined { + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement | undefined { return this.getIterables(current, previousCurrent, predicate).filter(e => e.id !== current.id)[0]; } protected getNextElement( current: SelectableBoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement | undefined { - const elements: SModelElement[] = []; + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement | undefined { + const elements: GModelElement[] = []; - if (current instanceof SConnectableElement) { + if (current instanceof GConnectableElement) { current.outgoingEdges.forEach(e => elements.push(e)); - } else if (current instanceof SEdge) { - const target = current.target as SModelElement; + } else if (current instanceof GEdge) { + const target = current.target as GModelElement; elements.push(target); } @@ -131,14 +132,14 @@ export class LocalElementNavigator implements ElementNavigator { protected getPreviousElement( current: SelectableBoundsAware, - predicate: (element: SModelElement) => boolean = () => true - ): SModelElement | undefined { - const elements: SModelElement[] = []; + predicate: (element: GModelElement) => boolean = () => true + ): GModelElement | undefined { + const elements: GModelElement[] = []; - if (current instanceof SConnectableElement) { + if (current instanceof GConnectableElement) { current.incomingEdges.forEach(e => elements.push(e)); - } else if (current instanceof SEdge) { - const source = current.source as SModelElement; + } else if (current instanceof GEdge) { + const source = current.source as GModelElement; elements.push(source); } diff --git a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts index 022d94b..4866aed 100644 --- a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts @@ -14,63 +14,63 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isBoundsAware, isSelectable, SChildElement, SModelElement, SModelRoot, SNode, TYPES, Bounds, Point } from '~glsp-sprotty'; -import { ElementNavigator } from './element-navigator'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { toArray } from 'sprotty/lib/utils/iterable'; -import { SelectableBoundsAware } from '../../../utils/smodel-util'; +import { Bounds, GChildElement, Point, GModelElement, GModelRoot, GNode, TYPES, isBoundsAware, isSelectable } from '@eclipse-glsp/sprotty'; +import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { SelectableBoundsAware } from '../../../utils/gmodel-util'; +import { ElementNavigator } from './element-navigator'; @injectable() export class PositionNavigator implements ElementNavigator { @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; previous( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getNearestElement(root, current, e => this.bounds(root, e).x < this.bounds(root, current).x); } next( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getNearestElement(root, current, e => this.bounds(root, e).x > this.bounds(root, current).x); } up( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getNearestElement(root, current, e => this.bounds(root, e).y < this.bounds(root, current).y); } down( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, previousCurrent?: SelectableBoundsAware, - predicate?: (element: SModelElement) => boolean - ): SModelElement | undefined { + predicate?: (element: GModelElement) => boolean + ): GModelElement | undefined { return this.getNearestElement(root, current, e => this.bounds(root, e).y > this.bounds(root, current).y); } protected getNearestElement( - root: Readonly, + root: Readonly, current: SelectableBoundsAware, filter: (e: SelectableBoundsAware) => boolean - ): SModelElement | undefined { + ): GModelElement | undefined { const elements = this.boundElements(root).filter(filter); return this.sortByDistance(root, current, elements)[0]; } - protected sortByDistance(root: SModelRoot, current: SelectableBoundsAware, elements: SelectableBoundsAware[]): SelectableBoundsAware[] { + protected sortByDistance(root: GModelRoot, current: SelectableBoundsAware, elements: SelectableBoundsAware[]): SelectableBoundsAware[] { // https://www.tutorialspoint.com/sort-array-of-points-by-ascending-distance-from-a-given-point-javascript const distance = (coor1: Point, coor2: Point): number => { const x = coor2.x - coor1.x; @@ -84,16 +84,16 @@ export class PositionNavigator implements ElementNavigator { ); } - protected boundElements(root: Readonly): SelectableBoundsAware[] { - return toArray(root.index.all().filter(e => e instanceof SNode && isSelectable(e) && isBoundsAware(e))) as SelectableBoundsAware[]; + protected boundElements(root: Readonly): SelectableBoundsAware[] { + return toArray(root.index.all().filter(e => e instanceof GNode && isSelectable(e) && isBoundsAware(e))) as SelectableBoundsAware[]; } - protected bounds(root: Readonly, element: SelectableBoundsAware): Bounds { + protected bounds(root: Readonly, element: SelectableBoundsAware): Bounds { return this.boundsInViewport(element, element.bounds, root); } - protected boundsInViewport(element: SModelElement, bounds: Bounds, viewport: SModelRoot): Bounds { - if (element instanceof SChildElement && element.parent !== viewport) { + protected boundsInViewport(element: GModelElement, bounds: Bounds, viewport: GModelRoot): Bounds { + if (element instanceof GChildElement && element.parent !== viewport) { return this.boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds, viewport); } else { return bounds; diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts index 5a30818..b40db5c 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts @@ -16,7 +16,7 @@ import { ContainerModule } from 'inversify'; import { FocusTrackerTool } from './focus-tracker-tool'; -import { BindingContext, TYPES, bindAsService } from '~glsp-sprotty'; +import { BindingContext, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; /** * Handles actions for tracking the focus of the cursor. diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts index ed82cd4..c5b4ec6 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { TYPES, ViewerOptions } from '~glsp-sprotty'; +import { TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { Tool } from '../../../base/tool-manager/tool'; import * as messages from '../toast/messages.json'; diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index 50f1d7b..d79df24 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { SetUIExtensionVisibilityAction, Action } from '~glsp-sprotty'; +import { SetUIExtensionVisibilityAction, Action } from '@eclipse-glsp/sprotty'; import { Tool } from '../../base/tool-manager/tool'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { ToolPalette } from '../tool-palette/tool-palette'; diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts index acf1e73..d027934 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, matchesKeystroke } from '~glsp-sprotty'; +import { Action, KeyListener, KeyTool, GModelElement, SetUIExtensionVisibilityAction, matchesKeystroke } from '@eclipse-glsp/sprotty'; import { BaseEditTool } from '../../tools/base-tools'; import { KeyShortcutUIExtension } from './accessible-key-shortcut'; @@ -42,7 +42,7 @@ export class AccessibleKeyShortcutTool extends BaseEditTool { export class AccessibleShortcutKeyListener extends KeyListener { protected readonly token = Symbol(AccessibleShortcutKeyListener.name); - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesActivateShortcutHelpKeystroke(event)) { return [SetUIExtensionVisibilityAction.create({ extensionId: KeyShortcutUIExtension.ID, visible: true })]; } diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index 5e955c3..6aeaf6f 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -16,7 +16,7 @@ import { injectable } from 'inversify'; import { groupBy } from 'lodash'; -import { AbstractUIExtension, Action, IActionHandler, ICommand, matchesKeystroke, SModelRoot } from '~glsp-sprotty'; +import { AbstractUIExtension, Action, IActionHandler, ICommand, matchesKeystroke, GModelRoot } from '@eclipse-glsp/sprotty'; export interface AccessibleKeyShortcutProvider { registerShortcutKey(): void; @@ -70,7 +70,7 @@ export class KeyShortcutUIExtension extends AbstractUIExtension implements IActi return KeyShortcutUIExtension.ID; } - override show(root: Readonly, ...contextElementIds: string[]): void { + override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.shortcutsContainer.focus(); } diff --git a/packages/client/src/features/accessibility/key-shortcut/di.config.ts b/packages/client/src/features/accessibility/key-shortcut/di.config.ts index 3d93ca2..d92459e 100644 --- a/packages/client/src/features/accessibility/key-shortcut/di.config.ts +++ b/packages/client/src/features/accessibility/key-shortcut/di.config.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, configureActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import '../../../../css/key-shortcut.css'; import { KeyShortcutUIExtension, SetAccessibleKeyShortcutAction } from './accessible-key-shortcut'; import { AccessibleKeyShortcutTool } from './accessible-key-shortcut-tool'; diff --git a/packages/client/src/features/accessibility/keyboard-grid/action.ts b/packages/client/src/features/accessibility/keyboard-grid/action.ts index 364d804..ab6238a 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/action.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/action.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasObjectProp, Point } from '~glsp-sprotty'; +import { Action, hasObjectProp, Point } from '@eclipse-glsp/sprotty'; export interface EnableKeyboardGridAction extends Action { kind: typeof EnableKeyboardGridAction.KIND; diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts index cd383fb..cc66ac6 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid-search-palette.ts @@ -15,15 +15,16 @@ ********************************************************************************/ import '../../../../css/keyboard.css'; +import { Action, GModelElement, GModelRoot, SetUIExtensionVisibilityAction } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { SEdge, SetUIExtensionVisibilityAction, SModelElement, SModelRoot, Action } from '~glsp-sprotty'; -import { KeyboardGridMetadata } from './constants'; +import { IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; import { RevealEdgeElementAutocompleteSuggestionProvider, RevealNamedElementAutocompleteSuggestionProvider, SearchAutocompletePalette } from '../search/search-palette'; -import { IAutocompleteSuggestionProvider } from '../../../features/autocomplete-palette/autocomplete-suggestion-providers'; +import { KeyboardGridMetadata } from './constants'; +import { GEdge } from '../../../model'; export namespace GridSearchPaletteMetadata { export const ID = 'grid-search-palette'; @@ -35,13 +36,13 @@ export class GridSearchPalette extends SearchAutocompletePalette { return GridSearchPaletteMetadata.ID; } - protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + protected override getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { return [new GridRevealNamedElementSuggestionProvider(), new GridRevealEdgeSuggestionProvider()]; } } export class GridRevealEdgeSuggestionProvider extends RevealEdgeElementAutocompleteSuggestionProvider { - protected override getActions(edge: SEdge): Action[] { + protected override getActions(edge: GEdge): Action[] { return [ ...super.getActions(edge), SetUIExtensionVisibilityAction.create({ @@ -52,7 +53,7 @@ export class GridRevealEdgeSuggestionProvider extends RevealEdgeElementAutocompl } } export class GridRevealNamedElementSuggestionProvider extends RevealNamedElementAutocompleteSuggestionProvider { - protected override getActions(nameable: SModelElement): Action[] { + protected override getActions(nameable: GModelElement): Action[] { return [ ...super.getActions(nameable), SetUIExtensionVisibilityAction.create({ diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 897b589..bff7ab7 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -21,12 +21,12 @@ import { ActionDispatcher, IActionHandler, SetUIExtensionVisibilityAction, - SModelRoot, + GModelRoot, TYPES, Action, ICommand, Point -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { KeyboardGridMetadata } from './constants'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from './action'; @@ -105,7 +105,7 @@ export class KeyboardGrid extends AbstractUIExtension implements IActionHandler } } - override show(root: Readonly, ...contextElementIds: string[]): void { + override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.containerElement.focus(); } diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts index 568bded..24dd3c8 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts @@ -16,7 +16,7 @@ import '../../../../css/keyboard.css'; import { inject, injectable } from 'inversify'; -import { ICommand, SetUIExtensionVisibilityAction, Action } from '~glsp-sprotty'; +import { ICommand, SetUIExtensionVisibilityAction, Action } from '@eclipse-glsp/sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { KeyboardPointer } from '../keyboard-pointer/keyboard-pointer'; import { KeyboardPointerMetadata } from '../keyboard-pointer/constants'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/actions.ts b/packages/client/src/features/accessibility/keyboard-pointer/actions.ts index d1c1637..438af50 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/actions.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/actions.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, hasNumberProp } from '~glsp-sprotty'; +import { Action, hasNumberProp } from '@eclipse-glsp/sprotty'; export interface SetKeyboardPointerRenderPositionAction extends Action { kind: typeof SetKeyboardPointerRenderPositionAction.KIND; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts index e851b0e..33d6d0a 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionDispatcher, SetUIExtensionVisibilityAction, CreateNodeOperation } from '~glsp-sprotty'; +import { IActionDispatcher, SetUIExtensionVisibilityAction, CreateNodeOperation } from '@eclipse-glsp/sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; import { KeyboardPointerMetadata } from './constants'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index d249015..6d09dff 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -21,7 +21,7 @@ import { FeatureModule, BindingContext, TriggerEdgeCreationAction -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GlobalKeyListenerTool } from '../global-keylistener-tool'; import { KeyboardPointer } from './keyboard-pointer'; import { KeyboardGrid } from '../keyboard-grid/keyboard-grid'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts index bff421a..315be5f 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { findChildrenAtPosition, findParentByFeature, SModelElement, Point } from '~glsp-sprotty'; +import { findChildrenAtPosition, findParentByFeature, GModelElement, Point } from '@eclipse-glsp/sprotty'; import { Containable, isContainable } from '../../hints/model'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; import { KeyboardPointerMetadata } from './constants'; @@ -36,7 +36,7 @@ export class KeyboardPointerPosition { return getAbsolutePositionByPoint(this.keyboardPointer.editorContextService.modelRoot, this.centerizedRenderPosition); } - childrenAtDiagramPosition(): SModelElement[] { + childrenAtDiagramPosition(): GModelElement[] { const position = this.diagramPosition; return [ @@ -46,7 +46,7 @@ export class KeyboardPointerPosition { } containableParentAtDiagramPosition(elementTypeId: string): { - container: (SModelElement & Containable) | undefined; + container: (GModelElement & Containable) | undefined; status: CursorCSS; } { const children = this.childrenAtDiagramPosition(); @@ -62,9 +62,9 @@ export class KeyboardPointerPosition { } private containableParentOf( - target: SModelElement, + target: GModelElement, elementTypeId: string - ): { container: (SModelElement & Containable) | undefined; status: CursorCSS } { + ): { container: (GModelElement & Containable) | undefined; status: CursorCSS } { const container = findParentByFeature(target, isContainable); return { container, @@ -72,7 +72,7 @@ export class KeyboardPointerPosition { }; } - private isCreationAllowed(container: (SModelElement & Containable) | undefined, elementTypeId: string): boolean | undefined { + private isCreationAllowed(container: (GModelElement & Containable) | undefined, elementTypeId: string): boolean | undefined { return container && container.isContainableElement(elementTypeId); } } diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index 2af0f5c..1249fd9 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -18,11 +18,11 @@ import { AbstractUIExtension, IActionDispatcher, IActionHandler, - SModelRoot, + GModelRoot, TYPES, Action, TriggerNodeCreationAction -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { SetKeyboardPointerRenderPositionAction } from './actions'; import { KeyboardPointerMetadata } from './constants'; import { KeyboardPointerKeyboardListener } from './keyboard-pointer-listener'; @@ -79,7 +79,7 @@ export class KeyboardPointer extends AbstractUIExtension implements IActionHandl containerElement.style.borderRadius = '100%'; } - protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...selectedElementIds: string[]): void { + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...selectedElementIds: string[]): void { this.render(); } diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index 18535e4..6928566 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import '../../../../css/keyboard-tool-palette.css'; -import { configureActionHandler, TYPES, bindAsService, BindingContext, FeatureModule } from '~glsp-sprotty'; +import { configureActionHandler, TYPES, bindAsService, BindingContext, FeatureModule } from '@eclipse-glsp/sprotty'; import { EnableToolPaletteAction } from '../../tool-palette/tool-palette'; import { KeyboardToolPalette } from './keyboard-tool-palette'; import { FocusDomAction } from '../actions'; diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index cb900a0..93dda95 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -23,7 +23,7 @@ import { RequestMarkersAction, SetContextActions, TriggerNodeCreationAction -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index 60a74e7..e2bd353 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -23,15 +23,15 @@ import { IActionHandler, ICommand, Point, - SModelRoot, + GModelRoot, SetViewportAction, TYPES, Viewport, findParentByFeature, isViewport -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; /** * Action for triggering moving of the viewport. @@ -115,7 +115,7 @@ export class MoveViewportHandler implements IActionHandler { this.dispatcher.dispatch(this.moveViewport(viewport, action.moveX, action.moveY)); } - protected moveViewport(viewport: SModelRoot & Viewport, offsetX: number, offSetY: number): SetViewportAction { + protected moveViewport(viewport: GModelRoot & Viewport, offsetX: number, offSetY: number): SetViewportAction { const newViewport: Viewport = { scroll: { x: viewport.scroll.x + offsetX, @@ -159,7 +159,7 @@ export class MoveElementHandler implements IActionHandler { } protected adaptViewport( - viewport: SModelRoot & Viewport, + viewport: GModelRoot & Viewport, newPoint: Point, moveX: number, moveY: number @@ -191,7 +191,7 @@ export class MoveElementHandler implements IActionHandler { ]); } - protected move(viewport: SModelRoot & Viewport, selectedElements: SelectableBoundsAware[], deltaX: number, deltaY: number): Action[] { + protected move(viewport: GModelRoot & Viewport, selectedElements: SelectableBoundsAware[], deltaX: number, deltaY: number): Action[] { const results: Action[] = []; if (selectedElements.length !== 0) { diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index 7e989b0..c9ff29a 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, FeatureModule, configureActionHandler } from '~glsp-sprotty'; +import { BindingContext, FeatureModule, configureActionHandler } from '@eclipse-glsp/sprotty'; import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler'; import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler'; diff --git a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts index 9875022..f1bccc3 100644 --- a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts @@ -19,21 +19,21 @@ import { throttle } from 'lodash'; import { Action, Bounds, + GChildElement, IActionDispatcher, IActionHandler, ICommand, Point, - SChildElement, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, SetViewportAction, TYPES, Viewport, findParentByFeature, isViewport -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; /** * Action for triggering zooming of the viewport. @@ -112,7 +112,7 @@ export class ZoomViewportHandler implements IActionHandler { this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor)); } - protected setNewZoomFactor(viewport: SModelRoot & Viewport, zoomFactor: number): SetViewportAction { + protected setNewZoomFactor(viewport: GModelRoot & Viewport, zoomFactor: number): SetViewportAction { const newZoom = viewport.zoom * zoomFactor; const newViewport = { @@ -150,7 +150,7 @@ export class ZoomElementHandler implements IActionHandler { this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor, center)); } - protected getCenter(viewport: SModelRoot & Viewport, selectedElements: SelectableBoundsAware[]): Point { + protected getCenter(viewport: GModelRoot & Viewport, selectedElements: SelectableBoundsAware[]): Point { // Get bounds of elements based on the viewport const allBounds = selectedElements.map(e => this.boundsInViewport(viewport, e, e.bounds)); const mergedBounds = allBounds.reduce((b0, b1) => Bounds.combine(b0, b1)); @@ -158,15 +158,15 @@ export class ZoomElementHandler implements IActionHandler { } // copy from center-fit.ts, translates the children bounds to the viewport bounds - protected boundsInViewport(viewport: SModelRoot & Viewport, element: SModelElement, bounds: Bounds): Bounds { - if (element instanceof SChildElement && element.parent !== viewport) { + protected boundsInViewport(viewport: GModelRoot & Viewport, element: GModelElement, bounds: Bounds): Bounds { + if (element instanceof GChildElement && element.parent !== viewport) { return this.boundsInViewport(viewport, element.parent, element.parent.localToParent(bounds) as Bounds); } else { return bounds; } } - protected setNewZoomFactor(viewport: SModelRoot & Viewport, zoomFactor: number, point: Point): SetViewportAction { + protected setNewZoomFactor(viewport: GModelRoot & Viewport, zoomFactor: number, point: Point): SetViewportAction { const newZoom = viewport.zoom * zoomFactor; const newViewport = { diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index 7869341..668eabc 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -24,13 +24,13 @@ import { ICommand, ISnapper, Point, - SModelElement, - SParentElement, + GModelElement, + GParentElement, TYPES -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextService } from '../../../base/editor-context-service'; import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/smodel-util'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; import { Resizable } from '../../change-bounds/model'; import { GridSnapper } from '../../change-bounds/snap'; @@ -115,7 +115,7 @@ export class ResizeElementHandler implements IActionHandler { } if (this.isValidBoundChange(element, { x, y }, { width, height })) { - const resizeElement = { id: element.id, bounds: { x, y, width, height } } as SModelElement & SParentElement & Resizable; + const resizeElement = { id: element.id, bounds: { x, y, width, height } } as GModelElement & GParentElement & Resizable; actions.push(ChangeBoundsOperation.create([toElementAndBounds(resizeElement)])); } }); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index c0c4878..41e3c6b 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, FeatureModule, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { BindingContext, FeatureModule, TYPES, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; import { ResizeElementAction, ResizeElementHandler } from './resize-key-handler'; import { ResizeKeyTool } from './resize-key-tool'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index 7ee776d..6e62c54 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -16,7 +16,7 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { Action, ISnapper, KeyListener, KeyTool, GModelElement, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; @@ -76,7 +76,7 @@ export class ResizeKeyListener extends KeyListener implements AccessibleKeyShort ); } - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { const actions = []; const selectedElementsIds = this.tool.selectionService.getSelectedElementIDs(); diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 98eb429..371d2b0 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext, FeatureModule, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index b9078c7..17f84e3 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -21,27 +21,27 @@ import { Action, CenterAction, LabeledAction, - SEdge, - SModelElement, - SModelRoot, - SNode, + GModelElement, + GModelRoot, + GNode, SelectAction, SelectAllAction, codiconCSSString, isNameable, name -} from '~glsp-sprotty'; -import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../autocomplete-palette/autocomplete-suggestion-providers'; -import { BaseAutocompletePalette } from '../../autocomplete-palette/base-autocomplete-palette'; +} from '@eclipse-glsp/sprotty'; +import { BaseAutocompletePalette } from '../../../base/auto-complete/base-autocomplete-palette'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { RepositionAction } from '../../../features/viewport/reposition'; +import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; +import { GEdge } from '../../../model'; const CSS_SEARCH_HIDDEN = 'search-hidden'; const CSS_SEARCH_HIGHLIGHTED = 'search-highlighted'; export class RevealNamedElementAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { - async retrieveSuggestions(root: Readonly, text: string): Promise { + async retrieveSuggestions(root: Readonly, text: string): Promise { const nameables = toArray(root.index.all().filter(element => isNameable(element))); return nameables.map(nameable => ({ element: nameable, @@ -53,13 +53,13 @@ export class RevealNamedElementAutocompleteSuggestionProvider implements IAutoco })); } - protected getActions(nameable: SModelElement): Action[] { + protected getActions(nameable: GModelElement): Action[] { return [SelectAction.create({ selectedElementsIDs: [nameable.id] }), CenterAction.create([nameable.id], { retainZoom: true })]; } } export class RevealNodesWithoutNameAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { - async retrieveSuggestions(root: Readonly, text: string): Promise { - const nodes = toArray(root.index.all().filter(element => !isNameable(element) && element instanceof SNode)); + async retrieveSuggestions(root: Readonly, text: string): Promise { + const nodes = toArray(root.index.all().filter(element => !isNameable(element) && element instanceof GNode)); return nodes.map(node => ({ element: node, action: { @@ -70,14 +70,14 @@ export class RevealNodesWithoutNameAutocompleteSuggestionProvider implements IAu })); } - protected getActions(nameable: SModelElement): Action[] { + protected getActions(nameable: GModelElement): Action[] { return [SelectAction.create({ selectedElementsIDs: [nameable.id] }), CenterAction.create([nameable.id], { retainZoom: true })]; } } export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocompleteSuggestionProvider { - async retrieveSuggestions(root: Readonly, text: string): Promise { - const edges = toArray(root.index.all().filter(element => element instanceof SEdge)) as SEdge[]; + async retrieveSuggestions(root: Readonly, text: string): Promise { + const edges = toArray(root.index.all().filter(element => element instanceof GEdge)) as GEdge[]; return edges.map(edge => ({ element: edge, action: { @@ -88,10 +88,10 @@ export class RevealEdgeElementAutocompleteSuggestionProvider implements IAutocom })); } - protected getActions(edge: SEdge): Action[] { + protected getActions(edge: GEdge): Action[] { return [SelectAction.create({ selectedElementsIDs: [edge.id] }), CenterAction.create([edge.sourceId, edge.targetId])]; } - protected getEdgeLabel(root: Readonly, edge: SEdge): string { + protected getEdgeLabel(root: Readonly, edge: GEdge): string { let sourceName = ''; let targetName = ''; @@ -124,14 +124,14 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { this.autocompleteWidget.inputField.placeholder = 'Search for elements'; containerElement.setAttribute('aria-label', 'Search Field'); } - protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { + protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { return [ new RevealNamedElementAutocompleteSuggestionProvider(), new RevealEdgeElementAutocompleteSuggestionProvider(), new RevealNodesWithoutNameAutocompleteSuggestionProvider() ]; } - protected async retrieveSuggestions(root: Readonly, input: string): Promise { + protected async retrieveSuggestions(root: Readonly, input: string): Promise { const providers = this.getSuggestionProviders(root, input); const suggestions = (await Promise.all(providers.map(provider => provider.retrieveSuggestions(root, input)))).flat(1); @@ -140,7 +140,7 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { return suggestions.map(s => s.action); } - protected override async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { + protected override async visibleSuggestionsChanged(root: Readonly, labeledActions: LabeledAction[]): Promise { await this.applyCSS(this.getHiddenElements(root, this.getSuggestionsFromLabeledActions(labeledActions)), CSS_SEARCH_HIDDEN); await this.deleteCSS( this.getSuggestionsFromLabeledActions(labeledActions).map(s => s.element), @@ -149,7 +149,7 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { } protected override async selectedSuggestionChanged( - root: Readonly, + root: Readonly, labeledAction?: LabeledAction | undefined ): Promise { await this.deleteAllCSS(root, CSS_SEARCH_HIGHLIGHTED); @@ -167,7 +167,7 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { } } - public override show(root: Readonly, ...contextElementIds: string[]): void { + public override show(root: Readonly, ...contextElementIds: string[]): void { this.actionDispatcher.dispatch(SelectAllAction.create(false)); super.show(root, ...contextElementIds); @@ -183,17 +183,17 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { super.hide(); } - protected applyCSS(elements: SModelElement[], cssClass: string): Promise { + protected applyCSS(elements: GModelElement[], cssClass: string): Promise { const actions = elements.map(element => applyCssClasses(element, cssClass)); return this.actionDispatcher.dispatchAll(actions); } - protected deleteCSS(elements: SModelElement[], cssClass: string): Promise { + protected deleteCSS(elements: GModelElement[], cssClass: string): Promise { const actions = elements.map(element => deleteCssClasses(element, cssClass)); return this.actionDispatcher.dispatchAll(actions); } - protected deleteAllCSS(root: Readonly, cssClass: string): Promise { + protected deleteAllCSS(root: Readonly, cssClass: string): Promise { const actions = toArray(root.index.all().map(element => deleteCssClasses(element, cssClass))); return this.actionDispatcher.dispatchAll(actions); } @@ -206,11 +206,11 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { return this.cachedSuggestions.filter(c => !labeledActions.find(s => isEqual(s, c.action))); } - protected getHiddenElements(root: Readonly, suggestions: AutocompleteSuggestion[]): SModelElement[] { + protected getHiddenElements(root: Readonly, suggestions: AutocompleteSuggestion[]): GModelElement[] { return toArray( root.index .all() - .filter(element => element instanceof SNode || element instanceof SEdge) + .filter(element => element instanceof GNode || element instanceof GEdge) .filter(element => suggestions.find(suggestion => suggestion.element.id === element.id) === undefined) ); } diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index b295d30..389c9d8 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -16,7 +16,7 @@ import { inject, injectable } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, KeyListener, KeyTool, SModelElement, SetUIExtensionVisibilityAction, TYPES } from '~glsp-sprotty'; +import { Action, KeyListener, KeyTool, GModelElement, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { Tool } from '../../../base/tool-manager/tool'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; @@ -59,7 +59,7 @@ export class SearchAutocompletePaletteKeyListener extends KeyListener implements ); } - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesSearchActivateKeystroke(event)) { return [ SetUIExtensionVisibilityAction.create({ diff --git a/packages/client/src/features/accessibility/toast/toast-handler.ts b/packages/client/src/features/accessibility/toast/toast-handler.ts index 8547529..66a882f 100644 --- a/packages/client/src/features/accessibility/toast/toast-handler.ts +++ b/packages/client/src/features/accessibility/toast/toast-handler.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '~glsp-sprotty'; +import { Action } from '@eclipse-glsp/sprotty'; export interface HideToastAction extends Action { kind: typeof HideToastAction.KIND; diff --git a/packages/client/src/features/accessibility/toast/toast-module.ts b/packages/client/src/features/accessibility/toast/toast-module.ts index 932a333..56db1a3 100644 --- a/packages/client/src/features/accessibility/toast/toast-module.ts +++ b/packages/client/src/features/accessibility/toast/toast-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, configureActionHandler, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, configureActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import '../../../../css/toast.css'; import { HideToastAction, ShowToastMessageAction } from './toast-handler'; import { Toast } from './toast-tool'; diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 1c85d73..9ac4451 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable, inject } from 'inversify'; -import { Action, AbstractUIExtension, IActionHandler, ICommand, TYPES } from '~glsp-sprotty'; +import { Action, AbstractUIExtension, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; import { IDiagramStartup } from '../../../base/model/diagram-loader'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { EditorContextService } from '../../../base/editor-context-service'; diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index 2bf9424..69b0293 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -21,12 +21,12 @@ import { Action, KeyListener, KeyTool, - SModelElement, - SRoutableElement, + GModelElement, + GRoutableElement, SelectAction, SwitchEditModeAction, isSelectable -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { Tool } from '../../../base/tool-manager/tool'; import { SResizeHandle } from '../../change-bounds/model'; @@ -57,7 +57,7 @@ export class DeselectKeyTool implements Tool { } export class DeselectKeyListener extends KeyListener { - override keyDown(target: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(target: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesDeselectKeystroke(event)) { const isResizeHandleActive = toArray(target.root.index.all().filter(el => el instanceof SResizeHandle)).length > 0; @@ -72,7 +72,7 @@ export class DeselectKeyListener extends KeyListener { actions.push(SelectAction.create({ deselectedElementsIDs: deselect.map(e => e.id) })); } - const routableDeselect = deselect.filter(e => e instanceof SRoutableElement).map(e => e.id); + const routableDeselect = deselect.filter(e => e instanceof GRoutableElement).map(e => e.id); if (routableDeselect.length > 0) { actions.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); } diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index c41389f..b1dc22e 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -16,7 +16,7 @@ import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, ISnapper, KeyListener, KeyTool, SModelElement, TYPES } from '~glsp-sprotty'; +import { Action, ISnapper, KeyListener, KeyTool, GModelElement, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; @@ -82,7 +82,7 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu ); } - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); if (selectedElementIds.length > 0) { diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 8bb8b7b..f9811ab 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; +import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { DeselectKeyTool } from './deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index ea4b3c3..ba8006c 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -22,13 +22,13 @@ import { KeyListener, KeyTool, Point, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, SetViewportAction, TYPES, Viewport, isViewport -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; @@ -125,7 +125,7 @@ export class ZoomKeyListener extends KeyListener { ); } - setNewZoomFactor(viewport: SModelElement & SModelRoot & Viewport, zoomFactor: number, point?: Point): SetViewportAction { + setNewZoomFactor(viewport: GModelElement & GModelRoot & Viewport, zoomFactor: number, point?: Point): SetViewportAction { let newViewport: Viewport; const newZoom = viewport.zoom * zoomFactor; @@ -146,7 +146,7 @@ export class ZoomKeyListener extends KeyListener { return SetViewportAction.create(viewport.id, newViewport, { animate: true }); } - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); if (this.matchesZoomViaGrid(event)) { diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index beb18fe..0b0d817 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -25,7 +25,7 @@ import { bindAsService, configureCommand, configureLayout -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; diff --git a/packages/client/src/features/bounds/freeform-layout.spec.ts b/packages/client/src/features/bounds/freeform-layout.spec.ts index 67e452b..5c37cd1 100644 --- a/packages/client/src/features/bounds/freeform-layout.spec.ts +++ b/packages/client/src/features/bounds/freeform-layout.spec.ts @@ -16,13 +16,13 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, SModelElement } from '~glsp-sprotty'; -import { gModel, layout, sComp, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, GModelElement } from '@eclipse-glsp/sprotty'; +import { createCompartment, createGraph, createLabel, createNode, layout, setupLayoutRegistry } from './layouter-test-util.spec'; describe('FreeFormLayouter', () => { const layoutRegistry = setupLayoutRegistry(); const log = new ConsoleLogger(); - const map = new Map(); + const map = new Map(); describe('issue-610', () => { it('recursive hGrab/vGrab', () => { @@ -42,19 +42,19 @@ describe('FreeFormLayouter', () => { * This test case checks recursive hGrab/vGrab functionality of nested compartments * See also issue 610 for details and an example model https://github.com/eclipse-glsp/glsp/issues/610. */ - const model = gModel(); - const category = sNode('category', 'vbox', { width: 410.0, height: 215.0 }, undefined, { + const model = createGraph(); + const category = createNode('category', 'vbox', { width: 410.0, height: 215.0 }, undefined, { vGrab: false, hGrab: false, hAlign: 'center', prefWidth: 410.0, prefHeight: 215.0 }); - const headerComp = sComp('comp:header', 'hbox'); - headerComp.children = [sLabel('Category 1')]; - const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); - const labelContainer = sComp('struct', 'hbox', { hGrab: true }); - labelContainer.children = [sLabel('left text', { hGrab: true }), sLabel('right text')]; + const headerComp = createCompartment('comp:header', 'hbox'); + headerComp.children = [createLabel('Category 1')]; + const childContainer = createCompartment('struct', 'freeform', { hGrab: true, vGrab: true }); + const labelContainer = createCompartment('struct', 'hbox', { hGrab: true }); + labelContainer.children = [createLabel('left text', { hGrab: true }), createLabel('right text')]; category.children = [headerComp, childContainer, labelContainer]; model.add(category); @@ -85,22 +85,22 @@ describe('FreeFormLayouter', () => { * ||_left____________right_|| * |_________________________| */ - const model = gModel(); - const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + const model = createGraph(); + const category = createNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { vGrab: false, hGrab: false, hAlign: 'center', prefWidth: 500.0, prefHeight: 375.0 }); - const headerComp = sComp('comp:header', 'hbox'); - headerComp.children = [sLabel('Category 2')]; - const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); - const childNode = sNode('task', 'hbox', undefined, { x: 170, y: 190 }); - childNode.children = [sLabel('Task node', undefined, { x: 5.0, y: 5.0 }, { width: 25.0, height: 20.0 })]; + const headerComp = createCompartment('comp:header', 'hbox'); + headerComp.children = [createLabel('Category 2')]; + const childContainer = createCompartment('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = createNode('task', 'hbox', undefined, { x: 170, y: 190 }); + childNode.children = [createLabel('Task node', undefined, { x: 5.0, y: 5.0 }, { width: 25.0, height: 20.0 })]; childContainer.children = [childNode]; - const labelContainer = sComp('struct', 'hbox', { hGrab: true }); - labelContainer.children = [sLabel('left text', { hGrab: true }), sLabel('right text')]; + const labelContainer = createCompartment('struct', 'hbox', { hGrab: true }); + labelContainer.children = [createLabel('left text', { hGrab: true }), createLabel('right text')]; category.children = [headerComp, childContainer, labelContainer]; model.add(category); @@ -131,19 +131,19 @@ describe('FreeFormLayouter', () => { * ||_______________________|| * |_________________________| */ - const model = gModel(); - const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + const model = createGraph(); + const category = createNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { vGrab: false, hGrab: false, hAlign: 'center', prefWidth: 500.0, prefHeight: 375.0 }); - const headerComp = sComp('comp:header', 'hbox'); - headerComp.children = [sLabel('Category 2')]; - const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); - const childNode = sNode('task', 'hbox', undefined, { x: 55, y: 15 }); - childNode.children = [sLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; + const headerComp = createCompartment('comp:header', 'hbox'); + headerComp.children = [createLabel('Category 2')]; + const childContainer = createCompartment('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = createNode('task', 'hbox', undefined, { x: 55, y: 15 }); + childNode.children = [createLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; childContainer.children = [childNode]; category.children = [headerComp, childContainer]; @@ -173,8 +173,8 @@ describe('FreeFormLayouter', () => { * ||_______________________|| * |_________________________| */ - const model = gModel(); - const category = sNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { + const model = createGraph(); + const category = createNode('category', 'vbox', { width: 500.0, height: 375.0 }, undefined, { vGrab: false, hGrab: false, hAlign: 'center', @@ -185,17 +185,17 @@ describe('FreeFormLayouter', () => { paddingTop: 10, paddingBottom: 10 }); - const headerComp = sComp('comp:header', 'hbox'); - headerComp.children = [sLabel('Category 2')]; - const childContainer = sComp('struct', 'freeform', { hGrab: true, vGrab: true }); - const childNode = sNode( + const headerComp = createCompartment('comp:header', 'hbox'); + headerComp.children = [createLabel('Category 2')]; + const childContainer = createCompartment('struct', 'freeform', { hGrab: true, vGrab: true }); + const childNode = createNode( 'task', 'hbox', undefined, { x: 55, y: 15 }, { paddingLeft: 10, paddingRight: 10, paddingTop: 10, paddingBottom: 10 } ); - childNode.children = [sLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; + childNode.children = [createLabel('Task node', undefined, undefined, { width: 50.0, height: 35.0 })]; childContainer.children = [childNode]; category.children = [headerComp, childContainer]; diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index a4cf0ff..8f6b684 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -20,12 +20,12 @@ import { Bounds, BoundsData, Dimension, + GChildElement, LayoutContainer, Point, - SChildElement, - SParentElement, + GParentElement, StatefulLayouter -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; /** * Layouts children of a container with explicit X/Y positions @@ -34,7 +34,7 @@ import { export class FreeFormLayouter extends AbstractLayout { static KIND = 'freeform'; - override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + override layout(container: GParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -55,7 +55,7 @@ export class FreeFormLayouter extends AbstractLayout { } protected getChildrenSize( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, containerOptions: AbstractLayoutOptions, layouter: StatefulLayouter ): Dimension { @@ -77,7 +77,7 @@ export class FreeFormLayouter extends AbstractLayout { } protected layoutChild( - child: SChildElement, + child: GChildElement, boundsData: BoundsData, bounds: Bounds, childOptions: AbstractLayoutOptions, @@ -97,7 +97,7 @@ export class FreeFormLayouter extends AbstractLayout { } protected override getFinalContainerBounds( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, lastOffset: Point, options: AbstractLayoutOptions, maxWidth: number, diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 52a42d2..5e90fa5 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -26,10 +26,10 @@ import { IActionDispatcher, RequestAction, ResponseAction, - SModelElement, - SRoutableElement -} from '~glsp-sprotty'; -import { calcElementAndRoute, isRoutable } from '../../utils/smodel-util'; + GModelElement, + GRoutableElement +} from '@eclipse-glsp/sprotty'; +import { calcElementAndRoute, isRoutable } from '../../utils/gmodel-util'; /** * Grabs the bounds from hidden SVG DOM elements, applies layouts, collects routes and fires {@link ComputedBoundsAction}s. @@ -41,10 +41,10 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { @inject(EdgeRouterRegistry) @optional() protected readonly edgeRouterRegistry?: EdgeRouterRegistry; protected element2route: ElementAndRoutingPoints[] = []; - protected edges: SRoutableElement[] = []; + protected edges: GRoutableElement[] = []; protected nodes: VNode[] = []; - override decorate(vnode: VNode, element: SModelElement): VNode { + override decorate(vnode: VNode, element: GModelElement): VNode { super.decorate(vnode, element); if (isRoutable(element)) { this.element2route.push(calcElementAndRoute(element, this.edgeRouterRegistry)); diff --git a/packages/client/src/features/bounds/hbox-layout.spec.ts b/packages/client/src/features/bounds/hbox-layout.spec.ts index dddca5e..b1a2679 100644 --- a/packages/client/src/features/bounds/hbox-layout.spec.ts +++ b/packages/client/src/features/bounds/hbox-layout.spec.ts @@ -17,20 +17,20 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, Dimension, SModelElement, SNode } from '~glsp-sprotty'; -import { layout, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, Dimension, GModelElement, GNode } from '@eclipse-glsp/sprotty'; +import { createLabel, createNode, layout, setupLayoutRegistry } from './layouter-test-util.spec'; describe('HBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); const log = new ConsoleLogger(); - const map = new Map(); + const map = new Map(); - function createModel(): SNode { - const model = sNode('node', undefined, Dimension.EMPTY); + function createModel(): GNode { + const model = createNode('node', undefined, Dimension.EMPTY); model.children = [ - sLabel('label1', undefined, undefined, { width: 1, height: 2 }), - sLabel('label2', undefined, undefined, { width: 2, height: 1 }), - sLabel('label3', undefined, undefined, { width: 3, height: 3 }) + createLabel('label1', undefined, undefined, { width: 1, height: 2 }), + createLabel('label2', undefined, undefined, { width: 2, height: 1 }), + createLabel('label3', undefined, undefined, { width: 3, height: 3 }) ]; model.layout = 'hbox'; return model; diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index e60c5da..4028037 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -18,17 +18,17 @@ import { Bounds, BoundsData, Dimension, + GChildElement, HBoxLayoutOptions, HBoxLayouter, LayoutContainer, Point, - SChildElement, - SModelElement, - SParentElement, + GModelElement, + GParentElement, StatefulLayouter, isBoundsAware, isLayoutableChild -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; export interface HBoxLayoutOptionsExt extends HBoxLayoutOptions { hGrab: boolean; @@ -44,7 +44,7 @@ export interface HBoxLayoutOptionsExt extends HBoxLayoutOptions { export class HBoxLayouterExt extends HBoxLayouter { static override KIND = HBoxLayouter.KIND; - override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + override layout(container: GParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -85,7 +85,7 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override getChildrenSize( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, containerOptions: HBoxLayoutOptionsExt, layouter: StatefulLayouter ): Dimension { @@ -114,7 +114,7 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override layoutChildren( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, layouter: StatefulLayouter, containerOptions: HBoxLayoutOptionsExt, maxWidth: number, @@ -152,7 +152,7 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override layoutChild( - child: SChildElement, + child: GChildElement, boundsData: BoundsData, bounds: Bounds, childOptions: HBoxLayoutOptionsExt, @@ -195,7 +195,7 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override getFixedContainerBounds( - container: SModelElement, + container: GModelElement, layoutOptions: HBoxLayoutOptionsExt, layouter: StatefulLayouter ): Bounds { @@ -211,20 +211,20 @@ export class HBoxLayouterExt extends HBoxLayouter { return Bounds.EMPTY; } - protected override getChildLayoutOptions(child: SChildElement, containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { + protected override getChildLayoutOptions(child: GChildElement, containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as HBoxLayoutOptionsExt; } - protected override getLayoutOptions(element: SModelElement): HBoxLayoutOptionsExt { + protected override getLayoutOptions(element: GModelElement): HBoxLayoutOptionsExt { return super.getLayoutOptions(element) as HBoxLayoutOptionsExt; } - protected getElementLayoutOptions(element: SModelElement): HBoxLayoutOptionsExt | undefined { + protected getElementLayoutOptions(element: GModelElement): HBoxLayoutOptionsExt | undefined { return (element as any).layoutOptions; } protected override getFinalContainerBounds( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, lastOffset: Point, options: HBoxLayoutOptionsExt, maxWidth: number, diff --git a/packages/client/src/features/bounds/layouter-test-util.spec.ts b/packages/client/src/features/bounds/layouter-test-util.spec.ts index 899987a..128c2f5 100644 --- a/packages/client/src/features/bounds/layouter-test-util.spec.ts +++ b/packages/client/src/features/bounds/layouter-test-util.spec.ts @@ -22,32 +22,32 @@ import { Dimension, LayoutRegistry, Point, - SCompartment, - SLabel, - SModelElement, - SNode, - SParentElement, + GCompartment, + GLabel, + GModelElement, + GNode, + GParentElement, TYPES, createFeatureSet, layoutableChildFeature -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { initializeDiagramContainer } from '../../default-modules'; -import { GLSPGraph } from '../../lib/model'; import { StatefulLayouterExt } from './layouter'; +import { GGraph } from '../../model'; -export function gModel(): GLSPGraph { - return new GLSPGraph(); +export function createGraph(): GGraph { + return new GGraph(); } -export function sNode( +export function createNode( type: string, nodeLayout?: string, size?: Dimension, position?: Point, layoutOptions?: { [key: string]: string | number | boolean } -): SNode { - const node = new SNode(); - node.features = createFeatureSet(SNode.DEFAULT_FEATURES, { enable: [layoutableChildFeature] }); +): GNode { + const node = new GNode(); + node.features = createFeatureSet(GNode.DEFAULT_FEATURES, { enable: [layoutableChildFeature] }); node.position = position || { x: 0, y: 0 @@ -63,23 +63,27 @@ export function sNode( return node; } -export function sComp(type: string, compLayout: string, layoutOptions?: { [key: string]: string | number | boolean }): SCompartment { - const comp = new SCompartment(); - comp.features = createFeatureSet(SCompartment.DEFAULT_FEATURES); +export function createCompartment( + type: string, + compLayout: string, + layoutOptions?: { [key: string]: string | number | boolean } +): GCompartment { + const comp = new GCompartment(); + comp.features = createFeatureSet(GCompartment.DEFAULT_FEATURES); comp.type = type; comp.layout = compLayout; comp.layoutOptions = layoutOptions; return comp; } -export function sLabel( +export function createLabel( labelText: string, layoutOptions?: { [key: string]: string | number | boolean }, position?: Point, size?: Dimension -): SLabel { - const label = new SLabel(); - label.features = createFeatureSet(SLabel.DEFAULT_FEATURES); +): GLabel { + const label = new GLabel(); + label.features = createFeatureSet(GLabel.DEFAULT_FEATURES); if (position) { label.position = position; } @@ -92,13 +96,13 @@ export function sLabel( return label; } -export function addToMap(map: Map, element: SModelElement): void { +export function addToMap(map: Map, element: GModelElement): void { map.set(element, { bounds: (element as any).bounds, boundsChanged: true, alignmentChanged: true }); - if (element instanceof SParentElement) { + if (element instanceof GParentElement) { element.children.forEach(c => addToMap(map, c)); } } @@ -106,8 +110,8 @@ export function addToMap(map: Map, element: SModelEle export function layout( layoutRegistry: LayoutRegistry, log: ConsoleLogger, - map: Map, - model: SNode | GLSPGraph + map: Map, + model: GNode | GGraph ): void { map.clear(); addToMap(map, model); diff --git a/packages/client/src/features/bounds/layouter.ts b/packages/client/src/features/bounds/layouter.ts index 98f9d74..829a568 100644 --- a/packages/client/src/features/bounds/layouter.ts +++ b/packages/client/src/features/bounds/layouter.ts @@ -21,15 +21,15 @@ import { LayoutContainer, LayoutRegistry, Layouter, - SModelElement, - SParentElement, + GModelElement, + GParentElement, StatefulLayouter, isLayoutContainer -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; @injectable() export class LayouterExt extends Layouter { - override layout(element2boundsData: Map): void { + override layout(element2boundsData: Map): void { new StatefulLayouterExt(element2boundsData, this.layoutRegistry, this.logger).layout(); } } @@ -42,7 +42,7 @@ export class LayouterExt extends Layouter { // Parent-to-children layout export class StatefulLayouterExt extends StatefulLayouter { - protected toBeLayouted2: (SParentElement & LayoutContainer)[]; + protected toBeLayouted2: (GParentElement & LayoutContainer)[]; /** * @@ -52,7 +52,7 @@ export class StatefulLayouterExt extends StatefulLayouter { * @param log The log. */ constructor( - protected readonly elementToBoundsData: Map, + protected readonly elementToBoundsData: Map, protected readonly layoutRegistry2: LayoutRegistry, log: ILogger ) { @@ -69,7 +69,7 @@ export class StatefulLayouterExt extends StatefulLayouter { } } - override getBoundsData(element: SModelElement): BoundsData { + override getBoundsData(element: GModelElement): BoundsData { let boundsData = this.elementToBoundsData.get(element); let bounds = (element as any).bounds; if (isLayoutContainer(element) && this.toBeLayouted2.indexOf(element) >= 0) { @@ -117,7 +117,7 @@ export class StatefulLayouterExt extends StatefulLayouter { } } - protected override doLayout(element: SParentElement & LayoutContainer): Bounds { + protected override doLayout(element: GParentElement & LayoutContainer): Bounds { const index = this.toBeLayouted2.indexOf(element); if (index >= 0) { this.toBeLayouted2.splice(index, 1); diff --git a/packages/client/src/features/bounds/vbox-layout.spec.ts b/packages/client/src/features/bounds/vbox-layout.spec.ts index 17d5bec..a289d0e 100644 --- a/packages/client/src/features/bounds/vbox-layout.spec.ts +++ b/packages/client/src/features/bounds/vbox-layout.spec.ts @@ -17,20 +17,20 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { BoundsData, ConsoleLogger, Dimension, SModelElement, SNode } from '~glsp-sprotty'; -import { layout, sLabel, sNode, setupLayoutRegistry } from './layouter-test-util.spec'; +import { BoundsData, ConsoleLogger, Dimension, GModelElement, GNode } from '@eclipse-glsp/sprotty'; +import { createLabel, createNode, layout, setupLayoutRegistry } from './layouter-test-util.spec'; describe('VBoxLayouter', () => { const layoutRegistry = setupLayoutRegistry(); const log = new ConsoleLogger(); - const map = new Map(); + const map = new Map(); - function createModel(): SNode { - const model = sNode('node', undefined, Dimension.EMPTY); + function createModel(): GNode { + const model = createNode('node', undefined, Dimension.EMPTY); model.children = [ - sLabel('label1', undefined, undefined, { width: 1, height: 2 }), - sLabel('label2', undefined, undefined, { width: 2, height: 1 }), - sLabel('label3', undefined, undefined, { width: 3, height: 3 }) + createLabel('label1', undefined, undefined, { width: 1, height: 2 }), + createLabel('label2', undefined, undefined, { width: 2, height: 1 }), + createLabel('label3', undefined, undefined, { width: 3, height: 3 }) ]; model.layout = 'vbox'; return model; diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 483dda0..95f59a6 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -18,17 +18,17 @@ import { Bounds, BoundsData, Dimension, + GChildElement, LayoutContainer, Point, - SChildElement, - SModelElement, - SParentElement, + GModelElement, + GParentElement, StatefulLayouter, VBoxLayoutOptions, VBoxLayouter, isBoundsAware, isLayoutableChild -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { hGrab: boolean; @@ -44,7 +44,7 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { export class VBoxLayouterExt extends VBoxLayouter { static override KIND = VBoxLayouter.KIND; - override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + override layout(container: GParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -85,7 +85,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override getChildrenSize( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, containerOptions: VBoxLayoutOptionsExt, layouter: StatefulLayouter ): Dimension { @@ -114,7 +114,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override layoutChildren( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, layouter: StatefulLayouter, containerOptions: VBoxLayoutOptionsExt, maxWidth: number, @@ -152,7 +152,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override layoutChild( - child: SChildElement, + child: GChildElement, boundsData: BoundsData, bounds: Bounds, childOptions: VBoxLayoutOptionsExt, @@ -195,7 +195,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override getFixedContainerBounds( - container: SModelElement, + container: GModelElement, layoutOptions: VBoxLayoutOptionsExt, layouter: StatefulLayouter ): Bounds { @@ -211,20 +211,20 @@ export class VBoxLayouterExt extends VBoxLayouter { return Bounds.EMPTY; } - protected override getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + protected override getChildLayoutOptions(child: GChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as VBoxLayoutOptionsExt; } - protected override getLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt { + protected override getLayoutOptions(element: GModelElement): VBoxLayoutOptionsExt { return super.getLayoutOptions(element) as VBoxLayoutOptionsExt; } - protected getElementLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt | undefined { + protected getElementLayoutOptions(element: GModelElement): VBoxLayoutOptionsExt | undefined { return (element as any).layoutOptions; } protected override getFinalContainerBounds( - container: SParentElement & LayoutContainer, + container: GParentElement & LayoutContainer, lastOffset: Point, options: VBoxLayoutOptionsExt, maxWidth: number, diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index cb0181a..9f679a8 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -15,24 +15,24 @@ ********************************************************************************/ import { BoundsAware, + GChildElement, Hoverable, + Locateable, + GModelElement, + GParentElement, + Selectable, hoverFeedbackFeature, isBoundsAware, isMoveable, - isSelectable, - Locateable, - SChildElement, - Selectable, - SModelElement, - SParentElement -} from '~glsp-sprotty'; + isSelectable +} from '@eclipse-glsp/sprotty'; export const resizeFeature = Symbol('resizeFeature'); export interface Resizable extends BoundsAware, Selectable {} -export function isResizable(element: SModelElement): element is SParentElement & Resizable { - return isBoundsAware(element) && isSelectable(element) && element instanceof SParentElement && element.hasFeature(resizeFeature); +export function isResizable(element: GModelElement): element is GParentElement & Resizable { + return isBoundsAware(element) && isSelectable(element) && element instanceof GParentElement && element.hasFeature(resizeFeature); } // eslint-disable-next-line no-shadow @@ -43,11 +43,11 @@ export enum ResizeHandleLocation { BottomRight = 'bottom-right' } -export function isBoundsAwareMoveable(element: SModelElement): element is SModelElement & Locateable & BoundsAware { +export function isBoundsAwareMoveable(element: GModelElement): element is GModelElement & Locateable & BoundsAware { return isMoveable(element) && isBoundsAware(element); } -export class SResizeHandle extends SChildElement implements Hoverable { +export class SResizeHandle extends GChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; constructor( @@ -71,7 +71,7 @@ export class SResizeHandle extends SChildElement implements Hoverable { } } -export function addResizeHandles(element: SParentElement): void { +export function addResizeHandles(element: GParentElement): void { removeResizeHandles(element); element.add(new SResizeHandle(ResizeHandleLocation.TopLeft)); element.add(new SResizeHandle(ResizeHandleLocation.TopRight)); @@ -79,6 +79,6 @@ export function addResizeHandles(element: SParentElement): void { element.add(new SResizeHandle(ResizeHandleLocation.BottomRight)); } -export function removeResizeHandles(element: SParentElement): void { +export function removeResizeHandles(element: GParentElement): void { element.removeAll(child => child instanceof SResizeHandle); } diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 8a1a599..55e1b9a 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { BoundsAware, Point, SModelElement, SNode, SParentElement } from '~glsp-sprotty'; +import { BoundsAware, Point, GModelElement, GNode, GParentElement } from '@eclipse-glsp/sprotty'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { SResizeHandle, isBoundsAwareMoveable } from './model'; @@ -31,7 +31,7 @@ export interface IMovementRestrictor { * @returns `true` if the the element is movable and moving to the given location is allowed, `false` otherwise. * Should also return `false` if the newLocation is `undefined`: */ - validate(element: SModelElement, newLocation?: Point): boolean; + validate(element: GModelElement, newLocation?: Point): boolean; /** * Feedback css-classes. Can be applied to elements that did fail the validation. */ @@ -46,12 +46,12 @@ export interface IMovementRestrictor { export class NoOverlapMovementRestrictor implements IMovementRestrictor { cssClasses = ['movement-not-allowed']; - validate(element: SModelElement, newLocation?: Point): boolean { + validate(element: GModelElement, newLocation?: Point): boolean { if (!isBoundsAwareMoveable(element) || !newLocation) { return false; } // Create ghost element at the newLocation - const ghostElement = Object.create(element) as SModelElement & BoundsAware; + const ghostElement = Object.create(element) as GModelElement & BoundsAware; ghostElement.bounds = { x: newLocation.x, y: newLocation.y, @@ -63,12 +63,12 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { return !Array.from( element.root.index .all() - .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof SNode) - .map(e => e as SModelElement & BoundsAware) + .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof GNode) + .map(e => e as GModelElement & BoundsAware) ).some(e => this.areOverlapping(e, ghostElement)); } - protected areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware): boolean { + protected areOverlapping(element1: GModelElement & BoundsAware, element2: GModelElement & BoundsAware): boolean { const b1 = toAbsoluteBounds(element1); const b2 = toAbsoluteBounds(element2); const r1TopLeft: Point = b1; @@ -97,11 +97,11 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { * @returns The corresponding {@link ModifyCSSFeedbackAction} */ export function createMovementRestrictionFeedback( - element: SModelElement, + element: GModelElement, movementRestrictor: IMovementRestrictor ): ModifyCSSFeedbackAction { - const elements: SModelElement[] = [element]; - if (element instanceof SParentElement) { + const elements: GModelElement[] = [element]; + if (element instanceof GParentElement) { element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } return ModifyCSSFeedbackAction.create({ elements, add: movementRestrictor.cssClasses }); @@ -114,11 +114,11 @@ export function createMovementRestrictionFeedback( * @returns The corresponding {@link ModifyCSSFeedbackAction} */ export function removeMovementRestrictionFeedback( - element: SModelElement, + element: GModelElement, movementRestrictor: IMovementRestrictor ): ModifyCSSFeedbackAction { - const elements: SModelElement[] = [element]; - if (element instanceof SParentElement) { + const elements: GModelElement[] = [element]; + if (element instanceof GParentElement) { element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } diff --git a/packages/client/src/features/change-bounds/snap.spec.ts b/packages/client/src/features/change-bounds/snap.spec.ts index 8ad6056..ff30aac 100644 --- a/packages/client/src/features/change-bounds/snap.spec.ts +++ b/packages/client/src/features/change-bounds/snap.spec.ts @@ -15,12 +15,12 @@ ********************************************************************************/ import { expect } from 'chai'; -import { SModelElement } from '~glsp-sprotty'; +import { GModelElement } from '@eclipse-glsp/sprotty'; import { GridSnapper, PointPositionUpdater } from './snap'; describe('GridSnapper', () => { it('snap', () => { - const element = new SModelElement(); + const element = new GModelElement(); const snapper = new GridSnapper(); expect(snapper.snap({ x: 0, y: 0 }, element)).to.be.deep.equals({ x: 0, y: 0 }); expect(snapper.snap({ x: 4, y: 5 }, element)).to.be.deep.equals({ x: 0, y: 10 }); @@ -31,7 +31,7 @@ describe('GridSnapper', () => { describe('PointPositionUpdater', () => { it('updatePosition with no last drag position', () => { - const element = new SModelElement(); + const element = new GModelElement(); const updater = new PointPositionUpdater(); expect(updater.updatePosition(element, { x: 0, y: 0 }, false)).to.be.undefined; expect(updater.updatePosition(element, { x: 0, y: 0 }, true)).to.be.undefined; @@ -47,7 +47,7 @@ describe('PointPositionUpdater', () => { }); it('updatePosition with no snapper', () => { - const element = new SModelElement(); + const element = new GModelElement(); const updater = new PointPositionUpdater(); resetUpdater(updater); expect(updater.updatePosition(element, { x: 0, y: 0 }, false)).to.be.undefined; @@ -61,7 +61,7 @@ describe('PointPositionUpdater', () => { }); it('updatePosition with snapper', () => { - const element = new SModelElement(); + const element = new GModelElement(); const snapper = new GridSnapper(); const updater = new PointPositionUpdater(snapper); resetUpdater(updater); diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 74c259b..7d81c48 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { ISnapper, Point, SModelElement, Writable, findParentByFeature, isViewport } from '~glsp-sprotty'; +import { ISnapper, Point, GModelElement, Writable, findParentByFeature, isViewport } from '@eclipse-glsp/sprotty'; /** * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. @@ -30,7 +30,7 @@ import { ISnapper, Point, SModelElement, Writable, findParentByFeature, isViewpo export class GridSnapper implements ISnapper { constructor(public grid: { x: number; y: number } = { x: 10, y: 10 }) {} - snap(position: Point, element: SModelElement): Point { + snap(position: Point, element: GModelElement): Point { return { x: Math.round(position.x / this.grid.x) * this.grid.x, y: Math.round(position.y / this.grid.y) * this.grid.y @@ -86,7 +86,7 @@ export class PointPositionUpdater { * @param isSnapEnabled if a snapper is defined you can disable it, e.g when a specific key is pressed `!event.altKey` * @returns current position or undefined if updater has no last drag position initialized */ - public updatePosition(target: SModelElement, mousePosition: Point, isSnapEnabled: boolean): Point | undefined { + public updatePosition(target: GModelElement, mousePosition: Point, isSnapEnabled: boolean): Point | undefined { if (this.lastDragPosition) { const newDragPosition = mousePosition; @@ -116,7 +116,7 @@ export class PointPositionUpdater { return undefined; } - protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { return isSnap && this.snapper ? this.snapper.snap(position, element) : { x: position.x, y: position.y }; } } diff --git a/packages/client/src/features/command-palette/command-palette-module.ts b/packages/client/src/features/command-palette/command-palette-module.ts index 64d0b19..bbe19a3 100644 --- a/packages/client/src/features/command-palette/command-palette-module.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CommandPalette, CommandPaletteActionProviderRegistry, FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; +import { CommandPalette, CommandPaletteActionProviderRegistry, FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import '../../../css/command-palette.css'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; diff --git a/packages/client/src/features/command-palette/command-palette-tool.ts b/packages/client/src/features/command-palette/command-palette-tool.ts index f377c92..80367a8 100644 --- a/packages/client/src/features/command-palette/command-palette-tool.ts +++ b/packages/client/src/features/command-palette/command-palette-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, postConstruct } from 'inversify'; -import { CommandPaletteKeyListener, KeyListener, KeyTool } from '~glsp-sprotty'; +import { CommandPaletteKeyListener, KeyListener, KeyTool } from '@eclipse-glsp/sprotty'; import { Tool } from '../../base/tool-manager/tool'; @injectable() diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 4fa5e49..d288d45 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -20,10 +20,10 @@ import { LabeledAction, Point, RequestContextActions, - SModelElement, + GModelElement, SetContextActions, TYPES -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; @@ -38,7 +38,7 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; - async getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { + async getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { const requestAction = RequestContextActions.create({ contextId: ServerCommandPalette.CONTEXT_ID, editorContext: this.editorContext.get({ diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 6ce4f45..1ae7c37 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, bindAsService } from '~glsp-sprotty'; +import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { GLSPContextMenuMouseListener } from './glsp-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index 6662ee3..6470961 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -14,14 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { DeleteElementOperation, IContextMenuItemProvider, MenuItem, Point, SModelRoot, TYPES } from '~glsp-sprotty'; +import { DeleteElementOperation, IContextMenuItemProvider, MenuItem, Point, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; @injectable() export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { @inject(TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; - async getItems(_root: Readonly, _lastMousePosition?: Point): Promise { + async getItems(_root: Readonly, _lastMousePosition?: Point): Promise { const editorContextService = await this.editorContextServiceProvider(); return [this.createDeleteMenuItem(editorContextService)]; } diff --git a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts index 19f1604..6075035 100644 --- a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts @@ -14,7 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, optional } from 'inversify'; -import { Action, ContextMenuProviderRegistry, IContextMenuServiceProvider, MouseListener, SModelElement, TYPES } from '~glsp-sprotty'; +import { + Action, + ContextMenuProviderRegistry, + IContextMenuServiceProvider, + MouseListener, + GModelElement, + TYPES +} from '@eclipse-glsp/sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; @injectable() @@ -30,7 +37,7 @@ export class GLSPContextMenuMouseListener extends MouseListener { /** * Opens the context menu. */ - override contextMenu(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + override contextMenu(target: GModelElement, event: MouseEvent): (Action | Promise)[] { return this.openContextMenu(target, event); } @@ -43,7 +50,7 @@ export class GLSPContextMenuMouseListener extends MouseListener { * * When the context menu is closed, we focus the diagram element again. */ - protected openContextMenu(target: SModelElement, event: MouseEvent): Promise[] { + protected openContextMenu(target: GModelElement, event: MouseEvent): Promise[] { if (!this.contextMenuService || !this.menuProvider) { return []; } diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index d609246..6bcd3eb 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -20,11 +20,11 @@ import { LabeledAction, Point, RequestContextActions, - SModelElement, + GModelElement, SetContextActions, TYPES, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; @@ -37,7 +37,7 @@ export class ServerContextMenuItemProvider implements IContextMenuItemProvider { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; - async getItems(root: Readonly, _lastMousePosition?: Point): Promise { + async getItems(root: Readonly, _lastMousePosition?: Point): Promise { const selectedElementIds = Array.from( root.index .all() diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index b1ef242..66170ed 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -22,13 +22,13 @@ import { IContextMenuItemProvider, MenuItem, Point, - SModelRoot, + GModelRoot, MessageAction, StatusAction, TYPES, hasStringProp, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; /** * An `InvokeCopyPasteAction` is dispatched by the client to initiate a cut, copy or paste operation. @@ -89,7 +89,7 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { @injectable() export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvider { - getItems(root: Readonly, _lastMousePosition?: Point): Promise { + getItems(root: Readonly, _lastMousePosition?: Point): Promise { const hasSelectedElements = Array.from(root.index.all().filter(isSelected)).length > 0; return Promise.resolve([ this.createCopyMenuItem(hasSelectedElements), diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 62407b9..50aceb8 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -23,7 +23,7 @@ import { SetClipboardDataAction, TYPES, ViewerOptions -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 5fe0234..7474d8d 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; +import { configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index 6f247cc..c4865bd 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import '../../../css/decoration.css'; import { GlspDecorationPlacer } from './decoration-placer'; diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 7eb12d0..4d1960d 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -14,14 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Decoration, DecorationPlacer, isSizeable, Point, SChildElement, SModelElement, SRoutableElement } from '~glsp-sprotty'; +import { Decoration, DecorationPlacer, GChildElement, Point, GModelElement, GRoutableElement, isSizeable } from '@eclipse-glsp/sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { protected static readonly DECORATION_OFFSET: Point = { x: 12, y: 10 }; - protected override getPosition(element: SModelElement & Decoration): Point { - if (element instanceof SChildElement && element.parent instanceof SRoutableElement) { + protected override getPosition(element: GModelElement & Decoration): Point { + if (element instanceof GChildElement && element.parent instanceof GRoutableElement) { return super.getPosition(element); } if (isSizeable(element)) { diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index cc89ed2..43bac9d 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -23,7 +23,7 @@ import { ExportSvgPostprocessor, FeatureModule, TYPES -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { ExportSvgActionHandler } from './export-svg-action-handler'; import { GLSPSvgExporter } from './glsp-svg-exporter'; diff --git a/packages/client/src/features/export/export-svg-action-handler.ts b/packages/client/src/features/export/export-svg-action-handler.ts index c0cbd2a..1aaac5a 100644 --- a/packages/client/src/features/export/export-svg-action-handler.ts +++ b/packages/client/src/features/export/export-svg-action-handler.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { saveAs } from 'file-saver'; import { injectable } from 'inversify'; -import { ExportSvgAction, IActionHandler } from '~glsp-sprotty'; +import { ExportSvgAction, IActionHandler } from '@eclipse-glsp/sprotty'; /** * The default handler for {@link ExportSvgAction}s. This generic handler can be used in diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 2060b3b..169412b 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -15,11 +15,11 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { v4 as uuid } from 'uuid'; -import { ExportSvgAction, RequestAction, SModelRoot, SvgExporter } from '~glsp-sprotty'; +import { ExportSvgAction, RequestAction, GModelRoot, SvgExporter } from '@eclipse-glsp/sprotty'; @injectable() export class GLSPSvgExporter extends SvgExporter { - override export(root: SModelRoot, _request?: RequestAction): void { + override export(root: GModelRoot, _request?: RequestAction): void { if (typeof document !== 'undefined') { const svgElement = this.findSvgElement(); if (svgElement) { @@ -34,7 +34,7 @@ export class GLSPSvgExporter extends SvgExporter { `style="width: ${bounds.width}px !important;height: ${bounds.height}px !important;border: none !important;` ); // do not give request/response id here as otherwise the action is treated as an unrequested response - this.actionDispatcher.dispatch(ExportSvgAction.create(svg, '')); + this.actionDispatcher.dispatch(ExportSvgAction.create(svg)); } finally { svgElement.id = originalId; } diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 11a3fa5..2e29dbe 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -13,22 +13,28 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement, SModelElementSchema, SModelExtension } from '~glsp-sprotty'; +import { GModelElement, GModelElementSchema } from '@eclipse-glsp/sprotty'; export const containerFeature = Symbol('containable'); -export interface Containable extends SModelExtension { - isContainableElement(input: SModelElement | SModelElementSchema | string): boolean; +/** + * Feature extension interface for {@link containerFeature}. + */ +export interface Containable { + isContainableElement(input: GModelElement | GModelElementSchema | string): boolean; } -export function isContainable(element: SModelElement): element is SModelElement & Containable { +export function isContainable(element: GModelElement): element is GModelElement & Containable { return element.hasFeature(containerFeature); } export const reparentFeature = Symbol('reparentFeature'); -export interface Reparentable extends SModelExtension {} +/** + * Feature extension interface for {@link reparentFeature}. + */ +export interface Reparentable {} -export function isReparentable(element: SModelElement): element is SModelElement & Reparentable { +export function isReparentable(element: GModelElement): element is GModelElement & Reparentable { return element.hasFeature(reparentFeature); } diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index f09b66e..5448aa3 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -13,19 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { expect } from 'chai'; -import { Container } from 'inversify'; -import * as sinon from 'sinon'; import { AnimationFrameSyncer, CommandExecutionContext, ConsoleLogger, EdgeTypeHint, - SChildElement, - SEdge, - SModelFactory, - SModelRoot, - SNode, + GChildElement, + GModelFactory, + GModelRoot, + GNode, SetTypeHintsAction, ShapeTypeHint, TYPES, @@ -35,14 +31,17 @@ import { editFeature, isDeletable, isMoveable -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { GEdge } from '../../model'; import { isResizable } from '../change-bounds/model'; import { isReconnectable } from '../reconnect/model'; import { Containable, isContainable, isReparentable } from './model'; import { ApplyTypeHintsAction, ApplyTypeHintsCommand, ITypeHintProvider, TypeHintProvider } from './type-hint-provider'; - describe('TypeHintProvider', () => { const container = new Container(); container.bind(GLSPActionDispatcher).toConstantValue(sinon.createStubInstance(GLSPActionDispatcher)); @@ -122,8 +121,8 @@ describe('TypeHintProvider', () => { }); }); describe('ApplyTypeHintCommand', () => { - function createCommandExecutionContext(child: SChildElement): CommandExecutionContext { - const root = new SModelRoot(); + function createCommandExecutionContext(child: GChildElement): CommandExecutionContext { + const root = new GModelRoot(); root.id = 'root'; root.type = 'root'; root.add(child); @@ -137,25 +136,25 @@ describe('ApplyTypeHintCommand', () => { }; } - function createNode(type?: string): SNode { - const node = new SNode(); + function createNode(type?: string): GNode { + const node = new GNode(); node.type = type ?? 'node'; node.id = 'node'; - node.features = createFeatureSet(SNode.DEFAULT_FEATURES); + node.features = createFeatureSet(GNode.DEFAULT_FEATURES); return node; } - function createEdge(type?: string): SEdge { - const edge = new SEdge(); + function createEdge(type?: string): GEdge { + const edge = new GEdge(); edge.type = type ?? 'edge'; edge.id = 'edge'; - edge.features = createFeatureSet(SEdge.DEFAULT_FEATURES); + edge.features = createFeatureSet(GEdge.DEFAULT_FEATURES); return edge; } const sandbox = sinon.createSandbox(); const container = new Container(); - const modelFactory = sinon.createStubInstance(SModelFactory); + const modelFactory = sinon.createStubInstance(GModelFactory); const typeHintProviderMock = sandbox.stub({ getEdgeTypeHint: () => undefined, getShapeTypeHint: () => undefined @@ -192,7 +191,7 @@ describe('ApplyTypeHintCommand', () => { typeHintProviderMock.getShapeTypeHint.returns(undefined); const result = command.execute(createCommandExecutionContext(createNode())); const element = result.children[0]; - expect(SNode.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ + expect(GNode.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ ...(element.features as Set) ]); }); @@ -233,7 +232,7 @@ describe('ApplyTypeHintCommand', () => { typeHintProviderMock.getShapeTypeHint.returns(shapeHint); typeHintProviderMock.getEdgeTypeHint.returns(edgeHint); const result = command.execute(createCommandExecutionContext(createNode())); - const element = result.children[0] as SNode; + const element = result.children[0] as GNode; const edge = createEdge(); expect(element.canConnect(edge, 'source')).to.be.true; expect(element.canConnect(edge, 'target')).to.be.true; @@ -244,7 +243,7 @@ describe('ApplyTypeHintCommand', () => { edgeHint.sourceElementTypeIds = []; edgeHint.targetElementTypeIds = []; const result = command.execute(createCommandExecutionContext(createNode())); - const element = result.children[0] as SNode; + const element = result.children[0] as GNode; const edge = createEdge(); expect(element.canConnect(edge, 'source')).to.be.false; expect(element.canConnect(edge, 'target')).to.be.false; @@ -255,7 +254,7 @@ describe('ApplyTypeHintCommand', () => { edgeHint.sourceElementTypeIds = ['node']; edgeHint.targetElementTypeIds = ['node']; const result = command.execute(createCommandExecutionContext(createNode())); - const element = result.children[0] as SNode; + const element = result.children[0] as GNode; const edge = createEdge(); expect(element.canConnect(edge, 'source')).to.be.true; expect(element.canConnect(edge, 'target')).to.be.true; @@ -266,7 +265,7 @@ describe('ApplyTypeHintCommand', () => { edgeHint.sourceElementTypeIds = ['node']; edgeHint.targetElementTypeIds = ['node']; const result = command.execute(createCommandExecutionContext(createNode('node:task:automated'))); - const element = result.children[0] as SNode; + const element = result.children[0] as GNode; const edge = createEdge(); expect(element.canConnect(edge, 'source')).to.be.true; expect(element.canConnect(edge, 'target')).to.be.true; @@ -277,7 +276,7 @@ describe('ApplyTypeHintCommand', () => { const node = createNode(); const originalCanConnectSpy = sinon.spy(node, 'canConnect'); const result = command.execute(createCommandExecutionContext(node)); - const element = result.children[0] as SNode; + const element = result.children[0] as GNode; const edge = createEdge(); expect(element.canConnect(edge, 'source')).to.be.true; expect(element.canConnect(edge, 'target')).to.be.true; @@ -295,21 +294,21 @@ describe('ApplyTypeHintCommand', () => { it('should return `false` if corresponding hint has no containable elements defined', () => { typeHintProviderMock.getShapeTypeHint.returns(shapeHint); const result = command.execute(createCommandExecutionContext(createNode('node'))); - const element = result.children[0] as SNode & Containable; + const element = result.children[0] as GNode & Containable; expect(element.isContainableElement('other')).to.be.false; }); it('should return `true` if corresponding hint has containable element with matching type', () => { typeHintProviderMock.getShapeTypeHint.returns(shapeHint); shapeHint.containableElementTypeIds = ['node']; const result = command.execute(createCommandExecutionContext(createNode('node'))); - const element = result.children[0] as SNode & Containable; + const element = result.children[0] as GNode & Containable; expect(element.isContainableElement('node')).to.be.true; }); it('should return `true` if corresponding hint as has containable element with matching super type', () => { typeHintProviderMock.getShapeTypeHint.returns(shapeHint); shapeHint.containableElementTypeIds = ['node']; const result = command.execute(createCommandExecutionContext(createNode('node'))); - const element = result.children[0] as SNode & Containable; + const element = result.children[0] as GNode & Containable; expect(element.isContainableElement('node:task:automated')).to.be.true; }); }); @@ -331,7 +330,7 @@ describe('ApplyTypeHintCommand', () => { typeHintProviderMock.getEdgeTypeHint.returns(undefined); const result = command.execute(createCommandExecutionContext(createEdge())); const element = result.children[0]; - expect(SEdge.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ + expect(GEdge.DEFAULT_FEATURES, 'Element should have default feature set').to.have.same.members([ ...(element.features as Set) ]); }); diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 2c4e5ce..093298d 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -21,12 +21,11 @@ import { EdgeTypeHint, IActionHandler, RequestTypeHintsAction, - SEdge, - SModelElement, - SModelElementSchema, - SModelRoot, - SRoutableElement, - SShapeElement, + GModelElement, + GModelElementSchema, + GModelRoot, + GRoutableElement, + GShapeElement, SetTypeHintsAction, ShapeTypeHint, TYPES, @@ -36,15 +35,17 @@ import { editFeature, isConnectable, moveFeature -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; + import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { IDiagramStartup } from '../../base/model/diagram-loader'; -import { getElementTypeId } from '../../utils/smodel-util'; +import { getElementTypeId } from '../../utils/gmodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; import { containerFeature, isContainable, reparentFeature } from './model'; +import { GEdge } from '../../model'; /** * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server @@ -86,19 +87,19 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { super(); } - execute(context: CommandExecutionContext): SModelRoot { + execute(context: CommandExecutionContext): GModelRoot { context.root.index.all().forEach(element => { - if (element instanceof SShapeElement || element instanceof SModelRoot) { + if (element instanceof GShapeElement || element instanceof GModelRoot) { return this.applyShapeTypeHint(element); } - if (element instanceof SEdge) { + if (element instanceof GEdge) { this.applyEdgeTypeHint(element); } }); return context.root; } - protected applyEdgeTypeHint(element: SModelElement): void { + protected applyEdgeTypeHint(element: GModelElement): void { const hint = this.typeHintProvider.getEdgeTypeHint(element); if (hint && element.features instanceof Set) { @@ -108,7 +109,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { } } - protected applyShapeTypeHint(element: SModelElement): void { + protected applyShapeTypeHint(element: GModelElement): void { const hint = this.typeHintProvider.getShapeTypeHint(element); if (hint && element.features instanceof Set) { addOrRemove(element.features, deletableFeature, hint.deletable); @@ -134,9 +135,9 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { * the `canConnect` implementation of `connectable` model elements (with a matching hint) will forward to this method. */ protected canConnect( - routable: SRoutableElement, + routable: GRoutableElement, role: 'source' | 'target', - element: SModelElement, + element: GModelElement, fallbackCanConnect?: CanConnectFn ): boolean { const edgeHint = this.typeHintProvider.getEdgeTypeHint(routable.type); @@ -156,7 +157,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { * Type hints aware wrapper function for `Containable.isContainableElement`. After type hints have been applied * the `isContainableElement` implementation of `containable` model elements (with a matching hint) will forward to this method. */ - protected isContainableElement(input: SModelElement | SModelElementSchema | string, hint: ShapeTypeHint): boolean { + protected isContainableElement(input: GModelElement | GModelElementSchema | string, hint: ShapeTypeHint): boolean { const elemenType = getElementTypeId(input) + ':'; return hint.containableElementTypeIds?.some(type => elemenType.startsWith(type)) ?? false; } @@ -185,14 +186,14 @@ export interface ITypeHintProvider { * @param input The model element whose type hint should be retrieved * @returns The most applicable hint of the given element or `undefined` if no matching hint is registered. */ - getShapeTypeHint(input: SModelElement | SModelElementSchema | string): ShapeTypeHint | undefined; + getShapeTypeHint(input: GModelElement | GModelElementSchema | string): ShapeTypeHint | undefined; /** * Retrieve the most applicable {@link EdgeTypeHint} for the given model element. * * @param input The model element whose type hint should be retrieved * @returns The most applicable hint of the given element or `undefined` if no matching hint is registered. */ - getEdgeTypeHint(input: SModelElement | SModelElementSchema | string): EdgeTypeHint | undefined; + getEdgeTypeHint(input: GModelElement | GModelElementSchema | string): EdgeTypeHint | undefined; } @injectable() @@ -214,15 +215,15 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDia this.feedbackActionDispatcher.registerFeedback(this, [ApplyTypeHintsAction.create()]); } - getShapeTypeHint(input: SModelElement | SModelElementSchema | string): ShapeTypeHint | undefined { + getShapeTypeHint(input: GModelElement | GModelElementSchema | string): ShapeTypeHint | undefined { return this.getTypeHint(input, this.shapeHints); } - getEdgeTypeHint(input: SModelElement | SModelElementSchema | string): EdgeTypeHint | undefined { + getEdgeTypeHint(input: GModelElement | GModelElementSchema | string): EdgeTypeHint | undefined { return this.getTypeHint(input, this.edgeHints); } - protected getTypeHint(input: SModelElement | SModelElementSchema | string, hints: Map): T | undefined { + protected getTypeHint(input: GModelElement | GModelElementSchema | string, hints: Map): T | undefined { const type = getElementTypeId(input); let hint = hints.get(type); // Check subtypes diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 7d3f879..0da5be7 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, SetTypeHintsAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SetTypeHintsAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hint-provider'; export const typeHintsModule = new FeatureModule((bind, unbind, isBound) => { diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 2f5389e..454141e 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -30,7 +30,7 @@ import { bindAsService, configureActionHandler, configureCommand -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GlspHoverMouseListener } from './hover'; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index d4e1be5..e5136fa 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -22,13 +22,13 @@ import { HoverMouseListener, IActionHandler, ICommand, - PreRenderedElementSchema, + GPreRenderedElementSchema, RequestPopupModelAction, - SModelElement, - SModelElementSchema, - SModelRootSchema, - SetPopupModelAction -} from '~glsp-sprotty'; + GModelElement, + GModelElementSchema, + SetPopupModelAction, + GModelRootSchema +} from '@eclipse-glsp/sprotty'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { EdgeCreationTool } from '../tools/edge-creation/edge-creation-tool'; @@ -61,14 +61,14 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio } } - override mouseOver(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + override mouseOver(target: GModelElement, event: MouseEvent): (Action | Promise)[] { if (this.enableHover) { return super.mouseOver(target, event); } return []; } - protected override startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { + protected override startMouseOverTimer(target: GModelElement, event: MouseEvent): Promise { this.stopMouseOverTimer(); return new Promise(resolve => { this.state.mouseOverTimer = window.setTimeout(() => { @@ -85,7 +85,7 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio }); } - protected createPopupModel(marker: GIssueMarker, bounds: Bounds): SModelRootSchema { + protected createPopupModel(marker: GIssueMarker, bounds: Bounds): GModelRootSchema { if (marker.issues !== undefined && marker.issues.length > 0) { return { type: 'html', @@ -97,13 +97,13 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio return { type: EMPTY_ROOT.type, id: EMPTY_ROOT.id }; } - protected createMarkerIssuePopup(marker: GIssueMarker): SModelElementSchema { + protected createMarkerIssuePopup(marker: GIssueMarker): GModelElementSchema { const message = this.createIssueMessage(marker); - return { + return { type: 'pre-rendered', id: 'popup-title', code: `
${message}
` - } as PreRenderedElementSchema; + }; } protected createIssueMessage(marker: GIssueMarker): string { diff --git a/packages/client/src/features/label-edit/edit-label-tool.ts b/packages/client/src/features/label-edit/edit-label-tool.ts index 93a456e..1649b64 100644 --- a/packages/client/src/features/label-edit/edit-label-tool.ts +++ b/packages/client/src/features/label-edit/edit-label-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from '~glsp-sprotty'; +import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from '@eclipse-glsp/sprotty'; import { BaseEditTool } from '../tools/base-tools'; @injectable() diff --git a/packages/client/src/features/label-edit/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts index a77bcbe..a208857 100644 --- a/packages/client/src/features/label-edit/edit-label-validator.ts +++ b/packages/client/src/features/label-edit/edit-label-validator.ts @@ -21,12 +21,12 @@ import { IEditLabelValidationDecorator, IEditLabelValidator, RequestEditValidationAction, - SModelElement, + GModelElement, SetEditValidationResultAction, Severity, TYPES, ValidationStatus -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; export namespace LabelEditValidation { @@ -52,7 +52,7 @@ export namespace LabelEditValidation { export class ServerEditLabelValidator implements IEditLabelValidator { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; - async validate(value: string, label: EditableLabel & SModelElement): Promise { + async validate(value: string, label: EditableLabel & GModelElement): Promise { const action = LabelEditValidation.createValidationRequestAction(value, label.id); const response = await this.actionDispatcher.requestUntil(action); return response ? this.getValidationResultFromResponse(response) : { severity: 'ok' }; diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 45d52ca..2e2ed1a 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ApplyLabelEditCommand, FeatureModule, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { ApplyLabelEditCommand, FeatureModule, TYPES, bindAsService, configureCommand } from '@eclipse-glsp/sprotty'; import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index 24f5d94..b8cf71a 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -14,10 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable deprecation/deprecation */ -import { expect } from 'chai'; -import { Container } from 'inversify'; -import 'mocha'; -import 'reflect-metadata'; import { Action, AnimationFrameSyncer, @@ -28,20 +24,27 @@ import { ElementAndBounds, ElementMove, FeatureSet, + GChildElement, + GModelFactory, + GModelRoot, + GNode, + GNodeSchema, IActionDispatcher, MoveAction, MoveCommand, RequestAction, ResponseAction, - SChildElement, - SGraphFactory, - SModelRoot, SetBoundsAction, - SetBoundsCommand, - TYPES -} from '~glsp-sprotty'; + SetBoundsCommand +} from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { Container } from 'inversify'; +import 'mocha'; +import 'reflect-metadata'; +import * as sinon from 'sinon'; import { defaultModule } from '../../base/default.module'; import { SelectionService } from '../../base/selection-service'; +import { GGraph } from '../../model'; import { resizeFeature } from '../change-bounds/model'; import { AlignElementsAction, @@ -68,10 +71,10 @@ class MockActionDispatcher implements IActionDispatcher { } class MockSelectionService extends SelectionService { - constructor(public modelRoot: SModelRoot) { + constructor(public modelRoot: GModelRoot) { super(); } - override getModelRoot(): Readonly { + override getModelRoot(): Readonly { return this.modelRoot; } } @@ -79,8 +82,6 @@ class MockSelectionService extends SelectionService { // Generic Test setup const container = new Container(); container.load(defaultModule); -container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); -const graphFactory = container.get(TYPES.IModelFactory); const actionDispatcher = new MockActionDispatcher(); @@ -101,23 +102,32 @@ const node3 = { }; const model = createModel(); -function createModel(): SModelRoot { - const root = graphFactory.createRoot({ - id: 'model1', - type: 'graph', - children: [node1, node2, node3] - }); +function createNode(schema: GNodeSchema): GNode { + const node = new GNode(); + const features = new Set(GNode.DEFAULT_FEATURES); + node.features = features; + Object.assign(node, schema); + return node; +} + +function createModel(): GModelRoot { + const root = new GGraph(); + root.features = new Set(GGraph.DEFAULT_FEATURES); + root.add(createNode(node1)); + root.add(createNode(node2)); + root.add(createNode(node3)); + root.children.forEach(child => applyFeature(child, resizeFeature)); return root; } -function applyFeature(element: SChildElement, feature: symbol): void { +function applyFeature(element: GChildElement, feature: symbol): void { (element.features as FeatureSet & Set).add(feature); } const context: CommandExecutionContext = { root: model, - modelFactory: graphFactory, + modelFactory: sinon.createStubInstance(GModelFactory), duration: 0, modelChanged: undefined!, logger: new ConsoleLogger(), @@ -128,7 +138,7 @@ const defaultSize = { height: 10, width: 10 }; describe('AlignElementsCommand', () => { let handler: AlignElementsActionHandler; - const setModel = (newModel: SModelRoot): void => { + const setModel = (newModel: GModelRoot): void => { handler['selectionService'] = new MockSelectionService(newModel); }; @@ -255,7 +265,7 @@ describe('AlignElementsCommand', () => { describe('ResizeElementsCommand', () => { let handler: ResizeElementsActionHandler; - const setModel = (newModel: SModelRoot): void => { + const setModel = (newModel: GModelRoot): void => { handler['selectionService'] = new MockSelectionService(newModel); }; @@ -338,10 +348,10 @@ describe('ResizeElementsCommand', () => { }); }); -function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { +function initModel(elementAndBounds: ElementAndBounds[]): GModelRoot { const mySetBoundsAction = SetBoundsAction.create(elementAndBounds); const setBoundsCommand = new SetBoundsCommand(mySetBoundsAction); - return setBoundsCommand.execute(context) as SModelRoot; + return setBoundsCommand.execute(context) as GModelRoot; } function assertAllBounds(allBounds: Map): void { diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 33841f2..b28c8d1 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -23,17 +23,17 @@ import { IActionHandler, ICommand, MoveAction, - SModelElement, + GModelElement, SetBoundsAction, TYPES, Writable, hasArrayProp, hasNumberProp, hasStringProp -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { SelectionService } from '../../base/selection-service'; import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; -import { BoundsAwareModelElement, getElements } from '../../utils/smodel-util'; +import { BoundsAwareModelElement, getElements } from '../../utils/gmodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; @@ -176,7 +176,7 @@ export abstract class LayoutElementsActionHandler implements IActionHandler { return getElements(index, selectedElements, this.isActionElement); } - protected abstract isActionElement(element: SModelElement): element is BoundsAwareModelElement; + protected abstract isActionElement(element: GModelElement): element is BoundsAwareModelElement; dispatchAction(action: Action): void { this.actionDispatcher.dispatch(action); @@ -270,7 +270,7 @@ export class ResizeElementsActionHandler extends LayoutElementsActionHandler { change(element, bounds); return toValidElementAndBounds(element, bounds, this.movementRestrictor); } - protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + protected isActionElement(element: GModelElement): element is BoundsAwareModelElement { return isResizable(element); } } @@ -483,7 +483,7 @@ export class AlignElementsActionHandler extends LayoutElementsActionHandler { } }; } - protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + protected isActionElement(element: GModelElement): element is BoundsAwareModelElement { return isBoundsAwareMoveable(element); } } diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index d66e1d5..a05828d 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, configureActionHandler } from '@eclipse-glsp/sprotty'; import { AlignElementsAction, AlignElementsActionHandler, diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index b8944b8..2f31af2 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -37,7 +37,7 @@ import { TYPES, hasObjectProp, hasStringProp -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; import { NavigationTargetResolver } from './navigation-target-resolver'; diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index fff4628..36d1024 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, NavigateToExternalTargetAction, NavigateToTargetAction, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, NavigateToExternalTargetAction, NavigateToTargetAction, configureActionHandler } from '@eclipse-glsp/sprotty'; import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index 207b718..5c20100 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -16,7 +16,7 @@ import { expect } from 'chai'; import 'mocha'; import 'reflect-metadata'; -import { NavigationTarget } from '~glsp-sprotty'; +import { NavigationTarget } from '@eclipse-glsp/sprotty'; describe('NavigationTarget', () => { it('should be able to set and get element IDs', () => { diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index c8634f7..8d7cdc4 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -22,7 +22,7 @@ import { ResponseAction, SetResolvedNavigationTargetAction, TYPES -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { IDiagramOptions } from '../../base/model/diagram-loader'; /** diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index 1f728f1..728a1f8 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -14,50 +14,52 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + GModelElement, + GRoutableElement, + GRoutingHandle, + RoutingHandleKind, edgeInProgressID, edgeInProgressTargetHandleID, - RoutingHandleKind, - selectFeature, - SModelElement, - SModelExtension, - SRoutableElement, - SRoutingHandle -} from '~glsp-sprotty'; + selectFeature +} from '@eclipse-glsp/sprotty'; export const reconnectFeature = Symbol('reconnectFeature'); -export interface Reconnectable extends SModelExtension {} +/** + * Feature extension interface for {@link reconnectFeature}. + */ +export interface Reconnectable {} -export function isReconnectable(element: SModelElement): element is SRoutableElement & Reconnectable { - return element instanceof SRoutableElement && element.hasFeature(reconnectFeature); +export function isReconnectable(element: GModelElement): element is GRoutableElement & Reconnectable { + return element instanceof GRoutableElement && element.hasFeature(reconnectFeature); } const ROUTING_HANDLE_SOURCE_INDEX = -2; -export function isReconnectHandle(element: SModelElement | undefined): element is SReconnectHandle { - return element !== undefined && element instanceof SReconnectHandle; +export function isReconnectHandle(element: GModelElement | undefined): element is GReconnectHandle { + return element !== undefined && element instanceof GReconnectHandle; } -export function addReconnectHandles(element: SRoutableElement): void { +export function addReconnectHandles(element: GRoutableElement): void { removeReconnectHandles(element); createReconnectHandle(element, 'source', ROUTING_HANDLE_SOURCE_INDEX); createReconnectHandle(element, 'target', element.routingPoints.length); } -export function removeReconnectHandles(element: SRoutableElement): void { - element.removeAll(child => child instanceof SReconnectHandle); +export function removeReconnectHandles(element: GRoutableElement): void { + element.removeAll(child => child instanceof GReconnectHandle); } -export function isSourceRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle): boolean { +export function isSourceRoutingHandle(edge: GRoutableElement, routingHandle: GReconnectHandle): boolean { return routingHandle.pointIndex === ROUTING_HANDLE_SOURCE_INDEX; } -export function isTargetRoutingHandle(edge: SRoutableElement, routingHandle: SReconnectHandle): boolean { +export function isTargetRoutingHandle(edge: GRoutableElement, routingHandle: GReconnectHandle): boolean { return routingHandle.pointIndex === edge.routingPoints.length; } -export function createReconnectHandle(edge: SRoutableElement, kind: RoutingHandleKind, routingPointIndex: number): SReconnectHandle { - const handle = new SReconnectHandle(); +export function createReconnectHandle(edge: GRoutableElement, kind: RoutingHandleKind, routingPointIndex: number): GReconnectHandle { + const handle = new GReconnectHandle(); handle.kind = kind; handle.pointIndex = routingPointIndex; handle.type = 'routing-point'; @@ -68,7 +70,7 @@ export function createReconnectHandle(edge: SRoutableElement, kind: RoutingHandl return handle; } -export class SReconnectHandle extends SRoutingHandle { +export class GReconnectHandle extends GRoutingHandle { override hasFeature(feature: symbol): boolean { return feature !== selectFeature && super.hasFeature(feature); } diff --git a/packages/client/src/features/routing/glsp-manhattan-edge-router.ts b/packages/client/src/features/routing/glsp-manhattan-edge-router.ts index 00e2d24..7d9d379 100644 --- a/packages/client/src/features/routing/glsp-manhattan-edge-router.ts +++ b/packages/client/src/features/routing/glsp-manhattan-edge-router.ts @@ -14,10 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ManhattanEdgeRouter, ResolvedHandleMove, SRoutableElement, almostEquals } from '~glsp-sprotty'; +import { ManhattanEdgeRouter, ResolvedHandleMove, GRoutableElement, almostEquals } from '@eclipse-glsp/sprotty'; export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { - protected override applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { + protected override applyInnerHandleMoves(edge: GRoutableElement, moves: ResolvedHandleMove[]): void { const route = this.route(edge); const routingPoints = edge.routingPoints; const minimalPointDistance = this.getOptions(edge).minimalPointDistance; diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 525a9ff..7fb859c 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -32,7 +32,7 @@ import { TYPES, bindAsService, configureCommand -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; export const routingModule = new FeatureModule((bind, unbind, isBound, rebind) => { diff --git a/packages/client/src/features/save/save-keylistener.ts b/packages/client/src/features/save/save-keylistener.ts index 242ef2d..521acbf 100644 --- a/packages/client/src/features/save/save-keylistener.ts +++ b/packages/client/src/features/save/save-keylistener.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, KeyListener, SModelRoot, SaveModelAction, matchesKeystroke } from '~glsp-sprotty'; +import { Action, KeyListener, GModelRoot, SaveModelAction, matchesKeystroke } from '@eclipse-glsp/sprotty'; export class SaveModelKeyboardListener extends KeyListener { - override keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { + override keyDown(_element: GModelRoot, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { return [SaveModelAction.create()]; } diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 1a52f94..c030660 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { SaveModelKeyboardListener } from './save-keylistener'; /** diff --git a/packages/client/src/features/select/select-feedback-command.ts b/packages/client/src/features/select/select-feedback-command.ts index 64bd28c..10ffcc4 100644 --- a/packages/client/src/features/select/select-feedback-command.ts +++ b/packages/client/src/features/select/select-feedback-command.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Command, CommandExecutionContext, SModelRoot, SelectAction, SprottySelectCommand, TYPES } from '~glsp-sprotty'; +import { Command, CommandExecutionContext, GModelRoot, SelectAction, SprottySelectCommand, TYPES } from '@eclipse-glsp/sprotty'; import { SelectFeedbackAction } from '../../base/selection-service'; @injectable() @@ -27,15 +27,15 @@ export class SelectFeedbackCommand extends Command { this.sprottySelectCommand = new SprottySelectCommand({ ...action, kind: SelectAction.KIND }); } - execute(context: CommandExecutionContext): SModelRoot { + execute(context: CommandExecutionContext): GModelRoot { return this.sprottySelectCommand.execute(context); } - undo(context: CommandExecutionContext): SModelRoot { + undo(context: CommandExecutionContext): GModelRoot { return this.sprottySelectCommand.undo(context); } - redo(context: CommandExecutionContext): SModelRoot { + redo(context: CommandExecutionContext): GModelRoot { return this.sprottySelectCommand.redo(context); } } diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index a9207a3..ef57880 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, SelectKeyboardListener, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SelectKeyboardListener, TYPES, bindAsService, configureCommand } from '@eclipse-glsp/sprotty'; import { SelectAllCommand, SelectCommand } from '../../base/selection-service'; import { SelectFeedbackCommand } from './select-feedback-command'; import { RankedSelectMouseListener } from './select-mouse-listener'; diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 90e4370..ad8c50d 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, BringToFrontAction, SModelElement, SelectAction, SelectMouseListener, Selectable } from '~glsp-sprotty'; +import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener, Selectable } from '@eclipse-glsp/sprotty'; import { Ranked } from '../../base/ranked'; /** @@ -24,8 +24,8 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ protected override handleSelectTarget( - selectableTarget: SModelElement & Selectable, - deselectedElements: SModelElement[], + selectableTarget: GModelElement & Selectable, + deselectedElements: GModelElement[], event: MouseEvent ): (Action | Promise)[] { const result: Action[] = []; @@ -40,13 +40,13 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra return result; } - protected override handleDeselectTarget(selectableTarget: SModelElement & Selectable, event: MouseEvent): (Action | Promise)[] { + protected override handleDeselectTarget(selectableTarget: GModelElement & Selectable, event: MouseEvent): (Action | Promise)[] { const result: Action[] = []; result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: [selectableTarget.id] })); return result; } - protected override handleDeselectAll(deselectedElements: SModelElement[], event: MouseEvent): (Action | Promise)[] { + protected override handleDeselectAll(deselectedElements: GModelElement[], event: MouseEvent): (Action | Promise)[] { const result: Action[] = []; result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: deselectedElements.map(e => e.id) })); return result; diff --git a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts index c586ae3..6636b30 100644 --- a/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts +++ b/packages/client/src/features/source-model-watcher/source-model-changed-action-handler.ts @@ -23,7 +23,7 @@ import { StatusAction, TYPES, ViewerOptions -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; /** * An external handler of the source model change event. diff --git a/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts index 4eadf9e..747cae2 100644 --- a/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, SourceModelChangedAction, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, SourceModelChangedAction, configureActionHandler } from '@eclipse-glsp/sprotty'; import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; export const sourceModelWatcherModule = new FeatureModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 6b1d7c1..f9ee341 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, StatusAction, TYPES, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, StatusAction, TYPES, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; import '../../../css/status-overlay.css'; import { StatusOverlay } from './status-overlay'; diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 3bac805..718df1d 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { AbstractUIExtension, IActionHandler, StatusAction, codiconCSSClasses } from '~glsp-sprotty'; +import { AbstractUIExtension, IActionHandler, StatusAction, codiconCSSClasses } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; diff --git a/packages/client/src/features/svg-metadata/metadata-placer.ts b/packages/client/src/features/svg-metadata/metadata-placer.ts index 166a29d..a63a78d 100644 --- a/packages/client/src/features/svg-metadata/metadata-placer.ts +++ b/packages/client/src/features/svg-metadata/metadata-placer.ts @@ -14,25 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { DOMHelper, GChildElement, GModelElement, GModelRoot, IVNodePostprocessor, TYPES, setAttr } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { DOMHelper, IVNodePostprocessor, SChildElement, SEdge, SModelElement, SModelRoot, TYPES, setAttr } from '~glsp-sprotty'; +import { GEdge } from '../../model'; @injectable() export class MetadataPlacer implements IVNodePostprocessor { @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; - decorate(vnode: VNode, element: SModelElement): VNode { - if (element instanceof SModelRoot) { + decorate(vnode: VNode, element: GModelElement): VNode { + if (element instanceof GModelRoot) { setAttr(vnode, 'data-svg-metadata-api', true); } setAttr(vnode, 'data-svg-metadata-type', element.type); - if (element instanceof SChildElement) { + if (element instanceof GChildElement) { setAttr(vnode, 'data-svg-metadata-parent-id', this.domHelper.createUniqueDOMElementId(element.parent)); } - if (element instanceof SEdge) { + if (element instanceof GEdge) { if (element.source !== undefined) { setAttr(vnode, 'data-svg-metadata-edge-source-id', this.domHelper.createUniqueDOMElementId(element.source)); } diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index f5fd50d..2d28205 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES } from '~glsp-sprotty'; +import { FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { MetadataPlacer } from './metadata-placer'; export const svgMetadataModule = new FeatureModule(bind => { diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index 70c1785..5d989e6 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, configureActionHandler, FeatureModule, TYPES } from '~glsp-sprotty'; +import { bindAsService, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import '../../../css/tool-palette.css'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { ToolPalette } from './tool-palette'; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 69ecc31..5135cf9 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -23,12 +23,12 @@ import { PaletteItem, RequestContextActions, RequestMarkersAction, - SModelRoot, + GModelRoot, SetContextActions, SetUIExtensionVisibilityAction, codiconCSSClasses, matchesKeystroke -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; @@ -105,7 +105,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, containerElement.setAttribute('aria-label', 'Tool-Palette'); } - protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { + protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { this.modelRootId = root.id; this.containerElement.style.maxHeight = PALETTE_HEIGHT; } diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 90b5a90..71f1890 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from '~glsp-sprotty'; +import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; @@ -52,7 +52,7 @@ export abstract class BaseEditTool implements Tool { } /** - * Registers `actions` to be sent out as feedback, i.e., changes that are re-established whenever the `SModelRoot` + * Registers `actions` to be sent out as feedback, i.e., changes that are re-established whenever the `GModelRoot` * has been set or updated. * * @param feedbackActions the actions to be sent out. diff --git a/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts index 2ad90f1..8165cbf 100644 --- a/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '@eclipse-glsp/sprotty'; import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index 975a1e9..3270821 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -21,22 +21,23 @@ import { CommandReturn, Disposable, ElementMove, + GChildElement, MouseListener, MoveAction, Point, - SChildElement, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, TYPES, findParentByFeature, hasStringProp, isMoveable, isSelectable, isViewport -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; + import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; -import { forEachElement } from '../../../utils/smodel-util'; +import { forEachElement } from '../../../utils/gmodel-util'; import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; import { ChangeBoundsTool } from './change-bounds-tool'; @@ -121,7 +122,7 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { */ export class FeedbackMoveMouseListener extends MouseListener implements Disposable { protected hasDragged = false; - protected rootElement?: SModelRoot; + protected rootElement?: GModelRoot; protected startDragPosition?: Point; protected elementId2startPos = new Map(); @@ -129,7 +130,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab super(); } - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { if (event.button === 0 && !(target instanceof SResizeHandle)) { const moveable = findParentByFeature(target, isMoveable); if (moveable !== undefined) { @@ -142,7 +143,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return []; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { this.mouseUp(target, event); @@ -161,7 +162,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return []; } - protected collectStartPositions(root: SModelRoot): void { + protected collectStartPositions(root: GModelRoot): void { this.rootElement = root; const selectedElements = root.index.all().filter(element => isSelectable(element) && element.selected); const elementsSet = new Set(selectedElements); @@ -174,8 +175,8 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab }); } - protected isChildOfSelected(selectedElements: Set, element: SModelElement): boolean { - while (element instanceof SChildElement) { + protected isChildOfSelected(selectedElements: Set, element: GModelElement): boolean { + while (element instanceof GChildElement) { element = element.parent; if (selectedElements.has(element)) { return true; @@ -184,7 +185,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return false; } - protected getElementMoves(target: SModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { + protected getElementMoves(target: GModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { if (!this.startDragPosition) { return undefined; } @@ -205,7 +206,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab } protected getElementMovesForDelta( - target: SModelElement, + target: GModelElement, delta: { x: number; y: number }, isSnap: boolean, finished: boolean @@ -239,7 +240,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return elementMoves; } - protected validateMove(startPosition: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { + protected validateMove(startPosition: Point, toPosition: Point, element: GModelElement, isFinished: boolean): Point { let newPosition = toPosition; if (this.tool.movementRestrictor) { const valid = this.tool.movementRestrictor.validate(element, toPosition); @@ -258,7 +259,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return newPosition; } - protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { if (isSnap && this.tool.snapper) { return this.tool.snapper.snap(position, element); } else { @@ -266,14 +267,14 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab } } - override mouseEnter(target: SModelElement, event: MouseEvent): Action[] { - if (target instanceof SModelRoot && event.buttons === 0 && !this.startDragPosition) { + override mouseEnter(target: GModelElement, event: MouseEvent): Action[] { + if (target instanceof GModelRoot && event.buttons === 0 && !this.startDragPosition) { this.mouseUp(target, event); } return []; } - override mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (this.startDragPosition) { const moveAction = this.getElementMoves(target, event, true); @@ -303,7 +304,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab this.elementId2startPos.clear(); } - override decorate(vnode: VNode, _element: SModelElement): VNode { + override decorate(vnode: VNode, _element: GModelElement): VNode { return vnode; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 0778ca7..0a832b1 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -27,20 +27,20 @@ import { EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, + GChildElement, ISnapper, MouseListener, Operation, Point, - SChildElement, - SConnectableElement, - SModelElement, - SModelRoot, - SParentElement, + GConnectableElement, + GModelElement, + GModelRoot, + GParentElement, SetBoundsAction, TYPES, findParentByFeature, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; @@ -51,7 +51,7 @@ import { forEachElement, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds -} from '../../../utils/smodel-util'; +} from '../../../utils/gmodel-util'; import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor, @@ -130,7 +130,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel protected pointPositionUpdater: PointPositionUpdater; // members for resize mode - protected activeResizeElement?: SModelElement; + protected activeResizeElement?: GModelElement; protected activeResizeHandle?: SResizeHandle; constructor(protected tool: ChangeBoundsTool) { @@ -138,11 +138,11 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel this.pointPositionUpdater = new PointPositionUpdater(tool.snapper); } - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { super.mouseDown(target, event); // If another button than the left mouse button was clicked or we are // still on the root element we don't need to execute the tool behavior - if (event.button !== 0 || target instanceof SModelRoot) { + if (event.button !== 0 || target instanceof GModelRoot) { return []; } // check if we have a resize handle (only single-selection) @@ -159,7 +159,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { super.mouseMove(target, event); if (this.isMouseDrag && this.activeResizeHandle) { // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements @@ -178,7 +178,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } - override draggingMouseUp(target: SModelElement, _event: MouseEvent): Action[] { + override draggingMouseUp(target: GModelElement, _event: MouseEvent): Action[] { if (this.pointPositionUpdater.isLastDragPositionUndefined()) { this.resetPosition(); return []; @@ -197,7 +197,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return actions; } - protected handleMoveOnServer(target: SModelElement): Action[] { + protected handleMoveOnServer(target: GModelElement): Action[] { const operations: Operation[] = []; operations.push(...this.handleMoveElementsOnServer(target)); @@ -208,15 +208,15 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return operations; } - protected handleMoveElementsOnServer(target: SModelElement): Operation[] { + protected handleMoveElementsOnServer(target: GModelElement): Operation[] { const result: Operation[] = []; const newBounds: ElementAndBounds[] = []; - const selectedElements: (SModelElement & BoundsAware)[] = []; + const selectedElements: (GModelElement & BoundsAware)[] = []; forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { selectedElements.push(element); }); - const selectionSet: Set = new Set(selectedElements); + const selectionSet: Set = new Set(selectedElements); selectedElements .filter(element => !this.isChildOfSelected(selectionSet, element)) .map(element => this.createElementAndBounds(element)) @@ -228,8 +228,8 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return result; } - protected isChildOfSelected(selectedElements: Set, element: SModelElement): boolean { - while (element instanceof SChildElement) { + protected isChildOfSelected(selectedElements: Set, element: GModelElement): boolean { + while (element instanceof GChildElement) { element = element.parent; if (selectedElements.has(element)) { return true; @@ -238,13 +238,13 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return false; } - protected handleMoveRoutingPointsOnServer(target: SModelElement): Operation[] { + protected handleMoveRoutingPointsOnServer(target: GModelElement): Operation[] { const newRoutingPoints: ElementAndRoutingPoints[] = []; const routerRegistry = this.tool.edgeRouterRegistry; if (routerRegistry) { // If client routing is enabled -> delegate routingpoints of connected edges to server forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { - if (element instanceof SConnectableElement) { + if (element instanceof GConnectableElement) { element.incomingEdges .map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) .forEach(ear => newRoutingPoints.push(ear)); @@ -268,7 +268,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return actions; } - selectionChanged(root: SModelRoot, selectedElements: string[]): void { + selectionChanged(root: GModelRoot, selectedElements: string[]): void { if (this.activeResizeElement) { if (selectedElements.includes(this.activeResizeElement.id)) { // our active element is still selected, nothing to do @@ -286,7 +286,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } } - protected setActiveResizeElement(target: SModelElement): boolean { + protected setActiveResizeElement(target: GModelElement): boolean { // check if we have a selected, moveable element (multi-selection allowed) const moveableElement = findParentByFeature(target, isBoundsAwareMoveable); if (isSelected(moveableElement)) { @@ -303,7 +303,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return false; } - protected isActiveResizeElement(element?: SModelElement): element is SParentElement & BoundsAware { + protected isActiveResizeElement(element?: GModelElement): element is GParentElement & BoundsAware { return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; } @@ -376,7 +376,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } - protected handleTopLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleTopLeftResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { return this.createSetBoundsAction( resizeElement, resizeElement.bounds.x + positionUpdate.x, @@ -386,7 +386,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel ); } - protected handleTopRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleTopRightResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { return this.createSetBoundsAction( resizeElement, resizeElement.bounds.x, @@ -396,7 +396,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel ); } - protected handleBottomLeftResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleBottomLeftResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { return this.createSetBoundsAction( resizeElement, resizeElement.bounds.x + positionUpdate.x, @@ -406,7 +406,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel ); } - protected handleBottomRightResize(resizeElement: SParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleBottomRightResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { return this.createSetBoundsAction( resizeElement, resizeElement.bounds.x, @@ -416,7 +416,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel ); } - protected createChangeBoundsAction(element: SModelElement & BoundsAware): Action[] { + protected createChangeBoundsAction(element: GModelElement & BoundsAware): Action[] { if (this.isValidBoundChange(element, element.bounds, element.bounds)) { return [ChangeBoundsOperation.create([toElementAndBounds(element)])]; } else if (this.initialBounds) { @@ -430,14 +430,14 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } - protected createElementAndBounds(element: SModelElement & BoundsAware): ElementAndBounds[] { + protected createElementAndBounds(element: GModelElement & BoundsAware): ElementAndBounds[] { if (this.isValidBoundChange(element, element.bounds, element.bounds)) { return [toElementAndBounds(element)]; } return []; } - protected createSetBoundsAction(element: SModelElement & BoundsAware, x: number, y: number, width: number, height: number): Action[] { + protected createSetBoundsAction(element: GModelElement & BoundsAware, x: number, y: number, width: number, height: number): Action[] { const newPosition = { x, y }; const newSize = { width, height }; const result: Action[] = []; @@ -457,19 +457,19 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return result; } - protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { return isSnap && this.tool.snapper ? this.tool.snapper.snap(position, element) : { x: position.x, y: position.y }; } - protected isValidBoundChange(element: SModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { + protected isValidBoundChange(element: GModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { return this.isValidSize(element, newSize) && this.isValidMove(element, newPosition); } - protected isValidSize(element: SModelElement & BoundsAware, size: Dimension): boolean { + protected isValidSize(element: GModelElement & BoundsAware, size: Dimension): boolean { return isValidSize(element, size); } - protected isValidMove(element: SModelElement & BoundsAware, newPosition: Point): boolean { + protected isValidMove(element: GModelElement & BoundsAware, newPosition: Point): boolean { return isValidMove(element, newPosition, this.tool.movementRestrictor); } } diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index ae87655..7cb919e 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, Point, RenderingContext, setAttr, svg } from '~glsp-sprotty'; +import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index ea38500..087b144 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -20,13 +20,13 @@ import { KeyListener, KeyTool, MouseListener, - SModelElement, + GModelElement, findParentByFeature, isCtrlOrCmd, isDeletable, isSelectable, matchesKeystroke -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction, Tool } from '../../../base/tool-manager/tool'; import { BaseEditTool } from '../base-tools'; @@ -58,7 +58,7 @@ export class DelKeyDeleteTool implements Tool { @injectable() export class DeleteKeyListener extends KeyListener { - override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Delete')) { const deleteElementIds = Array.from( element.root.index @@ -98,7 +98,7 @@ export class MouseDeleteTool extends BaseEditTool { @injectable() export class DeleteToolMouseListener extends MouseListener { - override mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: GModelElement, event: MouseEvent): Action[] { const deletableParent = findParentByFeature(target, isDeletable); if (deletableParent === undefined) { return []; diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 8f41819..1581148 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; export const deletionToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { diff --git a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts index 867967c..6402beb 100644 --- a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts @@ -19,20 +19,20 @@ import { BindingContext, CommandExecutionContext, CommandReturn, - SChildElement, - SDanglingAnchor, - SEdgeSchema, - SModelRoot, - SRoutableElement, + GChildElement, + GModelRoot, + GRoutableElement, TYPES, configureCommand, configureView, findParentByFeature, isBoundsAware, - isConnectable -} from '~glsp-sprotty'; + isConnectable, + GDanglingAnchor, + GEdgeSchema +} from '@eclipse-glsp/sprotty'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; -import { isRoutable } from '../../../utils/smodel-util'; +import { isRoutable } from '../../../utils/gmodel-util'; import { toAbsolutePosition } from '../../../utils/viewpoint-util'; import { FeedbackEdgeEndView } from './view'; @@ -40,7 +40,7 @@ export interface DrawFeedbackEdgeAction extends Action { kind: typeof DrawFeedbackEdgeAction.KIND; elementTypeId: string; sourceId: string; - edgeSchema?: Partial; + edgeSchema?: Partial; } export namespace DrawFeedbackEdgeAction { @@ -53,7 +53,7 @@ export namespace DrawFeedbackEdgeAction { export function create(options: { elementTypeId: string; sourceId: string; - edgeSchema?: Partial; + edgeSchema?: Partial; }): DrawFeedbackEdgeAction { return { kind: KIND, @@ -102,27 +102,27 @@ export class RemoveFeedbackEdgeCommand extends FeedbackCommand { } } -export class FeedbackEdgeEnd extends SDanglingAnchor { +export class FeedbackEdgeEnd extends GDanglingAnchor { static readonly TYPE = 'feedback-edge-end'; constructor( readonly sourceId: string, readonly elementTypeId: string, - public feedbackEdge: SRoutableElement | undefined = undefined, + public feedbackEdge: GRoutableElement | undefined = undefined, override readonly type: string = FeedbackEdgeEnd.TYPE ) { super(); } } -export function feedbackEdgeId(root: SModelRoot): string { +export function feedbackEdgeId(root: GModelRoot): string { return root.id + '_feedback_edge'; } -export function feedbackEdgeEndId(root: SModelRoot): string { +export function feedbackEdgeEndId(root: GModelRoot): string { return root.id + '_feedback_anchor'; } -export const defaultFeedbackEdgeSchema: Partial = { +export const defaultFeedbackEdgeSchema: Partial = { cssClasses: ['feedback-edge'], opacity: 0.3 }; @@ -131,7 +131,7 @@ export function drawFeedbackEdge( context: CommandExecutionContext, sourceId: string, elementTypeId: string, - edgeTemplate?: Partial + edgeTemplate?: Partial ): void { const root = context.root; const sourceChild = root.index.getById(sourceId); @@ -148,7 +148,7 @@ export function drawFeedbackEdge( edgeEnd.id = feedbackEdgeEndId(root); edgeEnd.position = toAbsolutePosition(source); - const edgeSchema: SEdgeSchema = { + const edgeSchema: GEdgeSchema = { id: feedbackEdgeId(root), type: elementTypeId, sourceId: source.id, @@ -165,13 +165,13 @@ export function drawFeedbackEdge( } } -export function removeDanglingFeedbackEdge(root: SModelRoot): void { +export function removeDanglingFeedbackEdge(root: GModelRoot): void { const feedbackEdge = root.index.getById(feedbackEdgeId(root)); const feedbackEdgeEnd = root.index.getById(feedbackEdgeEndId(root)); - if (feedbackEdge instanceof SChildElement) { + if (feedbackEdge instanceof GChildElement) { root.remove(feedbackEdge); } - if (feedbackEdgeEnd instanceof SChildElement) { + if (feedbackEdgeEnd instanceof GChildElement) { root.remove(feedbackEdgeEnd); } } diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index 933b437..78d2ee5 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, TYPES, TriggerEdgeCreationAction, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; import { configureDanglingFeedbackEdge } from './dangling-edge-feedback'; import { EdgeCreationTool } from './edge-creation-tool'; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts index 05cc634..d468b94 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts @@ -22,13 +22,13 @@ import { MoveAction, Point, PolylineEdgeRouter, - SConnectableElement, - SModelElement, + GConnectableElement, + GModelElement, findChildrenAtPosition, findParentByFeature, isBoundsAware, isConnectable -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { absoluteToParent, getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { FeedbackEdgeEnd, feedbackEdgeEndId } from './dangling-edge-feedback'; @@ -38,7 +38,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements super(); } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const root = target.root; const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { @@ -51,7 +51,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements .reverse() .find(element => isConnectable(element) && element.canConnect(edge, 'target')); - if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { + if (endAtMousePosition instanceof GConnectableElement && edge.source && isBoundsAware(edge.source)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(toAbsoluteBounds(edge.source))); if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { this.feedbackDispatcher.registerFeedback(this, [ @@ -67,7 +67,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements return []; } - protected computeAbsoluteAnchor(element: SConnectableElement, absoluteReferencePoint: Point, offset?: number): Point { + protected computeAbsoluteAnchor(element: GConnectableElement, absoluteReferencePoint: Point, offset?: number): Point { const referencePointInParent = absoluteToParent(element, absoluteReferencePoint); const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); let anchor = anchorComputer.getAnchor(element, referencePointInParent, offset); diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 5aa5f54..0c4420b 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -19,14 +19,13 @@ import { AnchorComputerRegistry, CreateEdgeOperation, RequestCheckEdgeAction, - SEdge, - SModelElement, + GModelElement, TYPES, TriggerEdgeCreationAction, findParentByFeature, isConnectable, isCtrlOrCmd -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; @@ -35,6 +34,7 @@ import { ITypeHintProvider } from '../../hints/type-hint-provider'; import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedback'; +import { GEdge } from '../../../model'; /** * Tool to create connections in a Diagram, by selecting a source and target node. @@ -73,9 +73,9 @@ export class EdgeCreationTool extends BaseCreationTool, context: RenderingContext): VNode { + render(model: Readonly, context: RenderingContext): VNode { const position: Point = (model as any).position ?? Point.ORIGIN; return ; } diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index b2c2901..f7c7752 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand } from '@eclipse-glsp/sprotty'; import { configureDanglingFeedbackEdge } from '../edge-creation/dangling-edge-feedback'; import { EdgeEditTool } from './edge-edit-tool'; import { diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index 657fa3b..b7fe227 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -29,9 +29,9 @@ import { MoveAction, Point, PolylineEdgeRouter, - SConnectableElement, - SModelElement, - SRoutingHandle, + GConnectableElement, + GModelElement, + GRoutingHandle, SwitchEditModeAction, SwitchEditModeCommand, TYPES, @@ -41,10 +41,10 @@ import { isBoundsAware, isConnectable, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; -import { forEachElement, isRoutable, isRoutingHandle } from '../../../utils/smodel-util'; +import { forEachElement, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { PointPositionUpdater } from '../../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; @@ -202,7 +202,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme super(); } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const root = target.root; const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { @@ -215,7 +215,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme element => isConnectable(element) && element.canConnect(edge, 'source') ); - if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { + if (endAtMousePosition instanceof GConnectableElement && edge.target && isBoundsAware(edge.target)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(edge.target.bounds)); if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { this.feedbackDispatcher.registerFeedback(this, [ @@ -231,7 +231,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme return []; } - protected computeAbsoluteAnchor(element: SConnectableElement, referencePoint: Point, offset?: number): Point { + protected computeAbsoluteAnchor(element: GConnectableElement, referencePoint: Point, offset?: number): Point { const anchorComputer = this.anchorRegistry.get(PolylineEdgeRouter.KIND, element.anchorKind); let anchor = anchorComputer.getAnchor(element, referencePoint, offset); // The anchor is computed in the local coordinate system of the element. @@ -259,7 +259,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { this.pointPositionUpdater = new PointPositionUpdater(snapper); } - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.button === 0) { const routingHandle = findParentByFeature(target, isRoutingHandle); @@ -273,7 +273,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return result; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { return this.mouseUp(target, event); @@ -286,7 +286,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return result; } - protected handleMoveOnClient(target: SModelElement, positionUpdate: Point, isSnap: boolean): Action[] { + protected handleMoveOnClient(target: GModelElement, positionUpdate: Point, isSnap: boolean): Action[] { const handleMoves: ElementMove[] = []; target.root.index .all() @@ -305,7 +305,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return []; } - protected toElementMove(element: SRoutingHandle, positionDelta: Point, isSnap: boolean): ElementMove | undefined { + protected toElementMove(element: GRoutingHandle, positionDelta: Point, isSnap: boolean): ElementMove | undefined { const point = this.getHandlePosition(element); if (point !== undefined) { const snappedPoint = this.getSnappedHandlePosition(element, point, isSnap); @@ -321,14 +321,14 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return undefined; } - protected getSnappedHandlePosition(element: SRoutingHandle, point: Point, isSnap: boolean): Point { + protected getSnappedHandlePosition(element: GRoutingHandle, point: Point, isSnap: boolean): Point { if (this.snapper && isSnap) { return this.snapper.snap(point, element); } return point; } - protected getHandlePosition(handle: SRoutingHandle): Point | undefined { + protected getHandlePosition(handle: GRoutingHandle): Point | undefined { if (this.edgeRouterRegistry) { const parent = handle.parent; if (!isRoutable(parent)) { @@ -341,12 +341,12 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return undefined; } - override mouseUp(_target: SModelElement, _event: MouseEvent): Action[] { + override mouseUp(_target: GModelElement, _event: MouseEvent): Action[] { this.pointPositionUpdater.resetPosition(); return []; } - override decorate(vnode: VNode, _element: SModelElement): VNode { + override decorate(vnode: VNode, _element: GModelElement): VNode { return vnode; } } diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index 17e187a..caf9877 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -23,21 +23,21 @@ import { EdgeRouterRegistry, ISnapper, ReconnectEdgeOperation, - SModelElement, - SModelRoot, - SRoutableElement, - SRoutingHandle, + GModelElement, + GModelRoot, + GRoutableElement, + GRoutingHandle, TYPES, canEditRouting, findParentByFeature, isConnectable, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; -import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/smodel-util'; -import { SReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; +import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; +import { GReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; import { BaseEditTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { @@ -102,11 +102,11 @@ export class EdgeEditTool extends BaseEditTool { export class EdgeEditListener extends DragAwareMouseListener implements ISelectionListener { // active selection data - protected edge?: SRoutableElement; - protected routingHandle?: SRoutingHandle; + protected edge?: GRoutableElement; + protected routingHandle?: GRoutingHandle; // new connectable (source or target) for edge - protected newConnectable?: SModelElement & Connectable; + protected newConnectable?: GModelElement & Connectable; // active reconnect handle data protected reconnectMode?: 'NEW_SOURCE' | 'NEW_TARGET'; @@ -115,11 +115,11 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti super(); } - protected isValidEdge(edge?: SRoutableElement): edge is SRoutableElement { + protected isValidEdge(edge?: GRoutableElement): edge is GRoutableElement { return edge !== undefined && edge.id !== feedbackEdgeId(edge.root) && isSelected(edge); } - protected setEdgeSelected(edge: SRoutableElement): void { + protected setEdgeSelected(edge: GRoutableElement): void { if (this.edge && this.edge.id !== edge.id) { // reset from a previously selected edge this.reset(); @@ -141,7 +141,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return this.edge !== undefined && isSelected(this.edge); } - protected setReconnectHandleSelected(edge: SRoutableElement, reconnectHandle: SReconnectHandle): void { + protected setReconnectHandleSelected(edge: GRoutableElement, reconnectHandle: GReconnectHandle): void { if (this.edge && this.edge.target && this.edge.source) { if (isSourceRoutingHandle(edge, reconnectHandle)) { this.tool.registerFeedback([ @@ -169,7 +169,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return this.reconnectMode === 'NEW_SOURCE'; } - protected setRoutingHandleSelected(edge: SRoutableElement, routingHandle: SRoutingHandle): void { + protected setRoutingHandleSelected(edge: GRoutableElement, routingHandle: GRoutingHandle): void { if (this.edge && this.edge.target && this.edge.source) { this.routingHandle = routingHandle; } @@ -179,7 +179,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return this.edge !== undefined && (this.edge.sourceId !== sourceId || this.edge.targetId !== targetId); } - protected setNewConnectable(connectable?: SModelElement & Connectable): void { + protected setNewConnectable(connectable?: GModelElement & Connectable): void { this.newConnectable = connectable; } @@ -191,7 +191,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return this.routingHandle !== undefined; } - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = super.mouseDown(target, event); if (event.button === 0) { const reconnectHandle = findParentByFeature(target, isReconnectHandle); @@ -214,7 +214,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return result; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const result = super.mouseMove(target, event); if (this.isMouseDrag) { // reset any selected connectables when we are dragging, maybe the user is just panning @@ -223,7 +223,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return result; } - override mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: GModelElement, event: MouseEvent): Action[] { const result = super.mouseUp(target, event); if (!this.isReadyToReconnect() && !this.isReadyToReroute()) { return result; @@ -249,7 +249,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return result; } - override mouseOver(target: SModelElement, _event: MouseEvent): Action[] { + override mouseOver(target: GModelElement, _event: MouseEvent): Action[] { if (this.edge && this.isReconnecting()) { const currentTarget = findParentByFeature(target, isConnectable); if (!this.newConnectable || currentTarget !== this.newConnectable) { @@ -269,7 +269,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti return []; } - selectionChanged(root: Readonly, selectedElements: string[]): void { + selectionChanged(root: Readonly, selectedElements: string[]): void { if (this.edge) { if (selectedElements.indexOf(this.edge.id) > -1) { // our active edge is still selected, nothing to do diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index c96178b..f2f1611 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Point, PointToPointLine } from '~glsp-sprotty'; +import { Bounds, Point, PointToPointLine } from '@eclipse-glsp/sprotty'; import { DrawMarqueeAction } from './marquee-tool-feedback'; export interface IMarqueeBehavior { diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 92d7d17..68a60cf 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -13,21 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, BoundsAware, DOMHelper, + GModelElement, + GModelRoot, + GNode, KeyListener, - SEdge, - SModelElement, - SModelRoot, - SNode, SelectAction, TYPES, isSelectable, isSelected -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; @@ -35,6 +34,7 @@ import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint- import { BaseEditTool } from '../base-tools'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; import { RemoveMarqueeAction } from './marquee-tool-feedback'; +import { GEdge } from '../../../model'; @injectable() export class MarqueeMouseTool extends BaseEditTool { @@ -61,40 +61,40 @@ export class MarqueeMouseTool extends BaseEditTool { export class MarqueeMouseListener extends DragAwareMouseListener { protected domHelper: DOMHelper; protected marqueeUtil: MarqueeUtil; - protected nodes: (SModelElement & BoundsAware)[]; + protected nodes: (GModelElement & BoundsAware)[]; protected edges: SVGGElement[]; protected previouslySelected: string[]; protected isActive = false; - constructor(domHelper: DOMHelper, root: SModelRoot, marqueeBehavior: IMarqueeBehavior | undefined) { + constructor(domHelper: DOMHelper, root: GModelRoot, marqueeBehavior: IMarqueeBehavior | undefined) { super(); this.domHelper = domHelper; this.marqueeUtil = new MarqueeUtil(marqueeBehavior); this.nodes = Array.from( root.index .all() - .map(e => e as SModelElement & BoundsAware) + .map(e => e as GModelElement & BoundsAware) .filter(e => isSelectable(e)) - .filter(e => e instanceof SNode) + .filter(e => e instanceof GNode) ); const sEdges = Array.from( root.index .all() - .filter(e => e instanceof SEdge) + .filter(e => e instanceof GEdge) .filter(e => isSelectable(e)) .map(e => e.id) ); this.edges = Array.from(document.querySelectorAll('g')).filter(e => sEdges.includes(this.domHelper.findSModelIdByDOMElement(e))); } - override mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { this.isActive = true; this.marqueeUtil.updateStartPoint(getAbsolutePosition(target, event)); if (event.ctrlKey) { this.previouslySelected = Array.from( target.root.index .all() - .map(e => e as SModelElement & BoundsAware) + .map(e => e as GModelElement & BoundsAware) .filter(e => isSelected(e)) .map(e => e.id) ); @@ -102,7 +102,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { return []; } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { this.marqueeUtil.updateCurrentPoint(getAbsolutePosition(target, event)); if (this.isActive) { const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isNodeMarked(toAbsoluteBounds(e))).map(e => e.id); @@ -113,7 +113,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { return []; } - override mouseUp(_target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(_target: GModelElement, event: MouseEvent): Action[] { this.isActive = false; if (event.shiftKey) { return [RemoveMarqueeAction.create()]; @@ -134,7 +134,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { @injectable() export class ShiftKeyListener extends KeyListener { - override keyUp(element: SModelElement, event: KeyboardEvent): Action[] { + override keyUp(element: GModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return []; } diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index f5fa387..6a7998b 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService, configureCommand, configureModelElement } from '~glsp-sprotty'; +import { FeatureModule, TYPES, bindAsService, configureCommand, configureModelElement } from '@eclipse-glsp/sprotty'; import { MarqueeMouseTool } from './marquee-mouse-tool'; import { MarqueeTool } from './marquee-tool'; import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-tool-feedback'; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts index b1848bf..e442891 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool-feedback.ts @@ -19,14 +19,14 @@ import { AnchorComputerRegistry, CommandExecutionContext, CommandReturn, + GChildElement, MouseListener, Point, - SChildElement, - SModelElement, - SModelRoot, + GModelElement, + GModelRoot, TYPES, hasObjectProp -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; export interface DrawMarqueeAction extends Action { @@ -95,12 +95,12 @@ export class MarqueeEndMovingMouseListener extends MouseListener { super(); } - override mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { return []; } } -export function marqueeId(root: SModelRoot): string { +export function marqueeId(root: GModelRoot): string { return root.id + '_' + MARQUEE; } @@ -122,9 +122,9 @@ export function drawMarquee(context: CommandExecutionContext, startPoint: Point, root.add(marquee); } -export function removeMarquee(root: SModelRoot): void { +export function removeMarquee(root: GModelRoot): void { const marquee = root.index.getById(marqueeId(root)); - if (marquee instanceof SChildElement) { + if (marquee instanceof GChildElement) { root.remove(marquee); } } diff --git a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts index f23df2c..06b2fa1 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, KeyListener, SModelElement } from '~glsp-sprotty'; +import { Action, KeyListener, GModelElement } from '@eclipse-glsp/sprotty'; import { EnableToolsAction } from '../../../base/tool-manager/tool'; import { BaseEditTool } from '../base-tools'; import { MarqueeMouseTool } from './marquee-mouse-tool'; @@ -36,7 +36,7 @@ export class MarqueeTool extends BaseEditTool { @injectable() export class MarqueeKeyListener extends KeyListener { - override keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return [EnableToolsAction.create([MarqueeMouseTool.ID])]; } diff --git a/packages/client/src/features/tools/marquee-selection/model.ts b/packages/client/src/features/tools/marquee-selection/model.ts index 1a61fde..61f3018 100644 --- a/packages/client/src/features/tools/marquee-selection/model.ts +++ b/packages/client/src/features/tools/marquee-selection/model.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { boundsFeature, Point, RectangularNode } from '~glsp-sprotty'; +import { boundsFeature, Point, RectangularNode } from '@eclipse-glsp/sprotty'; export class MarqueeNode extends RectangularNode { static override readonly DEFAULT_FEATURES = [boundsFeature]; diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 4087c3e..13470d8 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { RectangularNodeView, RenderingContext, svg } from '~glsp-sprotty'; +import { RectangularNodeView, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { MarqueeNode } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index 430160a..4fd94be 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '~glsp-sprotty'; +import { FeatureModule, TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; import { NodeCreationTool } from './node-creation-tool'; export const nodeCreationToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 20df9d3..329538e 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -18,13 +18,13 @@ import { Action, CreateNodeOperation, ISnapper, - SModelElement, - SNode, + GModelElement, + GNode, TYPES, TriggerNodeCreationAction, findParentByFeature, isCtrlOrCmd -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; @@ -54,7 +54,7 @@ export class NodeCreationTool extends BaseCreationTool(SDecoration.DEFAULT_FEATURES); + this.features = new Set(GDecoration.DEFAULT_FEATURES); } projectionCssClasses: string[]; projectedBounds?: Bounds; - override issues: SIssue[] = []; + override issues: GIssue[] = []; override type = 'marker'; computeProjectionCssClasses(): void { @@ -43,16 +43,16 @@ export class GIssueMarker extends SIssueMarker implements Projectable { } /** - * Retrieves the `SIssueMarker` contained by the provided model element as - * direct child or a newly instantiated `SIssueMarker` if no child - * `SIssueMarker` exists. - * @param modelElement for which the `SIssueMarker` should be retrieved or created. - * @returns the child `SIssueMarker` or a new `SIssueMarker` if no such child exists. + * Retrieves the `GIssueMarker` contained by the provided model element as + * direct child or a newly instantiated `GIssueMarker` if no child + * `GIssueMarker` exists. + * @param modelElement for which the `GIssueMarker` should be retrieved or created. + * @returns the child `GIssueMarker` or a new `GIssueMarker` if no such child exists. */ -export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMarker { +export function getOrCreateGIssueMarker(modelElement: GParentElement): GIssueMarker { let issueMarker: GIssueMarker | undefined; - issueMarker = getSIssueMarker(modelElement); + issueMarker = getGIssueMarker(modelElement); if (issueMarker === undefined) { issueMarker = new GIssueMarker(); @@ -66,12 +66,12 @@ export function getOrCreateSIssueMarker(modelElement: SParentElement): SIssueMar } /** - * Retrieves the `SIssueMarker` contained by the provided model element as - * direct child or `undefined` if such an `SIssueMarker` does not exist. - * @param modelElement for which the `SIssueMarker` should be retrieved. - * @returns the child `SIssueMarker` or `undefined` if no such child exists. + * Retrieves the `GIssueMarker` contained by the provided model element as + * direct child or `undefined` if such an `GIssueMarker` does not exist. + * @param modelElement for which the `GIssueMarker` should be retrieved. + * @returns the child `GIssueMarker` or `undefined` if no such child exists. */ -export function getSIssueMarker(modelElement: SParentElement): GIssueMarker | undefined { +export function getGIssueMarker(modelElement: GParentElement): GIssueMarker | undefined { let issueMarker: GIssueMarker | undefined; for (const child of modelElement.children) { @@ -84,13 +84,13 @@ export function getSIssueMarker(modelElement: SParentElement): GIssueMarker | un } /** - * Creates an `SIssue` with `severity` and `message` set according to + * Creates an `GIssue` with `severity` and `message` set according to * the `kind` and `description` of the provided `Marker`. - * @param marker `Marker` for that an `SIssue` should be created. - * @returns the created `SIssue`. + * @param marker `Marker` for that an `GIssue` should be created. + * @returns the created `GIssue`. */ -export function createSIssue(marker: Marker, parent?: SParentElement): SIssue { - const issue = new SIssue(); +export function createGIssue(marker: Marker, parent?: GParentElement): GIssue { + const issue = new GIssue(); issue.message = marker.description; switch (marker.kind) { @@ -110,8 +110,8 @@ export function createSIssue(marker: Marker, parent?: SParentElement): SIssue { return issue; } -export function getSeverity(marker: SIssueMarker): SIssueSeverity { - let currentSeverity: SIssueSeverity = 'info'; +export function getSeverity(marker: GIssueMarker): GIssueSeverity { + let currentSeverity: GIssueSeverity = 'info'; for (const severity of marker.issues.map(s => s.severity)) { if (severity === 'error') { return severity; diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 0c3fb98..e08fb90 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -13,77 +13,48 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { BoundsAware, GChildElement, GIssue, GModelElement, GModelRoot, GNode, GParentElement } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; import { Container } from 'inversify'; import 'mocha'; import 'reflect-metadata'; -import { - BoundsAware, - SChildElement, - SGraphFactory, - SIssue, - SIssueMarker, - SModelElement, - SModelRoot, - SNodeSchema, - SParentElement, - TYPES -} from '~glsp-sprotty'; import { defaultModule } from '../../base/default.module'; +import { GGraph } from '../../model'; import { decorationModule } from '../decoration/decoration-module'; +import { GIssueMarker } from './issue-marker'; import { MarkerNavigator } from './marker-navigator'; import { markerNavigatorModule } from './validation-modules'; describe('MarkerNavigator', () => { const container = new Container(); container.load(defaultModule, decorationModule, markerNavigatorModule); - // eslint-disable-next-line deprecation/deprecation - container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); - // eslint-disable-next-line deprecation/deprecation - const graphFactory = container.get(TYPES.IModelFactory); const markerNavigator = container.get(MarkerNavigator); + function createRoot(...nodeIds: string[]): GModelRoot { + const root = new GGraph(); + root.features = new Set(GGraph.DEFAULT_FEATURES); + root.id = 'root'; + root.type = 'graph'; + nodeIds.forEach(id => { + const node = new GNode(); + node.id = id; + node.type = 'node'; + node.features = new Set(GNode.DEFAULT_FEATURES); + root.add(node); + }); + return root; + } + + const rootWithoutAnyMarkers = createRoot('1'); + + const rootWithMarkers = createRoot('bottom-right', 'top-right', 'top-left', 'bottom-left'); - const rootWithoutAnyMarkers = graphFactory.createRoot({ - id: 'root', - type: 'graph', - children: [ - { - id: '1', - type: 'node' - } as SNodeSchema - ] - }) as SModelRoot; - - const rootWithMarkers = graphFactory.createRoot({ - id: 'root', - type: 'graph', - children: [ - { - id: 'bottom-right', - type: 'node' - } as SNodeSchema, - { - id: 'top-right', - type: 'node' - } as SNodeSchema, - { - id: 'top-left', - type: 'node' - } as SNodeSchema, - { - id: 'bottom-left', - type: 'node' - } as SNodeSchema - ] - }) as SModelRoot; - - const elementTopLeft = rootWithMarkers.children[2] as BoundsAware & SChildElement; + const elementTopLeft = rootWithMarkers.children[2] as BoundsAware & GChildElement; elementTopLeft.bounds = { width: 10, height: 10, x: 100, y: 100 }; - const elementTopRight = rootWithMarkers.children[1] as BoundsAware & SChildElement; + const elementTopRight = rootWithMarkers.children[1] as BoundsAware & GChildElement; elementTopRight.bounds = { width: 10, height: 10, x: 200, y: 100 }; - const elementBottomLeft = rootWithMarkers.children[3] as BoundsAware & SChildElement; + const elementBottomLeft = rootWithMarkers.children[3] as BoundsAware & GChildElement; elementBottomLeft.bounds = { width: 10, height: 10, x: 100, y: 200 }; - const elementBottomRight = rootWithMarkers.children[0] as BoundsAware & SChildElement; + const elementBottomRight = rootWithMarkers.children[0] as BoundsAware & GChildElement; elementBottomRight.bounds = { width: 10, height: 10, x: 200, y: 200 }; beforeEach('clear issue marker', () => { @@ -177,34 +148,34 @@ describe('MarkerNavigator', () => { }); }); -function clearMarker(elem: SParentElement): void { +function clearMarker(elem: GParentElement): void { elem.children.filter(isMarker).forEach(marker => elem.remove(marker)); } -function setIssues(elem: SParentElement, issues: SIssue[]): SIssueMarker { +function setIssues(elem: GParentElement, issues: GIssue[]): GIssueMarker { const marker = getOrCreateMarker(elem); marker.issues = issues; return marker; } -function getOrCreateMarker(elem: SParentElement): SIssueMarker { +function getOrCreateMarker(elem: GParentElement): GIssueMarker { const marker = findMarker(elem); - if (marker instanceof SIssueMarker) { + if (marker instanceof GIssueMarker) { return marker; } - return createMarker(elem as SParentElement); + return createMarker(elem as GParentElement); } -function findMarker(elem: SParentElement): SIssueMarker | undefined { +function findMarker(elem: GParentElement): GIssueMarker | undefined { return elem.children.find(isMarker); } -function isMarker(element: SModelElement): element is SIssueMarker { - return element instanceof SIssueMarker; +function isMarker(element: GModelElement): element is GIssueMarker { + return element instanceof GIssueMarker; } -function createMarker(elem: SParentElement): SIssueMarker { - const newMarker = new SIssueMarker(); +function createMarker(elem: GParentElement): GIssueMarker { + const newMarker = new GIssueMarker(); newMarker.type = 'marker'; newMarker.id = `${elem.id}_marker`; elem.add(newMarker); diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index b1780dc..883ca5e 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -13,20 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, BoundsAware, CenterAction, + GIssueSeverity, + GModelElement, + GModelRoot, IActionHandler, IContextMenuItemProvider, KeyListener, MenuItem, Point, - SIssueMarker, - SIssueSeverity, - SModelElement, - SModelRoot, SelectAction, Selectable, TYPES, @@ -36,18 +34,20 @@ import { isBoundsAware, isSelectable, matchesKeystroke -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { SelectionService } from '../../base/selection-service'; +import { getElements, isSelectableAndBoundsAware } from '../../utils/gmodel-util'; import { MarkerPredicates, collectIssueMarkers } from '../../utils/marker'; -import { getElements, isSelectableAndBoundsAware } from '../../utils/smodel-util'; +import { GIssueMarker } from './issue-marker'; export interface NavigateToMarkerAction extends Action { kind: typeof NavigateToMarkerAction.KIND; direction: MarkerNavigationDirection; selectedElementIds?: string[]; - severities: SIssueSeverity[]; + severities: GIssueSeverity[]; } export type MarkerNavigationDirection = 'next' | 'previous'; @@ -62,7 +62,7 @@ export namespace NavigateToMarkerAction { export function create(options: { direction?: MarkerNavigationDirection; selectedElementIds?: string[]; - severities?: SIssueSeverity[]; + severities?: GIssueSeverity[]; }): NavigateToMarkerAction { return { kind: KIND, @@ -72,8 +72,8 @@ export namespace NavigateToMarkerAction { }; } } -export class SModelElementComparator { - compare(_one: SModelElement, _other: SModelElement): number { +export class GModelElementComparator { + compare(_one: GModelElement, _other: GModelElement): number { return 0; } } @@ -81,7 +81,7 @@ export class SModelElementComparator { /** Specifies the order of two selectable and bounds-aware elements left-to-right and top-to-bottom. */ @injectable() export class LeftToRightTopToBottomComparator { - compare(one: SModelElement, other: SModelElement): number { + compare(one: GModelElement, other: GModelElement): number { const boundsOne = findParentByFeature(one, isSelectableAndBoundsAware); const boundsOther = findParentByFeature(other, isSelectableAndBoundsAware); if (boundsOne && boundsOther) { @@ -103,16 +103,16 @@ export class LeftToRightTopToBottomComparator { */ @injectable() export class MarkerNavigator { - static readonly ALL_SEVERITIES: SIssueSeverity[] = ['error', 'warning', 'info']; + static readonly ALL_SEVERITIES: GIssueSeverity[] = ['error', 'warning', 'info']; - @inject(SModelElementComparator) - protected markerComparator: SModelElementComparator; + @inject(GModelElementComparator) + protected markerComparator: GModelElementComparator; next( - root: Readonly, - current?: SModelElement & BoundsAware, - predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL - ): SIssueMarker | undefined { + root: Readonly, + current?: GModelElement & BoundsAware, + predicate: (marker: GIssueMarker) => boolean = MarkerPredicates.ALL + ): GIssueMarker | undefined { const markers = this.getMarkers(root, predicate); if (current === undefined) { return markers.length > 0 ? markers[0] : undefined; @@ -121,10 +121,10 @@ export class MarkerNavigator { } previous( - root: Readonly, - current?: SModelElement & BoundsAware, - predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL - ): SIssueMarker | undefined { + root: Readonly, + current?: GModelElement & BoundsAware, + predicate: (marker: GIssueMarker) => boolean = MarkerPredicates.ALL + ): GIssueMarker | undefined { const markers = this.getMarkers(root, predicate); if (current === undefined) { return markers.length > 0 ? markers[0] : undefined; @@ -132,12 +132,12 @@ export class MarkerNavigator { return markers[this.getPreviousIndex(current, markers) % markers.length]; } - protected getMarkers(root: Readonly, predicate: (marker: SIssueMarker) => boolean): SIssueMarker[] { + protected getMarkers(root: Readonly, predicate: (marker: GIssueMarker) => boolean): GIssueMarker[] { const markers = collectIssueMarkers(root); return markers.filter(predicate).sort(this.markerComparator.compare); } - protected getNextIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]): number { + protected getNextIndex(current: GModelElement & BoundsAware, markers: GIssueMarker[]): number { for (let index = 0; index < markers.length; index++) { if (this.markerComparator.compare(markers[index], current) > 0) { return index; @@ -146,7 +146,7 @@ export class MarkerNavigator { return 0; } - protected getPreviousIndex(current: SModelElement & BoundsAware, markers: SIssueMarker[]): number { + protected getPreviousIndex(current: GModelElement & BoundsAware, markers: GIssueMarker[]): number { for (let index = markers.length - 1; index >= 0; index--) { if (this.markerComparator.compare(markers[index], current) < 0) { return index; @@ -158,8 +158,8 @@ export class MarkerNavigator { @injectable() export class NavigateToMarkerActionHandler implements IActionHandler { - @inject(SModelElementComparator) - protected markerComparator: SModelElementComparator; + @inject(GModelElementComparator) + protected markerComparator: GModelElementComparator; @inject(MarkerNavigator) protected markerNavigator: MarkerNavigator; @@ -182,14 +182,14 @@ export class NavigateToMarkerActionHandler implements IActionHandler { } } - protected getSelectedElements(action: NavigateToMarkerAction): (SModelElement & Selectable)[] { + protected getSelectedElements(action: NavigateToMarkerAction): (GModelElement & Selectable)[] { if (action.selectedElementIds && action.selectedElementIds.length > 0) { return getElements(this.selectionService.getModelRoot().index, action.selectedElementIds, isSelectable); } return this.selectionService.getSelectedElements(); } - protected getTarget(action: NavigateToMarkerAction, selected: SModelElement[]): SIssueMarker | undefined { + protected getTarget(action: NavigateToMarkerAction, selected: GModelElement[]): GIssueMarker | undefined { const root = this.selectionService.getModelRoot(); const target = selected.sort(this.markerComparator.compare).find(isBoundsAware); if (action.direction === 'previous') { @@ -199,7 +199,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { } } - protected matchesSeverities(action: NavigateToMarkerAction, marker: SIssueMarker): boolean { + protected matchesSeverities(action: NavigateToMarkerAction, marker: GIssueMarker): boolean { return marker.issues.find(issue => action.severities.includes(issue.severity)) !== undefined; } } @@ -208,7 +208,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemProvider { @inject(SelectionService) protected selectionService: SelectionService; - getItems(root: Readonly, lastMousePosition?: Point): Promise { + getItems(root: Readonly, lastMousePosition?: Point): Promise { const selectedElementIds = Array.from(this.selectionService.getSelectedElementIDs()); const hasMarkers = collectIssueMarkers(root).length > 0; return Promise.resolve([ @@ -240,7 +240,7 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP @injectable() export class MarkerNavigatorKeyListener extends KeyListener { - override keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Period', 'ctrl')) { return [NavigateToMarkerAction.create({ direction: 'next' })]; } else if (matchesKeystroke(event, 'Comma', 'ctrl')) { diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 388c7d9..f67fe05 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -23,17 +23,16 @@ import { IActionHandler, ICommand, Marker, - SIssueMarker, - SParentElement, + GParentElement, SetMarkersAction, TYPES, hasArrayProp -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; -import { removeCssClasses } from '../../utils/smodel-util'; -import { GIssueMarker, createSIssue, getOrCreateSIssueMarker, getSIssueMarker, getSeverity } from './issue-marker'; +import { removeCssClasses } from '../../utils/gmodel-util'; +import { GIssueMarker, createGIssue, getOrCreateGIssueMarker, getGIssueMarker, getSeverity } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and @@ -141,7 +140,7 @@ export namespace ApplyMarkersAction { } /** - * Handles {@link ApplyMarkersAction}s by creating the corresponding {@link SIssueMarker}s and + * Handles {@link ApplyMarkersAction}s by creating the corresponding {@link GIssueMarker}s and * adding them to the graphical model. */ @injectable() @@ -155,9 +154,9 @@ export class ApplyMarkersCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { this.action.markers.forEach(marker => { const modelElement = context.root.index.getById(marker.elementId); - if (modelElement instanceof SParentElement) { - const issueMarker = getOrCreateSIssueMarker(modelElement); - const issue = createSIssue(marker); + if (modelElement instanceof GParentElement) { + const issueMarker = getOrCreateGIssueMarker(modelElement); + const issue = createGIssue(marker); issueMarker.issues.push(issue); if (issueMarker instanceof GIssueMarker) { issueMarker.computeProjectionCssClasses(); @@ -169,7 +168,7 @@ export class ApplyMarkersCommand extends FeedbackCommand { } } -function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { +function addMaxSeverityCSSClassToIssueParent(modelElement: GParentElement, issueMarker: GIssueMarker): void { const maxSeverityCSSClass = getSeverity(issueMarker); if (!modelElement.cssClasses) { modelElement.cssClasses = [maxSeverityCSSClass]; @@ -179,7 +178,7 @@ function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issue } } -function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { +function removeCSSClassFromIssueParent(modelElement: GParentElement, issueMarker: GIssueMarker): void { const severity = getSeverity(issueMarker); removeCssClasses(modelElement, [severity]); } @@ -198,8 +197,8 @@ export class DeleteMarkersCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { this.action.markers.forEach(marker => { const modelElement = context.root.index.getById(marker.elementId); - if (modelElement instanceof SParentElement) { - const issueMarker = getSIssueMarker(modelElement); + if (modelElement instanceof GParentElement) { + const issueMarker = getGIssueMarker(modelElement); if (issueMarker) { removeCSSClassFromIssueParent(modelElement, issueMarker); for (let index = 0; index < issueMarker.issues.length; ++index) { diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 7077390..3f78560 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, SetMarkersAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '~glsp-sprotty'; +import { FeatureModule, SetMarkersAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import { + GModelElementComparator, LeftToRightTopToBottomComparator, MarkerNavigator, MarkerNavigatorContextMenuItemProvider, MarkerNavigatorKeyListener, NavigateToMarkerAction, - NavigateToMarkerActionHandler, - SModelElementComparator + NavigateToMarkerActionHandler } from './marker-navigator'; import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersActionHandler, ValidationFeedbackEmitter } from './validate'; @@ -34,7 +34,7 @@ export const validationModule = new FeatureModule((bind, _unbind, isBound) => { }); export const markerNavigatorModule = new FeatureModule((bind, _unbind, isBound) => { - bind(SModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); + bind(GModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); bind(MarkerNavigator).toSelf().inSingletonScope(); configureActionHandler({ bind, isBound }, NavigateToMarkerAction.KIND, NavigateToMarkerActionHandler); }); diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index 457f080..f59e7ff 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable } from 'inversify'; -import { Action, IActionHandler, ICommand, ScrollMouseListener, SModelElement } from '~glsp-sprotty'; +import { Action, IActionHandler, ICommand, ScrollMouseListener, GModelElement } from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; @@ -32,7 +32,7 @@ export class GLSPScrollMouseListener extends ScrollMouseListener implements IAct } } - override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + override mouseDown(target: GModelElement, event: MouseEvent): (Action | Promise)[] { if (this.preventScrolling) { return []; } diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index 97e40b1..8c9d469 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -14,23 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { inject, injectable } from 'inversify'; import { Action, Bounds, + BoundsAwareViewportCommand, Dimension, - hasArrayProp, + GChildElement, + GModelElement, + GModelRoot, + TYPES, Viewport, - BoundsAwareViewportCommand, getRouteBounds, - isViewport, - SChildElement, - SEdge, - SModelElement, - SModelRoot, - TYPES -} from '~glsp-sprotty'; -import { inject, injectable } from 'inversify'; -import { calcElementAndRoute } from '../../utils/smodel-util'; + hasArrayProp, + isViewport +} from '@eclipse-glsp/sprotty'; +import { calcElementAndRoute } from '../../utils/gmodel-util'; +import { GEdge } from '../../model'; export interface RepositionAction extends Action { kind: typeof RepositionAction.KIND; @@ -63,13 +63,13 @@ export class RepositionCommand extends BoundsAwareViewportCommand { super(true); } - protected override boundsInViewport(element: SModelElement, bounds: Bounds, viewport: SModelRoot & Viewport): Bounds { - if (element instanceof SChildElement && element.parent !== viewport) { + protected override boundsInViewport(element: GModelElement, bounds: Bounds, viewport: GModelRoot & Viewport): Bounds { + if (element instanceof GChildElement && element.parent !== viewport) { return this.boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds, viewport); - } else if (element instanceof SEdge) { + } else if (element instanceof GEdge) { const edgeBounds = getRouteBounds(calcElementAndRoute(element).newRoutingPoints ?? []); - if (element instanceof SChildElement && element.parent !== viewport) { + if (element instanceof GChildElement && element.parent !== viewport) { return this.boundsInViewport(element.parent, element.parent.localToParent(edgeBounds), viewport); } @@ -83,7 +83,7 @@ export class RepositionCommand extends BoundsAwareViewportCommand { return this.action.elementIDs; } - getNewViewport(bounds: Bounds, model: SModelRoot): Viewport | undefined { + getNewViewport(bounds: Bounds, model: GModelRoot): Viewport | undefined { if (!Dimension.isValid(model.canvasBounds)) { return undefined; } @@ -108,7 +108,7 @@ export class RepositionCommand extends BoundsAwareViewportCommand { return undefined; } - protected isFullyVisible(bounds: Bounds, viewport: SModelRoot & Viewport): boolean { + protected isFullyVisible(bounds: Bounds, viewport: GModelRoot & Viewport): boolean { return ( bounds.x >= viewport.scroll.x && bounds.x + bounds.width <= viewport.scroll.x + viewport.canvasBounds.width / viewport.zoom && diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 71bd41f..68718ca 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionHandler, ViewerOptions, TYPES, Action } from '~glsp-sprotty'; +import { IActionHandler, ViewerOptions, TYPES, Action } from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { injectable, inject } from 'inversify'; import { FocusDomAction } from '../accessibility/actions'; diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 9d9eed0..6947078 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -25,7 +25,7 @@ import { SetViewportCommand, TYPES, ZoomMouseListener -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; import { RestoreViewportHandler } from './viewport-handler'; diff --git a/packages/client/src/glsp-sprotty/augmented-actions.ts b/packages/client/src/glsp-sprotty/augmented-actions.ts deleted file mode 100644 index 00c4b3b..0000000 --- a/packages/client/src/glsp-sprotty/augmented-actions.ts +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -/* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable-next-line no-restricted-imports*/ -import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; -import { SetBoundsAction } from 'sprotty-protocol/lib/actions'; - -/** - * Use module augmentation to add namespaces and the `is` utility function to sprotty actions that directly reused by GLSP - */ -declare module 'sprotty-protocol/lib/actions' { - namespace SetBoundsAction { - export function is(object: any): object is SetBoundsAction; - } -} - -SetBoundsAction.is = (object: any): object is SetBoundsAction => - Action.hasKind(object, SetBoundsAction.KIND) && hasArrayProp(object, 'bounds'); - -export { SetBoundsAction }; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6a724e4..c685677 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -14,10 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from '@eclipse-glsp/sprotty'; + // ------------------ Base ------------------ export * from './base/action-dispatcher'; export * from './base/action-handler-registry'; -export * from './base/argumentable'; +export * from './base/args-feature'; export * from './base/auto-complete/auto-complete-actions'; export * from './base/auto-complete/auto-complete-widget'; export * from './base/auto-complete/validation-decorator'; @@ -41,7 +43,6 @@ export * from './base/tool-manager/tool-manager'; export * from './base/view/mouse-tool'; export * from './base/view/view-registry'; -// // ------------------ Features ------------------ export * from './base/feedback/css-feedback'; export * from './base/view/mouse-tool'; @@ -109,18 +110,16 @@ export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; -export * from './glsp-sprotty'; -export * from './glsp-sprotty/types'; -// + // ------------------ Misc ------------------ export * from './default-modules'; -export * from './lib/model'; +export * from './model'; export * from './standalone-modules'; export * from './utils/argument-utils'; +export * from './utils/gmodel-util'; export * from './utils/html-utils'; export * from './utils/layout-utils'; export * from './utils/marker'; -export * from './utils/smodel-util'; export * from './utils/viewpoint-util'; export * from './views'; // ------------------ DI Modules ------------------ diff --git a/packages/client/src/lib/model.ts b/packages/client/src/model.ts similarity index 68% rename from packages/client/src/lib/model.ts rename to packages/client/src/model.ts index ce64c6d..3921802 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,28 +14,32 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + Args, Bounds, - exportFeature, - getRouteBounds, - isBounds, + GModelElementSchema, + GShapeElement, Point, - SEdge, - SGraph, - SModelElement, - SModelElementSchema, - viewportFeature -} from '~glsp-sprotty'; -import { Containable, containerFeature } from '../features/hints/model'; + SEdgeImpl, + SGraphImpl, + getRouteBounds, + isBounds +} from '@eclipse-glsp/sprotty'; +import { ArgsAware, argsFeature } from './base/args-feature'; +import { Containable, containerFeature } from './features/hints/model'; -export class GLSPGraph extends SGraph implements Containable { - static override readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, containerFeature]; - dirty = false; - isContainableElement(input: string | SModelElement | SModelElementSchema): boolean { +export class GGraph extends SGraphImpl implements Containable, ArgsAware { + static override readonly DEFAULT_FEATURES = [...SGraphImpl.DEFAULT_FEATURES, containerFeature, argsFeature]; + args?: Args; + isContainableElement(_input: string | GShapeElement | GModelElementSchema): boolean { return true; } } -export class GEdge extends SEdge { +export class GEdge extends SEdgeImpl implements ArgsAware { + static override readonly DEFAULT_FEATURES = [...SEdgeImpl.DEFAULT_FEATURES, argsFeature]; + + args?: Args; + override localToParent(point: Point | Bounds): Bounds { const bounds = getRouteBounds(this.routingPoints); const result = { diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index d5e4f65..5e6cbca 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -28,7 +28,7 @@ import { TYPES, UpdateProgressAction, configureActionHandler -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; import { standaloneExportModule } from './features/export/export-modules'; import { saveModule } from './features/save/save-module'; diff --git a/packages/client/src/utils/argument-utils.ts b/packages/client/src/utils/argument-utils.ts index 74b96ec..4b4a747 100644 --- a/packages/client/src/utils/argument-utils.ts +++ b/packages/client/src/utils/argument-utils.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement } from '~glsp-sprotty'; -import { hasArguments } from '../base/argumentable'; +import { GModelElement } from '@eclipse-glsp/sprotty'; +import { hasArgs } from '../base/args-feature'; export namespace GArgument { export function asNumber(argValue: string | number | boolean): number | undefined { @@ -41,24 +41,24 @@ export namespace GArgument { return argValues.map(asBoolean); } - export function getArgument(element: SModelElement | undefined, key: string): string | number | boolean | undefined { - return hasArguments(element) ? element.args[key] : undefined; + export function getArgument(element: GModelElement | undefined, key: string): string | number | boolean | undefined { + return hasArgs(element) ? element.args[key] : undefined; } - export function getNumber(element: SModelElement | undefined, key: string): number | undefined { - return hasArguments(element) ? asNumber(element.args[key]) : undefined; + export function getNumber(element: GModelElement | undefined, key: string): number | undefined { + return hasArgs(element) ? asNumber(element.args[key]) : undefined; } - export function getString(element: SModelElement | undefined, key: string): string | undefined { - return hasArguments(element) ? asString(element.args[key]) : undefined; + export function getString(element: GModelElement | undefined, key: string): string | undefined { + return hasArgs(element) ? asString(element.args[key]) : undefined; } - export function getBoolean(element: SModelElement | undefined, key: string): boolean | undefined { - return hasArguments(element) ? asBoolean(element.args[key]) : undefined; + export function getBoolean(element: GModelElement | undefined, key: string): boolean | undefined { + return hasArgs(element) ? asBoolean(element.args[key]) : undefined; } - export function getArguments(element: SModelElement | undefined, ...keys: string[]): (number | boolean | string)[] | undefined { - if (!hasArguments(element)) { + export function getArguments(element: GModelElement | undefined, ...keys: string[]): (number | boolean | string)[] | undefined { + if (!hasArgs(element)) { return undefined; } const values = []; @@ -71,17 +71,17 @@ export namespace GArgument { return values; } - export function getNumbers(element: SModelElement | undefined, ...keys: string[]): (number | undefined)[] | undefined { + export function getNumbers(element: GModelElement | undefined, ...keys: string[]): (number | undefined)[] | undefined { const values = getArguments(element, ...keys); return values ? asNumbers(values) : undefined; } - export function getStrings(element: SModelElement | undefined, ...keys: string[]): (string | undefined)[] | undefined { + export function getStrings(element: GModelElement | undefined, ...keys: string[]): (string | undefined)[] | undefined { const values = getArguments(element, ...keys); return values ? asStrings(values) : undefined; } - export function getBooleans(element: SModelElement | undefined, ...keys: string[]): (boolean | undefined)[] | undefined { + export function getBooleans(element: GModelElement | undefined, ...keys: string[]): (boolean | undefined)[] | undefined { const values = getArguments(element, ...keys); return values ? asBooleans(values) : undefined; } @@ -94,7 +94,7 @@ export namespace GArgument { export namespace EdgePadding { const KEY = 'edgePadding'; - export function from(element: SModelElement | undefined): number | undefined { + export function from(element: GModelElement | undefined): number | undefined { return GArgument.getNumber(element, KEY); } } @@ -114,7 +114,7 @@ export class CornerRadius { public readonly bottomLeft: number = topRight ) {} - static from(element: SModelElement | undefined): CornerRadius | undefined { + static from(element: GModelElement | undefined): CornerRadius | undefined { const radius = GArgument.getNumbers( element, this.KEY_RADIUS_TOP_LEFT, diff --git a/packages/client/src/utils/smodel-util.spec.ts b/packages/client/src/utils/gmodel-util.spec.ts similarity index 91% rename from packages/client/src/utils/smodel-util.spec.ts rename to packages/client/src/utils/gmodel-util.spec.ts index 7b3f241..ea4c815 100644 --- a/packages/client/src/utils/smodel-util.spec.ts +++ b/packages/client/src/utils/gmodel-util.spec.ts @@ -13,28 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { expect } from 'chai'; -import { Container } from 'inversify'; import { AbstractEdgeRouter, EdgeRouterRegistry, + GNode, + GRoutableElement, + GRoutingHandle, LinearRouteOptions, Point, ResolvedHandleMove, - RoutedPoint, - SEdge, - SGraph, - SNode, - SRoutableElement, - SRoutingHandle -} from '~glsp-sprotty'; + RoutedPoint +} from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { Container } from 'inversify'; import { routingModule } from '../features/routing/routing-module'; -import { ALL_ROUTING_POINTS, ROUTE_KINDS, ROUTING_POINT_KINDS, calcRoute } from './smodel-util'; +import { ALL_ROUTING_POINTS, ROUTE_KINDS, ROUTING_POINT_KINDS, calcRoute } from './gmodel-util'; +import { GEdge, GGraph } from '../model'; class TestRouter extends AbstractEdgeRouter { kind = 'test-router'; - route(edge: SRoutableElement): RoutedPoint[] { + route(edge: GRoutableElement): RoutedPoint[] { const pureRoute = edge.routingPoints.map( (point, idx) => { @@ -51,11 +50,11 @@ class TestRouter extends AbstractEdgeRouter { ]; } - createRoutingHandles(edge: SRoutableElement): void { + createRoutingHandles(edge: GRoutableElement): void { // do nothing } - protected getOptions(edge: SRoutableElement): LinearRouteOptions { + protected getOptions(edge: GRoutableElement): LinearRouteOptions { return { minimalPointDistance: 0, selfEdgeOffset: 0, @@ -63,32 +62,32 @@ class TestRouter extends AbstractEdgeRouter { }; } - protected getInnerHandlePosition(edge: SRoutableElement, route: RoutedPoint[], handle: SRoutingHandle): Point | undefined { + protected getInnerHandlePosition(edge: GRoutableElement, route: RoutedPoint[], handle: GRoutingHandle): Point | undefined { return undefined; } - protected applyInnerHandleMoves(edge: SRoutableElement, moves: ResolvedHandleMove[]): void { + protected applyInnerHandleMoves(edge: GRoutableElement, moves: ResolvedHandleMove[]): void { // do nothing } } describe('SModel Util', () => { describe('calcRoute', () => { - const graph = new SGraph(); + const graph = new GGraph(); - const source = new SNode(); + const source = new GNode(); source.id = 'node0'; source.position = { x: 0, y: 0 }; source.size = { width: 0, height: 0 }; graph.add(source); - const target = new SNode(); + const target = new GNode(); target.id = 'node1'; target.position = { x: 100, y: 100 }; target.size = { width: 0, height: 0 }; graph.add(target); - const edge = new SEdge(); + const edge = new GEdge(); edge.id = 'edge0'; edge.sourceId = 'node0'; edge.targetId = 'node1'; diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/gmodel-util.ts similarity index 79% rename from packages/client/src/utils/smodel-util.ts rename to packages/client/src/utils/gmodel-util.ts index 5e9101d..1b0b1dc 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -29,19 +29,19 @@ import { remove, RoutedPoint, Selectable, - SModelElement, - SModelElementSchema, - SRoutableElement, - SRoutingHandle, - TypeGuard -} from '~glsp-sprotty'; + GModelElement, + GRoutableElement, + GRoutingHandle, + TypeGuard, + GModelElementSchema +} from '@eclipse-glsp/sprotty'; /** - * Helper type to represent a filter predicate for {@link SModelElement}s. This is used to retrieve + * Helper type to represent a filter predicate for {@link GModelElement}s. This is used to retrieve * elements from the {@link ModelIndexImpl} that also conform to a second type `T`. Its mainly used for * retrieving elements that also implement a certain model features (e.g. selectable) */ -export type ModelFilterPredicate = (modelElement: SModelElement) => modelElement is SModelElement & T; +export type ModelFilterPredicate = (modelElement: GModelElement) => modelElement is GModelElement & T; /** * Retrieves all elements from the given {@link ModelIndexImpl} that match the given {@link ModelFilterPredicate} @@ -50,8 +50,8 @@ export type ModelFilterPredicate = (modelElement: SModelElement) => modelElem * @returns A {@link FluentIterable} of all indexed element that match the predicate * (correctly casted to also include the additional type of the predicate). */ -export function filter(index: ModelIndexImpl, predicate: ModelFilterPredicate): FluentIterable { - return index.all().filter(predicate) as FluentIterable; +export function filter(index: ModelIndexImpl, predicate: ModelFilterPredicate): FluentIterable { + return index.all().filter(predicate) as FluentIterable; } /** @@ -64,7 +64,7 @@ export function filter(index: ModelIndexImpl, predicate: ModelFilterPredicate export function forEachElement( index: ModelIndexImpl, predicate: ModelFilterPredicate, - runnable: (modelElement: SModelElement & T) => void + runnable: (modelElement: GModelElement & T) => void ): void { filter(index, predicate).forEach(runnable); } @@ -76,7 +76,7 @@ export function forEachElement( * @returns An array of all indexed element that match the predicate * (correctly casted to also include the additional type of the predicate). */ -export function getMatchingElements(index: ModelIndexImpl, predicate: ModelFilterPredicate): (SModelElement & T)[] { +export function getMatchingElements(index: ModelIndexImpl, predicate: ModelFilterPredicate): (GModelElement & T)[] { return Array.from(filter(index, predicate)); } @@ -88,9 +88,9 @@ export function getMatchingElements(index: ModelIndexImpl, predicate: ModelFi * @param guard Optional typeguard. If defined only elements that match the guard will be returned. * @returns An array of the model elements that correspond to the given ids and filter predicate. */ -export function getElements(index: ModelIndexImpl, elementsIDs: string[], guard?: TypeGuard): S[] { +export function getElements(index: ModelIndexImpl, elementsIDs: string[], guard?: TypeGuard): S[] { // Internal filter function that filters out undefined model elements and runs an optional typeguard check. - const filterFn = (element?: SModelElement): element is S => { + const filterFn = (element?: GModelElement): element is S => { if (element !== undefined) { return guard ? guard(element) : true; } @@ -128,81 +128,81 @@ export function isNotUndefined(element: T | undefined): element is T { } /** - * Adds a set of css classes to the given {@link SModelElement}. + * Adds a set of css classes to the given {@link GModelElement}. * @param element The element to which the css classes should be added. * @param cssClasses The set of css classes as string array. */ -export function addCssClasses(element: SModelElement, cssClasses: string[]): void { +export function addCssClasses(element: GModelElement, cssClasses: string[]): void { const elementCssClasses: string[] = element.cssClasses ?? []; distinctAdd(elementCssClasses, ...cssClasses); element.cssClasses = elementCssClasses; } /** - * Removes a set of css classes from the given {@link SModelElement}. + * Removes a set of css classes from the given {@link GModelElement}. * @param element The element from which the css classes should be removed. * @param cssClasses The set of css classes as string array. */ -export function removeCssClasses(root: SModelElement, cssClasses: string[]): void { +export function removeCssClasses(root: GModelElement, cssClasses: string[]): void { if (!root.cssClasses || root.cssClasses.length === 0) { return; } remove(root.cssClasses, ...cssClasses); } -export function isNonRoutableSelectedMovableBoundsAware(element: SModelElement): element is SelectableBoundsAware { +export function isNonRoutableSelectedMovableBoundsAware(element: GModelElement): element is SelectableBoundsAware { return isNonRoutableSelectedBoundsAware(element) && isMoveable(element); } /** - * A typeguard function to check wether a given {@link SModelElement} implements the {@link BoundsAware} model feature, - * the {@link Selectable} model feature and is actually selected. In addition, the element must not be a {@link SRoutableElement}. + * A typeguard function to check wether a given {@link GModelElement} implements the {@link BoundsAware} model feature, + * the {@link Selectable} model feature and is actually selected. In addition, the element must not be a {@link GRoutableElement}. * @param element The element to check. * @returns A type predicate indicating wether the element is of type {@link SelectableBoundsAware}. */ -export function isNonRoutableSelectedBoundsAware(element: SModelElement): element is SelectableBoundsAware { +export function isNonRoutableSelectedBoundsAware(element: GModelElement): element is SelectableBoundsAware { return isBoundsAware(element) && isSelected(element) && !isRoutable(element); } /** - * A type guard function to check wether a given {@link SModelElement} is a {@link SRoutableElement}. + * A type guard function to check wether a given {@link GModelElement} is a {@link GRoutableElement}. * @param element The element to check. - * @returns A type predicate indicating wether the element is a {@link SRoutableElement}. + * @returns A type predicate indicating wether the element is a {@link GRoutableElement}. */ -export function isRoutable(element: T): element is T & SRoutableElement { - return element instanceof SRoutableElement && (element as any).routingPoints !== undefined; +export function isRoutable(element: T): element is T & GRoutableElement { + return element instanceof GRoutableElement && (element as any).routingPoints !== undefined; } /** - * A typeguard function to check wether a given {@link SModelElement} is a {@link SRoutingHandle}. + * A typeguard function to check wether a given {@link GModelElement} is a {@link SRoutingHandle}. * @param element The element to check. * @returns A type predicate indicating wether the element is a {@link SRoutingHandle} */ -export function isRoutingHandle(element: SModelElement | undefined): element is SRoutingHandle { - return element !== undefined && element instanceof SRoutingHandle; +export function isRoutingHandle(element: GModelElement | undefined): element is GRoutingHandle { + return element !== undefined && element instanceof GRoutingHandle; } /** - * A typeguard function to check wether a given {@link SModelElement} implements the {@link Selectable} model feature and + * A typeguard function to check wether a given {@link GModelElement} implements the {@link Selectable} model feature and * the {@link BoundsAware} model feature. * @returns A type predicate indicating wether the element is of type {@link SelectableBoundsAware}. */ -export function isSelectableAndBoundsAware(element: SModelElement): element is SelectableBoundsAware { +export function isSelectableAndBoundsAware(element: GModelElement): element is SelectableBoundsAware { return isSelectable(element) && isBoundsAware(element); } /** - * Union type to describe {@link SModelElement}s that implement the {@link Selectable} and {@link BoundsAware} feature. + * Union type to describe {@link GModelElement}s that implement the {@link Selectable} and {@link BoundsAware} feature. */ -export type SelectableBoundsAware = SModelElement & BoundsAware & Selectable; +export type SelectableBoundsAware = GModelElement & BoundsAware & Selectable; /** - * Union type to describe {@link SModelElement}s that implement the {@link BoundsAware} feature. + * Union type to describe {@link GModelElement}s that implement the {@link BoundsAware} feature. */ -export type BoundsAwareModelElement = SModelElement & BoundsAware; +export type BoundsAwareModelElement = GModelElement & BoundsAware; /** - * Helper function to translate a given {@link SModelElement} into its corresponding {@link ElementAndBounds} representation. + * Helper function to translate a given {@link GModelElement} into its corresponding {@link ElementAndBounds} representation. * @param element The element to translate. * @returns The corresponding {@link ElementAndBounds} for the given element. */ @@ -221,12 +221,12 @@ export function toElementAndBounds(element: BoundsAwareModelElement): ElementAnd } /** - * Helper function to translate a given {@link SRoutableElement} into its corresponding + * Helper function to translate a given {@link GRoutableElement} into its corresponding * {@link ElementAndRoutingPoints} representation. * @param element The element to translate. * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. */ -export function toElementAndRoutingPoints(element: SRoutableElement): ElementAndRoutingPoints { +export function toElementAndRoutingPoints(element: GRoutableElement): ElementAndRoutingPoints { return { elementId: element.id, newRoutingPoints: element.routingPoints @@ -242,27 +242,27 @@ export const ROUTING_POINT_KINDS = ['linear', 'bezier-junction']; export const ROUTE_KINDS = [...ROUTING_POINT_KINDS, 'source', 'target']; /** - * Helper function to calculate the {@link ElementAndRoutingPoints} for a given {@link SRoutableElement}. + * Helper function to calculate the {@link ElementAndRoutingPoints} for a given {@link GRoutableElement}. * If client layout is activated, i.e., the edge routing registry is given and has a router for the element, then the routing - * points from the calculated route are used, otherwise we use the already specified routing points of the {@link SRoutableElement}. + * points from the calculated route are used, otherwise we use the already specified routing points of the {@link GRoutableElement}. * @param element The element to translate. * @param routerRegistry the edge router registry. * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. */ -export function calcElementAndRoutingPoints(element: SRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { +export function calcElementAndRoutingPoints(element: GRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { const newRoutingPoints = routerRegistry ? calcRoute(element, routerRegistry, ROUTING_POINT_KINDS) : element.routingPoints; return { elementId: element.id, newRoutingPoints }; } /** - * Helper function to calculate the route for a given {@link SRoutableElement}. + * Helper function to calculate the route for a given {@link GRoutableElement}. * If client layout is activated, i.e., the edge routing registry is given and has a router for the element, then the points - * from the calculated route are used, otherwise we use the already specified routing points of the {@link SRoutableElement}. + * from the calculated route are used, otherwise we use the already specified routing points of the {@link GRoutableElement}. * @param element The element to translate. * @param routerRegistry the edge router registry. * @returns The corresponding route for the given element. */ -export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { +export function calcElementAndRoute(element: GRoutableElement, routerRegistry?: EdgeRouterRegistry): ElementAndRoutingPoints { let route: Point[] | undefined = routerRegistry ? calcRoute(element, routerRegistry, ROUTE_KINDS) : undefined; if (!route) { // add source and target to the routing points @@ -274,7 +274,7 @@ export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: } /** - * Helper function to calculate the route for a given {@link SRoutableElement} by filtering duplicate points. + * Helper function to calculate the route for a given {@link GRoutableElement} by filtering duplicate points. * @param element The element to translate. * @param routerRegistry the edge router registry. * @param pointKinds the routing point kinds that should be considered. @@ -282,7 +282,7 @@ export function calcElementAndRoute(element: SRoutableElement, routerRegistry?: * @returns The corresponding route for the given element. */ export function calcRoute( - element: SRoutableElement, + element: GRoutableElement, routerRegistry: EdgeRouterRegistry, pointKinds: string[] | undefined = ALL_ROUTING_POINTS, tolerance = Number.EPSILON @@ -308,11 +308,11 @@ export function calcRoute( /** * Convenience function to retrieve the model element type from a given input. The input - * can either be a {@link SModelElement}, {@link SModelElementSchema} or a string. + * can either be a {@link GModelElement}, {@link GModelElementSchema} or a string. * @param input The type input. * @returns The corresponding model type as string. */ -export function getElementTypeId(input: SModelElement | SModelElementSchema | string): string { +export function getElementTypeId(input: GModelElement | GModelElementSchema | string): string { if (typeof input === 'string') { return input; } else { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 84a7bd7..b369d08 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -13,10 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, Point, SModelElement, Writable } from '~glsp-sprotty'; +import { + BoundsAware, + Dimension, + ElementAndBounds, + ElementMove, + ModelLayoutOptions, + Point, + GModelElement, + Writable +} from '@eclipse-glsp/sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; -export function minWidth(element: SModelElement & BoundsAware): number { +export function minWidth(element: GModelElement & BoundsAware): number { const layoutOptions = getLayoutOptions(element); if (layoutOptions !== undefined && typeof layoutOptions.minWidth === 'number') { return layoutOptions.minWidth; @@ -24,7 +33,7 @@ export function minWidth(element: SModelElement & BoundsAware): number { return 1; } -export function minHeight(element: SModelElement & BoundsAware): number { +export function minHeight(element: GModelElement & BoundsAware): number { const layoutOptions = getLayoutOptions(element); if (layoutOptions !== undefined && typeof layoutOptions.minHeight === 'number') { return layoutOptions.minHeight; @@ -32,7 +41,7 @@ export function minHeight(element: SModelElement & BoundsAware): number { return 1; } -export function getLayoutOptions(element: SModelElement): ModelLayoutOptions | undefined { +export function getLayoutOptions(element: GModelElement): ModelLayoutOptions | undefined { const layoutOptions = (element as any).layoutOptions; if (layoutOptions !== undefined) { return layoutOptions as ModelLayoutOptions; @@ -40,11 +49,11 @@ export function getLayoutOptions(element: SModelElement): ModelLayoutOptions | u return undefined; } -export function isValidSize(element: SModelElement & BoundsAware, size: Dimension): boolean { +export function isValidSize(element: GModelElement & BoundsAware, size: Dimension): boolean { return size.width >= minWidth(element) && size.height >= minHeight(element); } -export function isValidMove(element: SModelElement & BoundsAware, newPosition?: Point, movementRestrictor?: IMovementRestrictor): boolean { +export function isValidMove(element: GModelElement & BoundsAware, newPosition?: Point, movementRestrictor?: IMovementRestrictor): boolean { if (movementRestrictor) { return movementRestrictor.validate(element, newPosition); } @@ -52,7 +61,7 @@ export function isValidMove(element: SModelElement & BoundsAware, newPosition?: } export function toValidElementMove( - element: SModelElement & BoundsAware, + element: GModelElement & BoundsAware, move: ElementMove, movementRestrictor?: IMovementRestrictor ): ElementMove | undefined { @@ -63,7 +72,7 @@ export function toValidElementMove( } export function toValidElementAndBounds( - element: SModelElement & BoundsAware, + element: GModelElement & BoundsAware, bounds: Writable, movementRestrictor?: IMovementRestrictor ): ElementAndBounds | undefined { diff --git a/packages/client/src/utils/marker.ts b/packages/client/src/utils/marker.ts index 1feb59f..4665469 100644 --- a/packages/client/src/utils/marker.ts +++ b/packages/client/src/utils/marker.ts @@ -13,23 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { MarkerKind, SIssueMarker, SParentElement } from '~glsp-sprotty'; +import { GParentElement, MarkerKind } from '@eclipse-glsp/sprotty'; +import { GIssueMarker } from '../features/validation/issue-marker'; export namespace MarkerPredicates { export const ALL = (): boolean => true; - export const ERRORS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.ERROR); - export const WARNINGS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.WARNING); - export const INFOS = (marker: SIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.INFO); + export const ERRORS = (marker: GIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.ERROR); + export const WARNINGS = (marker: GIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.WARNING); + export const INFOS = (marker: GIssueMarker): boolean => hasIssueWithSeverity(marker, MarkerKind.INFO); - export function hasIssueWithSeverity(marker: SIssueMarker, severity: 'info' | 'warning' | 'error'): boolean { + export function hasIssueWithSeverity(marker: GIssueMarker, severity: 'info' | 'warning' | 'error'): boolean { return marker.issues.find(issue => issue.severity === severity) !== undefined; } } -export function collectIssueMarkers(root: SParentElement): SIssueMarker[] { +export function collectIssueMarkers(root: GParentElement): GIssueMarker[] { const markers = []; for (const child of root.children) { - if (child instanceof SIssueMarker) { + if (child instanceof GIssueMarker) { markers.push(child); } markers.push(...collectIssueMarkers(child)); diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 8fa7a5d..a6d1116 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -18,17 +18,17 @@ import { Bounds, BoundsAware, Dimension, + GChildElement, Point, - SChildElement, - SModelElement, + GModelElement, Viewport, findParentByFeature, isAlignable, isBoundsAware, isViewport, translateBounds -} from '~glsp-sprotty'; -import { BoundsAwareModelElement } from './smodel-util'; +} from '@eclipse-glsp/sprotty'; +import { BoundsAwareModelElement } from './gmodel-util'; /** * Return the position corresponding to this mouse event (Browser coordinates) @@ -43,10 +43,10 @@ import { BoundsAwareModelElement } from './smodel-util'; * @param mouseEvent * A mouseEvent */ -export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEvent): Point { +export function getAbsolutePosition(target: GModelElement, mouseEvent: MouseEvent): Point { return getAbsolutePositionByPoint(target, { x: mouseEvent.pageX, y: mouseEvent.pageY }); } -export function getAbsolutePositionByPoint(target: SModelElement, point: Point): Point { +export function getAbsolutePositionByPoint(target: GModelElement, point: Point): Point { let xPos = point.x; let yPos = point.y; const canvasBounds = target.root.canvasBounds; @@ -76,7 +76,7 @@ export function getAbsolutePositionByPoint(target: SModelElement, point: Point): * * @param target A bounds-aware element from the diagram */ -export function toAbsoluteBounds(element: SModelElement & BoundsAware): Bounds { +export function toAbsoluteBounds(element: GModelElement & BoundsAware): Bounds { const location = isAlignable(element) ? element.alignment : Point.ORIGIN; const x = location.x; const y = location.y; @@ -91,7 +91,7 @@ export function toAbsoluteBounds(element: SModelElement & BoundsAware): Bounds { * * @param target A bounds-aware element from the diagram */ -export function toAbsolutePosition(target: SModelElement & BoundsAware): Point { +export function toAbsolutePosition(target: GModelElement & BoundsAware): Point { return toAbsoluteBounds(target); } @@ -101,7 +101,7 @@ export function toAbsolutePosition(target: SModelElement & BoundsAware): Point { * * @param target A bounds-aware element from the diagram */ -export function toAbsoluteSize(target: SModelElement & BoundsAware): Dimension { +export function toAbsoluteSize(target: GModelElement & BoundsAware): Dimension { return toAbsoluteBounds(target); } @@ -113,7 +113,7 @@ export function toAbsoluteSize(target: SModelElement & BoundsAware): Dimension { * @param absolutePoint a point in absolute coordinates * @returns the equivalent point, relative to the element's parent coordinates */ -export function absoluteToParent(element: BoundsAwareModelElement & SChildElement, absolutePoint: Point): Point { +export function absoluteToParent(element: BoundsAwareModelElement & GChildElement, absolutePoint: Point): Point { if (isBoundsAware(element.parent)) { return absoluteToLocal(element.parent, absolutePoint); } diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 04ed663..db89de6 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -22,37 +22,36 @@ import { DiamondNodeView, ExpandButtonView, FeatureModule, - ForeignObjectElement, ForeignObjectView, - HtmlRoot, + GButton, + GCompartment, + GCompartmentView, + GForeignObjectElement, + GGraphView, + GHtmlRoot, + GLabel, + GNode, + GPort, + GPreRenderedElement, + GRoutingHandle, + GRoutingHandleView, + GShapedPreRenderedElement, + GViewportRootElement, HtmlRootView, - PreRenderedElement, PreRenderedView, RectangularNode, RectangularNodeView, - SButton, - SCompartment, - SCompartmentView, - SEdge, - SGraphView, - SLabel, - SLabelView, - SNode, - SPort, - SRoutingHandle, - SRoutingHandleView, - ShapedPreRenderedElement, + GLabelView, SvgViewportView, - ViewportRootElement, configureModelElement, moveFeature, selectFeature -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; import { GIssueMarker } from '../features/validation/issue-marker'; -import { GLSPGraph } from '../lib/model'; -import { GEdgeView } from './glsp-edge-view'; +import { GEdgeView } from './gedge-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; +import { GEdge, GGraph } from '../model'; export const baseViewModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; @@ -61,31 +60,31 @@ export const baseViewModule = new FeatureModule((bind, unbind, isBound, rebind) export function configureDefaultModelElements(context: Pick): void { // HTML elements - configureModelElement(context, DefaultTypes.HTML, HtmlRoot, HtmlRootView); + configureModelElement(context, DefaultTypes.HTML, GHtmlRoot, HtmlRootView); // generic elements - configureModelElement(context, DefaultTypes.FOREIGN_OBJECT, ForeignObjectElement, ForeignObjectView, { + configureModelElement(context, DefaultTypes.FOREIGN_OBJECT, GForeignObjectElement, ForeignObjectView, { disable: [selectFeature, moveFeature] }); - configureModelElement(context, DefaultTypes.PRE_RENDERED, PreRenderedElement, PreRenderedView); - configureModelElement(context, DefaultTypes.SHAPE_PRE_RENDERED, ShapedPreRenderedElement, PreRenderedView); + configureModelElement(context, DefaultTypes.PRE_RENDERED, GPreRenderedElement, PreRenderedView); + configureModelElement(context, DefaultTypes.SHAPE_PRE_RENDERED, GShapedPreRenderedElement, PreRenderedView); // SVG elements - configureModelElement(context, DefaultTypes.SVG, ViewportRootElement, SvgViewportView); + configureModelElement(context, DefaultTypes.SVG, GViewportRootElement, SvgViewportView); // graph elements - configureModelElement(context, DefaultTypes.GRAPH, GLSPGraph, SGraphView); - configureModelElement(context, DefaultTypes.NODE, SNode, RoundedCornerNodeView); - configureModelElement(context, DefaultTypes.COMPARTMENT, SCompartment, SCompartmentView); - configureModelElement(context, DefaultTypes.COMPARTMENT_HEADER, SCompartment, SCompartmentView); - configureModelElement(context, DefaultTypes.EDGE, SEdge, GEdgeView); - configureModelElement(context, DefaultTypes.PORT, SPort, RectangularNodeView); - configureModelElement(context, DefaultTypes.ROUTING_POINT, SRoutingHandle, SRoutingHandleView); - configureModelElement(context, DefaultTypes.VOLATILE_ROUTING_POINT, SRoutingHandle, SRoutingHandleView); - configureModelElement(context, DefaultTypes.LABEL, SLabel, SLabelView); + configureModelElement(context, DefaultTypes.GRAPH, GGraph, GGraphView); + configureModelElement(context, DefaultTypes.NODE, GNode, RoundedCornerNodeView); + configureModelElement(context, DefaultTypes.COMPARTMENT, GCompartment, GCompartmentView); + configureModelElement(context, DefaultTypes.COMPARTMENT_HEADER, GCompartment, GCompartmentView); + configureModelElement(context, DefaultTypes.EDGE, GEdge, GEdgeView); + configureModelElement(context, DefaultTypes.PORT, GPort, RectangularNodeView); + configureModelElement(context, DefaultTypes.ROUTING_POINT, GRoutingHandle, GRoutingHandleView); + configureModelElement(context, DefaultTypes.VOLATILE_ROUTING_POINT, GRoutingHandle, GRoutingHandleView); + configureModelElement(context, DefaultTypes.LABEL, GLabel, GLabelView); // UI elements - configureModelElement(context, DefaultTypes.BUTTON_EXPAND, SButton, ExpandButtonView); + configureModelElement(context, DefaultTypes.BUTTON_EXPAND, GButton, ExpandButtonView); configureModelElement(context, DefaultTypes.ISSUE_MARKER, GIssueMarker, GIssueMarkerView); // shapes diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index d2c3e31..f8680aa 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -15,21 +15,21 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { BoundsAware, RenderingContext, SChildElement, SCompartment, ShapeView, svg } from '~glsp-sprotty'; +import { GCompartment, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; @injectable() export class StructureCompartmentView extends ShapeView { - render(model: Readonly, context: RenderingContext): VNode | undefined { + render(model: Readonly, context: RenderingContext): VNode | undefined { if (!this.isVisible(model, context)) { return undefined; } return ( - + {context.renderChildren(model)} ); diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/gedge-view.tsx similarity index 88% rename from packages/client/src/views/glsp-edge-view.tsx rename to packages/client/src/views/gedge-view.tsx index 900c003..d615561 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -13,17 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Point, PolylineEdgeView, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { Point, PolylineEdgeView, RenderingContext, SEdge, svg } from '~glsp-sprotty'; import { EdgePadding } from '../utils/argument-utils'; +import { GEdge } from '../model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; @injectable() export class GEdgeView extends PolylineEdgeView { - override render(edge: Readonly, context: RenderingContext): VNode { + override render(edge: Readonly, context: RenderingContext): VNode { const router = this.edgeRouterRegistry.get(edge.routerKind); const route = router.route(edge); if (route.length === 0) { @@ -39,15 +40,15 @@ export class GEdgeView extends PolylineEdgeView { ); } - protected additionalClasses(_edge: Readonly, _context: RenderingContext): Classes { + protected additionalClasses(_edge: Readonly, _context: RenderingContext): Classes { return {}; } - protected override renderLine(_edge: SEdge, segments: Point[], _context: RenderingContext): VNode { + protected override renderLine(_edge: GEdge, segments: Point[], _context: RenderingContext): VNode { return ; } - protected override renderAdditionals(edge: SEdge, segments: Point[], _context: RenderingContext): VNode[] { + protected override renderAdditionals(edge: GEdge, segments: Point[], _context: RenderingContext): VNode[] { // for additional padding we draw another transparent path with larger stroke width const edgePadding = EdgePadding.from(edge); return edgePadding ? [this.renderMouseHandle(segments, edgePadding)] : []; diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index d432885..ec6dfb5 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -19,16 +19,16 @@ import { VNode, VNodeStyle, h } from 'snabbdom'; import { Bounds, EdgeRouterRegistry, + GViewportRootElement, IViewArgs, ProjectedViewportView, ProjectionParams, RenderingContext, ViewProjection, - ViewportRootElement, html, setAttr, setClass -} from '~glsp-sprotty'; +} from '@eclipse-glsp/sprotty'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: html }; @@ -40,7 +40,7 @@ const JSX = { createElement: html }; export class GLSPProjectionView extends ProjectedViewportView { @inject(EdgeRouterRegistry) edgeRouterRegistry: EdgeRouterRegistry; - override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { + override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { const svgElem = this.renderSvg(model, context, args); if (svgElem.data) { svgElem.data!.class = { 'sprotty-graph': true }; @@ -57,7 +57,7 @@ export class GLSPProjectionView extends ProjectedViewportView { return rootNode; } - protected override renderSvg(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { + protected override renderSvg(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { const edgeRouting = this.edgeRouterRegistry.routeAllChildren(model); const transform = `scale(${model.zoom}) translate(${-model.scroll.x},${-model.scroll.y})`; const ns = 'http://www.w3.org/2000/svg'; @@ -71,7 +71,7 @@ export class GLSPProjectionView extends ProjectedViewportView { protected override renderProjectionBar( projections: ViewProjection[], - model: Readonly, + model: Readonly, modelBounds: Bounds, orientation: 'horizontal' | 'vertical' ): VNode { @@ -94,7 +94,7 @@ export class GLSPProjectionView extends ProjectedViewportView { ); } - protected override renderViewport(model: Readonly, params: ProjectionParams): VNode { + protected override renderViewport(model: Readonly, params: ProjectionParams): VNode { let canvasSize; let viewportPos: number; if (params.orientation === 'horizontal') { @@ -134,7 +134,11 @@ export class GLSPProjectionView extends ProjectedViewportView { return
; } - protected override renderProjection(projection: ViewProjection, model: Readonly, params: ProjectionParams): VNode { + protected override renderProjection( + projection: ViewProjection, + model: Readonly, + params: ProjectionParams + ): VNode { let canvasSize; let projPos; let projSize: number; diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index 055b107..845fd97 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -15,7 +15,7 @@ ********************************************************************************/ export * from './base-view-module'; export * from './compartments'; -export * from './glsp-edge-view'; +export * from './gedge-view'; export * from './glsp-projection-view'; export * from './issue-marker-view'; export * from './rounded-corner'; diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 6225fe6..6766b18 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -14,16 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable max-len */ +import { GIssueSeverity, IssueMarkerView, RenderingContext, setClass, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IssueMarkerView, RenderingContext, setClass, SIssueMarker, SIssueSeverity, svg } from '~glsp-sprotty'; +import { GIssueMarker } from '../features/validation/issue-marker'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; @injectable() export class GIssueMarkerView extends IssueMarkerView { - override render(marker: SIssueMarker, _context: RenderingContext): VNode { + override render(marker: GIssueMarker, _context: RenderingContext): VNode { const maxSeverity = super.getMaxSeverity(marker); const group = ( @@ -37,7 +38,7 @@ export class GIssueMarkerView extends IssueMarkerView { return group; } - protected getGlspIssueMarkerBackground(severity: SIssueSeverity): VNode { + protected getGlspIssueMarkerBackground(severity: GIssueSeverity): VNode { switch (severity) { case 'warning': return ; @@ -51,7 +52,7 @@ export class GIssueMarkerView extends IssueMarkerView { return 8; // var(--theia-icon-size)=16px => 16/2=8 } - protected getGlspIssueMarkerPath(severity: SIssueSeverity): string { + protected getGlspIssueMarkerPath(severity: GIssueSeverity): string { switch (severity) { // paths used here are svg versions of codicons, resized to var(--theia-icon-size) 16px case 'error': // 'codicon-error' diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 2f03c8a..9e42614 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { Hoverable, RectangularNodeView, RenderingContext, SNode, SPort, SShapeElement, Selectable, svg } from '~glsp-sprotty'; +import { Hoverable, RectangularNodeView, RenderingContext, GNode, GPort, GShapeElement, Selectable, svg } from '@eclipse-glsp/sprotty'; import { CornerRadius } from '../utils/argument-utils'; import { RoundedCornerWrapper } from './rounded-corner'; @@ -24,7 +24,7 @@ const JSX = { createElement: svg }; @injectable() export class RoundedCornerNodeView extends RectangularNodeView { - override render(node: Readonly, context: RenderingContext): VNode | undefined { + override render(node: Readonly, context: RenderingContext): VNode | undefined { const cornerRadius = CornerRadius.from(node); if (!cornerRadius) { return this.renderWithoutRadius(node, context); @@ -44,7 +44,7 @@ export class RoundedCornerNodeView extends RectangularNodeView { ); } - protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode | undefined { + protected renderWithoutRadius(node: Readonly, context: RenderingContext): VNode | undefined { return super.render(node, context); } @@ -56,8 +56,8 @@ export class RoundedCornerNodeView extends RectangularNodeView { return ( , _context: RenderingContext): Classes { + protected additionalClasses(_node: Readonly, _context: RenderingContext): Classes { return {}; } @@ -97,6 +97,6 @@ export class RoundedCornerNodeView extends RectangularNodeView { } } -export function toClipPathId(node: Readonly): string { +export function toClipPathId(node: Readonly): string { return `${node.id}_clip_path`; } diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index 370a5c4..b70ba28 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension, Hoverable, Selectable, SShapeElement } from '~glsp-sprotty'; +import { Dimension, Hoverable, Selectable, GShapeElement } from '@eclipse-glsp/sprotty'; import { CornerRadius } from '../utils/argument-utils'; export interface RoundedCorner { @@ -27,7 +27,7 @@ export class RoundedCornerWrapper { protected _bottomRightCorner: RoundedCorner; protected _bottomLeftCorner: RoundedCorner; - constructor(public readonly element: SShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) {} + constructor(public readonly element: GShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) {} get size(): Dimension { return this.element.size; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index dc8e6ac..1f6d5ad 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,13 +1,8 @@ { "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { - "rootDir": "src", "outDir": "lib", - "reactNamespace": "JSX", - "baseUrl": ".", - "paths": { - "~glsp-sprotty": ["src/glsp-sprotty/index"] - } + "reactNamespace": "JSX" }, "include": ["src"] } diff --git a/packages/glsp-sprotty/.eslintrc.js b/packages/glsp-sprotty/.eslintrc.js new file mode 100644 index 0000000..3ea9dda --- /dev/null +++ b/packages/glsp-sprotty/.eslintrc.js @@ -0,0 +1,17 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '../../.eslintrc.js', + rules: { + 'no-restricted-imports': [ + 'warn', + { + name: 'sprotty-protocol', + message: 'Please use @eclipse-glsp/sprotty instead' + }, + { + name: 'sprotty-protocol/*', + message: "Please use '@eclipse-glsp/protocol' instead" + } + ] + } +}; diff --git a/packages/glsp-sprotty/LICENSE b/packages/glsp-sprotty/LICENSE new file mode 100644 index 0000000..e079b08 --- /dev/null +++ b/packages/glsp-sprotty/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. diff --git a/packages/glsp-sprotty/README.md b/packages/glsp-sprotty/README.md new file mode 100644 index 0000000..9e22533 --- /dev/null +++ b/packages/glsp-sprotty/README.md @@ -0,0 +1,14 @@ +# Eclipse GLSP - Client + +A web-based diagram client framework for the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp) based on [Eclipse Sprotty](https://github.com/eclipse/sprotty). + +## Building + +This project is built with `yarn` and is available from npm via [@eclipse-glsp/client](https://www.npmjs.com/package/@eclipse-glsp/client). + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). + +![alt](https://www.eclipse.org/glsp/images/diagramanimated.gif) diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json new file mode 100644 index 0000000..104957b --- /dev/null +++ b/packages/glsp-sprotty/package.json @@ -0,0 +1,50 @@ +{ + "name": "@eclipse-glsp/sprotty", + "version": "1.1.0-next", + "description": "Augmented reexport of the sprotty API for GLSP", + "homepage": "https://www.eclipse.org/glsp/", + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-client.git" + }, + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "author": { + "name": "Eclipse GLSP" + }, + "contributors": [ + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "main": "lib/index", + "types": "lib/index", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "tsc -b", + "clean": "rimraf lib tsconfig.tsbuildinfo", + "lint": "eslint --ext .ts,.tsx ./src", + "lint:ci": "yarn lint -o eslint.xml -f checkstyle", + "prepare": "yarn clean && yarn build", + "watch": "tsc -w" + }, + "dependencies": { + "@eclipse-glsp/protocol": "1.1.0-next", + "autocompleter": "^9.1.0", + "snabbdom": "^3.5.1", + "sprotty": "0.15.0-next.044bba2.13" + }, + "devDependencies": { + "@types/jsdom": "^21.1.3", + "@vscode/codicons": "^0.0.25", + "snabbdom-to-html": "^7.1.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/glsp-sprotty/src/action-override.ts b/packages/glsp-sprotty/src/action-override.ts new file mode 100644 index 0000000..8f4bf07 --- /dev/null +++ b/packages/glsp-sprotty/src/action-override.ts @@ -0,0 +1,124 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { + SButtonImpl as GButton, + SModelElementImpl as GModelElement, + SModelRootImpl as GModelRoot, + ICommand, + IActionHandler as SIActionHandler, + IButtonHandler as SIButtonHandler, + ICommandPaletteActionProvider as SICommandPaletteActionProvider, + IContextMenuItemProvider as SIContextMenuItemProvider, + KeyListener as SKeyListener, + MouseListener as SMouseListener +} from 'sprotty'; + +/* + * The GLSP-protocol comes with its own type definition for `Action`. However, sprotty + * also has a separate `Action` definition that is heavily integrated into core API concepts. + * While these two definitions are fully compatible it messes up IDE support for auto import + * when implementing/overriding these API Concepts. To bypass this issue we create sub types + * of the sprotty API concepts that use the Action definition from GLSP and export them instead. + */ + +/** + * An action handler accepts an action and reacts to it by returning either a command to be + * executed, or another action to be dispatched. + */ +export interface IActionHandler extends SIActionHandler { + handle(action: Action): ICommand | Action | void; +} + +export interface IButtonHandler extends SIButtonHandler { + buttonPressed(button: GButton): (Action | Promise)[]; +} + +export interface ICommandPaletteActionProvider extends SICommandPaletteActionProvider { + getActions(root: Readonly, text: string, lastMousePosition?: Point, index?: number): Promise; +} + +export interface IContextMenuItemProvider extends SIContextMenuItemProvider { + getItems(root: Readonly, lastMousePosition?: Point): Promise; +} + +@injectable() +export class KeyListener extends SKeyListener { + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + return []; + } + + override keyUp(element: GModelElement, event: KeyboardEvent): Action[] { + return []; + } +} + +@injectable() +export class MouseListener extends SMouseListener { + override mouseOver(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseOut(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseEnter(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseLeave(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseDown(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseMove(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override mouseUp(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override wheel(target: GModelElement, event: WheelEvent): (Action | Promise)[] { + return []; + } + + override doubleClick(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override contextMenu(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override dragOver(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override drop(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + return []; + } + + override decorate(vnode: VNode, element: GModelElement): VNode { + return vnode; + } +} diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts new file mode 100644 index 0000000..db64bf2 --- /dev/null +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable no-restricted-imports */ + +import { FeatureModule } from '@eclipse-glsp/protocol'; +import sprottyButtonModule from 'sprotty/lib/features/button/di.config'; +import sprottyEdgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; +import sprottyEdgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; +import { + edgeEditModule as sprottyEdgeEditModule, + labelEditUiModule as sprottyLabelEditUiModule +} from 'sprotty/lib/features/edit/di.config'; +import sprottyExpandModule from 'sprotty/lib/features/expand/di.config'; +import sprottyFadeModule from 'sprotty/lib/features/fade/di.config'; +import sprottyMoveModule from 'sprotty/lib/features/move/di.config'; +import sprottyUpdateModule from 'sprotty/lib/features/update/di.config'; +import sprottyZorderModule from 'sprotty/lib/features/zorder/di.config'; +import sprottyModelSourceModule from 'sprotty/lib/model-source/di.config'; +import sprottyDefaultModule from 'sprotty/lib/base/di.config'; +import sprottyOpenModule from 'sprotty/lib/features/open/di.config'; + +export const buttonModule = new FeatureModule(sprottyButtonModule.registry); +export const edgeEditModule = new FeatureModule(sprottyEdgeEditModule.registry); +export const edgeIntersectionModule = new FeatureModule(sprottyEdgeIntersectionModule.registry); +export const edgeLayoutModule = new FeatureModule(sprottyEdgeLayoutModule.registry); +export const expandModule = new FeatureModule(sprottyExpandModule.registry); +export const fadeModule = new FeatureModule(sprottyFadeModule.registry); +export const labelEditUiModule = new FeatureModule(sprottyLabelEditUiModule.registry); +export const modelSourceModule = new FeatureModule(sprottyModelSourceModule.registry); +export const moveModule = new FeatureModule(sprottyMoveModule.registry); +export const openModule = new FeatureModule(sprottyOpenModule.registry); +export const updateModule = new FeatureModule(sprottyUpdateModule.registry); +export const zorderModule = new FeatureModule(sprottyZorderModule.registry); + +export { sprottyDefaultModule }; diff --git a/packages/client/src/glsp-sprotty/index.ts b/packages/glsp-sprotty/src/index.ts similarity index 52% rename from packages/client/src/glsp-sprotty/index.ts rename to packages/glsp-sprotty/src/index.ts index 5550423..95b54c8 100644 --- a/packages/client/src/glsp-sprotty/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable no-restricted-imports */ + /** * Selective custom reexport of the sprotty API. Also provides augmentations/adaptions * of certain type definitions for GLSP. Using this custom reexport allows us to gracefully @@ -24,65 +24,68 @@ * @eclipse-glsp/protocol */ export * from '@eclipse-glsp/protocol'; -/* - * sprotty-protocol - */ -export { - BringToFrontAction, - CollapseExpandAction, - CollapseExpandAllAction, - ElementAndAlignment, - GetSelectionAction, - GetViewportAction, - HoverFeedbackAction, - MoveAction, - SelectionResult, - SetBoundsAction, - ViewportResult -} from 'sprotty-protocol/lib/actions'; -export { Viewport } from 'sprotty-protocol/lib/model'; + +export * from './action-override'; + /* * sprotty */ // ------------------ Base ------------------ + +// Exclude definition for labeled actions +// export * from 'sprotty/lib/base/actions/action'; export * from 'sprotty/lib/base/actions/action-dispatcher'; -export * from 'sprotty/lib/base/actions/action-handler'; +export { + ActionHandlerRegistration, + ActionHandlerRegistry, + configureActionHandler, + onAction +} from 'sprotty/lib/base/actions/action-handler'; export * from 'sprotty/lib/base/actions/diagram-locker'; + export * from 'sprotty/lib/base/animations/animation'; export * from 'sprotty/lib/base/animations/animation-frame-syncer'; export * from 'sprotty/lib/base/animations/easing'; + export * from 'sprotty/lib/base/commands/command'; export * from 'sprotty/lib/base/commands/command-registration'; export * from 'sprotty/lib/base/commands/command-stack'; export * from 'sprotty/lib/base/commands/command-stack-options'; + export * from 'sprotty/lib/base/features/initialize-canvas'; -export { SetModelCommand } from 'sprotty/lib/base/features/set-model'; +export * from 'sprotty/lib/base/features/set-model'; +// Exclude SModelElementImpl as it as exported with augmentation module export { FeatureSet, + SChildElementImpl as GChildElement, + SModelElementImpl as GModelElement, + SModelRootImpl as GModelRoot, + SParentElementImpl as GParentElement, IModelIndex, ModelIndexImpl, - SChildElementImpl as SChildElement, - SModelElementImpl as SModelElement, - SModelRootImpl as SModelRoot, - SParentElementImpl as SParentElement, createRandomId, isParent } from 'sprotty/lib/base/model/smodel'; -export * from 'sprotty/lib/base/model/smodel-extension'; -export * from 'sprotty/lib/base/model/smodel-factory'; + export { - containsSome, - findParent, - findParentByFeature, - registerModelElement, - translateBounds, - translatePoint -} from 'sprotty/lib/base/model/smodel-utils'; + CustomFeatures, + EMPTY_ROOT, + SModelElementConstructor as GModelElementConstructor, + SModelElementRegistration as GModelElementRegistration, + SModelFactory as GModelFactory, + IModelFactory, + // exported without alias we extend it in glsp-client to `GModelRegistry` + SModelRegistry, + createFeatureSet +} from 'sprotty/lib/base/model/smodel-factory'; +export * from 'sprotty/lib/base/model/smodel-utils'; + export * from 'sprotty/lib/base/ui-extensions/ui-extension'; export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; + export * from 'sprotty/lib/base/views/dom-helper'; -export * from 'sprotty/lib/base/views/key-tool'; -export * from 'sprotty/lib/base/views/mouse-tool'; +export { KeyTool } from 'sprotty/lib/base/views/key-tool'; +export { MouseEventKind, MousePositionTracker, MouseTool, PopupMouseTool } from 'sprotty/lib/base/views/mouse-tool'; export * from 'sprotty/lib/base/views/thunk-view'; export * from 'sprotty/lib/base/views/view'; export * from 'sprotty/lib/base/views/viewer'; @@ -90,14 +93,14 @@ export * from 'sprotty/lib/base/views/viewer-cache'; export * from 'sprotty/lib/base/views/viewer-options'; export * from 'sprotty/lib/base/views/vnode-postprocessor'; export * from 'sprotty/lib/base/views/vnode-utils'; -export * from 'sprotty/lib/features/bounds/abstract-layout'; + +// Exclude sprotty types and export augmented GLSP types instead +// export * from 'sprotty/lib/base/types'; +export * from './types'; + // ------------------ Features ------------------ -export { - RequestBoundsCommand, - ResolvedElementAndAlignment, - ResolvedElementAndBounds, - SetBoundsCommand -} from 'sprotty/lib/features/bounds/bounds-manipulation'; +export * from 'sprotty/lib/features/bounds/abstract-layout'; +export * from 'sprotty/lib/features/bounds/bounds-manipulation'; export * from 'sprotty/lib/features/bounds/hbox-layout'; export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; export * from 'sprotty/lib/features/bounds/layout'; @@ -105,11 +108,10 @@ export * from 'sprotty/lib/features/bounds/layout-options'; export { Alignable, BoundsAware, + SShapeElementImpl as GShapeElement, LayoutContainer, LayoutableChild, ModelLayoutOptions, - SShapeElementImpl as SShapeElement, - alignFeature, boundsFeature, findChildrenAtPosition, getAbsoluteBounds, @@ -122,86 +124,95 @@ export { layoutContainerFeature, layoutableChildFeature } from 'sprotty/lib/features/bounds/model'; -export * from 'sprotty/lib/features/bounds/stack-layout'; +// exclude stack layout as its not supported in GLSP +// export * from 'sprotty/lib/features/bounds/stack-layout'; export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; + +export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; +export { SButtonImpl as GButton, SButtonSchema as GButtonSchema } from 'sprotty/lib/features/button/model'; + export { - ButtonHandlerRegistry, - IButtonHandler, - IButtonHandlerRegistration, - configureButtonHandler -} from 'sprotty/lib/features/button/button-handler'; -export { SButtonImpl as SButton, SButtonSchema } from 'sprotty/lib/features/button/model'; -export * from 'sprotty/lib/features/command-palette/action-providers'; + CommandPaletteActionProviderRegistry, + RevealNamedElementActionProvider +} from 'sprotty/lib/features/command-palette/action-providers'; export * from 'sprotty/lib/features/command-palette/command-palette'; + +// Exclude menu item. Aready provided by glsp-protocol export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; -export * from 'sprotty/lib/features/context-menu/menu-providers'; +export { ContextMenuProviderRegistry, DeleteContextMenuItemProvider } from 'sprotty/lib/features/context-menu/menu-providers'; export * from 'sprotty/lib/features/context-menu/mouse-listener'; -export * from 'sprotty/lib/features/decoration/decoration-placer'; -export * from 'sprotty/lib/features/decoration/model'; -export * from 'sprotty/lib/features/decoration/views'; -export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; -export * from 'sprotty/lib/features/edge-intersection/sweepline'; + +export * from 'sprotty/lib/features/edge-layout/di.config'; export * from 'sprotty/lib/features/edge-layout/edge-layout'; export * from 'sprotty/lib/features/edge-layout/model'; -export * from 'sprotty/lib/features/edit/create'; -export * from 'sprotty/lib/features/edit/create-on-drag'; +// Exclude client-side creation features (not supported in GLSP) +// export * from 'sprotty/lib/features/edit/create'; +// export * from 'sprotty/lib/features/edit/create-on-drag'; export * from 'sprotty/lib/features/edit/delete'; export * from 'sprotty/lib/features/edit/edit-label'; export * from 'sprotty/lib/features/edit/edit-label-ui'; export * from 'sprotty/lib/features/edit/edit-routing'; export * from 'sprotty/lib/features/edit/model'; -export * from 'sprotty/lib/features/edit/reconnect'; +// export * from 'sprotty/lib/features/edit/reconnect'; + export * from 'sprotty/lib/features/expand/expand'; export * from 'sprotty/lib/features/expand/model'; export * from 'sprotty/lib/features/expand/views'; -export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, RequestExportSvgAction } from 'sprotty/lib/features/export/export'; +// Exclude RequestExportSvgAction. Already provided by glsp-protocol +export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor } from 'sprotty/lib/features/export/export'; export * from 'sprotty/lib/features/export/model'; -export { ExportSvgAction, SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; +// Exclude ExportSvgAction. Already provided by glsp-protocol +export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; + export * from 'sprotty/lib/features/fade/fade'; export * from 'sprotty/lib/features/fade/model'; -export { - AbstractHoverMouseListener, - ClosePopupActionHandler, - HoverFeedbackCommand, - HoverKeyListener, - HoverMouseListener, - HoverState, - PopupHoverMouseListener, - SetPopupModelCommand -} from 'sprotty/lib/features/hover/hover'; + +export * from 'sprotty/lib/features/hover/hover'; export * from 'sprotty/lib/features/hover/model'; -export { PopupPositionUpdater } from 'sprotty/lib/features/hover/popup-position-updater'; -export * from 'sprotty/lib/features/move/model'; +export * from 'sprotty/lib/features/hover/popup-position-updater'; + +// Alias SModel types +export * from 'sprotty/lib/features/decoration/decoration-placer'; export { - ElementMove, - LocationPostprocessor, - MorphEdgesAnimation, - MoveAnimation, - MoveCommand, - MoveMouseListener, - ResolvedElementMove, - ResolvedHandleMove -} from 'sprotty/lib/features/move/move'; + Decoration, + SDecoration as GDecoration, + SIssue as GIssue, + SIssueSeverity as GIssueSeverity, + // Export as is, we extend it glsp-client to `GIssueMarker` + SIssueMarker +} from 'sprotty/lib/features/decoration/model'; +export * from 'sprotty/lib/features/decoration/views'; + +export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; +export * from 'sprotty/lib/features/edge-intersection/sweepline'; + +export * from 'sprotty/lib/features/move/model'; +export * from 'sprotty/lib/features/move/move'; export * from 'sprotty/lib/features/move/snap'; + export * from 'sprotty/lib/features/nameable/model'; + export * from 'sprotty/lib/features/open/model'; export * from 'sprotty/lib/features/open/open'; + export * from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; + export * from 'sprotty/lib/features/routing/abstract-edge-router'; export * from 'sprotty/lib/features/routing/anchor'; export * from 'sprotty/lib/features/routing/bezier-anchors'; export * from 'sprotty/lib/features/routing/bezier-edge-router'; export * from 'sprotty/lib/features/routing/manhattan-anchors'; export * from 'sprotty/lib/features/routing/manhattan-edge-router'; +// Alias SModel types export { Connectable, + SConnectableElementImpl as GConnectableElement, + SDanglingAnchorImpl as GDanglingAnchor, + SRoutableElementImpl as GRoutableElement, + SRoutingHandleImpl as GRoutingHandle, RoutingHandleKind, - SConnectableElementImpl as SConnectableElement, - SDanglingAnchorImpl as SDanglingAnchor, - SRoutableElementImpl as SRoutableElement, - SRoutingHandleImpl as SRoutingHandle, connectableFeature, edgeInProgressID, edgeInProgressTargetHandleID, @@ -213,7 +224,9 @@ export * from 'sprotty/lib/features/routing/polyline-anchors'; export * from 'sprotty/lib/features/routing/polyline-edge-router'; export * from 'sprotty/lib/features/routing/routing'; export * from 'sprotty/lib/features/routing/views'; + export * from 'sprotty/lib/features/select/model'; +// Alias Select commands with sprotty prefix to avoid clash with glsp-client export { GetSelectionCommand, SelectKeyboardListener, @@ -221,124 +234,88 @@ export { SelectAllCommand as SprottySelectAllCommand, SelectCommand as SprottySelectCommand } from 'sprotty/lib/features/select/select'; -export { UndoRedoKeyListener } from 'sprotty/lib/features/undo-redo/undo-redo'; + +// Exclude undo-redo as the client provides its own implementation +// export * from 'sprotty/lib/features/undo-redo/undo-redo'; + export * from 'sprotty/lib/features/update/model-matching'; -export { UpdateAnimationData, UpdateModelCommand } from 'sprotty/lib/features/update/update-model'; -export { - BoundsAwareViewportCommand, - CenterCommand, - CenterKeyboardListener, - FitToScreenCommand -} from 'sprotty/lib/features/viewport/center-fit'; +export * from 'sprotty/lib/features/update/update-model'; + +export * from 'sprotty/lib/features/viewport/center-fit'; export * from 'sprotty/lib/features/viewport/model'; export * from 'sprotty/lib/features/viewport/scroll'; -export { GetViewportCommand, SetViewportCommand, ViewportAnimation } from 'sprotty/lib/features/viewport/viewport'; -export * from 'sprotty/lib/features/viewport/viewport-root'; +// Alias SModel types +export * from 'sprotty/lib/features/viewport/viewport'; +export { ViewportRootElementImpl as GViewportRootElement } from 'sprotty/lib/features/viewport/viewport-root'; export * from 'sprotty/lib/features/viewport/zoom'; -export { BringToFrontCommand, ZOrderElement } from 'sprotty/lib/features/zorder/zorder'; + +export * from 'sprotty/lib/features/zorder/zorder'; + +// ------------------ Graph ------------------ +// Alias SModel types export { - SCompartmentImpl as SCompartment, - SEdgeImpl as SEdge, - SGraphImpl as SGraph, - SGraphIndex, - SLabelImpl as SLabel, - SNodeImpl as SNode, - SPortImpl as SPort + SCompartmentImpl as GCompartment, + SGraphIndex as GGraphIndex, + SLabelImpl as GLabel, + SNodeImpl as GNode, + SPortImpl as GPort, + // Exported as is, we extend it in glsp-client to GEdge + SEdgeImpl, + // Exported as is, we extend it in glsp-client to GGraph + SGraphImpl } from 'sprotty/lib/graph/sgraph'; -export * from 'sprotty/lib/lib/virtualize'; -// ------------------ Graph ------------------ -export * from 'sprotty/lib/graph/sgraph-factory'; -export * from 'sprotty/lib/graph/views'; +// Alias SModel types +export { + BezierCurveEdgeView, + SBezierControlHandleView as GBezierControlHandleView, + SBezierCreateHandleView as GBezierCreateHandleView, + SCompartmentView as GCompartmentView, + SGraphView as GGraphView, + SLabelView as GLabelView, + SRoutingHandleView as GRoutingHandleView, + JumpingPolylineEdgeView, + PolylineEdgeView, + PolylineEdgeViewWithGapsOnIntersections +} from 'sprotty/lib/graph/views'; + +export * from './feature-modules'; + +// ------------------ Library ------------------ + export * from 'sprotty/lib/lib/generic-views'; export * from 'sprotty/lib/lib/html-views'; export * from 'sprotty/lib/lib/jsx'; +// Alias SModel types export { CircularNode, CircularPort, DiamondNode, - ForeignObjectElementImpl as ForeignObjectElement, - HtmlRootImpl as HtmlRoot, - PreRenderedElementImpl as PreRenderedElement, + ForeignObjectElementImpl as GForeignObjectElement, + HtmlRootImpl as GHtmlRoot, + PreRenderedElementImpl as GPreRenderedElement, + ShapedPreRenderedElementImpl as GShapedPreRenderedElement, RectangularNode, - RectangularPort, - ShapedPreRenderedElementImpl as ShapedPreRenderedElement + RectangularPort } from 'sprotty/lib/lib/model'; -// ------------------ Library ------------------ export * from 'sprotty/lib/lib/modules'; export * from 'sprotty/lib/lib/svg-views'; + // ------------------ Model Source ------------------ export * from 'sprotty/lib/model-source/commit-model'; -export { DiagramServerProxy } from 'sprotty/lib/model-source/diagram-server'; -export * from 'sprotty/lib/model-source/local-model-source'; +// Exclude as not supported in GLSP +// export * from 'sprotty/lib/model-source/diagram-server'; +// export * from 'sprotty/lib/model-source/local-model-source'; export * from 'sprotty/lib/model-source/logging'; export * from 'sprotty/lib/model-source/model-source'; -export * from 'sprotty/lib/model-source/websocket'; +// export * from 'sprotty/lib/model-source/websocket'; + // ------------------ Utilities ------------------ export * from 'sprotty/lib/utils/browser'; export * from 'sprotty/lib/utils/codicon'; export * from 'sprotty/lib/utils/color'; -export { Diamond, Insets, Line, Orientation, PointToPointLine, intersection } from 'sprotty/lib/utils/geometry'; +export * from 'sprotty/lib/utils/geometry'; export * from 'sprotty/lib/utils/inversify'; export * from 'sprotty/lib/utils/iterable'; export * from 'sprotty/lib/utils/keyboard'; export * from 'sprotty/lib/utils/logging'; export * from 'sprotty/lib/utils/registry'; -/** - * Misc glsp adaptions/augmentations - */ -export * from './augmented-actions'; -export * from './types'; -/** - * Modules - */ -import defaultModule from 'sprotty/lib/base/di.config'; -import boundsModule from 'sprotty/lib/features/bounds/di.config'; -import buttonModule from 'sprotty/lib/features/button/di.config'; -import commandPaletteModule from 'sprotty/lib/features/command-palette/di.config'; -import contextMenuModule from 'sprotty/lib/features/context-menu/di.config'; -import decorationModule from 'sprotty/lib/features/decoration/di.config'; -import edgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; -import edgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; -import { edgeEditModule, labelEditModule, labelEditUiModule } from 'sprotty/lib/features/edit/di.config'; -import expandModule from 'sprotty/lib/features/expand/di.config'; -import exportModule from 'sprotty/lib/features/export/di.config'; -import fadeModule from 'sprotty/lib/features/fade/di.config'; -import hoverModule from 'sprotty/lib/features/hover/di.config'; -import moveModule from 'sprotty/lib/features/move/di.config'; -import openModule from 'sprotty/lib/features/open/di.config'; -import routingModule from 'sprotty/lib/features/routing/di.config'; -import selectModule from 'sprotty/lib/features/select/di.config'; -import undoRedoModule from 'sprotty/lib/features/undo-redo/di.config'; -import updateModule from 'sprotty/lib/features/update/di.config'; -import viewportModule from 'sprotty/lib/features/viewport/di.config'; -import zorderModule from 'sprotty/lib/features/zorder/di.config'; -import graphModule from 'sprotty/lib/graph/di.config'; -import modelSourceModule from 'sprotty/lib/model-source/di.config'; - -export { - buttonModule, - edgeEditModule, - edgeIntersectionModule, - edgeLayoutModule, - expandModule, - fadeModule, - graphModule, - labelEditUiModule, - modelSourceModule, - moveModule, - openModule, - boundsModule as sprottyBoundsModule, - commandPaletteModule as sprottyCommandModule, - contextMenuModule as sprottyContextMenuModule, - decorationModule as sprottyDecorationModule, - defaultModule as sprottyDefaultModule, - exportModule as sprottyExportModule, - hoverModule as sprottyHoverModule, - labelEditModule as sprottyLabelEditModule, - routingModule as sprottyRoutingModule, - selectModule as sprottySelectModule, - undoRedoModule as sprottyUndoRedoModule, - viewportModule as sprottyViewportModule, - updateModule, - zorderModule -}; diff --git a/packages/client/src/glsp-sprotty/types.ts b/packages/glsp-sprotty/src/types.ts similarity index 97% rename from packages/client/src/glsp-sprotty/types.ts rename to packages/glsp-sprotty/src/types.ts index 9ed2206..8929af9 100644 --- a/packages/client/src/glsp-sprotty/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -// eslint-disable-next-line no-restricted-imports import { TYPES as SprottyTYPES } from 'sprotty'; /** * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service * identifiers. + * */ export const TYPES = { ...SprottyTYPES, diff --git a/packages/glsp-sprotty/tsconfig.json b/packages/glsp-sprotty/tsconfig.json new file mode 100644 index 0000000..f3d8c20 --- /dev/null +++ b/packages/glsp-sprotty/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@eclipse-glsp/ts-config", + "compilerOptions": { + "outDir": "lib", + "reactNamespace": "JSX" + }, + "include": ["src"] +} diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 6773f9c..d1dd6a9 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "0.14.0-next.02bbac0.26", + "sprotty-protocol": "0.15.0-next.044bba2.13", "uuid": "7.0.3", "vscode-jsonrpc": "^8.0.2" }, @@ -54,7 +54,7 @@ "@types/uuid": "3.4.5" }, "peerDependencies": { - "inversify": "^5.1.1" + "inversify": "^6.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/src/action-protocol/base-protocol.spec.ts b/packages/protocol/src/action-protocol/base-protocol.spec.ts index 782bb69..3cfe713 100644 --- a/packages/protocol/src/action-protocol/base-protocol.spec.ts +++ b/packages/protocol/src/action-protocol/base-protocol.spec.ts @@ -16,6 +16,7 @@ /* eslint-disable max-len */ import { expect } from 'chai'; import { Action, ActionMessage, CompoundOperation, Operation, RejectAction, RequestAction, ResponseAction } from './base-protocol'; +import { AnyObject } from '../utils/type-util'; /** * Tests for the utility functions declared in the namespaces of the protocol @@ -233,4 +234,5 @@ interface SomeRequestAction extends RequestAction { interface SomeResponseAction extends ResponseAction { kind: 'someResponse'; } -const isSomeCustomAction = (object: any): object is SomeCustomAction => object !== undefined && object.kind === 'custom'; +const isSomeCustomAction = (object: unknown): object is SomeCustomAction => + AnyObject.is(object) && 'kind' in object && object.kind === 'custom'; diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts index 9bd572c..542f1c6 100644 --- a/packages/protocol/src/action-protocol/base-protocol.ts +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; -import { AnyObject, hasArrayProp, hasStringProp, TypeGuard } from '../utils/type-util'; +import { AnyObject, TypeGuard, hasArrayProp, hasStringProp } from '../utils/type-util'; /** * An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. @@ -159,7 +159,7 @@ export interface RejectAction extends ResponseAction, sprotty.RejectAction { export namespace RejectAction { export const KIND = 'rejectRequest'; - export function is(object: any): object is RejectAction { + export function is(object: unknown): object is RejectAction { return Action.hasKind(object, RejectAction.KIND) && hasStringProp(object, 'message'); } @@ -188,8 +188,8 @@ export interface Operation extends Action { } export namespace Operation { - export function is(object: any): object is Operation { - return Action.is(object) && (object as any).isOperation === true; + export function is(object: unknown): object is Operation { + return Action.is(object) && 'isOperation' in object && object.isOperation === true; } /** @@ -198,7 +198,7 @@ export namespace Operation { * @param kind The expected operation kind. * @returns A type literal indicating wether the given object is an operation with the given kind. */ - export function hasKind(object: any, kind: string): object is Operation { + export function hasKind(object: unknown, kind: string): object is Operation { return Operation.is(object) && object.kind === kind; } } @@ -219,7 +219,7 @@ export interface CompoundOperation extends Operation { export namespace CompoundOperation { export const KIND = 'compound'; - export function is(object: any): object is CompoundOperation { + export function is(object: unknown): object is CompoundOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'operationList'); } diff --git a/packages/protocol/src/action-protocol/client-notification.ts b/packages/protocol/src/action-protocol/client-notification.ts index e9106c2..c84924b 100644 --- a/packages/protocol/src/action-protocol/client-notification.ts +++ b/packages/protocol/src/action-protocol/client-notification.ts @@ -43,7 +43,7 @@ export interface StatusAction extends Action { export namespace StatusAction { export const KIND = 'status'; - export function is(object: any): object is StatusAction { + export function is(object: unknown): object is StatusAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'severity') && hasStringProp(object, 'message'); } @@ -89,7 +89,7 @@ export interface MessageAction extends Action { export namespace MessageAction { export const KIND = 'message'; - export function is(object: any): object is MessageAction { + export function is(object: unknown): object is MessageAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'message') && hasStringProp(object, 'severity'); } @@ -136,7 +136,7 @@ export interface StartProgressAction extends Action { export namespace StartProgressAction { export const KIND = 'startProgress'; - export function is(object: any): object is StartProgressAction { + export function is(object: unknown): object is StartProgressAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId') && hasStringProp(object, 'title'); } @@ -171,7 +171,7 @@ export interface UpdateProgressAction extends Action { export namespace UpdateProgressAction { export const KIND = 'updateProgress'; - export function is(object: any): object is UpdateProgressAction { + export function is(object: unknown): object is UpdateProgressAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId'); } @@ -209,7 +209,7 @@ export interface EndProgressAction extends Action { export namespace EndProgressAction { export const KIND = 'endProgress'; - export function is(object: any): object is EndProgressAction { + export function is(object: unknown): object is EndProgressAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'progressId'); } diff --git a/packages/protocol/src/action-protocol/clipboard.ts b/packages/protocol/src/action-protocol/clipboard.ts index 45b6c38..3f5399e 100644 --- a/packages/protocol/src/action-protocol/clipboard.ts +++ b/packages/protocol/src/action-protocol/clipboard.ts @@ -32,7 +32,7 @@ export interface RequestClipboardDataAction extends RequestAction export namespace RequestContextActions { export const KIND = 'requestContextActions'; - export function is(object: any): object is RequestContextActions { + export function is(object: unknown): object is RequestContextActions { return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'contextId') && hasObjectProp(object, 'editorContext'); } @@ -72,7 +72,7 @@ export interface SetContextActions extends ResponseAction { export namespace SetContextActions { export const KIND = 'setContextActions'; - export function is(object: any): object is SetContextActions { + export function is(object: unknown): object is SetContextActions { return Action.hasKind(object, KIND) && hasArrayProp(object, 'actions'); } diff --git a/packages/protocol/src/action-protocol/edge-modification.ts b/packages/protocol/src/action-protocol/edge-modification.ts index dd90162..4a32913 100644 --- a/packages/protocol/src/action-protocol/edge-modification.ts +++ b/packages/protocol/src/action-protocol/edge-modification.ts @@ -50,7 +50,7 @@ export interface ReconnectEdgeOperation extends Operation { export namespace ReconnectEdgeOperation { export const KIND = 'reconnectEdge'; - export function is(object: any): object is ReconnectEdgeOperation { + export function is(object: unknown): object is ReconnectEdgeOperation { return ( Operation.hasKind(object, KIND) && hasStringProp(object, 'edgeElementId') && @@ -91,7 +91,7 @@ export interface ChangeRoutingPointsOperation extends Operation { export namespace ChangeRoutingPointsOperation { export const KIND = 'changeRoutingPoints'; - export function is(object: any): object is ChangeRoutingPointsOperation { + export function is(object: unknown): object is ChangeRoutingPointsOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'newRoutingPoints'); } diff --git a/packages/protocol/src/action-protocol/element-creation.ts b/packages/protocol/src/action-protocol/element-creation.ts index 391a274..0b9c286 100644 --- a/packages/protocol/src/action-protocol/element-creation.ts +++ b/packages/protocol/src/action-protocol/element-creation.ts @@ -36,7 +36,7 @@ export interface CreateOperation extends Operation { } export namespace CreateOperation { - export function is(object: any): object is CreateOperation { + export function is(object: unknown): object is CreateOperation { return Operation.is(object) && hasStringProp(object, 'elementTypeId'); } @@ -46,7 +46,7 @@ export namespace CreateOperation { * @param kind The expected operation kind. * @returns A type literal indicating wether the given object is a create operation with the given kind. */ - export function hasKind(object: any, kind: string): object is CreateOperation { + export function hasKind(object: unknown, kind: string): object is CreateOperation { return CreateOperation.is(object) && object.kind === kind; } } @@ -75,7 +75,7 @@ export interface CreateNodeOperation extends CreateOperation { export namespace CreateNodeOperation { export const KIND = 'createNode'; - export function is(object: any): object is CreateNodeOperation { + export function is(object: unknown): object is CreateNodeOperation { return CreateOperation.hasKind(object, KIND); } @@ -108,7 +108,7 @@ export interface CreateEdgeOperation extends CreateOperation { export namespace CreateEdgeOperation { export const KIND = 'createEdge'; - export function is(object: any): object is CreateEdgeOperation { + export function is(object: unknown): object is CreateEdgeOperation { return ( CreateOperation.hasKind(object, KIND) && hasStringProp(object, 'sourceElementId') && hasStringProp(object, 'targetElementId') ); @@ -144,7 +144,7 @@ export interface DeleteElementOperation extends Operation, Omit; + readonly data: Record; } diff --git a/packages/protocol/src/action-protocol/element-type-hints.ts b/packages/protocol/src/action-protocol/element-type-hints.ts index 6954679..6af66ff 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SModelElement } from 'sprotty-protocol'; -import { SModelElementSchema } from '.'; +import { GModelElementSchema } from '../model/model-schema'; import { hasArrayProp, hasBooleanProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; @@ -55,7 +54,7 @@ export interface ShapeTypeHint extends TypeHint { readonly reparentable: boolean; /** - * The types of elements that can be contained by this element (if any) + * The types of elements that can be contained by this element (if unknown) */ readonly containableElementTypeIds?: string[]; } @@ -72,13 +71,13 @@ export interface EdgeTypeHint extends TypeHint { /** * Allowed source element types for this edge type. - * If not defined any element can be used as source element for this edge. + * If not defined unknown element can be used as source element for this edge. */ readonly sourceElementTypeIds?: string[]; /** * Allowed target element types for this edge type - * If not defined any element can be used as target element for this edge. + * If not defined unknown element can be used as target element for this edge. */ readonly targetElementTypeIds?: string[]; @@ -106,7 +105,7 @@ export interface RequestTypeHintsAction extends RequestAction { export namespace RequestMarkersAction { export const KIND = 'requestMarkers'; - export function is(object: any): object is RequestMarkersAction { + export function is(object: unknown): object is RequestMarkersAction { return RequestAction.hasKind(object, KIND) && hasArrayProp(object, 'elementsIDs'); } @@ -97,7 +97,7 @@ export namespace RequestMarkersAction { /** * Instructs the client to add markers to the diagram. * Typically, this is a response to the {@link RequestMarkersAction} containing all validation markers, but can be sent by the server at - * any time. + * unknown time. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks * and creating new `SetMarkersActions`. */ @@ -118,7 +118,7 @@ export interface SetMarkersAction extends ResponseAction { export namespace SetMarkersAction { export const KIND = 'setMarkers'; - export function is(object: any): object is SetMarkersAction { + export function is(object: unknown): object is SetMarkersAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); } @@ -150,7 +150,7 @@ export interface DeleteMarkersAction extends Action { export namespace DeleteMarkersAction { export const KIND = 'deleteMarkers'; - export function is(object: any): object is DeleteMarkersAction { + export function is(object: unknown): object is DeleteMarkersAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); } diff --git a/packages/protocol/src/action-protocol/index.ts b/packages/protocol/src/action-protocol/index.ts index ad23bda..4913dd1 100644 --- a/packages/protocol/src/action-protocol/index.ts +++ b/packages/protocol/src/action-protocol/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,7 +29,6 @@ export * from './model-data'; export * from './model-edit-mode'; export * from './model-layout'; export * from './model-saving'; -export * from './model-structure'; export * from './node-modification'; export * from './tool-palette'; export * from './types'; diff --git a/packages/protocol/src/action-protocol/model-data.ts b/packages/protocol/src/action-protocol/model-data.ts index 02b0211..0b87c28 100644 --- a/packages/protocol/src/action-protocol/model-data.ts +++ b/packages/protocol/src/action-protocol/model-data.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; +import { GModelRootSchema } from '../model/model-schema'; import { hasObjectProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; -import { SModelRootSchema } from './model-structure'; import { Args } from './types'; /** @@ -36,7 +36,7 @@ export interface RequestModelAction extends RequestAction, sprot export namespace RequestModelAction { export const KIND = 'requestModel'; - export function is(object: any): object is RequestModelAction { + export function is(object: unknown): object is RequestModelAction { return RequestAction.hasKind(object, KIND); } @@ -59,17 +59,17 @@ export interface SetModelAction extends ResponseAction, sprotty.SetModelAction { /** * The new graphical model root. */ - newRoot: SModelRootSchema; + newRoot: GModelRootSchema; } export namespace SetModelAction { export const KIND = 'setModel'; - export function is(object: any): object is SetModelAction { + export function is(object: unknown): object is SetModelAction { return Action.hasKind(object, KIND) && hasObjectProp(object, 'newRoot'); } - export function create(newRoot: SModelRootSchema, options: { responseId?: string } = {}): SetModelAction { + export function create(newRoot: GModelRootSchema, options: { responseId?: string } = {}): SetModelAction { return { kind: KIND, responseId: '', @@ -88,7 +88,7 @@ export namespace SetModelAction { export interface UpdateModelAction extends Action, Omit { kind: typeof UpdateModelAction.KIND; - newRoot: SModelRootSchema; + newRoot: GModelRootSchema; /** * Boolean flag to indicate wether updated/changed elements should be animated in the diagram. */ @@ -98,11 +98,11 @@ export interface UpdateModelAction extends Action, Omit, sprotty.RequestBoundsAction { +export interface RequestBoundsAction extends RequestAction, Omit { kind: typeof RequestBoundsAction.KIND; /** * The model root element for which to compute the bounds. */ - newRoot: SModelRootSchema; + newRoot: GModelRootSchema; } export namespace RequestBoundsAction { export const KIND = 'requestBounds'; - export function is(object: any): object is RequestBoundsAction { + export function is(object: unknown): object is RequestBoundsAction { return RequestAction.hasKind(object, KIND) && hasObjectProp(object, 'newRoot'); } - export function create(newRoot: SModelRootSchema, options: { requestId?: string } = {}): RequestBoundsAction { + export function create(newRoot: GModelRootSchema, options: { requestId?: string } = {}): RequestBoundsAction { return { kind: KIND, requestId: '', @@ -84,7 +84,7 @@ export interface ComputedBoundsAction extends ResponseAction, sprotty.ComputedBo export namespace ComputedBoundsAction { export const KIND = 'computedBounds'; - export function is(object: any): object is ComputedBoundsAction { + export function is(object: unknown): object is ComputedBoundsAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'bounds'); } @@ -123,7 +123,7 @@ export interface LayoutOperation extends Operation, Omit { export namespace RequestExportSvgAction { export const KIND = 'requestExportSvg'; - export function is(object: any): object is RequestExportSvgAction { + export function is(object: unknown): object is RequestExportSvgAction { return RequestAction.hasKind(object, KIND); } @@ -121,7 +121,7 @@ export interface ExportSvgAction extends ResponseAction { export namespace ExportSvgAction { export const KIND = 'exportSvg'; - export function is(object: any): object is ExportSvgAction { + export function is(object: unknown): object is ExportSvgAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'svg'); } diff --git a/packages/protocol/src/action-protocol/model-structure.ts b/packages/protocol/src/action-protocol/model-structure.ts deleted file mode 100644 index 97b8e70..0000000 --- a/packages/protocol/src/action-protocol/model-structure.ts +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Bounds } from 'sprotty-protocol'; -import * as sprotty from 'sprotty-protocol/lib/model'; -import { hasStringProp } from '../utils/type-util'; - -/** - * The schema of an SModelElement describes its serializable form. The actual class-based model is derived - * its schema whenever the client or server deserializes a received schema`. - * Each model element must have a unique ID and a type that is used on the client to look up its view. - */ -export interface SModelElementSchema extends sprotty.SModelElement { - id: string; - /** - * Type to look up the graphical representation of this element. - */ - type: string; - - children?: SModelElementSchema[]; - - /** - * CSS classes that should be applied on the rendered SVG element representing this element. - */ - cssClasses?: string[]; -} - -export namespace SModelElementSchema { - /** - * Typeguard function to check wether the given object is an {@link SModelElementSchema}. - * @param object The object to check. - * @returns A type literal indicating wether the given object is of type {@link SModelElementSchema}. - */ - export function is(object: any): object is SModelElementSchema { - return typeof object === 'object' && hasStringProp(object, 'type') && hasStringProp(object, 'id'); - } -} - -/** - * Serializable schema for the root element of the model tree. - */ -export interface SModelRootSchema extends SModelElementSchema { - /** - * Bounds of this element in the canvas. - */ - canvasBounds?: Bounds; - - /** - * The revision number identifies single versions of the models sent by the server. - */ - revision?: number; -} diff --git a/packages/protocol/src/action-protocol/node-modification.spec.ts b/packages/protocol/src/action-protocol/node-modification.spec.ts index fa8213a..c9f0eb1 100644 --- a/packages/protocol/src/action-protocol/node-modification.spec.ts +++ b/packages/protocol/src/action-protocol/node-modification.spec.ts @@ -19,7 +19,7 @@ import { Dimension, Point } from 'sprotty-protocol'; import { ChangeBoundsOperation, ChangeContainerOperation } from './node-modification'; /** - * The schema of an SModelElement describes its serializable form. The actual class-based model is derived + * The schema of an GModelElement describes its serializable form. The actual class-based model is derived * its schema whenever the client or server deserializes a received schema`. * Each model element must have a unique ID and a type that is used on the client to look up its view. */ diff --git a/packages/protocol/src/action-protocol/node-modification.ts b/packages/protocol/src/action-protocol/node-modification.ts index 5eb014e..e80ef3b 100644 --- a/packages/protocol/src/action-protocol/node-modification.ts +++ b/packages/protocol/src/action-protocol/node-modification.ts @@ -35,7 +35,7 @@ export interface ChangeBoundsOperation extends Operation { export namespace ChangeBoundsOperation { export const KIND = 'changeBounds'; - export function is(object: any): object is ChangeBoundsOperation { + export function is(object: unknown): object is ChangeBoundsOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'newBounds'); } @@ -76,7 +76,7 @@ export interface ChangeContainerOperation extends Operation { export namespace ChangeContainerOperation { export const KIND = 'changeContainer'; - export function is(object: any): object is ChangeContainerOperation { + export function is(object: unknown): object is ChangeContainerOperation { return Operation.hasKind(object, KIND) && hasStringProp(object, 'elementId') && hasStringProp(object, 'targetContainerId'); } diff --git a/packages/protocol/src/action-protocol/tool-palette.ts b/packages/protocol/src/action-protocol/tool-palette.ts index f1d38db..e4bd9f0 100644 --- a/packages/protocol/src/action-protocol/tool-palette.ts +++ b/packages/protocol/src/action-protocol/tool-palette.ts @@ -41,7 +41,7 @@ export interface TriggerNodeCreationAction extends Action { export namespace TriggerNodeCreationAction { export const KIND = 'triggerNodeCreation'; - export function is(object: any): object is TriggerNodeCreationAction { + export function is(object: unknown): object is TriggerNodeCreationAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'elementTypeId'); } @@ -78,7 +78,7 @@ export interface TriggerEdgeCreationAction extends Action { export namespace TriggerEdgeCreationAction { export const KIND = 'triggerEdgeCreation'; - export function is(object: any): object is TriggerEdgeCreationAction { + export function is(object: unknown): object is TriggerEdgeCreationAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'elementTypeId'); } diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index d4c7847..5f7a34c 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -98,8 +98,8 @@ export interface EditorContext { } export namespace EditorContext { - export function is(object: any): object is EditorContext { - return object !== undefined && hasArrayProp(object, 'selectedElementIds'); + export function is(object: unknown): object is EditorContext { + return AnyObject.is(object) && hasArrayProp(object, 'selectedElementIds'); } } /** @@ -124,7 +124,7 @@ export interface LabeledAction { } export namespace LabeledAction { - export function is(object: any): object is LabeledAction { + export function is(object: unknown): object is LabeledAction { return AnyObject.is(object) && hasStringProp(object, 'label') && hasArrayProp(object, 'actions'); } @@ -152,7 +152,7 @@ export interface PaletteItem extends LabeledAction { } export namespace PaletteItem { - export function is(object: any): object is PaletteItem { + export function is(object: unknown): object is PaletteItem { return LabeledAction.is(object) && hasStringProp(object, 'id') && hasStringProp(object, 'sortString'); } @@ -168,7 +168,7 @@ export namespace PaletteItem { export type TriggerElementCreationAction = TriggerEdgeCreationAction | TriggerNodeCreationAction; - export function isTriggerElementCreationAction(object: any): object is TriggerElementCreationAction { + export function isTriggerElementCreationAction(object: unknown): object is TriggerElementCreationAction { return TriggerNodeCreationAction.is(object) || TriggerEdgeCreationAction.is(object); } } @@ -200,7 +200,7 @@ export interface MenuItem extends LabeledAction { } export namespace MenuItem { - export function is(object: any): object is MenuItem { + export function is(object: unknown): object is MenuItem { return LabeledAction.is(object) && hasStringProp(object, 'id'); } } diff --git a/packages/protocol/src/action-protocol/undo-redo.ts b/packages/protocol/src/action-protocol/undo-redo.ts index 61d68d0..a219fec 100644 --- a/packages/protocol/src/action-protocol/undo-redo.ts +++ b/packages/protocol/src/action-protocol/undo-redo.ts @@ -28,7 +28,7 @@ export interface UndoAction extends Omit { export namespace UndoAction { export const KIND = 'glspUndo'; - export function is(object: any): object is UndoAction { + export function is(object: unknown): object is UndoAction { return Action.hasKind(object, KIND); } @@ -51,7 +51,7 @@ export interface RedoAction extends Omit { export namespace RedoAction { export const KIND = 'glspRedo'; - export function is(object: any): object is RedoAction { + export function is(object: unknown): object is RedoAction { return Action.hasKind(object, KIND); } diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index de5da5e..8a677cc 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -47,7 +47,7 @@ export interface CenterAction extends Action, sprotty.CenterAction { export namespace CenterAction { export const KIND = 'center'; - export function is(object: any): object is CenterAction { + export function is(object: unknown): object is CenterAction { return Action.hasKind(object, KIND) && hasBooleanProp(object, 'animate') && hasBooleanProp(object, 'retainZoom'); } @@ -94,7 +94,7 @@ export interface FitToScreenAction extends Action, sprotty.FitToScreenAction { export namespace FitToScreenAction { export const KIND = 'fit'; - export function is(object: any): object is FitToScreenAction { + export function is(object: unknown): object is FitToScreenAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds') && hasBooleanProp(object, 'animate'); } diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index 298b459..d8f1683 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import * as util from 'util'; -import { Action, ActionMessage } from '../action-protocol'; +import { Action, ActionMessage } from '../action-protocol/base-protocol'; import { expectToThrowAsync } from '../utils/test-util'; import { BaseGLSPClient, GLOBAL_HANDLER_ID } from './base-glsp-client'; import { ClientState } from './glsp-client'; diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts index edc0a2d..d8cc78c 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -16,7 +16,7 @@ import { Deferred } from 'sprotty-protocol'; import { Disposable } from 'vscode-jsonrpc'; -import { Action, ActionMessage } from '../action-protocol'; +import { Action, ActionMessage } from '../action-protocol/base-protocol'; import { distinctAdd, remove } from '../utils/array-util'; import { Emitter, Event } from '../utils/event'; import { ActionMessageHandler, ClientState, GLSPClient } from './glsp-client'; diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index aba78ff..7b2f95c 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -19,6 +19,7 @@ import { ActionMessage } from '../action-protocol'; import { Disposable } from '../utils/disposable'; import { Event } from '../utils/event'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; +import { AnyObject, hasStringProp } from '../utils/type-util'; export class ApplicationIdProvider { private static _applicationId?: string; @@ -131,7 +132,7 @@ export interface GLSPClient { shutdownServer(): void; /** - * Stops the client and disposes any resources. During the stop procedure the client is in the `Stopping` state and will + * Stops the client and disposes unknown resources. During the stop procedure the client is in the `Stopping` state and will * transition to either `Stopped` or `ServerError`. * * @returns A promise that resolves after the server was stopped and disposed. @@ -161,8 +162,8 @@ export namespace GLSPClient { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - export function isOptions(object: any): object is Options { - return typeof object === 'object' && 'id' in object && typeof object['id'] === 'string'; + export function isOptions(object: unknown): object is Options { + return AnyObject.is(object) && hasStringProp(object, 'id'); } export const protocolVersion = '1.0.0'; diff --git a/packages/protocol/src/client-server-protocol/glsp-server.ts b/packages/protocol/src/client-server-protocol/glsp-server.ts index a4e1d87..f670c87 100644 --- a/packages/protocol/src/client-server-protocol/glsp-server.ts +++ b/packages/protocol/src/client-server-protocol/glsp-server.ts @@ -81,7 +81,7 @@ export interface GLSPServer { /** * The `shutdown` notification is sent from the client to the server if the client disconnects from the server (e.g. * the client application has been closed). - * This gives the server a chance to clean up and dispose any resources dedicated to the client and its sessions. + * This gives the server a chance to clean up and dispose unknown resources dedicated to the client and its sessions. * All {@link GLSPServerListener}s are notified via the {@link GLSPServerListener.serverShutDown} method. * Afterwards the server instance is considered to be disposed and can no longer be used for handling requests. * diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index d9c5741..51b7c3f 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -33,17 +33,17 @@ class StubMessageConnection implements MessageConnection { throw new Error('Method not implemented.'); } - onRequest(...args: any[]): Disposable { + onRequest(...args: unknown[]): Disposable { return Disposable.create(() => {}); } hasPendingResponse(): boolean { return false; } - sendNotification(...args: any[]): Promise { + sendNotification(...args: unknown[]): Promise { return Promise.resolve(); } - onNotification(...args: any[]): Disposable { + onNotification(...args: unknown[]): Disposable { return Disposable.create(() => {}); } @@ -55,7 +55,7 @@ class StubMessageConnection implements MessageConnection { } onUnhandledProgress = this.mockEvent; - trace(...args: any[]): Promise { + trace(...args: unknown[]): Promise { return Promise.resolve(); } onError = this.mockEvent; @@ -238,7 +238,7 @@ describe('Base JSON-RPC GLSP Client', () => { it('Should be in error state after connection error', async () => { // mock setup resetClient(false); - const listeners: ((e: any) => any)[] = []; + const listeners: ((e: unknown) => unknown)[] = []; connection.onError.callsFake(listener => { listeners.push(listener); return Disposable.create(() => remove(listeners, listener)); @@ -251,7 +251,7 @@ describe('Base JSON-RPC GLSP Client', () => { it('Should be in error state after connection close while running', async () => { // mock setup resetClient(false); - const listeners: ((e: any) => any)[] = []; + const listeners: ((e: unknown) => unknown)[] = []; connection.onClose.callsFake(listener => { listeners.push(listener); return Disposable.create(() => remove(listeners, listener)); diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts index db741fc..dac1ede 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/glsp-jsonrpc-client.ts @@ -27,7 +27,7 @@ export namespace JsonrpcGLSPClient { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - export function isOptions(object: any): object is Options { + export function isOptions(object: unknown): object is Options { return GLSPClient.isOptions(object) && 'connectionProvider' in object; } @@ -39,7 +39,7 @@ export namespace JsonrpcGLSPClient { export const ShutdownNotification = new NotificationType0('shutdown'); export const ClientNotReadyMsg = 'JsonrpcGLSPClient is not ready yet'; - export function error(message: string, ...optionalParams: any[]): void { + export function error(message: string, ...optionalParams: unknown[]): void { console.error(`[JsonrpcGLSPClient] ${message}`, optionalParams); } } diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts b/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts index 7f1fa63..c22a471 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/websocket-connection.ts @@ -36,8 +36,8 @@ import { */ export interface WebSocketWrapper extends Disposable { send(content: string | ArrayBufferLike | ArrayBufferView): void; - onMessage(cb: (data: any) => void): void; - onError(cb: (reason: any) => void): void; + onMessage(cb: (data: unknown) => void): void; + onError(cb: (reason: unknown) => void): void; onClose(cb: (code: number, reason: string) => void): void; } @@ -66,7 +66,7 @@ export function wrap(socket: WebSocket): WebSocketWrapper { export class WebSocketMessageReader extends AbstractMessageReader { protected state: 'initial' | 'listening' | 'closed' = 'initial'; protected callback?: DataCallback; - protected eventQueue: Array<{ message?: unknown; error?: any }> = []; + protected eventQueue: Array<{ message?: unknown; error?: unknown }> = []; constructor(protected readonly socket: WebSocketWrapper) { super(); @@ -105,7 +105,7 @@ export class WebSocketMessageReader extends AbstractMessageReader { } } - protected override fireError(error: any): void { + protected override fireError(error: unknown): void { if (this.state === 'initial') { this.eventQueue.push({ error }); } else if (this.state === 'listening') { diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 0d7e65a..d47a1d9 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -13,38 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { SetBoundsAction, SetViewportAction } from 'sprotty-protocol/lib/actions'; -import { Action } from './action-protocol'; -import { hasBooleanProp, hasObjectProp, hasStringProp } from './utils/type-util'; - -// Add the is() function to the namespace declarations of sprotty-protocol actions -declare module 'sprotty-protocol/lib/actions' { - // eslint-disable-next-line @typescript-eslint/no-shadow - namespace SetViewportAction { - export function is(object: any): object is SetViewportAction; - } - - // eslint-disable-next-line @typescript-eslint/no-shadow - namespace SetBoundsAction { - export function is(object: any): object is SetBoundsAction; - } -} - -SetViewportAction.is = (object: any): object is SetViewportAction => - Action.hasKind(object, SetViewportAction.KIND) && - hasStringProp(object, 'elementId') && - hasObjectProp(object, 'newViewport') && - hasBooleanProp(object, 'animate'); - -SetBoundsAction.is = (object: any): object is SetBoundsAction => - Action.hasKind(object, SetBoundsAction.KIND) && hasObjectProp(object, 'bounds'); // Partial reexport of sprotty-protocol export { Viewport } from 'sprotty-protocol/lib/model'; export * from 'sprotty-protocol/lib/utils/async'; export * from 'sprotty-protocol/lib/utils/geometry'; export * from 'sprotty-protocol/lib/utils/json'; -export * from 'sprotty-protocol/lib/utils/model-utils'; +export { applyBounds, cloneModel, findElement, getBasicType, getSubType } from 'sprotty-protocol/lib/utils/model-utils'; // Default export of @eclipse-glsp/protocol export * from './action-protocol'; export * from './client-server-protocol/base-glsp-client'; @@ -58,9 +33,9 @@ export * from './client-server-protocol/jsonrpc/ws-connection-provider'; export * from './client-server-protocol/types'; export * from './model/default-types'; export * from './model/model-schema'; +export * from './sprotty-actions'; export * from './utils/array-util'; export * from './utils/di-util'; export * from './utils/disposable'; export * from './utils/event'; export * from './utils/type-util'; -export { SetBoundsAction, SetViewportAction }; diff --git a/packages/protocol/src/model/model-schema.ts b/packages/protocol/src/model/model-schema.ts index e0f3939..f8f7177 100644 --- a/packages/protocol/src/model/model-schema.ts +++ b/packages/protocol/src/model/model-schema.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,22 +13,49 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/** - * Reexport of the sprotty-protocol model definitions with a `Schema` suffix. We use a class based - * model representation on client and server side. The `Schema` suffix is used for type that represent - * the serializable description of these graphical model elements. - * The basic types `SModelElementSchema` and `SRootElementSchema` are already exported by the base action protocol so - * we only have to reexport the advanced model element interfaces from sprotty-protocol. +import { + SCompartment as GCompartmentSchema, + SEdge as GEdgeSchema, + ForeignObjectElement as GForeignObjectElementSchema, + SGraph as GGraphSchema, + HtmlRoot as GHtmlRootSchema, + SLabel as GLabelSchema, + SModelElement as GModelElementSchema, + SModelRoot as GModelRootSchema, + SNode as GNodeSchema, + SPort as GPortSchema, + PreRenderedElement as GPreRenderedElementSchema, + SShapeElement as GShapeElementSchema, + ShapedPreRenderedElement as GShapePreRenderedElementSchema, + ViewportRootElement as GViewPortRootElementSchema +} from 'sprotty-protocol/lib/model'; +import { hasStringProp } from '../utils/type-util'; + +/** Serializable representation of GModel elements. This is the transfer format + * used to exchange model information between client and server. Both the client and + * server have a deserialization mechanism in place to convert a GModelElementSchema + * into the corresponding class-based model (or EMF-based in case of the Java Server). + * + * To ensure sprotty compatibility all types are just aliases of the corresponding Sprotty SModel API element. */ + +export function isGModelElementSchema(object: any): object is GModelElementSchema { + return typeof object === 'object' && hasStringProp(object, 'type') && hasStringProp(object, 'id'); +} + export { - HtmlRoot as HtmlRootSchema, - PreRenderedElement as PreRenderedElementSchema, - SCompartment as SCompartmentSchema, - SEdge as SEdgeSchema, - SGraph as SGraphSchema, - ShapedPreRenderedElement as ShapedPreRenderedElementSchema, - SLabel as SLabelSchema, - SNode as SNodeSchema, - SPort as SPortSchema, - SShapeElement as SShapeElementSchema -} from 'sprotty-protocol/lib/model'; + GCompartmentSchema, + GEdgeSchema, + GForeignObjectElementSchema, + GGraphSchema, + GHtmlRootSchema, + GLabelSchema, + GModelElementSchema, + GModelRootSchema, + GNodeSchema, + GPortSchema, + GPreRenderedElementSchema, + GShapeElementSchema, + GShapePreRenderedElementSchema, + GViewPortRootElementSchema +}; diff --git a/packages/protocol/src/sprotty-actions.ts b/packages/protocol/src/sprotty-actions.ts new file mode 100644 index 0000000..fd08c6a --- /dev/null +++ b/packages/protocol/src/sprotty-actions.ts @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ + +import { + BringToFrontAction, + CollapseExpandAction, + CollapseExpandAllAction, + GetSelectionAction, + GetViewportAction, + HoverFeedbackAction, + MoveAction, + OpenAction, + SelectionResult, + SetBoundsAction, + SetViewportAction, + ViewportResult +} from 'sprotty-protocol/lib/actions'; +import { Action, RequestAction } from './action-protocol/base-protocol'; +import { hasArrayProp, hasBooleanProp, hasObjectProp, hasStringProp } from './utils/type-util'; + +/* + * Subset of the actions defined in sprotty-protocol that are reused as is i.e. they don't have a dedicated + * replacement in the GLSP action protocol. + * + * Module augmentation is used to provide utility functions (typeguards etc.) via corresponding namespace. + */ + +declare module 'sprotty-protocol/lib/actions' { + namespace BringToFrontAction { + function is(object: unknown): object is BringToFrontAction; + } + namespace CollapseExpandAction { + function is(object: unknown): object is CollapseExpandAction; + } + namespace CollapseExpandAllAction { + function is(object: unknown): object is CollapseExpandAllAction; + } + namespace GetSelectionAction { + function is(object: unknown): object is GetSelectionAction; + } + namespace GetViewportAction { + function is(object: unknown): object is GetViewportAction; + } + namespace HoverFeedbackAction { + function is(object: unknown): object is HoverFeedbackAction; + } + namespace MoveAction { + function is(object: unknown): object is MoveAction; + } + namespace OpenAction { + function is(object: unknown): object is OpenAction; + } + namespace SelectionResult { + function is(object: unknown): object is SelectionResult; + } + namespace SetBoundsAction { + function is(object: unknown): object is SetBoundsAction; + } + namespace SetViewportAction { + function is(object: unknown): object is SetViewportAction; + } + namespace ViewportResult { + function is(object: unknown): object is ViewportResult; + } +} + +CollapseExpandAction.is = (object): object is CollapseExpandAction => + Action.hasKind(object, CollapseExpandAction.KIND) && hasArrayProp(object, 'expandIds') && hasArrayProp(object, 'collapseIds'); + +CollapseExpandAllAction.is = (object): object is CollapseExpandAllAction => + Action.hasKind(object, CollapseExpandAllAction.KIND) && hasBooleanProp(object, 'expand'); + +GetSelectionAction.is = (object): object is GetSelectionAction => RequestAction.hasKind(object, GetSelectionAction.KIND); + +GetViewportAction.is = (object): object is GetViewportAction => RequestAction.hasKind(object, GetViewportAction.KIND); + +HoverFeedbackAction.is = (object): object is HoverFeedbackAction => + Action.hasKind(object, HoverFeedbackAction.KIND) && hasStringProp(object, 'mouseoverElement') && hasBooleanProp(object, 'mouseIsOver'); + +MoveAction.is = (object): object is MoveAction => + Action.hasKind(object, MoveAction.KIND) && + hasArrayProp(object, 'moves') && + hasBooleanProp(object, 'animate') && + hasBooleanProp(object, 'finished'); + +OpenAction.is = (object): object is OpenAction => Action.hasKind(object, OpenAction.KIND) && hasStringProp(object, 'elementId'); + +SelectionResult.is = (object): object is SelectionResult => + Action.hasKind(object, SelectionResult.KIND) && hasArrayProp(object, 'selectedElementsIDs'); + +SetBoundsAction.is = (object): object is SetBoundsAction => Action.hasKind(object, SetBoundsAction.KIND) && hasObjectProp(object, 'bounds'); + +SetViewportAction.is = (object): object is SetViewportAction => + Action.hasKind(object, SetViewportAction.KIND) && + hasStringProp(object, 'elementId') && + hasObjectProp(object, 'newViewport') && + hasBooleanProp(object, 'animate'); + +ViewportResult.is = (object): object is ViewportResult => + Action.hasKind(object, ViewportResult.KIND) && hasObjectProp(object, 'viewport') && hasObjectProp(object, 'canvasBounds'); + +export { + BringToFrontAction, + CollapseExpandAction, + CollapseExpandAllAction, + GetSelectionAction, + GetViewportAction, + HoverFeedbackAction, + MoveAction, + OpenAction, + SelectionResult, + SetBoundsAction, + SetViewportAction, + ViewportResult +}; diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 6bee3c5..501d524 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -131,7 +131,7 @@ export function distinctAdd(array: T[], ...values: T[]): void { * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. * @returns A type predicate indicating wether the given object has passed the type guard check. */ -export function isArrayOfType(object: any, typeGuard: (elem: any) => elem is T, supportEmpty = false): object is T[] { +export function isArrayOfType(object: unknown, typeGuard: (elem: unknown) => elem is T, supportEmpty = false): object is T[] { return isArrayMatching(object, element => typeGuard(element), supportEmpty); } @@ -143,7 +143,7 @@ export function isArrayOfType(object: any, typeGuard: (elem: any) => elem is * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. * @returns A type predicate indicating wether the given object has passed the type guard check. */ -export function isArrayOfClass(object: any, constructor: Constructor, supportEmpty = false): object is T[] { +export function isArrayOfClass(object: unknown, constructor: Constructor, supportEmpty = false): object is T[] { return isArrayMatching(object, element => element instanceof constructor, supportEmpty); } @@ -155,7 +155,7 @@ export function isArrayOfClass(object: any, constructor: Constructor, supp * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. * @returns A type predicate indicating wether the given object has passed the type guard check. */ -export function isArrayOfPrimitive(object: any, primitiveType: Primitive, supportEmpty = false): object is T[] { +export function isArrayOfPrimitive(object: unknown, primitiveType: Primitive, supportEmpty = false): object is T[] { return isArrayMatching(object, element => typeof element === primitiveType, supportEmpty); } @@ -166,7 +166,7 @@ export function isArrayOfPrimitive(object: any, primitiveType: Primitive, sup * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. * @returns A type predicate indicating wether the given object has passed the type guard check. */ -export function isStringArray(object: any, supportEmpty = false): object is string[] { +export function isStringArray(object: unknown, supportEmpty = false): object is string[] { return isArrayOfPrimitive(object, 'string', supportEmpty); } @@ -177,6 +177,6 @@ export function isStringArray(object: any, supportEmpty = false): object is stri * @param supportEmpty A flag to determine wether empty arrays be matched by the predicate.. * @returns `true` if the given object is an array and all elements match the given predicate. `false` otherwise. */ -export function isArrayMatching(object: any, predicate: (elem: any) => boolean, supportEmpty = false): boolean { +export function isArrayMatching(object: unknown, predicate: (elem: unknown) => boolean, supportEmpty = false): boolean { return Array.isArray(object) && object.every(predicate) && (supportEmpty || object.length > 0); } diff --git a/packages/protocol/src/utils/disposable.spec.ts b/packages/protocol/src/utils/disposable.spec.ts index a452636..9e664dc 100644 --- a/packages/protocol/src/utils/disposable.spec.ts +++ b/packages/protocol/src/utils/disposable.spec.ts @@ -23,7 +23,7 @@ describe('Disposable', () => { it('should return false for a primitive', () => { expect(Disposable.is('A')).to.be.false; }); - it('should return false for any function', () => { + it('should return false for unknown function', () => { expect(Disposable.is('A'.toString)).to.be.false; }); it('should return true for the return value of Disposable.create()', () => { diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 07b8dc8..3a12f52 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -56,7 +56,7 @@ export namespace Disposable { */ export class DisposableCollection implements Disposable { protected readonly disposables: Disposable[] = []; - errorHandler?: (err: any) => void; + errorHandler?: (err: unknown) => void; constructor(...toDispose: Disposable[]) { toDispose.forEach(d => this.push(d)); diff --git a/packages/protocol/src/utils/event.ts b/packages/protocol/src/utils/event.ts index 03dd201..3505f4c 100644 --- a/packages/protocol/src/utils/event.ts +++ b/packages/protocol/src/utils/event.ts @@ -43,7 +43,7 @@ export interface Event extends jsonrpc.Event { * @param disposables An array to which the {@link Disposable} for removing the listener will be added. * @returns a {@link Disposable} to remove the listener again. */ - (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable; + (listener: (e: T) => unknown, thisArgs?: unknown, disposables?: Disposable[]): Disposable; } /** diff --git a/packages/protocol/src/utils/test-util.ts b/packages/protocol/src/utils/test-util.ts index deaa74c..178bf20 100644 --- a/packages/protocol/src/utils/test-util.ts +++ b/packages/protocol/src/utils/test-util.ts @@ -37,14 +37,15 @@ export async function delay(timeout: number): Promise { * @param message - Optional message to match with error message */ export async function expectToThrowAsync(toEvaluate: () => MaybePromise, message?: string): Promise { - let err: Error | undefined = undefined; + let err: unknown | undefined = undefined; try { await toEvaluate(); - } catch (error: any) { + } catch (error) { err = error; } if (message) { - expect(err?.message).to.be.equal(message); + expect(err instanceof Error, 'The error cause should be an instance of Error').to.be.true; + expect((err as Error)?.message).to.be.equal(message); } else { expect(err).to.be.an('Error'); } diff --git a/packages/protocol/src/utils/type-util.spec.ts b/packages/protocol/src/utils/type-util.spec.ts index d51a996..8922a75 100644 --- a/packages/protocol/src/utils/type-util.spec.ts +++ b/packages/protocol/src/utils/type-util.spec.ts @@ -32,7 +32,7 @@ describe('TypeUtil', () => { // eslint-disable-next-line no-null/no-null expect(AnyObject.is(null)).to.be.false; }); - it('should return false for any object of primitive type', () => { + it('should return false for unknown object of primitive type', () => { expect(AnyObject.is('')).to.be.false; expect(AnyObject.is(5)).to.be.false; expect(AnyObject.is(true)).to.be.false; diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 13b5747..f3d8da5 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** Helper type to describe any defined object*/ export type AnyObject = object; export namespace AnyObject { @@ -36,14 +37,14 @@ export type Primitive = string | number | boolean | bigint | symbol | undefined /** * Utility type to describe objects that have a constructor function i.e. classes. */ -export interface Constructor { - new (...args: any[]): T; +export interface Constructor { + new (...args: A): T; } /** * Utility type to declare a given type `T` as writable. Essentially this removes * all readonly modifiers of the type`s properties. Please use with care and only in instances - * where you know that overwriting a readonly property is safe and doesn't cause any unintended side effects. + * where you know that overwriting a readonly property is safe and doesn't cause unknown unintended side effects. */ // eslint-disable-next-line @typescript-eslint/ban-types export type Writable = { -readonly [P in keyof T]: Writable }; @@ -65,7 +66,7 @@ export type TypeGuard = (element: any) => element is T; * @returns The typeguard for this class. */ export function toTypeGuard(constructor: Constructor): TypeGuard { - return (element: unknown): element is G => element instanceof constructor; + return (element: any): element is G => element instanceof constructor; } /** @@ -117,7 +118,7 @@ export function hasNumberProp(object: AnyObject, propertyKey: string, optional = * @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined * @returns `true` if the object has property with matching key of type `object`. */ -export function hasObjectProp(object: AnyObject, propertyKey: string, optional = false): boolean { +export function hasObjectProp(object: AnyObject, propertyKey: T, optional = false): boolean { const property = (object as any)[propertyKey]; return property !== undefined ? AnyObject.is(property) : optional; } diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index e81a993..db53a4c 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -3,9 +3,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "reactNamespace": "JSX", - "baseUrl": "." + "reactNamespace": "JSX" }, - "exclude": ["**/*.spec.ts", "src/utils/test-util.ts"], "include": ["src"] } From 17790b2e153d8a6a24ea2784e852ba1cf0fdfa70 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 9 Oct 2023 14:23:39 -0700 Subject: [PATCH 265/566] Add missing export for IActionHandlerInitializer (#292) --- packages/glsp-sprotty/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 95b54c8..5f72a68 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -38,6 +38,7 @@ export * from 'sprotty/lib/base/actions/action-dispatcher'; export { ActionHandlerRegistration, ActionHandlerRegistry, + IActionHandlerInitializer, configureActionHandler, onAction } from 'sprotty/lib/base/actions/action-handler'; From 166790c4d8a4b9fb2c83f373859894cf9f017576 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Oct 2023 04:53:59 -0700 Subject: [PATCH 266/566] GLSP-865 Update to latest protocol version (#61) Update to latest protocol version and add gmodel class + builder for `GForeignObjectElement` --- examples/workflow-server/tsconfig.json | 1 - packages/graph/src/default-types.ts | 2 +- packages/graph/src/gforeign-object-element.ts | 44 +++++++++++++++++++ .../graph/src/{ghtmlroot.ts => ghtml-root.ts} | 0 packages/graph/src/gmodel-element.ts | 23 ++-------- packages/graph/src/index.ts | 3 +- packages/graph/tsconfig.json | 9 ++-- packages/layout-elk/tsconfig.json | 17 ++----- .../src/common/command/recording-command.ts | 5 +-- .../request-clipboard-data-action-handler.ts | 6 +-- .../features/model/gmodel-serializer.ts | 22 +++++----- .../model/model-submission-handler.ts | 2 +- .../common/gmodel/paste-operation-handler.ts | 4 +- .../server/src/node/gmodel/gmodel-storage.ts | 6 +-- packages/server/tsconfig.json | 14 ++---- 15 files changed, 84 insertions(+), 74 deletions(-) create mode 100644 packages/graph/src/gforeign-object-element.ts rename packages/graph/src/{ghtmlroot.ts => ghtml-root.ts} (100%) diff --git a/examples/workflow-server/tsconfig.json b/examples/workflow-server/tsconfig.json index 22dce50..919b4c0 100644 --- a/examples/workflow-server/tsconfig.json +++ b/examples/workflow-server/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": ".", "esModuleInterop": true, "resolveJsonModule": true }, diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts index cb04e64..39d09e4 100644 --- a/packages/graph/src/default-types.ts +++ b/packages/graph/src/default-types.ts @@ -18,7 +18,7 @@ import { GButton } from './gbutton'; import { GCompartment } from './gcompartment'; import { GEdge } from './gedge'; import { GGraph } from './ggraph'; -import { GHtmlRoot } from './ghtmlroot'; +import { GHtmlRoot } from './ghtml-root'; import { GIssueMarker } from './gissue-marker'; import { GLabel } from './glabel'; import { GModelElementConstructor } from './gmodel-element'; diff --git a/packages/graph/src/gforeign-object-element.ts b/packages/graph/src/gforeign-object-element.ts new file mode 100644 index 0000000..ae0a159 --- /dev/null +++ b/packages/graph/src/gforeign-object-element.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GShapePreRenderedElementBuilder, GShapedPreRenderedElement } from './gshaped-prerendered-element'; + +/** + * A `foreignObject` element to be transferred to the DOM within the SVG. + * + * This can be useful to to benefit from e.g. HTML rendering features, such as line wrapping, inside of + * the SVG diagram. Note that `foreignObject` is not supported by all browsers and SVG viewers may not + * support rendering the `foreignObject` content. + * + * If no dimensions are specified in the schema element, this element will obtain the dimension of + * its parent to fill the entire available room. Thus, this element requires specified bounds itself + * or bounds to be available for its parent. + */ +export class GForeignObjectElement extends GShapedPreRenderedElement { + static override builder(): GForeignObjectElementBuilder { + return new GForeignObjectElementBuilder(GForeignObjectElement); + } + namespace: string; +} + +export class GForeignObjectElementBuilder< + G extends GForeignObjectElement = GForeignObjectElement +> extends GShapePreRenderedElementBuilder { + namespace(namespace: string): this { + this.proxy.namespace = namespace; + return this; + } +} diff --git a/packages/graph/src/ghtmlroot.ts b/packages/graph/src/ghtml-root.ts similarity index 100% rename from packages/graph/src/ghtmlroot.ts rename to packages/graph/src/ghtml-root.ts diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts index c88080e..37889fc 100644 --- a/packages/graph/src/gmodel-element.ts +++ b/packages/graph/src/gmodel-element.ts @@ -17,29 +17,16 @@ import { Args, Bounds, Dimension, - flatPush, + GModelElementSchema, + GModelRootSchema, JsonPrimitive, MaybeArray, Point, - SModelElementSchema, - SModelRootSchema + flatPush } from '@eclipse-glsp/protocol'; import * as uuid from 'uuid'; export type GModelElementConstructor = new () => G; -/** - * Represents a `GModeElement` serialized as plain JSON object. - */ -export type GModelElementSchema = SModelElementSchema; - -export namespace GModelElementSchema { - /** - * Typeguard function to check wether the given object is an {@link GModelElementSchema}. - * @param object The object to check. - * @returns A type literal indicating wether the given object is of type {@link GModelElementSchema}. - */ - export const is = SModelElementSchema.is; -} /** * Base type for all elements of the graphical model. @@ -187,9 +174,7 @@ export abstract class GModelElementBuilder { } } -export type GModelRootSchema = SModelRootSchema; - -export class GModelRoot extends GModelElement implements SModelRootSchema { +export class GModelRoot extends GModelElement implements GModelRootSchema { static builder(): GModelRootBuilder { return new GModelRootBuilder(GModelRoot); } diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index 2360334..a8c2ac2 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -20,8 +20,9 @@ export * from './gbutton'; export * from './gcompartment'; export * from './gedge'; export * from './gedge-layoutable'; +export * from './gforeign-object-element'; export * from './ggraph'; -export * from './ghtmlroot'; +export * from './ghtml-root'; export * from './gissue-marker'; export * from './glabel'; export * from './glayoutable'; diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json index 40952a0..db53a4c 100644 --- a/packages/graph/tsconfig.json +++ b/packages/graph/tsconfig.json @@ -3,10 +3,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "reactNamespace": "JSX", - "baseUrl": "src", + "reactNamespace": "JSX" }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/packages/layout-elk/tsconfig.json b/packages/layout-elk/tsconfig.json index 280894d..0cac18e 100644 --- a/packages/layout-elk/tsconfig.json +++ b/packages/layout-elk/tsconfig.json @@ -4,17 +4,8 @@ "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", - "baseUrl": "src", - "types": [ - "node", - "mocha", - "reflect-metadata" - ] + "types": ["node", "mocha", "reflect-metadata"] }, - "exclude": [ - "**/*.spec.ts" - ], - "include": [ - "src" - ] -} \ No newline at end of file + "exclude": ["**/*.spec.ts"], + "include": ["src"] +} diff --git a/packages/server/src/common/command/recording-command.ts b/packages/server/src/common/command/recording-command.ts index d3ac655..4fecee7 100644 --- a/packages/server/src/common/command/recording-command.ts +++ b/packages/server/src/common/command/recording-command.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRootSchema } from '@eclipse-glsp/graph'; -import { AnyObject, MaybePromise, SModelRootSchema } from '@eclipse-glsp/protocol'; +import { AnyObject, GModelElementSchema, GModelRootSchema, MaybePromise } from '@eclipse-glsp/protocol'; import * as jsonPatch from 'fast-json-patch'; import { GModelSerializer } from '../features/model/gmodel-serializer'; import { ModelState } from '../features/model/model-state'; @@ -102,7 +101,7 @@ export class GModelRecordingCommand extends AbstractRecordingCommand { + protected override postChange(newModel: GModelElementSchema): MaybePromise { const newRoot = this.serializer.createRoot(newModel); this.modelState.updateRoot(newRoot); } diff --git a/packages/server/src/common/features/clipboard/request-clipboard-data-action-handler.ts b/packages/server/src/common/features/clipboard/request-clipboard-data-action-handler.ts index 9106313..152ca61 100644 --- a/packages/server/src/common/features/clipboard/request-clipboard-data-action-handler.ts +++ b/packages/server/src/common/features/clipboard/request-clipboard-data-action-handler.ts @@ -16,10 +16,10 @@ import { Action, ClipboardData, + GModelElementSchema, MaybePromise, RequestClipboardDataAction, - SetClipboardDataAction, - SModelElementSchema + SetClipboardDataAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; @@ -37,7 +37,7 @@ export class RequestClipboardDataActionHandler implements ActionHandler { protected modelSerializer: GModelSerializer; execute(action: RequestClipboardDataAction): MaybePromise { - const schemas: SModelElementSchema[] = []; + const schemas: GModelElementSchema[] = []; const index = this.modelState.index; const selectedElements = index.getAll(action.editorContext.selectedElementIds); const clipboardData: ClipboardData = { format: 'application/json' }; diff --git a/packages/server/src/common/features/model/gmodel-serializer.ts b/packages/server/src/common/features/model/gmodel-serializer.ts index 8d64167..3c31018 100644 --- a/packages/server/src/common/features/model/gmodel-serializer.ts +++ b/packages/server/src/common/features/model/gmodel-serializer.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* Derived from sprotty (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel-factory.ts) */ -import { GModelElement, GModelElementConstructor, GModelElementSchema, GModelRoot, GModelRootSchema } from '@eclipse-glsp/graph'; -import { SModelElementSchema, SModelRootSchema } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementConstructor, GModelRoot } from '@eclipse-glsp/graph'; +import { GModelElementSchema, GModelRootSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { DiagramConfiguration } from '../../diagram/diagram-configuration'; import { GLSPServerError } from '../../utils/glsp-server-error'; @@ -65,7 +65,7 @@ export class DefaultGModelSerializer implements GModelSerializer { @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; reservedKeys = ['children', 'parent', 'index', 'source', 'target']; - createRoot(schema: SModelElementSchema): GModelRoot { + createRoot(schema: GModelElementSchema): GModelRoot { const constructor = this.getConfiguredConstructor(schema); if (constructor) { const element = new constructor(); @@ -77,7 +77,7 @@ export class DefaultGModelSerializer implements GModelSerializer { throw new GLSPServerError(`No constructor is configured in DiagramConfiguration for type ${schema.type}`); } - getConfiguredConstructor(schema: SModelElementSchema): GModelElementConstructor | undefined { + getConfiguredConstructor(schema: GModelElementSchema): GModelElementConstructor | undefined { let key = schema.type; while (!this.diagramConfiguration.typeMapping.has(key)) { const i = key.lastIndexOf(':'); @@ -95,7 +95,7 @@ export class DefaultGModelSerializer implements GModelSerializer { return constructor; } - createElement(schema: SModelElementSchema, parent?: GModelElement): GModelElement { + createElement(schema: GModelElementSchema, parent?: GModelElement): GModelElement { const constructor = this.getConfiguredConstructor(schema); if (constructor) { const element = new constructor(); @@ -109,7 +109,7 @@ export class DefaultGModelSerializer implements GModelSerializer { throw new GLSPServerError(`No constructor is configured in DiagramConfiguration for type ${schema.type}`); } - createSchema(element: GModelElement): SModelElementSchema { + createSchema(element: GModelElement): GModelElementSchema { const schema = {}; for (const key in element) { if (!this.isReserved(element, key)) { @@ -121,15 +121,15 @@ export class DefaultGModelSerializer implements GModelSerializer { } (schema as any)['children'] = (element.children ?? []).map(child => this.createSchema(child)); - return schema as SModelElementSchema; + return schema as GModelElementSchema; } - protected initializeRoot(root: GModelRoot, schema: SModelRootSchema): GModelRoot { + protected initializeRoot(root: GModelRoot, schema: GModelRootSchema): GModelRoot { this.initializeParent(root, schema); return root; } - protected initializeElement(element: GModelElement, schema: SModelElementSchema): GModelElement { + protected initializeElement(element: GModelElement, schema: GModelRootSchema): GModelElement { for (const key in schema) { if (!this.isReserved(element, key)) { const value = (schema as any)[key]; @@ -141,7 +141,7 @@ export class DefaultGModelSerializer implements GModelSerializer { return element; } - protected initializeChild(child: GModelElement, schema: SModelElementSchema, parent?: GModelElement): GModelElement { + protected initializeChild(child: GModelElement, schema: GModelElementSchema, parent?: GModelElement): GModelElement { this.initializeParent(child, schema); if (parent) { child.parent = parent; @@ -149,7 +149,7 @@ export class DefaultGModelSerializer implements GModelSerializer { return child; } - protected initializeParent(parent: GModelElement, schema: SModelElementSchema): GModelElement { + protected initializeParent(parent: GModelElement, schema: GModelElementSchema): GModelElement { this.initializeElement(parent, schema); if (schema.children) { parent.children = schema.children.map(childSchema => this.createElement(childSchema, parent)); diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index 2601ab2..334fdb8 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRootSchema } from '@eclipse-glsp/graph'; import { Action, DirtyStateChangeReason, + GModelRootSchema, MarkersReason, MaybePromise, RequestBoundsAction, diff --git a/packages/server/src/common/gmodel/paste-operation-handler.ts b/packages/server/src/common/gmodel/paste-operation-handler.ts index 6feb24a..e12a5dc 100644 --- a/packages/server/src/common/gmodel/paste-operation-handler.ts +++ b/packages/server/src/common/gmodel/paste-operation-handler.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GBoundsAware, GEdge, GModelElement, isGBoundsAware } from '@eclipse-glsp/graph'; -import { EditorContext, MaybePromise, PasteOperation, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; +import { EditorContext, GModelElementSchema, MaybePromise, PasteOperation, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import * as uuid from 'uuid'; import { Command } from '../command/command'; @@ -38,7 +38,7 @@ export class GModelPasteOperationHandler extends GModelOperationHandler { } protected getCopiedElements(jsonString: string): GModelElement[] { - const schemas: SModelElementSchema[] = JSON.parse(jsonString); + const schemas: GModelElementSchema[] = JSON.parse(jsonString); return schemas.map(schema => this.modelSerializer.createElement(schema, this.modelState.root)); } diff --git a/packages/server/src/node/gmodel/gmodel-storage.ts b/packages/server/src/node/gmodel/gmodel-storage.ts index 77a9385..449d5df 100644 --- a/packages/server/src/node/gmodel/gmodel-storage.ts +++ b/packages/server/src/node/gmodel/gmodel-storage.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GGraph, GModelElementSchema } from '@eclipse-glsp/graph'; -import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; +import { GGraph } from '@eclipse-glsp/graph'; +import { MaybePromise, RequestModelAction, SaveModelAction, isGModelElementSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { GModelSerializer } from '../../common/features/model/gmodel-serializer'; import { ModelState } from '../../common/features/model/model-state'; @@ -40,7 +40,7 @@ export class GModelStorage extends AbstractJsonModelStorage { loadSourceModel(action: RequestModelAction): MaybePromise { const sourceUri = this.getSourceUri(action); - const rootSchema = this.loadFromFile(sourceUri, GModelElementSchema.is); + const rootSchema = this.loadFromFile(sourceUri, isGModelElementSchema); const root = this.modelSerializer.createRoot(rootSchema); this.modelState.updateRoot(root); } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index cafad90..1e4a6e5 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -2,14 +2,8 @@ "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", - "outDir": "lib", - "baseUrl": "src" + "outDir": "lib" }, - "exclude": [ - "**/*.spec.ts", - "src/common/test/mock-util.ts" - ], - "include": [ - "src" - ] -} \ No newline at end of file + "exclude": ["**/*.spec.ts", "src/common/test/mock-util.ts"], + "include": ["src"] +} From e8207165b31cc8ea6dd176ef5521ff3263388076 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Oct 2023 05:11:23 -0700 Subject: [PATCH 267/566] Update dependencies (#1136) In preparation for the 2.0.0 release --- dev-packages/README.md | 5 ++++- dev-packages/cli/package.json | 12 ++++++------ dev-packages/cli/tsconfig.json | 7 ++----- dev-packages/config-test/package.json | 8 ++++---- dev-packages/config/package.json | 16 ++++++++-------- dev-packages/eslint-config/package.json | 12 ++++++------ dev-packages/mocha-config/package.json | 6 +++--- dev-packages/nyc-config/package.json | 4 ++-- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/mocha.json | 2 +- 10 files changed, 37 insertions(+), 37 deletions(-) diff --git a/dev-packages/README.md b/dev-packages/README.md index 1e122a3..f038e5c 100644 --- a/dev-packages/README.md +++ b/dev-packages/README.md @@ -5,11 +5,14 @@ Common shared development packages for Eclipse GLSP components that are implemen ## Components - [`@eclipse-glsp/cli`](./cli/README.md): Provides helpful scrips and commands for developing glsp components as well as release engineering. -- [`@eclipse-glsp/config`](./config/README.md): Provides a meta package that export common configuration objects for: +- [`@eclipse-glsp/config`](./config/README.md): Provides a meta package that exports common configuration objects for: - [Typescript](https://www.typescriptlang.org/) (`tsconfig.json`) - [ESLint](https://eslint.org/) (`.eslintrc`) - [Prettier](https://prettier.io/) (`.prettierrc`). - [Mocha](https://mochajs.org/) (`.mocharc.json`). +- [`@eclipse-glsp/config-test`](./config-test//README.md): Provides a meta package that exports common test configuration object on top of `@eclipse-glsp/config`: + - [Mocha](https://www.npmjs.com/package/@eclipse-glsp/mocha-config) (`.mocharc`) + - [NYC](https://www.npmjs.com/package/@eclipse-glsp/nyc-config): (`.nycrc`) The packages are available via npm and are used by all GLSP components implemented with Typescript. diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index c8406bb..0b2b102 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "commander": "^10.0.1", - "glob": "^8.0.3", + "glob": "^10.3.10", "node-fetch": "^2.6.11", "readline-sync": "^1.4.10", "semver": "^7.5.1", @@ -50,11 +50,11 @@ }, "devDependencies": { "@eclipse-glsp/config": "1.0.0-next", - "@types/glob": "^8.0.0", - "@types/node-fetch": "2.6.2", - "@types/readline-sync": "1.4.4", - "@types/semver": "7.3.12", - "@types/shelljs": "0.8.11" + "@types/glob": "^8.1.0", + "@types/node-fetch": "^2.6.6", + "@types/readline-sync": "^1.4.5", + "@types/semver": "^7.5.3", + "@types/shelljs": "^0.8.13" }, "publishConfig": { "access": "public" diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index 14c273a..8157757 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -3,11 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "baseUrl": ".", "esModuleInterop": true, "resolveJsonModule": true }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index d385b15..ce262a4 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -28,10 +28,10 @@ "@eclipse-glsp/mocha-config": "1.0.0-next", "@eclipse-glsp/nyc-config": "1.0.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@types/chai": "^4.3.5", - "@types/mocha": "^10.0.1", - "@types/sinon": "^10.0.13", - "chai": "^4.3.7", + "@types/chai": "^4.3.7", + "@types/mocha": "^10.0.2", + "@types/sinon": "^10.0.19", + "chai": "^4.3.10", "ignore-styles": "^5.0.1", "mocha": "^10.2.0", "mocha-jenkins-reporter": "^0.4.8", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 0668030..b7aeae1 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -27,18 +27,18 @@ "@eclipse-glsp/eslint-config": "1.0.0-next", "@eclipse-glsp/prettier-config": "1.0.0-next", "@eclipse-glsp/ts-config": "1.0.0-next", - "@typescript-eslint/eslint-plugin": "^5.59.7", - "@typescript-eslint/parser": "^5.59.7", - "eslint": "^8.41.0", - "eslint-config-prettier": "^8.8.0", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-deprecation": "^1.4.1", + "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.1", "eslint-plugin-no-null": "^1.0.2", - "prettier": "^2.8.8", + "prettier": "^3.0.3", "reflect-metadata": "^0.1.13", - "rimraf": "^5.0.1" + "rimraf": "^5.0.5" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index b7f3dc4..f20b348 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -26,14 +26,14 @@ ], "main": "index.js", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.29.0", - "eslint-config-prettier": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "eslint": "^8.51.0", + "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-deprecation": "^1.3.3", + "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.26.0", + "eslint-plugin-import": "^2.28.1", "eslint-plugin-no-null": "^1.0.2" }, "publishConfig": { diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index a25b826..58e0b8f 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -30,9 +30,9 @@ ".mocharc.json" ], "peerDependencies": { - "ignore-styles": "^5.0.0", - "mocha": "^10.0.0", - "reflect-metadata": "^0.1.0" + "ignore-styles": "^5.0.1", + "mocha": "^10.2.0", + "reflect-metadata": "^0.1.13" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 2940bce..a760c78 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -26,8 +26,8 @@ ], "main": "index.json", "peerDependencies": { - "@istanbuljs/nyc-config-typescript": "^1.0.0", - "nyc": "^15.0.0" + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "nyc": "15.1.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 68b91d6..de74508 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "~2.4.3" + "prettier-plugin-packagejson": "~2.4.6" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/mocha.json b/dev-packages/ts-config/mocha.json index fc29724..bdacf5c 100644 --- a/dev-packages/ts-config/mocha.json +++ b/dev-packages/ts-config/mocha.json @@ -3,5 +3,5 @@ "extends": "./tsconfig.json", "compilerOptions": { "types": ["node", "mocha", "reflect-metadata"] - }, + } } From df55cc9484b869383b1c195f9882c1a60295812e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 13 Oct 2023 04:51:52 -0700 Subject: [PATCH 268/566] Bugfixes for 2.0 (#62) - Fix cut operation handler Ensure that the GModelCutOperation handler does not execute a command itself and instead dispatches a corresponding DeleteOperation - Fix dependecy resolvement in delete-operaetion handler --- .../server/src/common/gmodel/cut-operation-handler.ts | 8 +++++--- .../src/common/gmodel/delete-operation-handler.ts | 11 ++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/server/src/common/gmodel/cut-operation-handler.ts b/packages/server/src/common/gmodel/cut-operation-handler.ts index 370500d..5dc58b8 100644 --- a/packages/server/src/common/gmodel/cut-operation-handler.ts +++ b/packages/server/src/common/gmodel/cut-operation-handler.ts @@ -28,9 +28,11 @@ export class GModelCutOperationHandler extends GModelOperationHandler { createCommand(operation: CutOperation): MaybePromise { const cuttableElementIds = this.getElementToCut(operation); - return cuttableElementIds.length > 0 // - ? this.commandOf(() => this.actionDispatcher.dispatch(DeleteElementOperation.create(cuttableElementIds))) - : undefined; + // If we have cuttable elements we dispatch a DeleteElementOperation otherwise do nothing + if (cuttableElementIds.length > 0) { + this.actionDispatcher.dispatch(DeleteElementOperation.create(cuttableElementIds)); + } + return undefined; } protected getElementToCut(cutOperation: CutOperation): string[] { diff --git a/packages/server/src/common/gmodel/delete-operation-handler.ts b/packages/server/src/common/gmodel/delete-operation-handler.ts index 0b65f82..5e020f2 100644 --- a/packages/server/src/common/gmodel/delete-operation-handler.ts +++ b/packages/server/src/common/gmodel/delete-operation-handler.ts @@ -72,7 +72,7 @@ export class GModelDeleteOperationHandler extends GModelOperationHandler { } const dependents = new Set(); - this.collectDependents(dependents, nodeToDelete); + this.collectDependents(dependents, nodeToDelete, false); dependents.forEach(dependant => { const index = this.modelState.root.children.findIndex(element => element === dependant); @@ -85,22 +85,23 @@ export class GModelDeleteOperationHandler extends GModelOperationHandler { return true; } - protected collectDependents(dependents: Set, nodeToDelete: GModelElement): void { + protected collectDependents(dependents: Set, nodeToDelete: GModelElement, isChild: boolean): void { if (dependents.has(nodeToDelete)) { return; } if (nodeToDelete.children.length > 0) { - nodeToDelete.children.forEach(child => this.collectDependents(dependents, child)); + nodeToDelete.children.forEach(child => this.collectDependents(dependents, child, true)); } if (nodeToDelete instanceof GNode) { const index = this.modelState.index; + index.getIncomingEdges(nodeToDelete).forEach(incoming => { - this.collectDependents(dependents, incoming); + dependents.add(incoming); }); index.getOutgoingEdges(nodeToDelete).forEach(outgoing => { - this.collectDependents(dependents, outgoing); + dependents.add(outgoing); }); } From 79e377f4b031139c09cdab5b7621d5535fcac9cc Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 13 Oct 2023 06:51:19 -0700 Subject: [PATCH 269/566] Bugfixes for 2.0.0 (#293) * Bugfixes for 2.0.0 - Add copy-paste support for standalone via keyboard short cuts - Trigger auto layout operation via keyboard short cut (Shift+ctrl+L) - Ensure that the viewport handler does not steal focus when integrated in an application frame (i.e. Theia) - Make context menu listener selection aware. If the context menu was triggered for a selectable target (or parent): do nothing if the element is already selected, otherwise set the selection to the selectable element Remove the current selection if the context menu element is not selectable. This behavior is consistent with other graphical tools like DrawIO, Google drawings, Papyrus etc. * Fix copy paste support Attach clipboard event listeners to window instead of container element. This is fine for the standalone case since we don't have multiple widgets here --- .../glsp-context-menu-mouse-listener.ts | 52 ++++++++++++--- .../features/copy-paste/copy-paste-modules.ts | 8 +-- .../copy-paste/copy-paste-standalone.ts | 65 +++++++++++++++++++ .../src/features/viewport/viewport-handler.ts | 19 ++++-- packages/client/src/standalone-modules.ts | 26 ++++++-- 5 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 packages/client/src/features/copy-paste/copy-paste-standalone.ts diff --git a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts index 6075035..b391b08 100644 --- a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts @@ -13,26 +13,41 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, ContextMenuProviderRegistry, + GModelElement, + IContextMenuService, IContextMenuServiceProvider, MouseListener, - GModelElement, - TYPES + SelectAction, + TYPES, + findParentByFeature, + isSelectable } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional, postConstruct } from 'inversify'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; @injectable() export class GLSPContextMenuMouseListener extends MouseListener { @inject(TYPES.IContextMenuServiceProvider) @optional() - protected readonly contextMenuService?: IContextMenuServiceProvider; + protected contextMenuServiceProvider?: IContextMenuServiceProvider; @inject(TYPES.IContextMenuProviderRegistry) @optional() - protected readonly menuProvider?: ContextMenuProviderRegistry; + protected menuProvider?: ContextMenuProviderRegistry; + + @inject(GLSPActionDispatcher) + protected actionDispatcher: GLSPActionDispatcher; + + protected menuService?: IContextMenuService; + + @postConstruct() + protected initialize(): void { + this.contextMenuServiceProvider?.().then(menuService => (this.menuService = menuService)); + } /** * Opens the context menu. @@ -44,6 +59,7 @@ export class GLSPContextMenuMouseListener extends MouseListener { /** * Opens the context menu. * + * - update selection state (if context menu target is selectable) * - query the context menu service and the context menu elements * - show the context menu * - send a focus state change to indicate that the diagram becomes inactive, once the context menu is shown @@ -51,17 +67,33 @@ export class GLSPContextMenuMouseListener extends MouseListener { * When the context menu is closed, we focus the diagram element again. */ protected openContextMenu(target: GModelElement, event: MouseEvent): Promise[] { - if (!this.contextMenuService || !this.menuProvider) { + if (!this.menuService || !this.menuProvider) { return []; } + return [this.showContextMenuItems(target, event)]; + } + + protected async showContextMenuItems(target: GModelElement, event: MouseEvent): Promise { + await this.handleContextElementSelection(target, event); const mousePosition = { x: event.x, y: event.y }; - const result = Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]) - .then(([menuService, menuItems]) => menuService.show(menuItems, mousePosition, () => this.focusEventTarget(event))) - .then((): Action => FocusStateChangedAction.create(false)); + const menuItems = await this.menuProvider!.getItems(target.root, mousePosition); + this.menuService!.show(menuItems, mousePosition, () => this.focusEventTarget(event)); + return FocusStateChangedAction.create(false); + } - return [result]; + // Clear selection the context menu target is not selectable + // Otherwise either maintain current selection if target is already selected or single select the current target. + protected async handleContextElementSelection(target: GModelElement, event: MouseEvent): Promise { + const selectableTarget = findParentByFeature(target, isSelectable); + if (!selectableTarget) { + return this.actionDispatcher.dispatch(SelectAction.setSelection([])); + } + if (!selectableTarget.selected) { + return this.actionDispatcher.dispatch(SelectAction.setSelection([selectableTarget.id])); + } + return; } protected focusEventTarget(event: MouseEvent): void { diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 7474d8d..435a150 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; -import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; +import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; +import { CopyPasteStartup } from './copy-paste-standalone'; export const copyPasteModule = new FeatureModule((bind, _unbind, isBound) => { bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); @@ -29,9 +29,7 @@ export const copyPasteModule = new FeatureModule((bind, _unbind, isBound) => { */ export const standaloneCopyPasteModule = new FeatureModule( (bind, _unbind, isBound) => { - bind(TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); - bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); - configureActionHandler({ bind, isBound }, InvokeCopyPasteAction.KIND, InvokeCopyPasteActionHandler); + bindAsService(bind, TYPES.IDiagramStartup, CopyPasteStartup); }, { requires: copyPasteModule } ); diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts new file mode 100644 index 0000000..065e809 --- /dev/null +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Disposable, DisposableCollection, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional, preDestroy } from 'inversify'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { ICopyPasteHandler } from './copy-paste-handler'; +/** + * Startup service to hook up the copy&paste event handler + */ +@injectable() +export class CopyPasteStartup implements IDiagramStartup, Disposable { + @inject(TYPES.ICopyPasteHandler) + @optional() + protected copyPasteHandler?: ICopyPasteHandler; + + protected toDispose = new DisposableCollection(); + + postModelInitialization(): void { + if (!this.copyPasteHandler) { + return; + } + const copyListener = (e: ClipboardEvent): void => { + this.copyPasteHandler?.handleCopy(e); + e.preventDefault(); + }; + const cutListener = (e: ClipboardEvent): void => { + this.copyPasteHandler?.handleCut(e); + e.preventDefault(); + }; + const pasteListener = (e: ClipboardEvent): void => { + this.copyPasteHandler?.handlePaste(e); + e.preventDefault(); + }; + window.addEventListener('copy', copyListener); + window.addEventListener('cut', cutListener); + window.addEventListener('paste', pasteListener); + + this.toDispose.push( + Disposable.create(() => { + window.removeEventListener('copy', copyListener); + window.removeEventListener('cut', cutListener); + window.removeEventListener('paste', pasteListener); + }) + ); + } + + @preDestroy() + dispose(): void { + this.toDispose.dispose(); + } +} diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 68718ca..44bc751 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionHandler, ViewerOptions, TYPES, Action } from '@eclipse-glsp/sprotty'; +import { Action, IActionHandler, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { FocusTracker } from '../../base/focus/focus-tracker'; +import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; -import { injectable, inject } from 'inversify'; import { FocusDomAction } from '../accessibility/actions'; -import { IDiagramStartup } from '../../base/model/diagram-loader'; /** * Focuses the graph on different actions. @@ -26,7 +27,11 @@ import { IDiagramStartup } from '../../base/model/diagram-loader'; export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { protected readonly graphSelector = '[data-svg-metadata-type="graph"]'; - @inject(TYPES.ViewerOptions) protected options: ViewerOptions; + @inject(TYPES.ViewerOptions) + protected options: ViewerOptions; + + @inject(FocusTracker) + protected focusTracker: FocusTracker; handle(action: Action): void | Action { if (EnableDefaultToolsAction.is(action) || (FocusDomAction.is(action) && action.id === 'graph')) { @@ -40,8 +45,10 @@ export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { } protected focusGraph(): void { - const container = document.getElementById(this.options.baseDiv)?.querySelector(this.graphSelector) as HTMLElement | null; - container?.focus(); + if (this.focusTracker.hasFocus) { + const container = this.focusTracker.diagramElement?.querySelector(this.graphSelector); + container?.focus(); + } } // https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 5e6cbca..cbbb874 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -14,21 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, EndProgressAction, FeatureModule, + GModelElement, IActionHandler, ICommand, ILogger, - ModuleConfiguration, + KeyListener, + LayoutOperation, MessageAction, + ModuleConfiguration, StartProgressAction, TYPES, UpdateProgressAction, - configureActionHandler + bindAsService, + configureActionHandler, + matchesKeystroke } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; import { standaloneExportModule } from './features/export/export-modules'; import { saveModule } from './features/save/save-module'; @@ -37,13 +42,14 @@ import { undoRedoModule } from './features/undo-redo/undo-redo-module'; import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; import { standaloneViewportModule } from './features/viewport/viewport-modules'; -export const standaloneFallbackModule = new FeatureModule((bind, unbind, isBound, rebind) => { +export const standaloneDefaultModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; bind(FallbackActionHandler).toSelf().inSingletonScope(); configureActionHandler(context, MessageAction.KIND, FallbackActionHandler); configureActionHandler(context, StartProgressAction.KIND, FallbackActionHandler); configureActionHandler(context, UpdateProgressAction.KIND, FallbackActionHandler); configureActionHandler(context, EndProgressAction.KIND, FallbackActionHandler); + bindAsService(context, TYPES.KeyListener, LayoutKeyListener); }); /** @@ -60,6 +66,16 @@ export class FallbackActionHandler implements IActionHandler { } } +@injectable() +export class LayoutKeyListener extends KeyListener { + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'KeyL', 'ctrlCmd', 'shift')) { + return [LayoutOperation.create()]; + } + return []; + } +} + /** * Configuration of all `standalone` modules. * @@ -77,7 +93,7 @@ export const STANDALONE_MODULES = [ standaloneMarkerNavigatorModule, standaloneSelectModule, standaloneExportModule, - standaloneFallbackModule, + standaloneDefaultModule, saveModule, undoRedoModule ] as const; From 8bf663a504b900eb1b8f3786b5364b9e1a40941e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 13 Oct 2023 08:46:37 -0700 Subject: [PATCH 270/566] v.2.0.0 (#1138) Release commit for version 2.0.0. - Update changelog to reflect all relevant changes - Update Dockerfiles to node 18 and Java 17 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 0b2b102..004cc72 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "1.0.0-next", + "version": "2.0.0", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "1.0.0-next", + "@eclipse-glsp/config": "~2.0.0", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index ce262a4..87f0ca7 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "1.0.0-next", - "@eclipse-glsp/nyc-config": "1.0.0-next", + "@eclipse-glsp/mocha-config": "~2.0.0", + "@eclipse-glsp/nyc-config": "~2.0.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index b7aeae1..b79c090 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "1.0.0-next", - "@eclipse-glsp/prettier-config": "1.0.0-next", - "@eclipse-glsp/ts-config": "1.0.0-next", + "@eclipse-glsp/eslint-config": "~2.0.0", + "@eclipse-glsp/prettier-config": "~2.0.0", + "@eclipse-glsp/ts-config": "~2.0.0", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 3a2d5c9..6234f9d 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "1.0.0-next", + "version": "2.0.0", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "1.0.0-next", - "@eclipse-glsp/config": "1.0.0-next", - "@eclipse-glsp/config-test": "1.0.0-next" + "@eclipse-glsp/cli": "~2.0.0", + "@eclipse-glsp/config": "~2.0.0", + "@eclipse-glsp/config-test": "~2.0.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index f20b348..6165b4d 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 58e0b8f..4eb4961 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index a760c78..784f40a 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index de74508..b86feab 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 4e2166f..6eef59e 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "1.0.0-next", + "version": "2.0.0", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From a6cdd480fc7ee1c482974dd3d2c0641118b0fd44 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 15 Oct 2023 03:15:40 -0700 Subject: [PATCH 271/566] v2.0.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 7 ++++--- packages/protocol/package.json | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 525370d..896c55f 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "1.1.0-next", + "version": "2.0.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -43,7 +43,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "1.1.0-next", + "@eclipse-glsp/client": "~2.0.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index bf00384..17cab85 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "1.1.0-next", + "version": "2.0.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -31,8 +31,8 @@ "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "1.1.0-next", - "@eclipse-glsp/client": "1.1.0-next" + "@eclipse-glsp-examples/workflow-glsp": "~2.0.0", + "@eclipse-glsp/client": "~2.0.0" }, "devDependencies": { "@types/shelljs": "0.8.12", diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..f6991fa 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.0.0" } diff --git a/packages/client/package.json b/packages/client/package.json index a9e1226..16556b4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "1.1.0-next", + "version": "2.0.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -47,7 +47,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "1.1.0-next", + "@eclipse-glsp/sprotty": "~2.0.0", "autocompleter": "^9.1.0", "file-saver": "^2.0.5", "lodash": "4.17.21" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 104957b..95ee481 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "1.1.0-next", + "version": "2.0.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -34,10 +34,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "1.1.0-next", + "@eclipse-glsp/protocol": "~2.0.0", "autocompleter": "^9.1.0", "snabbdom": "^3.5.1", - "sprotty": "0.15.0-next.044bba2.13" + "sprotty": "1.0.0", + "sprotty-protocol": "1.0.0" }, "devDependencies": { "@types/jsdom": "^21.1.3", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index d1dd6a9..58a1766 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "1.1.0-next", + "version": "2.0.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From b936f72f9d519fea9b9d9f7161b739d82e9628c7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 15 Oct 2023 04:40:46 -0700 Subject: [PATCH 272/566] v2.0.0 (#63) --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index ce91df4..317561f 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "1.1.0-next", + "version": "2.0.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 1b88a91..59f62a2 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "1.1.0-next", + "version": "2.0.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "1.1.0-next", - "@eclipse-glsp/server": "1.1.0-next", + "@eclipse-glsp/layout-elk": "~2.0.0", + "@eclipse-glsp/server": "~2.0.0", "inversify": "^6.0.1" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 9e97aa3..4daf0b2 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "1.1.0-next", + "version": "2.0.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.0.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 1bd357a..20c229d 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "1.1.0-next", + "version": "2.0.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "1.1.0-next", + "@eclipse-glsp/server": "~2.0.0", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 7124179..2c6026e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "1.1.0-next", + "version": "2.0.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -59,8 +59,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "1.1.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "~2.0.0", + "@eclipse-glsp/protocol": "2.0.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From b110d776624788d74795d29b6665bdcaad77f907 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 6 Nov 2023 03:34:10 -0800 Subject: [PATCH 273/566] GLSP-845: Update next version publishing (#297) Part of eclipse-glsp/glsp/issues/845 Co-authored-by: Philip Langer --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 896c55f..ca847da 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.0.0", + "version": "2.1.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -43,7 +43,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "~2.0.0", + "@eclipse-glsp/client": "2.1.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 17cab85..3065b99 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.0.0", + "version": "2.1.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -31,8 +31,8 @@ "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "~2.0.0", - "@eclipse-glsp/client": "~2.0.0" + "@eclipse-glsp-examples/workflow-glsp": "2.1.0-next", + "@eclipse-glsp/client": "2.1.0-next" }, "devDependencies": { "@types/shelljs": "0.8.12", diff --git a/packages/client/package.json b/packages/client/package.json index 16556b4..7da1f4b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.0.0", + "version": "2.1.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -47,7 +47,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "~2.0.0", + "@eclipse-glsp/sprotty": "2.1.0-next", "autocompleter": "^9.1.0", "file-saver": "^2.0.5", "lodash": "4.17.21" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 95ee481..8c360af 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.0.0", + "version": "2.1.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -34,7 +34,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "~2.0.0", + "@eclipse-glsp/protocol": "2.1.0-next", "autocompleter": "^9.1.0", "snabbdom": "^3.5.1", "sprotty": "1.0.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 58a1766..d694fe1 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.0.0", + "version": "2.1.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 801d6620c1f9a066e7ab1d08b48df8553588ef82 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 6 Nov 2023 05:32:34 -0800 Subject: [PATCH 274/566] GLSP-845: Update next version publishing (#64) Part of eclipse-glsp/glsp/issues/845 --- examples/workflow-server-bundled/package.json | 3 +-- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- .../src/common/features/model/model-submission-handler.ts | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 317561f..c5de4ca 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.0.0", + "version": "2.1.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", @@ -42,7 +42,6 @@ ], "scripts": { "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map", - "prepare": "yarn clean", "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 59f62a2..d71e1e9 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.0.0", + "version": "2.1.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "~2.0.0", - "@eclipse-glsp/server": "~2.0.0", + "@eclipse-glsp/layout-elk": "2.1.0-next", + "@eclipse-glsp/server": "2.1.0-next", "inversify": "^6.0.1" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 4daf0b2..8e4346e 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.0.0", + "version": "2.1.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.0.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 20c229d..541c75d 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.0.0", + "version": "2.1.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "~2.0.0", + "@eclipse-glsp/server": "2.1.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 2c6026e..73cf765 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.0.0", + "version": "2.1.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -59,8 +59,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "~2.0.0", - "@eclipse-glsp/protocol": "2.0.0", + "@eclipse-glsp/graph": "2.1.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index 334fdb8..4b97e94 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -74,7 +74,7 @@ export class ModelSubmissionHandler { /** * Returns a list of actions to submit the initial revision of the client-side model, based on the injected - * {@link GModelState}. Typically this method is invoked by the {@link RequestModelActionHandler} when the diagram + * {@link ModelState}. Typically this method is invoked by the {@link RequestModelActionHandler} when the diagram * is (re)loaded. *

* These actions are not processed by this {@link ModelSubmissionHandler}, but should be either manually dispatched From 1d7ccc399c2531fd4d1a3a7fcb513c7f8a00ddbd Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 6 Nov 2023 16:30:35 +0100 Subject: [PATCH 275/566] Restore Projection bars (#298) Relates to https://github.com/eclipse-glsp/glsp/issues/1122 --- examples/workflow-standalone/css/diagram.css | 21 +++++++++ packages/client/css/glsp-sprotty.css | 44 +++++++++++++++---- packages/client/css/toast.css | 5 +-- .../client/src/views/glsp-projection-view.tsx | 10 ++--- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 59029d4..0f21caf 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -62,3 +62,24 @@ polygon.sprotty-node { .sprotty-node.selected { stroke: rgb(87, 87, 214); } + +.bordered-projection-bar { + border-color: #a1a1a1; +} + +.sprotty-viewport { + border-width: 1px; + border-color: #555555; +} + +.sprotty-projection-bar.horizontal.bordered-projection-bar { + height: 15px; +} + +.sprotty-projection-bar.vertical.bordered-projection-bar { + width: 15px; +} + +.projection-scroll-bar { + background-color: #555555; +} \ No newline at end of file diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 8bbd826..29d5740 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -230,37 +230,63 @@ } .sprotty-projection-bar.vertical.bordered-projection-bar { - width: var(--theia-scrollbar-rail-width); + position: absolute; + top: 0; + right: 0; + width: 20px; + height: 100%; } .sprotty-projection-bar.horizontal.bordered-projection-bar { - height: var(--theia-scrollbar-rail-width); + position: absolute; + bottom: 0; + left: 0; + height: 20px; + width: 100%; +} + +.sprotty-projection-bar.horizontal .sprotty-projection, +.sprotty-projection-bar.horizontal .sprotty-viewport { + position: absolute; + height: 100%; + top: 0; +} + +.sprotty-projection-bar.vertical .sprotty-projection, +.sprotty-projection-bar.vertical .sprotty-viewport { + position: absolute; + width: 100%; + left: 0; } .projection-scroll-bar { + position: absolute; + background: transparent; + background-color: #aaa; + z-index: 1; + opacity: 0.3; transition: opacity 0.8s linear; } +.sprotty-viewport { + z-index: 1; + border-style: solid; + border-width: 2px; +} + .mouse-enter .projection-scroll-bar { opacity: 0.3; - background: transparent; - background-color: #aaa; - z-index: 1; } .mouse-leave .projection-scroll-bar { opacity: 0; pointer-events: none; - background: transparent; - background-color: #aaa; - z-index: 1; } .bordered-projection-bar { border-left: 1px solid rgba(212, 212, 212, 0.2); border-top: 1px solid rgba(212, 212, 212, 0.2); fill: transparent; - width: var(--theia-scrollbar-rail-width); } .search-hidden { diff --git a/packages/client/css/toast.css b/packages/client/css/toast.css index 9746112..5d532bd 100644 --- a/packages/client/css/toast.css +++ b/packages/client/css/toast.css @@ -15,12 +15,11 @@ ********************************************************************************/ .toast { position: absolute; - bottom: 1rem; - width: 100%; + bottom: 48px; /* same as top of toolbar by default */ display: grid; grid-template-columns: 1fr 1fr 1fr; grid-column-gap: 1rem; - margin: 0 1rem; + left: 40px; /* same as right of toolbar by default */ } .toast-column-left { diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index ec6dfb5..2837cb5 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -14,8 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { VNode, VNodeStyle, h } from 'snabbdom'; import { Bounds, EdgeRouterRegistry, @@ -29,6 +27,8 @@ import { setAttr, setClass } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { VNode, VNodeStyle, h } from 'snabbdom'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: html }; @@ -123,13 +123,11 @@ export class GLSPProjectionView extends ProjectedViewportView { params.orientation === 'horizontal' ? { left: `${viewportPos}px`, - width: `${viewportSize}px`, - border: 'none' + width: `${viewportSize}px` } : { top: `${viewportPos}px`, - height: `${viewportSize}px`, - border: 'none' + height: `${viewportSize}px` }; return

; } From 21bdc93e4ce1cc3fe9b9218620b10d7787d2d6ec Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 3 Nov 2023 21:40:27 +0100 Subject: [PATCH 276/566] Allow setting `ViewerOptions` and set default zoom limits Sprotty introduced zoom limits, so we should allow setting them. This change allows to optionally specify all `ViewerOptions` in `configureDiagramOptions` and sets very generous `zoomLimits` by default. This should also mitigate the bug when you zoom beyond `Number.maxValue`. --- .../client/src/base/model/diagram-loader.ts | 2 +- packages/client/src/default-modules.ts | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 002b62e..926a568 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { AnyObject, ApplicationIdProvider, @@ -29,6 +28,7 @@ import { TYPES, hasNumberProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; import { GLSPModelSource } from './glsp-model-source'; diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 1220590..4087b01 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Container } from 'inversify'; import { BindingContext, ContainerConfiguration, @@ -32,6 +31,7 @@ import { resolveContainerConfiguration, zorderModule } from '@eclipse-glsp/sprotty'; +import { Container } from 'inversify'; import { defaultModule } from './base/default.module'; import { IDiagramOptions } from './base/model/diagram-loader'; import { boundsModule } from './features/bounds/bounds-module'; @@ -116,16 +116,21 @@ export function createDiagramOptionsModule(options: IDiagramOptions): FeatureMod * In addition to binding the {@link IDiagramOptions} this function also overrides the * {@link ViewerOptions} to match the given client id. * @param context The binding context - * @param options The {@link IDiagramOptions} that should be bound + * @param diagramOptions The {@link IDiagramOptions} that should be bound + * @param viewerOptions Optional {@link ViewerOptions} that should be configured */ -export function configureDiagramOptions(context: BindingContext, options: IDiagramOptions): void { - const viewerOptions: Partial = { - baseDiv: options.clientId, - hiddenDiv: options.clientId + '_hidden' - }; - configureViewerOptions(context, viewerOptions); - - context.bind(TYPES.IDiagramOptions).toConstantValue(options); +export function configureDiagramOptions( + context: BindingContext, + diagramOptions: IDiagramOptions, + viewerOptions?: Partial +): void { + configureViewerOptions(context, { + baseDiv: diagramOptions.clientId, + hiddenDiv: diagramOptions.clientId + '_hidden', + zoomLimits: { min: 0.1, max: 20 }, + ...viewerOptions + }); + context.bind(TYPES.IDiagramOptions).toConstantValue(diagramOptions); } /** From 90f82fc6749c27f6ef52e4c8c15450a056c8a60a Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 9 Nov 2023 17:46:57 +0100 Subject: [PATCH 277/566] Keyboard move and resize improvements (#295) * Show client-side feedback without animation * Trigger server operation with a debounce delay * Respect snap modifier (ALT) for move * Respect movement restrictor for move Fixes https://github.com/eclipse-glsp/glsp/issues/1156 --- .../accessibility/move-zoom/move-handler.ts | 170 +++++++++++------- .../resize-key-tool/resize-key-handler.ts | 42 +++-- .../accessibility/search/search-palette.ts | 10 +- .../view-key-tools/movement-key-tool.ts | 35 ++-- .../change-bounds-tool-feedback.ts | 10 +- packages/client/src/utils/viewpoint-util.ts | 18 +- 6 files changed, 185 insertions(+), 100 deletions(-) diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index e2bd353..56b6ba4 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -14,24 +14,34 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { throttle } from 'lodash'; import { Action, ChangeBoundsOperation, + DisposableCollection, + ElementAndBounds, + ElementMove, + GModelRoot, IActionDispatcher, IActionHandler, ICommand, + ISnapper, + MoveAction, Point, - GModelRoot, SetViewportAction, TYPES, Viewport, findParentByFeature, + isBoundsAware, isViewport } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { DebouncedFunc, debounce } from 'lodash'; import { EditorContextService } from '../../../base/editor-context-service'; +import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; +import { isValidMove } from '../../../utils/layout-utils'; +import { outsideOfViewport } from '../../../utils/viewpoint-util'; +import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; /** * Action for triggering moving of the viewport. @@ -77,6 +87,10 @@ export interface MoveElementAction extends Action { * used to specify the amount to be moved in the y-axis */ moveY: number; + /** + * used to specify whether we should snap to the grid + */ + snap: boolean; } export namespace MoveElementAction { @@ -86,24 +100,24 @@ export namespace MoveElementAction { return Action.hasKind(object, KIND); } - export function create(elementIds: string[], moveX: number, moveY: number): MoveElementAction { - return { kind: KIND, elementIds, moveX, moveY }; + export function create(elementIds: string[], moveX: number, moveY: number, snap: boolean = true): MoveElementAction { + return { kind: KIND, elementIds, moveX, moveY, snap }; } } -/* The MoveViewportHandler class is an implementation of the IActionHandler interface that handles -moving of the viewport. */ +/** + * Action handler for moving of the viewport. + */ @injectable() export class MoveViewportHandler implements IActionHandler { @inject(EditorContextService) protected editorContextService: EditorContextService; - - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - protected readonly throttledHandleViewportMove = throttle((action: MoveViewportAction) => this.handleMoveViewport(action), 150); + @inject(TYPES.IActionDispatcher) + protected dispatcher: IActionDispatcher; handle(action: Action): void | Action | ICommand { if (MoveViewportAction.is(action)) { - this.throttledHandleViewportMove(action); + this.handleMoveViewport(action); } } @@ -123,87 +137,113 @@ export class MoveViewportHandler implements IActionHandler { }, zoom: viewport.zoom }; - - return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + return SetViewportAction.create(viewport.id, newViewport, { animate: false }); } } -/* The MoveElementHandler class is an implementation of the IActionHandler interface that handles -moving elements. */ +/** + * Action handler for moving elements. + */ @injectable() export class MoveElementHandler implements IActionHandler { @inject(EditorContextService) protected editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - protected readonly throttledHandleElementMove = throttle((action: MoveElementAction) => this.handleMoveElement(action), 150); + + @inject(TYPES.IActionDispatcher) + protected dispatcher: IActionDispatcher; + + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackDispatcher: IFeedbackActionDispatcher; + + @inject(TYPES.ISnapper) + @optional() + readonly snapper?: ISnapper; + + @inject(TYPES.IMovementRestrictor) + @optional() + readonly movementRestrictor?: IMovementRestrictor; + + protected debouncedChangeBounds?: DebouncedFunc<() => void>; + protected disposableFeedback = new DisposableCollection(); handle(action: Action): void | Action | ICommand { if (MoveElementAction.is(action)) { - this.throttledHandleElementMove(action); + this.handleMoveElement(action); } } handleMoveElement(action: MoveElementAction): void { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + const modelRoot = this.editorContextService.modelRoot; + const viewport = findParentByFeature(modelRoot, isViewport); if (!viewport) { return; } - const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + const viewportActions: Action[] = []; + const elementMoves: ElementMove[] = []; + const elements = getElements(modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + for (const element of elements) { + const newPosition = this.getTargetBounds(element, action); + elementMoves.push({ + elementId: element.id, + fromPosition: { + x: element.bounds.x, + y: element.bounds.y + }, + toPosition: newPosition + }); + if (outsideOfViewport(newPosition, viewport)) { + viewportActions.push(MoveViewportAction.create(action.moveX, action.moveY)); + } + } - this.dispatcher.dispatchAll(this.move(viewport, elements, action.moveX, action.moveY)); - } + this.dispatcher.dispatchAll(viewportActions); + const moveAction = MoveAction.create(elementMoves, { animate: false }); + this.disposableFeedback.push(this.feedbackDispatcher.registerFeedback(this, [moveAction])); - protected getBounds(element: SelectableBoundsAware, offSetX: number, offSetY: number): Point { - return { x: element.bounds.x + offSetX, y: element.bounds.y + offSetY }; + this.scheduleChangeBounds(this.toElementAndBounds(elementMoves)); } - protected adaptViewport( - viewport: GModelRoot & Viewport, - newPoint: Point, - moveX: number, - moveY: number - ): MoveViewportAction | undefined { - if ( - newPoint.x < viewport.scroll.x || - newPoint.x > viewport.scroll.x + viewport.canvasBounds.width || - newPoint.y < viewport.scroll.y || - newPoint.y > viewport.scroll.y + viewport.canvasBounds.height - ) { - return MoveViewportAction.create(moveX, moveY); + protected getTargetBounds(element: SelectableBoundsAware, action: MoveElementAction): Point { + let position = { x: element.bounds.x + action.moveX, y: element.bounds.y + action.moveY }; + if (this.snapper && action.snap) { + position = this.snapper.snap(position, element); + } + if (!isValidMove(element, position, this.movementRestrictor)) { + // reset to position before the move, if not valid + position = { x: element.bounds.x, y: element.bounds.y }; } - return; + return position; } - protected moveElement(element: SelectableBoundsAware, offSetX: number, offSetY: number): ChangeBoundsOperation { - return ChangeBoundsOperation.create([ - { - elementId: element.id, - newSize: { - width: element.bounds.width, - height: element.bounds.height - }, - newPosition: { - x: element.bounds.x + offSetX, - y: element.bounds.y + offSetY - } - } - ]); + protected scheduleChangeBounds(elementAndBounds: ElementAndBounds[]): void { + this.debouncedChangeBounds?.cancel(); + this.debouncedChangeBounds = debounce(() => { + this.disposableFeedback.dispose(); + this.dispatcher.dispatchAll([ChangeBoundsOperation.create(elementAndBounds)]); + this.debouncedChangeBounds = undefined; + }, 300); + this.debouncedChangeBounds(); } - protected move(viewport: GModelRoot & Viewport, selectedElements: SelectableBoundsAware[], deltaX: number, deltaY: number): Action[] { - const results: Action[] = []; - - if (selectedElements.length !== 0) { - selectedElements.forEach(currentElement => { - results.push(this.moveElement(currentElement, deltaX, deltaY)); - const newPosition = this.getBounds(currentElement, deltaX, deltaY); - const viewportAction = this.adaptViewport(viewport, newPosition, deltaX, deltaY); - if (viewportAction) { - results.push(viewportAction); - } - }); + protected toElementAndBounds(elementMoves: ElementMove[]): ElementAndBounds[] { + const elementBounds: ElementAndBounds[] = []; + for (const elementMove of elementMoves) { + const element = this.editorContextService.modelRoot.index.getById(elementMove.elementId); + if (element && isBoundsAware(element)) { + elementBounds.push({ + elementId: elementMove.elementId, + newSize: { + height: element.bounds.height, + width: element.bounds.width + }, + newPosition: { + x: elementMove.toPosition.x, + y: elementMove.toPosition.y + } + }); + } } - return results; + return elementBounds; } } diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index 668eabc..6e3ce70 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -14,23 +14,28 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, ChangeBoundsOperation, Dimension, + DisposableCollection, + ElementAndBounds, + GModelElement, + GParentElement, IActionDispatcher, IActionHandler, ICommand, ISnapper, Point, - GModelElement, - GParentElement, + SetBoundsAction, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { DebouncedFunc, debounce } from 'lodash'; import { EditorContextService } from '../../../base/editor-context-service'; -import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; +import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; +import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; import { Resizable } from '../../change-bounds/model'; import { GridSnapper } from '../../change-bounds/snap'; @@ -67,7 +72,14 @@ export class ResizeElementHandler implements IActionHandler { @inject(EditorContextService) protected editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected dispatcher: IActionDispatcher; + + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackDispatcher: IFeedbackActionDispatcher; + + protected debouncedChangeBounds?: DebouncedFunc<() => void>; + protected disposableFeedback = new DisposableCollection(); // Default x resize used if GridSnapper is not provided static readonly defaultResizeX = 20; @@ -92,11 +104,21 @@ export class ResizeElementHandler implements IActionHandler { handleResizeElement(action: ResizeElementAction): void { const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); - this.dispatcher.dispatchAll(this.resize(elements, action)); + const elementAndBounds = this.computeElementAndBounds(elements, action); + + this.disposableFeedback.push(this.feedbackDispatcher.registerFeedback(this, [SetBoundsAction.create(elementAndBounds)])); + + this.debouncedChangeBounds?.cancel(); + this.debouncedChangeBounds = debounce(() => { + this.disposableFeedback.dispose(); + this.dispatcher.dispatchAll([ChangeBoundsOperation.create(elementAndBounds)]); + this.debouncedChangeBounds = undefined; + }, 300); + this.debouncedChangeBounds(); } - protected resize(elements: SelectableBoundsAware[], action: ResizeElementAction): Action[] { - const actions: Action[] = []; + protected computeElementAndBounds(elements: SelectableBoundsAware[], action: ResizeElementAction): ElementAndBounds[] { + const elementAndBounds: ElementAndBounds[] = []; elements.forEach(element => { const { x, y, width: oldWidth, height: oldHeight } = element.bounds; @@ -116,11 +138,11 @@ export class ResizeElementHandler implements IActionHandler { if (this.isValidBoundChange(element, { x, y }, { width, height })) { const resizeElement = { id: element.id, bounds: { x, y, width, height } } as GModelElement & GParentElement & Resizable; - actions.push(ChangeBoundsOperation.create([toElementAndBounds(resizeElement)])); + elementAndBounds.push(toElementAndBounds(resizeElement)); } }); - return actions; + return elementAndBounds; } protected isValidBoundChange(element: SelectableBoundsAware, newPosition: Point, newSize: Dimension): boolean { diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index 17f84e3..330c0b7 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -14,27 +14,27 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { isEqual } from 'lodash'; -import { toArray } from 'sprotty/lib/utils/iterable'; import { Action, CenterAction, - LabeledAction, GModelElement, GModelRoot, GNode, + LabeledAction, SelectAction, SelectAllAction, codiconCSSString, isNameable, name } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { isEqual } from 'lodash'; +import { toArray } from 'sprotty/lib/utils/iterable'; import { BaseAutocompletePalette } from '../../../base/auto-complete/base-autocomplete-palette'; +import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { RepositionAction } from '../../../features/viewport/reposition'; -import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; import { GEdge } from '../../../model'; const CSS_SEARCH_HIDDEN = 'search-hidden'; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index b1dc22e..770caaa 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelElement, ISnapper, KeyListener, KeyTool, Point, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, ISnapper, KeyListener, KeyTool, GModelElement, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; @@ -63,14 +63,18 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu protected readonly token = MoveKeyListener.name; - protected grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY }; - constructor(protected readonly tool: MovementKeyTool) { super(); + } + protected get grid(): Point { if (this.tool.snapper instanceof GridSnapper) { - this.grid = this.tool.snapper.grid; + return this.tool.snapper.grid; } + return { + x: MoveKeyListener.defaultMoveX, + y: MoveKeyListener.defaultMoveY + }; } registerShortcutKey(): void { @@ -84,22 +88,25 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); + const snap = !event.altKey; + const offsetX = snap ? this.grid.x : 1; + const offsetY = snap ? this.grid.y : 1; if (selectedElementIds.length > 0) { if (this.matchesMoveUpKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, 0, -this.grid.x)]; + return [MoveElementAction.create(selectedElementIds, 0, -offsetY, snap)]; } else if (this.matchesMoveDownKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, 0, this.grid.x)]; + return [MoveElementAction.create(selectedElementIds, 0, offsetY, snap)]; } else if (this.matchesMoveRightKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, this.grid.x, 0)]; + return [MoveElementAction.create(selectedElementIds, offsetX, 0, snap)]; } else if (this.matchesMoveLeftKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, -this.grid.x, 0)]; + return [MoveElementAction.create(selectedElementIds, -offsetX, 0, snap)]; } } else { if (this.matchesMoveUpKeystroke(event)) { - return [MoveViewportAction.create(0, -this.grid.x)]; + return [MoveViewportAction.create(0, -this.grid.y)]; } else if (this.matchesMoveDownKeystroke(event)) { - return [MoveViewportAction.create(0, this.grid.x)]; + return [MoveViewportAction.create(0, this.grid.y)]; } else if (this.matchesMoveRightKeystroke(event)) { return [MoveViewportAction.create(this.grid.x, 0)]; } else if (this.matchesMoveLeftKeystroke(event)) { @@ -110,18 +117,18 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu } protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowUp'); + return matchesKeystroke(event, 'ArrowUp') || matchesKeystroke(event, 'ArrowUp', 'alt'); } protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowDown'); + return matchesKeystroke(event, 'ArrowDown') || matchesKeystroke(event, 'ArrowDown', 'alt'); } protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowRight'); + return matchesKeystroke(event, 'ArrowRight') || matchesKeystroke(event, 'ArrowRight', 'alt'); } protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowLeft'); + return matchesKeystroke(event, 'ArrowLeft') || matchesKeystroke(event, 'ArrowLeft', 'alt'); } } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index 3270821..cb711d4 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -13,8 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { VNode } from 'snabbdom'; import { Action, CommandExecutionContext, @@ -22,11 +20,11 @@ import { Disposable, ElementMove, GChildElement, + GModelElement, + GModelRoot, MouseListener, MoveAction, Point, - GModelElement, - GModelRoot, TYPES, findParentByFeature, hasStringProp, @@ -34,6 +32,8 @@ import { isSelectable, isViewport } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { VNode } from 'snabbdom'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; @@ -197,7 +197,7 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab y: (event.pageY - this.startDragPosition.y) / zoom }; - const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, !event.shiftKey, finished); + const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, !event.altKey, finished); if (elementMoves.length > 0) { return MoveAction.create(elementMoves, { animate: false, finished }); } else { diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index a6d1116..9df7546 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -19,8 +19,9 @@ import { BoundsAware, Dimension, GChildElement, - Point, GModelElement, + GModelRoot, + Point, Viewport, findParentByFeature, isAlignable, @@ -133,3 +134,18 @@ export function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: const absoluteElementBounds = toAbsoluteBounds(element); return { x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y }; } + +/** + * Returns `true` if `point` is outside of the `viewport`. + * @param point The point to check. + * @param viewport The viewport. + * @returns `true` if `point` is outside, `false` otherwise. + */ +export function outsideOfViewport(point: Point, viewport: GModelRoot & Viewport): boolean { + return ( + point.x < viewport.scroll.x || + point.x > viewport.scroll.x + viewport.canvasBounds.width || + point.y < viewport.scroll.y || + point.y > viewport.scroll.y + viewport.canvasBounds.height + ); +} From 20d9a855805b7321dcfe2be1f6ae8747ff6d7854 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 13 Nov 2023 01:37:27 -0800 Subject: [PATCH 278/566] Fix download example-server script (#302) --- examples/workflow-standalone/scripts/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index f6991fa..4c3ec43 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.0.0" + "version": "2.0.1" } From 9a426a8f923b4f0007725de341cbedebbf7db68f Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 14 Nov 2023 12:17:04 +0100 Subject: [PATCH 279/566] Ensure 'postChange' is properly handled as async Fixes https://github.com/eclipse-glsp/glsp/issues/1162 --- packages/server/src/common/command/recording-command.ts | 6 +++--- .../server/src/common/operations/json-operation-handler.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/server/src/common/command/recording-command.ts b/packages/server/src/common/command/recording-command.ts index 4fecee7..c8e4bb6 100644 --- a/packages/server/src/common/command/recording-command.ts +++ b/packages/server/src/common/command/recording-command.ts @@ -33,7 +33,7 @@ export abstract class AbstractRecordingCommand imp const afterState = await this.getJsonObject(); this.undoPatch = jsonPatch.compare(afterState, beforeState); this.redoPatch = jsonPatch.compare(beforeState, afterState); - this.postChange?.(afterState); + await this.postChange?.(afterState); } /** @@ -65,14 +65,14 @@ export abstract class AbstractRecordingCommand imp async undo(): Promise { if (this.undoPatch) { const result = this.applyPatch(await this.getJsonObject(), this.undoPatch); - this.postChange?.(result.newDocument); + await this.postChange?.(result.newDocument); } } async redo(): Promise { if (this.redoPatch) { const result = this.applyPatch(await this.getJsonObject(), this.redoPatch); - this.postChange?.(result.newDocument); + await this.postChange?.(result.newDocument); } } diff --git a/packages/server/src/common/operations/json-operation-handler.ts b/packages/server/src/common/operations/json-operation-handler.ts index 0bd5079..44e1560 100644 --- a/packages/server/src/common/operations/json-operation-handler.ts +++ b/packages/server/src/common/operations/json-operation-handler.ts @@ -38,8 +38,8 @@ import { OperationHandler } from './operation-handler'; * Has to be implemented in order to reuse the {@link JsonOperationHandler} API. */ export interface JsonModelState extends ModelState { - readonly sourceModel: JsonObject; - updateSourceModel(sourceModel: JsonObject): void; + readonly sourceModel: MaybePromise; + updateSourceModel(sourceModel: JsonObject): MaybePromise; } export namespace JsonModelState { @@ -62,7 +62,7 @@ export class JsonRecordingCommand exte } protected override postChange(newModel: JsonObject): MaybePromise { - this.modelState.updateSourceModel(newModel); + return this.modelState.updateSourceModel(newModel); } } From a2723b184247a5ee23f5f14e69efa2f7825a45ed Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 14 Nov 2023 12:21:05 +0100 Subject: [PATCH 280/566] Use '127.0.0.1' instead of 'localhost' to avoid ipv4/ipv6 issues Fixes https://github.com/eclipse-glsp/glsp/issues/1164 --- packages/server/src/node/launch/socket-cli-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/node/launch/socket-cli-parser.ts b/packages/server/src/node/launch/socket-cli-parser.ts index 561b261..423397d 100644 --- a/packages/server/src/node/launch/socket-cli-parser.ts +++ b/packages/server/src/node/launch/socket-cli-parser.ts @@ -24,7 +24,7 @@ export interface SocketLaunchOptions extends LaunchOptions { export const defaultSocketLaunchOptions: Required = { ...defaultLaunchOptions, port: 0, - host: 'localhost' + host: '127.0.0.1' }; export function createSocketCliParser( From bf2153343ab0c86499fb4562dc916b7002b691b4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 16 Nov 2023 10:37:37 +0100 Subject: [PATCH 281/566] GLSP-1163: Fix GModelCreateNodeOperationHandler - Ensure that the element to delete is removed from its direct parent instead of always using the model root - Remove lookup of topLevel node or edge before deletion. - Ensure that edges of ports are also considered during dependants collection - Avoid adding of child elements to the dependants collection (as they are implicitly deleted when deleting the parent) Fixes https://github.com/eclipse-glsp/glsp/issues/1163 - Fix to narrow typing in GModelIndex.getIncomingEdges --- .../src/common/features/model/gmodel-index.ts | 14 +++---- .../common/gmodel/delete-operation-handler.ts | 42 +++++++------------ 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/packages/server/src/common/features/model/gmodel-index.ts b/packages/server/src/common/features/model/gmodel-index.ts index bf8dc59..db282df 100644 --- a/packages/server/src/common/features/model/gmodel-index.ts +++ b/packages/server/src/common/features/model/gmodel-index.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GEdge, GModelElement, GModelElementConstructor, GModelRoot, GNode } from '@eclipse-glsp/graph'; +import { GEdge, GModelElement, GModelElementConstructor, GModelRoot } from '@eclipse-glsp/graph'; import { TypeGuard } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { getOrThrow, GLSPServerError } from '../../utils/glsp-server-error'; +import { GLSPServerError, getOrThrow } from '../../utils/glsp-server-error'; /** * Is used to index all child elements of a {@link GModelRoot} by their id. Offers a set @@ -137,20 +137,20 @@ export class GModelIndex { } /** - * Returns all incoming edges for a node. + * Returns all incoming edges for a node (or port). * - * @param node The node where the edges are connected. + * @param node The node (or port) where the edges are connected. * * @returns All incoming edges. */ - getIncomingEdges(node: GNode): GEdge[] { + getIncomingEdges(node: GModelElement): GEdge[] { return this.getAllEdges().filter(edge => edge.targetId === node.id); } /** - * Returns all outgoing edges for a node. + * Returns all outgoing edges for a node (or port). * - * @param node The node where the edges are connected. + * @param node The node (or port) where the edges are connected. * * @returns All outgoing edges. */ diff --git a/packages/server/src/common/gmodel/delete-operation-handler.ts b/packages/server/src/common/gmodel/delete-operation-handler.ts index 5e020f2..6e78d28 100644 --- a/packages/server/src/common/gmodel/delete-operation-handler.ts +++ b/packages/server/src/common/gmodel/delete-operation-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GEdge, GModelElement, GNode } from '@eclipse-glsp/graph'; +import { GModelElement, GNode, GPort } from '@eclipse-glsp/graph'; import { DeleteElementOperation, MaybePromise } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { Command } from '../command/command'; @@ -65,19 +65,18 @@ export class GModelDeleteOperationHandler extends GModelOperationHandler { return false; } - const nodeToDelete = this.findTopLevelElement(element); - if (!nodeToDelete.parent) { - this.logger.warn("The requested node doesn't have a parent; it can't be deleted"); + if (!element.parent) { + this.logger.warn("The requested element doesn't have a parent; it can't be deleted"); return false; } const dependents = new Set(); - this.collectDependents(dependents, nodeToDelete, false); + this.collectDependents(dependents, element, false); dependents.forEach(dependant => { - const index = this.modelState.root.children.findIndex(element => element === dependant); + const index = dependant.parent.children.findIndex(element => element === dependant); if (index > -1) { - this.modelState.root.children.splice(index, 1); + dependant.parent.children.splice(index, 1); } this.allDependentsIds.add(dependant.id); }); @@ -85,38 +84,27 @@ export class GModelDeleteOperationHandler extends GModelOperationHandler { return true; } - protected collectDependents(dependents: Set, nodeToDelete: GModelElement, isChild: boolean): void { - if (dependents.has(nodeToDelete)) { + protected collectDependents(dependents: Set, elementToDelete: GModelElement, isChild: boolean): void { + if (dependents.has(elementToDelete)) { return; } - if (nodeToDelete.children.length > 0) { - nodeToDelete.children.forEach(child => this.collectDependents(dependents, child, true)); + if (elementToDelete.children.length > 0) { + elementToDelete.children.forEach(child => this.collectDependents(dependents, child, true)); } - if (nodeToDelete instanceof GNode) { + if (elementToDelete instanceof GNode || elementToDelete instanceof GPort) { const index = this.modelState.index; - index.getIncomingEdges(nodeToDelete).forEach(incoming => { + index.getIncomingEdges(elementToDelete).forEach(incoming => { dependents.add(incoming); }); - index.getOutgoingEdges(nodeToDelete).forEach(outgoing => { + index.getOutgoingEdges(elementToDelete).forEach(outgoing => { dependents.add(outgoing); }); } - - dependents.add(nodeToDelete); - } - - protected findTopLevelElement(element: GModelElement): GModelElement { - if (element instanceof GNode || element instanceof GEdge) { - return element; - } - - const parent = element.parent; - if (!parent) { - return element; + if (!isChild) { + dependents.add(elementToDelete); } - return this.findTopLevelElement(parent); } } From 42e3aa013a669aef6438a3f920c097b49aa99342 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 21 Nov 2023 00:23:23 +0100 Subject: [PATCH 282/566] Support rendering of ghost element when creating elements (#301) * Support rendering of ghost element when creating elements - Introduce new element template module -- Adding new elements to the diagram based on templates -- Removing templates again - Support local bounds calculation for client-only added elements -- Must trigger RequestBounds for hidden view calculation -- Must not modify main view until ComputedBounds response is handled -- Mark ResizeHandles as not to be considered for bounds calculation -- Fix positioning of decorations for hidden view calculation - Add ghost element extension to tool palette -- Optional ghost element field in the trigger node creation action -- Ghost element is added as feedback to the mouse cursor -- If ghost element is dynamic, templates (palette items) are reloaded - Bonus: Re-calculate local bounds for resize behavior Fixes https://github.com/eclipse-glsp/glsp/issues/1159 --- packages/client/css/ghost-element.css | 25 ++++ packages/client/css/glsp-sprotty.css | 5 + packages/client/src/base/args-feature.ts | 14 ++- .../client/src/base/feedback/css-feedback.ts | 5 +- packages/client/src/default-modules.ts | 2 + .../keyboard-tool-palette-module.ts | 16 ++- .../keyboard-tool-palette.ts | 31 +++-- .../src/features/bounds/bounds-module.ts | 6 + .../bounds/glsp-hidden-bounds-updater.ts | 18 +-- .../src/features/bounds/local-bounds.ts | 91 +++++++++++++++ .../bounds/set-bounds-feedback-command.ts | 70 +++++++++++ .../src/features/change-bounds/model.ts | 5 +- .../features/decoration/decoration-placer.ts | 6 +- .../element-template/add-template-element.ts | 109 +++++++++++++++++ .../element-template-module.ts | 24 ++++ ...ouse-tracking-element-position-listener.ts | 110 ++++++++++++++++++ .../remove-template-element.ts | 56 +++++++++ .../tool-palette/tool-palette-module.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 56 ++++++--- .../tools/change-bounds/change-bounds-tool.ts | 22 ++-- .../src/features/tools/change-bounds/view.tsx | 2 +- .../node-creation/node-creation-module.ts | 14 ++- .../tools/node-creation/node-creation-tool.ts | 64 +++++++--- packages/client/src/index.ts | 4 + .../src/action-protocol/tool-palette.ts | 24 +++- .../protocol/src/action-protocol/types.ts | 6 + .../protocol/src/utils/disposable.spec.ts | 7 ++ packages/protocol/src/utils/disposable.ts | 11 +- 28 files changed, 721 insertions(+), 86 deletions(-) create mode 100644 packages/client/css/ghost-element.css create mode 100644 packages/client/src/features/bounds/local-bounds.ts create mode 100644 packages/client/src/features/bounds/set-bounds-feedback-command.ts create mode 100644 packages/client/src/features/element-template/add-template-element.ts create mode 100644 packages/client/src/features/element-template/element-template-module.ts create mode 100644 packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts create mode 100644 packages/client/src/features/element-template/remove-template-element.ts diff --git a/packages/client/css/ghost-element.css b/packages/client/css/ghost-element.css new file mode 100644 index 0000000..2ced756 --- /dev/null +++ b/packages/client/css/ghost-element.css @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.ghost-element { + /* we are a true ghost so we do not want to be used as a target for any mouse event */ + pointer-events: none; + opacity: 0.8; +} + +.ghost-element.hidden { + display: none; +} diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 29d5740..2fe87a9 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -72,6 +72,11 @@ fill: #1d80d1; } +.sprotty-hidden .sprotty-resize-handle { + /** resize handles should not be considered as part of the elements bounds */ + display: none; +} + .sprotty-edge { fill: none; stroke-width: 1.5px; diff --git a/packages/client/src/base/args-feature.ts b/packages/client/src/base/args-feature.ts index 680b6fc..6e958c4 100644 --- a/packages/client/src/base/args-feature.ts +++ b/packages/client/src/base/args-feature.ts @@ -28,10 +28,20 @@ export interface ArgsAware { args?: Args; } -export function isArgsAware(element: GModelElement): element is GModelElement & ArgsAware { - return element.hasFeature(argsFeature); +export function isArgsAware(element?: GModelElement): element is GModelElement & ArgsAware { + return element !== undefined && element.hasFeature(argsFeature); } export function hasArgs(element?: GModelElement): element is GModelElement & Required { return element !== undefined && isArgsAware(element) && element.args !== undefined; } + +export function ensureArgs(element?: GModelElement): element is GModelElement & Required { + if (!isArgsAware(element)) { + return false; + } + if (element.args === undefined) { + element.args = {}; + } + return true; +} diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 7d1cd9d..b13b976 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, GModelElement, GModelRoot, TYPES, hasArrayProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { addCssClasses, getElements, removeCssClasses } from '../../utils/gmodel-util'; import { FeedbackCommand } from './feedback-command'; @@ -69,6 +69,9 @@ export class ModifyCssFeedbackCommand extends FeedbackCommand { } } +export const CSS_GHOST_ELEMENT = 'ghost-element'; +export const CSS_HIDDEN = 'hidden'; + export enum CursorCSS { DEFAULT = 'default-mode', OVERLAP_FORBIDDEN = 'overlap-forbidden-mode', diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 4087b01..8d07bec 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -39,6 +39,7 @@ import { commandPaletteModule } from './features/command-palette/command-palette import { contextMenuModule } from './features/context-menu/context-menu-module'; import { copyPasteModule } from './features/copy-paste/copy-paste-modules'; import { decorationModule } from './features/decoration/decoration-module'; +import { elementTemplateModule } from './features/element-template/element-template-module'; import { exportModule } from './features/export/export-modules'; import { typeHintsModule } from './features/hints/type-hints-module'; import { hoverModule } from './features/hover/hover-module'; @@ -90,6 +91,7 @@ export const DEFAULT_MODULES = [ edgeCreationToolModule, edgeEditToolModule, deletionToolModule, + elementTemplateModule, nodeCreationToolModule, changeBoundsToolModule, marqueeSelectionToolModule, diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index 6928566..9f26fb0 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -15,11 +15,19 @@ ********************************************************************************/ import '../../../../css/keyboard-tool-palette.css'; -import { configureActionHandler, TYPES, bindAsService, BindingContext, FeatureModule } from '@eclipse-glsp/sprotty'; +import { + BindingContext, + FeatureModule, + SetModelAction, + TYPES, + UpdateModelAction, + bindAsService, + configureActionHandler +} from '@eclipse-glsp/sprotty'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { EnableToolPaletteAction } from '../../tool-palette/tool-palette'; -import { KeyboardToolPalette } from './keyboard-tool-palette'; import { FocusDomAction } from '../actions'; -import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { KeyboardToolPalette } from './keyboard-tool-palette'; export const keyboardToolPaletteModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; @@ -32,4 +40,6 @@ export function configureKeyboardToolPaletteTool(context: BindingContext): void configureActionHandler(context, EnableDefaultToolsAction.KIND, KeyboardToolPalette); configureActionHandler(context, FocusDomAction.KIND, KeyboardToolPalette); configureActionHandler(context, EnableToolPaletteAction.KIND, KeyboardToolPalette); + configureActionHandler(context, UpdateModelAction.KIND, KeyboardToolPalette); + configureActionHandler(context, SetModelAction.KIND, KeyboardToolPalette); } diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index 93dda95..a20e0d2 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -13,35 +13,35 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { - ICommand, - SetUIExtensionVisibilityAction, Action, + ICommand, PaletteItem, RequestContextActions, RequestMarkersAction, SetContextActions, + SetUIExtensionVisibilityAction, TriggerNodeCreationAction } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; -import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; import { - createIcon, - changeCodiconClass, - createToolGroup, + EnableToolPaletteAction, ToolPalette, + changeCodiconClass, compare, - EnableToolPaletteAction + createIcon, + createToolGroup } from '../../tool-palette/tool-palette'; -import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; +import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; +import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; import { FocusDomAction } from '../actions'; import { EdgeAutocompletePaletteMetadata } from '../edge-autocomplete/edge-autocomplete-palette'; -import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; -import { ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; +import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; import * as messages from '../toast/messages.json'; +import { ShowToastMessageAction } from '../toast/toast-handler'; const SEARCH_ICON_ID = 'search'; const PALETTE_ICON_ID = 'symbol-color'; @@ -119,7 +119,7 @@ export class KeyboardToolPalette extends ToolPalette { } override handle(action: Action): ICommand | Action | void { - if (action.kind === EnableToolPaletteAction.KIND) { + if (EnableToolPaletteAction.is(action)) { const requestAction = RequestContextActions.create({ contextId: ToolPalette.ID, editorContext: { @@ -134,9 +134,6 @@ export class KeyboardToolPalette extends ToolPalette { ]); } }); - } else if (action.kind === EnableDefaultToolsAction.KIND) { - this.changeActiveButton(); - this.restoreFocus(); } else if (FocusDomAction.is(action) && action.id === ToolPalette.ID) { if (this.containerElement.contains(document.activeElement)) { this.toggleShortcutVisibility(); @@ -144,6 +141,8 @@ export class KeyboardToolPalette extends ToolPalette { this.showShortcuts(); } this.containerElement.focus(); + } else { + super.handle(action); } } diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 0b0d817..239472e 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -30,6 +30,8 @@ import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; import { LayouterExt } from './layouter'; +import { LocalComputedBoundsCommand } from './local-bounds'; +import { SetBoundsFeebackCommand } from './set-bounds-feedback-command'; import { VBoxLayouterExt } from './vbox-layout'; export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { @@ -38,6 +40,10 @@ export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) configureCommand(context, RequestBoundsCommand); bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); + + configureCommand(context, LocalComputedBoundsCommand); + configureCommand(context, SetBoundsFeebackCommand); + bind(TYPES.Layouter).to(LayouterExt).inSingletonScope(); bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 5e90fa5..115776e 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -14,22 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; -import { VNode } from 'snabbdom'; import { Action, ComputedBoundsAction, Deferred, EdgeRouterRegistry, ElementAndRoutingPoints, + GModelElement, + GRoutableElement, HiddenBoundsUpdater, IActionDispatcher, RequestAction, - ResponseAction, - GModelElement, - GRoutableElement + ResponseAction } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { VNode } from 'snabbdom'; import { calcElementAndRoute, isRoutable } from '../../utils/gmodel-util'; +import { LocalComputedBoundsAction, LocalRequestBoundsAction } from './local-bounds'; /** * Grabs the bounds from hidden SVG DOM elements, applies layouts, collects routes and fires {@link ComputedBoundsAction}s. @@ -56,7 +57,7 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { const actions = this.captureActions(() => super.postUpdate(cause)); actions .filter(action => ComputedBoundsAction.is(action)) - .forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction))); + .forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction, cause))); this.element2route = []; } @@ -72,7 +73,10 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { } } - protected enhanceAction(action: ComputedBoundsAction): ComputedBoundsAction { + protected enhanceAction(action: ComputedBoundsAction, cause?: Action): ComputedBoundsAction { + if (LocalRequestBoundsAction.is(cause)) { + LocalComputedBoundsAction.mark(action); + } action.routes = this.element2route.length === 0 ? undefined : this.element2route; return action; } diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts new file mode 100644 index 0000000..82e6c8a --- /dev/null +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -0,0 +1,91 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + ActionDispatcher, + Command, + CommandExecutionContext, + CommandResult, + CommandReturn, + ComputedBoundsAction, + ComputedBoundsApplicator, + GModelRoot, + GModelRootSchema, + RequestBoundsAction, + TYPES +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { ServerAction } from '../../base/model/glsp-model-source'; + +export namespace LocalRequestBoundsAction { + export function is(object: unknown): object is RequestBoundsAction { + return RequestBoundsAction.is(object) && !ServerAction.is(object); + } + + export function fromCommand(context: CommandExecutionContext, actionDispatcher: ActionDispatcher, cause?: Action): CommandResult { + // do not modify the main model (modelChanged = false) but request local bounds calculation on hidden model + actionDispatcher.dispatch(RequestBoundsAction.create(context.root as unknown as GModelRootSchema)); + return { + model: context.root, + modelChanged: false, + cause + }; + } +} + +export namespace LocalComputedBoundsAction { + export function is(object: unknown): object is RequestBoundsAction { + return ComputedBoundsAction.is(object) && ServerAction.is(object); + } + + export function mark(action: ComputedBoundsAction): ComputedBoundsAction { + // mimic: we mark the computed bounds action as coming from the server so it is not sent to the server and handled locally + ServerAction.mark(action); + return action; + } +} + +@injectable() +export class LocalComputedBoundsCommand extends Command { + static readonly KIND: string = ComputedBoundsAction.KIND; + + @inject(ComputedBoundsApplicator) protected readonly computedBoundsApplicator: ComputedBoundsApplicator; + + constructor(@inject(TYPES.Action) readonly action: ComputedBoundsAction) { + super(); + } + + override execute(context: CommandExecutionContext): GModelRoot | CommandResult { + if (LocalComputedBoundsAction.is(this.action)) { + // apply computed bounds from the hidden model and return updated model to render new main model + this.computedBoundsApplicator.apply(context.root as unknown as GModelRootSchema, this.action); + return context.root; + } + // computed bounds action from server -> we do not care and do not trigger any update of the main model + return { + model: context.root, + modelChanged: false + }; + } + + override undo(context: CommandExecutionContext): CommandReturn { + return context.root; + } + + override redo(context: CommandExecutionContext): CommandReturn { + return context.root; + } +} diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts new file mode 100644 index 0000000..c8cd331 --- /dev/null +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + CommandExecutionContext, + CommandReturn, + ElementAndBounds, + SetBoundsAction, + SetBoundsCommand, + TYPES, + isLayoutContainer +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { LocalRequestBoundsAction } from './local-bounds'; + +export interface SetBoundsFeedbackAction extends Omit { + kind: typeof SetBoundsFeedbackAction.KIND; +} + +export namespace SetBoundsFeedbackAction { + export const KIND = 'setBoundsFeedback'; + + export function is(object: any): object is SetBoundsFeedbackAction { + return Action.hasKind(object, KIND); + } + + export function create(bounds: ElementAndBounds[]): SetBoundsFeedbackAction { + return { kind: KIND, bounds }; + } +} + +@injectable() +export class SetBoundsFeebackCommand extends SetBoundsCommand implements FeedbackCommand { + static override readonly KIND: string = SetBoundsFeedbackAction.KIND; + + readonly priority: number = 0; + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + + override execute(context: CommandExecutionContext): CommandReturn { + super.execute(context); + + // apply set bounds as layout options so that when we calculate the bounds they are considered by the layouter + this.action.bounds.forEach(bounds => { + const element = context.root.index.getById(bounds.elementId); + if (element && isLayoutContainer(element)) { + const options = element.layoutOptions ?? {}; + options.prefHeight = bounds.newSize.height; + options.prefWidth = bounds.newSize.width; + element.layoutOptions = options; + } + }); + return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action); + } +} diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 9f679a8..d024241 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -16,10 +16,10 @@ import { BoundsAware, GChildElement, - Hoverable, - Locateable, GModelElement, GParentElement, + Hoverable, + Locateable, Selectable, hoverFeedbackFeature, isBoundsAware, @@ -72,7 +72,6 @@ export class SResizeHandle extends GChildElement implements Hoverable { } export function addResizeHandles(element: GParentElement): void { - removeResizeHandles(element); element.add(new SResizeHandle(ResizeHandleLocation.TopLeft)); element.add(new SResizeHandle(ResizeHandleLocation.TopRight)); element.add(new SResizeHandle(ResizeHandleLocation.BottomLeft)); diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 4d1960d..3fd2135 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Decoration, DecorationPlacer, GChildElement, GModelElement, GRoutableElement, Point, isSizeable } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { Decoration, DecorationPlacer, GChildElement, Point, GModelElement, GRoutableElement, isSizeable } from '@eclipse-glsp/sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { @@ -26,8 +26,8 @@ export class GlspDecorationPlacer extends DecorationPlacer { } if (isSizeable(element)) { return { - x: GlspDecorationPlacer.DECORATION_OFFSET.x * element.bounds.width, - y: GlspDecorationPlacer.DECORATION_OFFSET.y * element.bounds.height + x: -GlspDecorationPlacer.DECORATION_OFFSET.x, + y: -GlspDecorationPlacer.DECORATION_OFFSET.y }; } return Point.ORIGIN; diff --git a/packages/client/src/features/element-template/add-template-element.ts b/packages/client/src/features/element-template/add-template-element.ts new file mode 100644 index 0000000..7bc81ba --- /dev/null +++ b/packages/client/src/features/element-template/add-template-element.ts @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + CommandExecutionContext, + CommandResult, + ElementTemplate, + GChildElement, + GModelElementSchema, + TYPES, + distinctAdd, + remove +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { isNotUndefined } from '../../utils/gmodel-util'; +import { LocalRequestBoundsAction } from '../bounds/local-bounds'; + +export interface AddTemplateElementsAction extends Action { + kind: typeof AddTemplateElementsAction.KIND; + templates: ElementTemplate[]; + addClasses?: string[]; + removeClasses?: string[]; +} + +export namespace AddTemplateElementsAction { + export const KIND = 'addTemplateElements'; + + export function create(options: Omit): AddTemplateElementsAction { + return { + kind: KIND, + ...options + }; + } +} + +export function getTemplateElementId(template: ElementTemplate): string { + return typeof template === 'string' ? `${template}_feedback` : template.id; +} + +@injectable() +export class AddTemplateElementsFeedbackCommand extends FeedbackCommand { + static readonly KIND = AddTemplateElementsAction.KIND; + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + + constructor(@inject(TYPES.Action) readonly action: AddTemplateElementsAction) { + super(); + } + + override execute(context: CommandExecutionContext): CommandResult { + this.action.templates + .map(template => templateToSchema(template, context)) + .filter(isNotUndefined) + .map(schema => context.modelFactory.createElement(schema)) + .map(element => this.applyRootCssClasses(element, this.action.addClasses, this.action.removeClasses)) + .forEach(templateElement => context.root.add(templateElement)); + return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action); + } + + protected applyRootCssClasses(element: GChildElement, addClasses?: string[], removeClasses?: string[]): GChildElement { + element.cssClasses = modifyCssClasses(element.cssClasses, addClasses, removeClasses); + return element; + } +} + +export function templateToSchema(template: ElementTemplate, context: CommandExecutionContext): GModelElementSchema | undefined { + if (typeof template === 'string') { + const element = context.root.index.getById(template); + const schema = element ? context.modelFactory.createSchema(element) : undefined; + if (schema) { + adaptSchemaIds(schema); + } + return schema; + } + return template; +} + +function adaptSchemaIds(schema: GModelElementSchema): GModelElementSchema { + schema.id = getTemplateElementId(schema.id); + schema.children?.forEach(child => adaptSchemaIds(child)); + return schema; +} + +function modifyCssClasses(source?: string[], toAdd?: string[], toRemove?: string[]): string[] { + const classes = source ?? []; + if (toAdd) { + distinctAdd(classes, ...toAdd); + } + if (toRemove) { + remove(classes, ...toRemove); + } + return classes; +} diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts new file mode 100644 index 0000000..3008231 --- /dev/null +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { FeatureModule, configureCommand } from '@eclipse-glsp/sprotty'; +import { AddTemplateElementsFeedbackCommand } from './add-template-element'; +import { RemoveTemplateElementsFeedbackCommand } from './remove-template-element'; + +export const elementTemplateModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureCommand(context, AddTemplateElementsFeedbackCommand); + configureCommand(context, RemoveTemplateElementsFeedbackCommand); +}); diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts new file mode 100644 index 0000000..a79a410 --- /dev/null +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, Disposable, GModelElement, ISnapper, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; +import { IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; +import { Tool } from '../../base/tool-manager/tool'; +import { getAbsolutePosition } from '../../utils/viewpoint-util'; +import { + IMovementRestrictor, + createMovementRestrictionFeedback, + removeMovementRestrictionFeedback +} from '../change-bounds/movement-restrictor'; + +export interface PositioningTool extends Tool { + readonly snapper?: ISnapper; + readonly movementRestrictor?: IMovementRestrictor; + + registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable; + deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): void; +} + +@injectable() +export class MouseTrackingElementPositionListener extends DragAwareMouseListener { + element?: GModelElement; + currentPosition?: Point; + + constructor( + protected elementId: string, + protected tool: PositioningTool, + protected cursorPosition: 'top-left' | 'middle' = 'top-left' + ) { + super(); + } + + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { + super.mouseMove(target, event); + const element = target.root.index.getById(this.elementId); + this.element = element; + if (!element) { + return []; + } + + let newPosition = getAbsolutePosition(target, event); + if (this.cursorPosition === 'middle' && isBoundsAware(element)) { + newPosition = Point.subtract(newPosition, { x: element.bounds.width / 2, y: element.bounds.height / 2 }); + } + newPosition = this.snap(newPosition, element); + + const finished = false; + if (isMoveable(element)) { + newPosition = this.validateMove(this.currentPosition ?? newPosition, newPosition, element, finished); + } + this.currentPosition = newPosition; + const moveGhostElement = MoveAction.create( + [ + { + elementId: element.id, + toPosition: newPosition + } + ], + { animate: false, finished } + ); + this.tool.registerFeedback([moveGhostElement], this); + return element.cssClasses?.includes(CSS_HIDDEN) + ? [ModifyCSSFeedbackAction.create({ elements: [element.id], remove: [CSS_HIDDEN] })] + : []; + } + + protected snap(position: Point, element: GModelElement, isSnap = true): Point { + if (isSnap && this.tool.snapper) { + return this.tool.snapper.snap(position, element); + } else { + return position; + } + } + + protected validateMove(startPosition: Point, toPosition: Point, element: GModelElement, isFinished: boolean): Point { + let newPosition = toPosition; + if (this.tool.movementRestrictor) { + const valid = this.tool.movementRestrictor.validate(element, toPosition); + let action; + if (!valid) { + action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); + if (isFinished) { + newPosition = startPosition; + } + } else { + action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); + } + this.tool.registerFeedback([action], this, [removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)]); + } + return newPosition; + } +} diff --git a/packages/client/src/features/element-template/remove-template-element.ts b/packages/client/src/features/element-template/remove-template-element.ts new file mode 100644 index 0000000..d979868 --- /dev/null +++ b/packages/client/src/features/element-template/remove-template-element.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, CommandExecutionContext, CommandReturn, ElementTemplate, GChildElement, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { getTemplateElementId } from './add-template-element'; + +export interface RemoveTemplateElementsAction extends Action { + kind: typeof RemoveTemplateElementsAction.KIND; + templates: ElementTemplate[]; +} + +export namespace RemoveTemplateElementsAction { + export const KIND = 'removeTemplateElements'; + + export function create(options: Omit): RemoveTemplateElementsAction { + return { + kind: KIND, + ...options + }; + } +} + +@injectable() +export class RemoveTemplateElementsFeedbackCommand extends FeedbackCommand { + static readonly KIND = RemoveTemplateElementsAction.KIND; + + constructor(@inject(TYPES.Action) readonly action: RemoveTemplateElementsAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const index = context.root.index; + for (const template of this.action.templates) { + const element = index.getById(getTemplateElementId(template)); + if (element && element instanceof GChildElement) { + element.parent.remove(element); + } + } + return context.root; + } +} diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index 5d989e6..a8a4f4b 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { bindAsService, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import { bindAsService, configureActionHandler, FeatureModule, SetModelAction, TYPES, UpdateModelAction } from '@eclipse-glsp/sprotty'; import '../../../css/tool-palette.css'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { ToolPalette } from './tool-palette'; @@ -22,4 +22,6 @@ export const toolPaletteModule = new FeatureModule((bind, _unbind, isBound, _reb bindAsService(bind, TYPES.IUIExtension, ToolPalette); bind(TYPES.IDiagramStartup).toService(ToolPalette); configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); + configureActionHandler({ bind, isBound }, UpdateModelAction.KIND, ToolPalette); + configureActionHandler({ bind, isBound }, SetModelAction.KIND, ToolPalette); }); diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 5135cf9..e2ccb21 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -13,22 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, Action, + GModelRoot, IActionHandler, ICommand, MarkersReason, PaletteItem, RequestContextActions, RequestMarkersAction, - GModelRoot, SetContextActions, + SetModelAction, SetUIExtensionVisibilityAction, + TriggerNodeCreationAction, + UpdateModelAction, codiconCSSClasses, matchesKeystroke } from '@eclipse-glsp/sprotty'; +import { inject, injectable, postConstruct } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; @@ -73,6 +76,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; + protected dynamic = false; protected bodyDiv?: HTMLElement; protected lastActiveButton?: HTMLElement; protected defaultToolsButton: HTMLElement; @@ -333,11 +337,15 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - handle(action: EnableDefaultToolsAction): ICommand | Action | void { + handle(action: Action): ICommand | Action | void { this.changeActiveButton(); - if (this.focusTracker.hasFocus) { - // if focus was deliberately taken do not restore focus to the palette - this.focusTracker.diagramElement?.focus(); + if (UpdateModelAction.is(action) || SetModelAction.is(action)) { + this.reloadPaletteBody(); + } else if (EnableDefaultToolsAction.is(action)) { + if (this.focusTracker.hasFocus) { + // if focus was deliberately taken do not restore focus to the palette + this.focusTracker.diagramElement?.focus(); + } } } @@ -360,11 +368,6 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } } - protected handleSetContextActions(action: SetContextActions): void { - this.paletteItems = action.actions.map(e => e as PaletteItem); - this.createBody(); - } - protected requestFilterUpdate(filter: string): void { // Initialize the copy if it's empty if (this.paletteItemsCopy.length === 0) { @@ -395,6 +398,17 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } async preRequestModel(): Promise { + await this.setPaletteItems(); + if (!this.editorContext.isReadonly) { + this.show(this.editorContext.modelRoot); + } + } + + async postRequestModel(): Promise { + this.reloadPaletteBody(); + } + + protected async setPaletteItems(): Promise { const requestAction = RequestContextActions.create({ contextId: ToolPalette.ID, editorContext: { @@ -402,9 +416,23 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, } }); const response = await this.actionDispatcher.request(requestAction); - this.paletteItems = response.actions.map(e => e as PaletteItem); - if (!this.editorContext.isReadonly) { - this.show(this.editorContext.modelRoot); + this.paletteItems = response.actions.map(action => action as PaletteItem); + this.dynamic = this.paletteItems.some(item => this.hasDynamicAction(item)); + } + + protected hasDynamicAction(item: PaletteItem): boolean { + const dynamic = !!item.actions.find(action => TriggerNodeCreationAction.is(action) && action.ghostElement?.dynamic); + if (dynamic) { + return dynamic; + } + return item.children?.some(child => this.hasDynamicAction(child)) || false; + } + + protected async reloadPaletteBody(): Promise { + if (this.dynamic) { + await this.setPaletteItems(); + this.paletteItemsCopy = []; + this.requestFilterUpdate(this.searchField.value); } } } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 0a832b1..672c767 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -28,15 +28,14 @@ import { ElementAndBounds, ElementAndRoutingPoints, GChildElement, - ISnapper, - MouseListener, - Operation, - Point, GConnectableElement, GModelElement, GModelRoot, GParentElement, - SetBoundsAction, + ISnapper, + MouseListener, + Operation, + Point, TYPES, findParentByFeature, isSelected @@ -45,13 +44,14 @@ import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener' import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { PointPositionUpdater } from '../../../features/change-bounds/snap'; -import { isValidMove, isValidSize } from '../../../utils/layout-utils'; import { calcElementAndRoutingPoints, forEachElement, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; +import { isValidMove, isValidSize } from '../../../utils/layout-utils'; +import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-command'; import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor, @@ -335,7 +335,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel if (this.initialBounds && this.activeResizeHandle && resetBounds) { // we only reset the bounds if an active resize operation was cancelled due to the tool being disabled resetFeedback.push( - SetBoundsAction.create([ + SetBoundsFeedbackAction.create([ { elementId: this.activeResizeElement.id, newPosition: this.initialBounds, @@ -424,7 +424,9 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel if (this.tool.movementRestrictor) { actions.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - actions.push(SetBoundsAction.create([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }])); + actions.push( + SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }]) + ); return actions; } return []; @@ -446,12 +448,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel if (this.tool.movementRestrictor) { result.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - result.push(SetBoundsAction.create([{ elementId: element.id, newPosition, newSize }])); + result.push(SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition, newSize }])); } else if (this.isValidSize(element, newSize)) { if (this.tool.movementRestrictor) { result.push(createMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - result.push(SetBoundsAction.create([{ elementId: element.id, newPosition, newSize }])); + result.push(SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition, newSize }])); } return result; diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index 7cb919e..f0a4243 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index 4fd94be..3691616 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -14,10 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { FeatureModule, TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { elementTemplateModule } from '../../element-template/element-template-module'; import { NodeCreationTool } from './node-creation-tool'; -export const nodeCreationToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.ITool, NodeCreationTool); - configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); -}); +export const nodeCreationToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.ITool, NodeCreationTool); + configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); + }, + { requires: elementTemplateModule } +); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 329538e..edefe88 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -13,40 +13,61 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, CreateNodeOperation, - ISnapper, GModelElement, GNode, + ISnapper, + Point, TYPES, TriggerNodeCreationAction, findParentByFeature, isCtrlOrCmd } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import '../../../../css/ghost-element.css'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; -import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { getAbsolutePosition } from '../../../utils/viewpoint-util'; +import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; +import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; +import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; +import { RemoveTemplateElementsAction } from '../../element-template/remove-template-element'; import { Containable, isContainable } from '../../hints/model'; import { BaseCreationTool } from '../base-tools'; @injectable() -export class NodeCreationTool extends BaseCreationTool { +export class NodeCreationTool extends BaseCreationTool implements PositioningTool { static ID = 'tool_create_node'; protected isTriggerAction = TriggerNodeCreationAction.is; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; get id(): string { return NodeCreationTool.ID; } doEnable(): void { + let trackingListener: MouseTrackingElementPositionListener | undefined; + const ghostElement = this.triggerAction.ghostElement; + if (ghostElement) { + trackingListener = new MouseTrackingElementPositionListener(getTemplateElementId(ghostElement.template), this, 'middle'); + this.toDisposeOnDisable.push( + this.registerFeedback( + [AddTemplateElementsAction.create({ templates: [ghostElement.template], addClasses: [CSS_HIDDEN, CSS_GHOST_ELEMENT] })], + ghostElement, + [RemoveTemplateElementsAction.create({ templates: [ghostElement.template] })] + ), + this.mouseTool.registerListener(trackingListener) + ); + } + this.toDisposeOnDisable.push( - this.mouseTool.registerListener(new NodeCreationToolMouseListener(this.triggerAction, this)), + this.mouseTool.registerListener(new NodeCreationToolMouseListener(this.triggerAction, this, trackingListener)), this.registerFeedback([cursorFeedbackAction(CursorCSS.NODE_CREATION)], this, [cursorFeedbackAction()]) ); } @@ -56,7 +77,11 @@ export class NodeCreationTool extends BaseCreationTool + ): TriggerNodeCreationAction { return { kind: KIND, elementTypeId, diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index 5f7a34c..a2486a0 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import * as sprotty from 'sprotty-protocol'; import { Dimension, Point } from 'sprotty-protocol'; +import { GModelElementSchema } from '../model/model-schema'; import { AnyObject, hasArrayProp, hasStringProp } from '../utils/type-util'; import { Action } from './base-protocol'; import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from './tool-palette'; @@ -27,6 +28,11 @@ export interface Args { [key: string]: sprotty.JsonPrimitive; } +/** + * The template for a model element, i.e., either a reference to an existing element by element id or an element schema. + */ +export type ElementTemplate = string | GModelElementSchema; + /** * The ElementAndBounds type is used to associate new bounds with a model element, which is referenced via its id. */ diff --git a/packages/protocol/src/utils/disposable.spec.ts b/packages/protocol/src/utils/disposable.spec.ts index 9e664dc..7de19b4 100644 --- a/packages/protocol/src/utils/disposable.spec.ts +++ b/packages/protocol/src/utils/disposable.spec.ts @@ -66,6 +66,13 @@ describe('Disposable', () => { toRemove.dispose(); expect(disposableCollection['disposables'].length).to.be.equal(0); }); + it('should add one disposable function to the collection and remove it again', () => { + const disposable = (): void => {}; + const toRemove = disposableCollection.push(disposable); + expect(disposableCollection['disposables'].length).to.be.equal(1); + toRemove.dispose(); + expect(disposableCollection['disposables'].length).to.be.equal(0); + }); }); describe('dispose', () => { describe('should invoke dispose on all elements of the collection exactly once', () => { diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 3a12f52..4ee5286 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as jsonrpc from 'vscode-jsonrpc'; -import { remove } from '../utils/array-util'; +import { isArrayOfType, remove } from '../utils/array-util'; import { AnyObject, hasFunctionProp } from '../utils/type-util'; /** @@ -85,9 +85,12 @@ export class DisposableCollection implements Disposable { * @param disposables The disposables that should be added * @returns A disposable that removes the previously pushed values from the collection when invoked */ - push(...disposables: Disposable[]): Disposable { - this.disposables.push(...disposables); - return Disposable.create(() => remove(this.disposables, ...disposables)); + push(...disposables: Disposable[]): Disposable; + push(...disposables: (() => void)[]): Disposable; + push(...disposables: (() => void)[] | Disposable[]): Disposable { + const toAdd = isArrayOfType(disposables, Disposable.is) ? disposables : disposables.map(Disposable.create); + this.disposables.push(...toAdd); + return Disposable.create(() => remove(this.disposables, ...toAdd)); } get isDisposed(): boolean { From 4434e1d79387fae0cbe7947167f42e7f2d15db5d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 21 Nov 2023 03:57:52 -0800 Subject: [PATCH 283/566] GLSP-1170: Deprecate `ISModelRootListener` (#303) Replace `ISModelRootListener` with `IGModelRootListener` and add deprecation layer --- packages/client/src/base/command-stack.ts | 15 ++++++++++----- packages/client/src/base/default.module.ts | 4 ++-- packages/client/src/base/selection-service.ts | 18 ++++++++++++------ packages/glsp-sprotty/src/types.ts | 9 +++++++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 58c70df..8a9e559 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -13,33 +13,38 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, multiInject, optional, preDestroy } from 'inversify'; import { CommandStack, Disposable, DisposableCollection, Emitter, Event, - ICommand, GModelRoot, + ICommand, SetModelCommand, TYPES, UpdateModelCommand } from '@eclipse-glsp/sprotty'; +import { injectable, multiInject, optional, preDestroy } from 'inversify'; /** * A hook to listen for model root changes. Will be called after a server update * has been processed */ -export interface ISModelRootListener { +export interface IGModelRootListener { modelRootChanged(root: Readonly): void; } +/** + * @deprecated Use {@link IGModelRootListener} instead + */ +export type ISModelRootListener = IGModelRootListener; + @injectable() export class GLSPCommandStack extends CommandStack implements Disposable { - @multiInject(TYPES.ISModelRootListener) + @multiInject(TYPES.IGModelRootListener) @optional() - protected modelRootListeners: ISModelRootListener[] = []; + protected modelRootListeners: IGModelRootListener[] = []; protected toDispose = new DisposableCollection(); protected override initialize(): void { diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 32a3a69..c33254e 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '@vscode/codicons/dist/codicon.css'; import { ActionHandlerRegistry, FeatureModule, @@ -30,6 +29,7 @@ import { configureCommand, sprottyDefaultModule } from '@eclipse-glsp/sprotty'; +import '@vscode/codicons/dist/codicon.css'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; @@ -96,7 +96,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); bind(SelectionService).toSelf().inSingletonScope(); - bind(TYPES.ISModelRootListener).toService(SelectionService); + bind(TYPES.IGModelRootListener).toService(SelectionService); // Feedback Support ------------------------------------ // Generic re-usable feedback modifying css classes diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index 231d5e6..c8ace33 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { Action, Command, @@ -23,9 +22,9 @@ import { Emitter, Event, GChildElement, - ILogger, GModelElement, GModelRoot, + ILogger, SelectAction, SelectAllAction, Selectable, @@ -36,8 +35,9 @@ import { isSelectable, pluck } from '@eclipse-glsp/sprotty'; +import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { getElements, getMatchingElements } from '../utils/gmodel-util'; -import { ISModelRootListener } from './command-stack'; +import { IGModelRootListener } from './command-stack'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; export interface ISelectionListener { @@ -51,7 +51,7 @@ export interface SelectionChange { } @injectable() -export class SelectionService implements ISModelRootListener, Disposable { +export class SelectionService implements IGModelRootListener, Disposable { protected root: Readonly; protected selectedElementIDs: Set = new Set(); @@ -197,7 +197,10 @@ export class SelectCommand extends Command { protected selected: GModelElement[] = []; protected deselected: GModelElement[] = []; - constructor(@inject(TYPES.Action) public action: SelectAction, @inject(SelectionService) public selectionService: SelectionService) { + constructor( + @inject(TYPES.Action) public action: SelectAction, + @inject(SelectionService) public selectionService: SelectionService + ) { super(); } @@ -234,7 +237,10 @@ export class SelectAllCommand extends Command { static readonly KIND = SprottySelectAllCommand.KIND; protected previousSelection: Map = new Map(); - constructor(@inject(TYPES.Action) public action: SelectAllAction, @inject(SelectionService) public selectionService: SelectionService) { + constructor( + @inject(TYPES.Action) public action: SelectAllAction, + @inject(SelectionService) public selectionService: SelectionService + ) { super(); } diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 8929af9..a826755 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -17,8 +17,10 @@ import { TYPES as SprottyTYPES } from 'sprotty'; /** * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service * identifiers. - * */ + +const IGModelRootListener = Symbol('IGModelRootListener'); + export const TYPES = { ...SprottyTYPES, IAsyncClipboardService: Symbol('IAsyncClipboardService'), @@ -28,7 +30,10 @@ export const TYPES = { ITypeHintProvider: Symbol('ITypeHintProvider'), IMovementRestrictor: Symbol('IMovementRestrictor'), ISelectionListener: Symbol('ISelectionListener'), - ISModelRootListener: Symbol('ISModelRootListener'), + /** @deprecated Use {@link TYPES.IGModelRootListener} instead */ + // eslint-disable-next-line deprecation/deprecation + ISModelRootListener: IGModelRootListener, + IGModelRootListener: IGModelRootListener, IContextMenuProvider: Symbol('IContextMenuProvider'), ICopyPasteHandler: Symbol('ICopyPasteHandler'), ITool: Symbol('ITool'), From 27c4cb0a11b00c7bb14692b6ede32699d8c9b58f Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 22 Nov 2023 00:01:45 +0100 Subject: [PATCH 284/566] Add ghost elements to tool palette items through trigger actions (#65) * Add ghost elements to tool palette items through trigger actions Relates to https://github.com/eclipse-glsp/glsp/issues/1159 * PR Review: Add easier hooks for customization * Update yarn lock --------- Co-authored-by: Tobias Ortmayr --- .../handler/create-activity-node-handler.ts | 14 ++++++++----- .../common/handler/create-category-handler.ts | 12 +++++++---- .../src/common/handler/create-task-handler.ts | 12 +++++++---- .../gmodel-create-node-operation-handler.ts | 20 +++++++++++++++++-- .../operations/json-operation-handler.ts | 17 +++++++++++++++- 5 files changed, 59 insertions(+), 16 deletions(-) diff --git a/examples/workflow-server/src/common/handler/create-activity-node-handler.ts b/examples/workflow-server/src/common/handler/create-activity-node-handler.ts index 6f5139a..fdbd8dd 100644 --- a/examples/workflow-server/src/common/handler/create-activity-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-activity-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CreateNodeOperation, GNode, Point } from '@eclipse-glsp/server'; +import { CreateNodeOperation, GNode, GhostElement, Point } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; import { ActivityNode, ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -25,10 +25,14 @@ export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperat return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined): ActivityNodeBuilder { + protected builder(point: Point = Point.ORIGIN, elementTypeId = this.elementTypeIds[0]): ActivityNodeBuilder { return ActivityNode.builder() - .position(point ?? Point.ORIGIN) - .type(this.elementTypeIds[0]) - .nodeType(ModelTypes.toNodeType(this.elementTypeIds[0])); + .position(point) + .type(elementTypeId) + .nodeType(ModelTypes.toNodeType(elementTypeId)); + } + + override createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { + return { template: this.serializer.createSchema(this.builder(undefined, elementTypeId).build()) }; } } diff --git a/examples/workflow-server/src/common/handler/create-category-handler.ts b/examples/workflow-server/src/common/handler/create-category-handler.ts index aab7e69..702af1d 100644 --- a/examples/workflow-server/src/common/handler/create-category-handler.ts +++ b/examples/workflow-server/src/common/handler/create-category-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ArgsUtil, CreateNodeOperation, GNode, Point } from '@eclipse-glsp/server'; +import { ArgsUtil, CreateNodeOperation, GNode, GhostElement, Point } from '@eclipse-glsp/server'; import { Category, CategoryNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @@ -26,12 +26,16 @@ export class CreateCategoryHandler extends CreateWorkflowNodeOperationHandler { return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined): CategoryNodeBuilder { + protected builder(point: Point = Point.ORIGIN, elementTypeId = this.elementTypeIds[0]): CategoryNodeBuilder { return Category.builder() - .type(this.elementTypeIds[0]) - .position(point ?? Point.ORIGIN) + .type(elementTypeId) + .position(point) .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(Category).length) .addArgs(ArgsUtil.cornerRadius(5)) .children(); } + + override createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { + return { template: this.serializer.createSchema(this.builder(undefined, elementTypeId).build()), dynamic: true }; + } } diff --git a/examples/workflow-server/src/common/handler/create-task-handler.ts b/examples/workflow-server/src/common/handler/create-task-handler.ts index 1ea8fb4..477885f 100644 --- a/examples/workflow-server/src/common/handler/create-task-handler.ts +++ b/examples/workflow-server/src/common/handler/create-task-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; +import { GhostElement, Point } from '@eclipse-glsp/protocol'; import { CreateNodeOperation, GNode } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; import { TaskNode, TaskNodeBuilder } from '../graph-extension'; @@ -26,12 +26,16 @@ export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandl return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined): TaskNodeBuilder { + protected builder(point: Point = Point.ORIGIN, elementTypeId = this.elementTypeIds[0]): TaskNodeBuilder { return TaskNode.builder() .position(point ?? Point.ORIGIN) .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(TaskNode).length) - .type(this.elementTypeIds[0]) - .taskType(ModelTypes.toNodeType(this.elementTypeIds[0])) + .type(elementTypeId) + .taskType(ModelTypes.toNodeType(elementTypeId)) .children(); } + + override createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { + return { template: this.serializer.createSchema(this.builder(undefined, elementTypeId).build()), dynamic: true }; + } } diff --git a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts index 6c87dbb..7c4f69b 100644 --- a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts +++ b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts @@ -15,7 +15,10 @@ ********************************************************************************/ import { GModelElement, GNode } from '@eclipse-glsp/graph'; -import { CreateNodeOperation, MaybePromise, Point, SelectAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; +import { + Args, CreateNodeOperation, GhostElement, MaybePromise, Point, SelectAction, + TriggerNodeCreationAction +} from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { Command } from '../command/command'; @@ -55,7 +58,20 @@ export abstract class GModelCreateNodeOperationHandler extends GModelOperationHa } getTriggerActions(): TriggerNodeCreationAction[] { - return this.elementTypeIds.map(typeId => TriggerNodeCreationAction.create(typeId)); + return this.elementTypeIds.map(elementTypeId => this.createTriggerNodeCreationAction(elementTypeId)); + } + + protected createTriggerNodeCreationAction(elementTypeId: string): TriggerNodeCreationAction { + return TriggerNodeCreationAction.create(elementTypeId, + { ghostElement: this.createTriggerGhostElement(elementTypeId), args: this.createTriggerArgs(elementTypeId) } ); + } + + protected createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { + return undefined; + } + + protected createTriggerArgs(elementTypeId: string): Args | undefined { + return undefined; } /** diff --git a/packages/server/src/common/operations/json-operation-handler.ts b/packages/server/src/common/operations/json-operation-handler.ts index 44e1560..46c03b9 100644 --- a/packages/server/src/common/operations/json-operation-handler.ts +++ b/packages/server/src/common/operations/json-operation-handler.ts @@ -16,8 +16,10 @@ import { GModelElement } from '@eclipse-glsp/graph'; import { AnyObject, + Args, CreateEdgeOperation, CreateNodeOperation, + GhostElement, MaybePromise, Point, TriggerEdgeCreationAction, @@ -93,7 +95,20 @@ export abstract class JsonCreateNodeOperationHandler extends JsonOperationHandle abstract override createCommand(operation: CreateNodeOperation): MaybePromise; getTriggerActions(): TriggerNodeCreationAction[] { - return this.elementTypeIds.map(typeId => TriggerNodeCreationAction.create(typeId)); + return this.elementTypeIds.map(elementTypeId => this.createTriggerNodeCreationAction(elementTypeId)); + } + + protected createTriggerNodeCreationAction(elementTypeId: string): TriggerNodeCreationAction { + return TriggerNodeCreationAction.create(elementTypeId, + { ghostElement: this.createTriggerGhostElement(elementTypeId), args: this.createTriggerArgs(elementTypeId) } ); + } + + protected createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { + return undefined; + } + + protected createTriggerArgs(elementTypeId: string): Args | undefined { + return undefined; } /** From 17e6e3f672dadf2f78d3b3b136dba4fa7bf550b5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 26 Nov 2023 23:44:21 -0800 Subject: [PATCH 285/566] Ensure async usage of JsonOperationHandler.commandOf (#69) Make runnable of command return a MaybePromise to be consistent with the doExecute method of recording commands and support async recordings. --- .../src/common/operations/json-operation-handler.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/server/src/common/operations/json-operation-handler.ts b/packages/server/src/common/operations/json-operation-handler.ts index 46c03b9..dad2e1f 100644 --- a/packages/server/src/common/operations/json-operation-handler.ts +++ b/packages/server/src/common/operations/json-operation-handler.ts @@ -55,7 +55,10 @@ export namespace JsonModelState { * to the `sourceModel` of the given {@link JsonModelState} during the given `doExecute` function */ export class JsonRecordingCommand extends AbstractRecordingCommand { - constructor(protected modelState: JsonModelState, protected doExecute: () => MaybePromise) { + constructor( + protected modelState: JsonModelState, + protected doExecute: () => MaybePromise + ) { super(); } @@ -75,7 +78,7 @@ export class JsonRecordingCommand exte */ @injectable() export abstract class JsonOperationHandler extends OperationHandler { - protected commandOf(runnable: () => void): Command { + protected commandOf(runnable: () => MaybePromise): Command { if (!JsonModelState.is(this.modelState)) { throw new Error('Cannot create command. The underlying model state does not implement the `JsonModelState` interface'); } @@ -99,8 +102,10 @@ export abstract class JsonCreateNodeOperationHandler extends JsonOperationHandle } protected createTriggerNodeCreationAction(elementTypeId: string): TriggerNodeCreationAction { - return TriggerNodeCreationAction.create(elementTypeId, - { ghostElement: this.createTriggerGhostElement(elementTypeId), args: this.createTriggerArgs(elementTypeId) } ); + return TriggerNodeCreationAction.create(elementTypeId, { + ghostElement: this.createTriggerGhostElement(elementTypeId), + args: this.createTriggerArgs(elementTypeId) + }); } protected createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { From 7a4500d6b26105fdb7564c766acbd887314d3b19 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Mon, 27 Nov 2023 15:28:02 +0100 Subject: [PATCH 286/566] Set suggestion container position to `fixed` (#304) Fixes https://github.com/eclipse-glsp/glsp/issues/1177 --- .../client/src/base/auto-complete/auto-complete-widget.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 40376ae..4577dc4 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -13,17 +13,17 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; import { Action, + GModelRoot, ILogger, LabeledAction, - GModelRoot, ValidationStatus, codiconCSSClasses, matchesKeystroke, toArray } from '@eclipse-glsp/sprotty'; +import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; import { AutoCompleteValue } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; @@ -196,6 +196,7 @@ export class AutoCompleteWidget { maxHeight: number ): void { // move container into our UIExtension container as this is already positioned correctly + container.style.position = 'fixed'; if (this.containerElement) { this.containerElement.appendChild(container); From 51fcecd98617ba36f79fa08ae2dea567d7383dce Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 29 Nov 2023 00:23:40 -0800 Subject: [PATCH 287/566] GLSP-1166 Fix dependency injection cycle in selection service (#305) Avoid binding of SelectionService as `IModelRootListner`. Instead directly inject the`CommandStack` into `SelectionService` and manually register itself as `IGModelRootListener`. This avoids a circular dependency injection if any of the `SelectionListner`s wants to inject the `ActionDispatcher`. Fixes eclipse-glsp/glsp/issues/1166 --- packages/client/src/base/default.module.ts | 1 - packages/client/src/base/selection-service.ts | 11 +++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index c33254e..6690f86 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -96,7 +96,6 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); bind(SelectionService).toSelf().inSingletonScope(); - bind(TYPES.IGModelRootListener).toService(SelectionService); // Feedback Support ------------------------------------ // Generic re-usable feedback modifying css classes diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index c8ace33..bb3b154 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -37,7 +37,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { getElements, getMatchingElements } from '../utils/gmodel-util'; -import { IGModelRootListener } from './command-stack'; +import { GLSPCommandStack, IGModelRootListener } from './command-stack'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; export interface ISelectionListener { @@ -61,6 +61,9 @@ export class SelectionService implements IGModelRootListener, Disposable { @inject(TYPES.ILogger) protected logger: ILogger; + @inject(TYPES.ICommandStack) + protected commandStack: GLSPCommandStack; + @multiInject(TYPES.ISelectionListener) @optional() protected selectionListeners: ISelectionListener[] = []; @@ -69,7 +72,11 @@ export class SelectionService implements IGModelRootListener, Disposable { @postConstruct() protected initialize(): void { - this.toDispose.push(this.onSelectionChangedEmitter); + this.toDispose.push( + this.onSelectionChangedEmitter, + this.commandStack.onModelRootChanged(root => this.modelRootChanged(root)) + ); + this.selectionListeners.forEach(listener => this.onSelectionChanged(change => listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements)) ); From b9684256a69d8b6b0b9efd16f0b515d92a341c43 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 30 Nov 2023 00:56:02 -0800 Subject: [PATCH 288/566] GLSP-1181: Fix support for simultaneously open diagrams (#307) Fixes https://github.com/eclipse-glsp/glsp/issues/1181 Also: Update changelog to reflect latest changes --- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 33 +++++++++++++++---- .../jsonrpc/base-jsonrpc-glsp-client.ts | 9 ++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 51b7c3f..5538f76 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -19,6 +19,7 @@ import * as sinon from 'sinon'; import { Disposable, Event, MessageConnection, NotificationHandler, ProgressType } from 'vscode-jsonrpc'; import { ActionMessage } from '../../action-protocol/base-protocol'; import { remove } from '../../utils/array-util'; +import { Emitter } from '../../utils/event'; import { expectToThrowAsync } from '../../utils/test-util'; import { ClientState } from '../glsp-client'; import { InitializeResult } from '../types'; @@ -68,13 +69,23 @@ class StubMessageConnection implements MessageConnection { inspect(): void {} } +class TestJsonRpcClient extends BaseJsonrpcGLSPClient { + protected override onActionMessageNotificationEmitter = new Emitter({ + onFirstListenerAdd: () => (this.firstListenerAdded = true), + onLastListenerRemove: () => (this.lastListenerRemoved = true) + }); + + firstListenerAdded: boolean; + lastListenerRemoved: boolean; +} + describe('Base JSON-RPC GLSP Client', () => { const sandbox = sinon.createSandbox(); const connection = sandbox.stub(new StubMessageConnection()); - let client = new BaseJsonrpcGLSPClient({ id: 'test', connectionProvider: connection }); + let client = new TestJsonRpcClient({ id: 'test', connectionProvider: connection }); async function resetClient(setRunning = true): Promise { sandbox.reset(); - client = new BaseJsonrpcGLSPClient({ id: 'test', connectionProvider: connection }); + client = new TestJsonRpcClient({ id: 'test', connectionProvider: connection }); if (setRunning) { return client.start(); } @@ -221,16 +232,24 @@ describe('Base JSON-RPC GLSP Client', () => { // eslint-disable-next-line @typescript-eslint/no-empty-function const handler = sandbox.spy((_message: ActionMessage): void => {}); - it('should fail if client is not running', async () => { + it('should be registered to message emitter if client is not running', async () => { await resetClient(false); - await expectToThrowAsync(() => client.onActionMessage(handler)); - expect(connection.onNotification.called).to.be.false; + client.onActionMessage(handler); + expect(client.firstListenerAdded).to.be.true; }); - it('should invoked the corresponding connection method', async () => { + it('should be registered to message emitter if client is running', async () => { await resetClient(); client.onActionMessage(handler, 'someId'); - expect(connection.onNotification.withArgs(JsonrpcGLSPClient.ActionMessageNotification).calledOnce).to.be.true; + expect(client.firstListenerAdded).to.be.true; + }); + it('should unregister lister if dispose is invoked', () => { + resetClient(false); + const clientId = 'clientId'; + const toDispose = client.onActionMessage(handler, clientId); + expect(client.firstListenerAdded).to.be.true; + toDispose.dispose(); + expect(client.lastListenerRemoved).to.be.true; }); }); diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 47e9dd7..9cc1adc 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -36,6 +36,11 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.onServerInitializedEmitter.event; } + protected onActionMessageNotificationEmitter = new Emitter(); + protected get onActionMessageNotification(): Event { + return this.onActionMessageNotificationEmitter.event; + } + constructor(options: JsonrpcGLSPClient.Options) { Object.assign(this, options); this.state = ClientState.Initial; @@ -66,7 +71,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { } onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable { - return this.checkedConnection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, msg => { + return this.onActionMessageNotification(msg => { if (!clientId || msg.clientId === clientId) { handler(msg); } @@ -113,6 +118,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { connection.dispose(); this.state = ClientState.Stopped; this.onStop = undefined; + this.onActionMessageNotificationEmitter.dispose(); this.connectionPromise = undefined; this.resolvedConnection = undefined; })); @@ -129,6 +135,7 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { const connection = typeof this.connectionProvider === 'function' ? await this.connectionProvider() : this.connectionProvider; connection.onError(data => this.handleConnectionError(data[0], data[1], data[2])); connection.onClose(() => this.handleConnectionClosed()); + connection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, msg => this.onActionMessageNotificationEmitter.fire(msg)); return connection; } From 70802882adcd5d4886f8e0e62bb44acc483a52bf Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 15 Dec 2023 12:35:13 +0100 Subject: [PATCH 289/566] Add Helper Lines module to provide feedback and helper line manager (#306) Helper Lines: - Draw helper lines when bounds of given elements match with existing - Draw selection bounds if more than one element is considered - Use manager class to draw them on client-side move or bounds change Integration: - Create PointPositionSnapper for more fine-grained snapping - Use PointPositionSnapper in PointPositionUpdater - Use PointPositionUpdater wherever necessary Other changes: - Centralize unsnap-modifier as Shift key - Ensure unsnapped moves are possible in keyboard tool - Fix typo in 'SetBoundsFeebackCommand' and some file names - Make popup mouse event transparent by default - Ensure we export everything from dedicated indices files - Use 'next' for workflow server standalone - Add restructuring of tools to CHANGELOG - Remove ghost feedback only after element is created --- .../src/workflow-diagram-module.ts | 11 +- .../workflow-standalone/scripts/config.json | 2 +- packages/client/css/glsp-sprotty.css | 1 + packages/client/css/helper-lines.css | 33 ++ .../client/src/base/auto-complete/index.ts | 20 ++ packages/client/src/base/feedback/index.ts | 19 ++ packages/client/src/base/focus/index.ts | 17 + packages/client/src/base/index.ts | 31 ++ packages/client/src/base/model/index.ts | 19 ++ .../client/src/base/tool-manager/index.ts | 17 + packages/client/src/base/view/index.ts | 18 + packages/client/src/default-modules.ts | 4 +- .../accessibility/edge-autocomplete/index.ts | 19 ++ .../accessibility/element-navigation/index.ts | 21 ++ .../accessibility/focus-tracker/index.ts | 17 + .../src/features/accessibility/index.ts | 30 ++ .../accessibility/key-shortcut/index.ts | 18 + .../accessibility/keyboard-grid/index.ts | 20 ++ .../accessibility/keyboard-pointer/index.ts | 21 ++ .../keyboard-tool-palette/index.ts | 17 + .../features/accessibility/move-zoom/index.ts | 18 + .../accessibility/resize-key-tool/index.ts | 18 + .../features/accessibility/search/index.ts | 18 + .../src/features/accessibility/toast/index.ts | 18 + .../accessibility/view-key-tools/index.ts | 19 ++ .../view-key-tools/movement-key-tool.ts | 34 +- .../src/features/bounds/bounds-module.ts | 7 +- packages/client/src/features/bounds/index.ts | 23 ++ .../src/features/bounds/local-bounds.ts | 8 +- .../bounds/set-bounds-feedback-command.ts | 2 +- .../src/features/change-bounds/index.ts | 20 ++ .../src/features/change-bounds/model.ts | 20 +- .../change-bounds/point-position-updater.ts | 120 +++++++ .../change-bounds/position-snapper.ts | 69 ++++ .../src/features/change-bounds/snap.spec.ts | 5 +- .../client/src/features/change-bounds/snap.ts | 90 +---- .../src/features/command-palette/index.ts | 18 + .../client/src/features/context-menu/index.ts | 19 ++ .../client/src/features/copy-paste/index.ts | 19 ++ .../client/src/features/decoration/index.ts | 17 + .../src/features/element-template/index.ts | 19 ++ ...ouse-tracking-element-position-listener.ts | 51 +-- packages/client/src/features/export/index.ts | 18 + .../helper-lines/helper-line-feedback.ts | 269 +++++++++++++++ .../helper-line-manager-default.ts | 114 +++++++ .../helper-lines/helper-line-manager.ts | 21 ++ .../helper-lines/helper-line-module.ts | 47 +++ .../client/src/features/helper-lines/index.ts | 21 ++ .../client/src/features/helper-lines/model.ts | 140 ++++++++ .../client/src/features/helper-lines/view.tsx | 60 ++++ packages/client/src/features/hints/index.ts | 18 + packages/client/src/features/hover/index.ts | 17 + packages/client/src/features/index.ts | 42 +++ .../client/src/features/label-edit/index.ts | 18 + packages/client/src/features/layout/index.ts | 17 + .../client/src/features/navigation/index.ts | 18 + .../client/src/features/reconnect/index.ts | 16 + packages/client/src/features/routing/index.ts | 17 + packages/client/src/features/save/index.ts | 17 + packages/client/src/features/select/index.ts | 18 + .../features/source-model-watcher/index.ts | 17 + ...dule.ts => source-model-watcher-module.ts} | 0 packages/client/src/features/status/index.ts | 17 + .../client/src/features/svg-metadata/index.ts | 17 + .../client/src/features/tool-palette/index.ts | 17 + .../change-bounds-tool-feedback.ts | 168 ++++++---- ...module.ts => change-bounds-tool-module.ts} | 0 .../tools/change-bounds/change-bounds-tool.ts | 23 +- .../src/features/tools/change-bounds/index.ts | 19 ++ .../src/features/tools/deletion/index.ts | 17 + .../src/features/tools/edge-creation/index.ts | 20 ++ .../edge-edit/edge-edit-tool-feedback.ts | 41 ++- .../tools/edge-edit/edge-edit-tool.ts | 11 +- .../src/features/tools/edge-edit/index.ts | 18 + packages/client/src/features/tools/index.ts | 23 ++ .../features/tools/marquee-selection/index.ts | 22 ++ .../tools/marquee-selection/marquee-tool.ts | 18 +- .../src/features/tools/node-creation/index.ts | 17 + .../tools/node-creation/node-creation-tool.ts | 22 +- .../client/src/features/undo-redo/index.ts | 17 + .../client/src/features/validation/index.ts | 19 ++ .../client/src/features/viewport/index.ts | 19 ++ packages/client/src/index.ts | 152 +-------- packages/client/src/re-exports.ts | 16 + packages/client/src/utils/geometry-util.ts | 101 ++++++ packages/client/src/utils/gmodel-util.ts | 51 ++- packages/client/src/utils/html-utils.ts | 6 + packages/client/src/utils/index.ts | 22 ++ packages/client/src/utils/viewpoint-util.ts | 8 + packages/client/src/views/index.ts | 4 +- packages/glsp-sprotty/src/index.ts | 309 +---------------- packages/glsp-sprotty/src/re-exports.ts | 311 ++++++++++++++++++ packages/glsp-sprotty/src/types.ts | 1 + .../src/client-server-protocol/index.ts | 20 ++ .../client-server-protocol/jsonrpc/index.ts | 20 ++ packages/protocol/src/index.ts | 30 +- packages/protocol/src/model/index.ts | 17 + packages/protocol/src/re-exports.ts | 20 ++ packages/protocol/src/utils/array-util.ts | 15 +- packages/protocol/src/utils/index.ts | 21 ++ 100 files changed, 2787 insertions(+), 754 deletions(-) create mode 100644 packages/client/css/helper-lines.css create mode 100644 packages/client/src/base/auto-complete/index.ts create mode 100644 packages/client/src/base/feedback/index.ts create mode 100644 packages/client/src/base/focus/index.ts create mode 100644 packages/client/src/base/index.ts create mode 100644 packages/client/src/base/model/index.ts create mode 100644 packages/client/src/base/tool-manager/index.ts create mode 100644 packages/client/src/base/view/index.ts create mode 100644 packages/client/src/features/accessibility/edge-autocomplete/index.ts create mode 100644 packages/client/src/features/accessibility/element-navigation/index.ts create mode 100644 packages/client/src/features/accessibility/focus-tracker/index.ts create mode 100644 packages/client/src/features/accessibility/index.ts create mode 100644 packages/client/src/features/accessibility/key-shortcut/index.ts create mode 100644 packages/client/src/features/accessibility/keyboard-grid/index.ts create mode 100644 packages/client/src/features/accessibility/keyboard-pointer/index.ts create mode 100644 packages/client/src/features/accessibility/keyboard-tool-palette/index.ts create mode 100644 packages/client/src/features/accessibility/move-zoom/index.ts create mode 100644 packages/client/src/features/accessibility/resize-key-tool/index.ts create mode 100644 packages/client/src/features/accessibility/search/index.ts create mode 100644 packages/client/src/features/accessibility/toast/index.ts create mode 100644 packages/client/src/features/accessibility/view-key-tools/index.ts create mode 100644 packages/client/src/features/bounds/index.ts create mode 100644 packages/client/src/features/change-bounds/index.ts create mode 100644 packages/client/src/features/change-bounds/point-position-updater.ts create mode 100644 packages/client/src/features/change-bounds/position-snapper.ts create mode 100644 packages/client/src/features/command-palette/index.ts create mode 100644 packages/client/src/features/context-menu/index.ts create mode 100644 packages/client/src/features/copy-paste/index.ts create mode 100644 packages/client/src/features/decoration/index.ts create mode 100644 packages/client/src/features/element-template/index.ts create mode 100644 packages/client/src/features/export/index.ts create mode 100644 packages/client/src/features/helper-lines/helper-line-feedback.ts create mode 100644 packages/client/src/features/helper-lines/helper-line-manager-default.ts create mode 100644 packages/client/src/features/helper-lines/helper-line-manager.ts create mode 100644 packages/client/src/features/helper-lines/helper-line-module.ts create mode 100644 packages/client/src/features/helper-lines/index.ts create mode 100644 packages/client/src/features/helper-lines/model.ts create mode 100644 packages/client/src/features/helper-lines/view.tsx create mode 100644 packages/client/src/features/hints/index.ts create mode 100644 packages/client/src/features/hover/index.ts create mode 100644 packages/client/src/features/index.ts create mode 100644 packages/client/src/features/label-edit/index.ts create mode 100644 packages/client/src/features/layout/index.ts create mode 100644 packages/client/src/features/navigation/index.ts create mode 100644 packages/client/src/features/reconnect/index.ts create mode 100644 packages/client/src/features/routing/index.ts create mode 100644 packages/client/src/features/save/index.ts create mode 100644 packages/client/src/features/select/index.ts create mode 100644 packages/client/src/features/source-model-watcher/index.ts rename packages/client/src/features/source-model-watcher/{source-model-wacher-module.ts => source-model-watcher-module.ts} (100%) create mode 100644 packages/client/src/features/status/index.ts create mode 100644 packages/client/src/features/svg-metadata/index.ts create mode 100644 packages/client/src/features/tool-palette/index.ts rename packages/client/src/features/tools/change-bounds/{change-boounds-tool-module.ts => change-bounds-tool-module.ts} (100%) create mode 100644 packages/client/src/features/tools/change-bounds/index.ts create mode 100644 packages/client/src/features/tools/deletion/index.ts create mode 100644 packages/client/src/features/tools/edge-creation/index.ts create mode 100644 packages/client/src/features/tools/edge-edit/index.ts create mode 100644 packages/client/src/features/tools/index.ts create mode 100644 packages/client/src/features/tools/marquee-selection/index.ts create mode 100644 packages/client/src/features/tools/node-creation/index.ts create mode 100644 packages/client/src/features/undo-redo/index.ts create mode 100644 packages/client/src/features/validation/index.ts create mode 100644 packages/client/src/features/viewport/index.ts create mode 100644 packages/client/src/re-exports.ts create mode 100644 packages/client/src/utils/geometry-util.ts create mode 100644 packages/client/src/utils/index.ts create mode 100644 packages/glsp-sprotty/src/re-exports.ts create mode 100644 packages/protocol/src/client-server-protocol/index.ts create mode 100644 packages/protocol/src/client-server-protocol/jsonrpc/index.ts create mode 100644 packages/protocol/src/model/index.ts create mode 100644 packages/protocol/src/re-exports.ts create mode 100644 packages/protocol/src/utils/index.ts diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index b3d1f08..5c32db6 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -19,18 +19,18 @@ import { DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, + GCompartment, + GCompartmentView, GEdge, GGraph, GLSPProjectionView, + GLabel, + GLabelView, GridSnapper, LogLevel, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, - GCompartment, - GCompartmentView, - GLabel, - GLabelView, StructureCompartmentView, TYPES, bindAsService, @@ -38,6 +38,7 @@ import { configureDefaultModelElements, configureModelElement, editLabelFeature, + helperLineModule, initializeDiagramContainer } from '@eclipse-glsp/client'; import 'balloon-css/balloon.min.css'; @@ -81,5 +82,5 @@ export function createWorkflowDiagramContainer(...containerConfiguration: Contai } export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { - return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, ...containerConfiguration); + return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, helperLineModule, ...containerConfiguration); } diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index 4c3ec43..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.0.1" + "version": "next" } diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 2fe87a9..8d17f5c 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -46,6 +46,7 @@ max-width: 400px; min-width: 100px; z-index: 1; + pointer-events: none; } .sprotty-popup > div { diff --git a/packages/client/css/helper-lines.css b/packages/client/css/helper-lines.css new file mode 100644 index 0000000..07a7de0 --- /dev/null +++ b/packages/client/css/helper-lines.css @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.helper-line { + pointer-events: none; + stroke: red; + stroke-width: 1px; + opacity: 1; +} + +.selection-bounds { + pointer-events: none; + fill: blue; + fill-opacity: 0.05; + stroke-linejoin: miter; + stroke-linecap: round; + stroke: darkblue; + stroke-width: 1px; + stroke-dasharray: 2; +} diff --git a/packages/client/src/base/auto-complete/index.ts b/packages/client/src/base/auto-complete/index.ts new file mode 100644 index 0000000..fb5f7b3 --- /dev/null +++ b/packages/client/src/base/auto-complete/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './auto-complete-actions'; +export * from './auto-complete-widget'; +export * from './autocomplete-suggestion-providers'; +export * from './base-autocomplete-palette'; +export * from './validation-decorator'; diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts new file mode 100644 index 0000000..9a753e0 --- /dev/null +++ b/packages/client/src/base/feedback/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './css-feedback'; +export * from './feedback-action-dispatcher'; +export * from './feedback-command'; +export * from './update-model-command'; diff --git a/packages/client/src/base/focus/index.ts b/packages/client/src/base/focus/index.ts new file mode 100644 index 0000000..4de0e8f --- /dev/null +++ b/packages/client/src/base/focus/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './focus-state-change-action'; +export * from './focus-tracker'; diff --git a/packages/client/src/base/index.ts b/packages/client/src/base/index.ts new file mode 100644 index 0000000..66976fb --- /dev/null +++ b/packages/client/src/base/index.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './action-dispatcher'; +export * from './action-handler-registry'; +export * from './args-feature'; +export * from './auto-complete'; +export * from './command-stack'; +export * from './default.module'; +export * from './drag-aware-mouse-listener'; +export * from './editor-context-service'; +export * from './feedback'; +export * from './focus'; +export * from './model'; +export * from './ranked'; +export * from './selection-clearing-mouse-listener'; +export * from './selection-service'; +export * from './tool-manager'; +export * from './view'; diff --git a/packages/client/src/base/model/index.ts b/packages/client/src/base/model/index.ts new file mode 100644 index 0000000..75f2f15 --- /dev/null +++ b/packages/client/src/base/model/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './diagram-loader'; +export * from './glsp-model-source'; +export * from './model-initialization-constraint'; +export * from './model-registry'; diff --git a/packages/client/src/base/tool-manager/index.ts b/packages/client/src/base/tool-manager/index.ts new file mode 100644 index 0000000..b903332 --- /dev/null +++ b/packages/client/src/base/tool-manager/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './tool-manager'; +export * from './tool'; diff --git a/packages/client/src/base/view/index.ts b/packages/client/src/base/view/index.ts new file mode 100644 index 0000000..fb4efbe --- /dev/null +++ b/packages/client/src/base/view/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './key-tool'; +export * from './mouse-tool'; +export * from './view-registry'; diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 8d07bec..e260d15 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -48,11 +48,11 @@ import { layoutModule } from './features/layout/layout-module'; import { navigationModule } from './features/navigation/navigation-module'; import { routingModule } from './features/routing/routing-module'; import { selectModule } from './features/select/select-module'; -import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-wacher-module'; +import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-watcher-module'; import { statusModule } from './features/status/status-module'; import { svgMetadataModule } from './features/svg-metadata/svg-metadata-module'; import { toolPaletteModule } from './features/tool-palette/tool-palette-module'; -import { changeBoundsToolModule } from './features/tools/change-bounds/change-boounds-tool-module'; +import { changeBoundsToolModule } from './features/tools/change-bounds/change-bounds-tool-module'; import { deletionToolModule } from './features/tools/deletion/deletion-tool-module'; import { edgeCreationToolModule } from './features/tools/edge-creation/edege-creation-module'; import { edgeEditToolModule } from './features/tools/edge-edit/edge-edit-module'; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/index.ts b/packages/client/src/features/accessibility/edge-autocomplete/index.ts new file mode 100644 index 0000000..b6f9eee --- /dev/null +++ b/packages/client/src/features/accessibility/edge-autocomplete/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './action'; +export * from './edge-autocomplete-context'; +export * from './edge-autocomplete-palette'; +export * from './edge-autocomplete-tool'; diff --git a/packages/client/src/features/accessibility/element-navigation/index.ts b/packages/client/src/features/accessibility/element-navigation/index.ts new file mode 100644 index 0000000..2f30cda --- /dev/null +++ b/packages/client/src/features/accessibility/element-navigation/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './diagram-navigation-tool'; +export * from './element-navigation-module'; +export * from './element-navigator'; +export * from './left-right-top-bottom-navigator'; +export * from './local-element-navigator'; +export * from './position-navigator'; diff --git a/packages/client/src/features/accessibility/focus-tracker/index.ts b/packages/client/src/features/accessibility/focus-tracker/index.ts new file mode 100644 index 0000000..aefa9ea --- /dev/null +++ b/packages/client/src/features/accessibility/focus-tracker/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './focus-tracker-module'; +export * from './focus-tracker-tool'; diff --git a/packages/client/src/features/accessibility/index.ts b/packages/client/src/features/accessibility/index.ts new file mode 100644 index 0000000..8135cf7 --- /dev/null +++ b/packages/client/src/features/accessibility/index.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './accessibility-module'; +export * from './actions'; +export * from './edge-autocomplete'; +export * from './element-navigation'; +export * from './focus-tracker'; +export * from './global-keylistener-tool'; +export * from './key-shortcut'; +export * from './keyboard-grid'; +export * from './keyboard-pointer'; +export * from './keyboard-tool-palette'; +export * from './move-zoom'; +export * from './resize-key-tool'; +export * from './search'; +export * from './toast'; +export * from './view-key-tools'; diff --git a/packages/client/src/features/accessibility/key-shortcut/index.ts b/packages/client/src/features/accessibility/key-shortcut/index.ts new file mode 100644 index 0000000..8ff23f3 --- /dev/null +++ b/packages/client/src/features/accessibility/key-shortcut/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './accessible-key-shortcut-tool'; +export * from './accessible-key-shortcut'; +export * from './di.config'; diff --git a/packages/client/src/features/accessibility/keyboard-grid/index.ts b/packages/client/src/features/accessibility/keyboard-grid/index.ts new file mode 100644 index 0000000..d3b3f2e --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-grid/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './action'; +export * from './constants'; +export * from './keyboard-grid-search-palette'; +export * from './keyboard-grid'; +export * from './keyboard-node-grid'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/index.ts b/packages/client/src/features/accessibility/keyboard-pointer/index.ts new file mode 100644 index 0000000..6ec6851 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-pointer/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './actions'; +export * from './constants'; +export * from './keyboard-pointer-listener'; +export * from './keyboard-pointer-module'; +export * from './keyboard-pointer-position'; +export * from './keyboard-pointer'; diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts new file mode 100644 index 0000000..f49dcc4 --- /dev/null +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './keyboard-tool-palette-module'; +export * from './keyboard-tool-palette'; diff --git a/packages/client/src/features/accessibility/move-zoom/index.ts b/packages/client/src/features/accessibility/move-zoom/index.ts new file mode 100644 index 0000000..55c7171 --- /dev/null +++ b/packages/client/src/features/accessibility/move-zoom/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './move-handler'; +export * from './move-zoom-module'; +export * from './zoom-handler'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/index.ts b/packages/client/src/features/accessibility/resize-key-tool/index.ts new file mode 100644 index 0000000..60481ad --- /dev/null +++ b/packages/client/src/features/accessibility/resize-key-tool/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './resize-key-handler'; +export * from './resize-key-module'; +export * from './resize-key-tool'; diff --git a/packages/client/src/features/accessibility/search/index.ts b/packages/client/src/features/accessibility/search/index.ts new file mode 100644 index 0000000..7cf637d --- /dev/null +++ b/packages/client/src/features/accessibility/search/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './search-palette-module'; +export * from './search-palette'; +export * from './search-tool'; diff --git a/packages/client/src/features/accessibility/toast/index.ts b/packages/client/src/features/accessibility/toast/index.ts new file mode 100644 index 0000000..a15b255 --- /dev/null +++ b/packages/client/src/features/accessibility/toast/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './toast-handler'; +export * from './toast-module'; +export * from './toast-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/index.ts b/packages/client/src/features/accessibility/view-key-tools/index.ts new file mode 100644 index 0000000..9333700 --- /dev/null +++ b/packages/client/src/features/accessibility/view-key-tools/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './deselect-key-tool'; +export * from './movement-key-tool'; +export * from './view-key-tools-module'; +export * from './zoom-key-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 770caaa..ddee997 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -14,13 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelElement, ISnapper, KeyListener, KeyTool, Point, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, ISnapper, KeyListener, KeyTool, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; -import { GridSnapper } from '../../change-bounds/snap'; +import { GridSnapper, unsnapModifier, useSnap } from '../../change-bounds/snap'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; @@ -63,18 +63,14 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu protected readonly token = MoveKeyListener.name; + protected grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY }; + constructor(protected readonly tool: MovementKeyTool) { super(); - } - protected get grid(): Point { if (this.tool.snapper instanceof GridSnapper) { - return this.tool.snapper.grid; + this.grid = this.tool.snapper.grid; } - return { - x: MoveKeyListener.defaultMoveX, - y: MoveKeyListener.defaultMoveY - }; } registerShortcutKey(): void { @@ -86,9 +82,9 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu ); } - override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); - const snap = !event.altKey; + const snap = useSnap(event); const offsetX = snap ? this.grid.x : 1; const offsetY = snap ? this.grid.y : 1; @@ -104,31 +100,31 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu } } else { if (this.matchesMoveUpKeystroke(event)) { - return [MoveViewportAction.create(0, -this.grid.y)]; + return [MoveViewportAction.create(0, -offsetY)]; } else if (this.matchesMoveDownKeystroke(event)) { - return [MoveViewportAction.create(0, this.grid.y)]; + return [MoveViewportAction.create(0, offsetY)]; } else if (this.matchesMoveRightKeystroke(event)) { - return [MoveViewportAction.create(this.grid.x, 0)]; + return [MoveViewportAction.create(offsetX, 0)]; } else if (this.matchesMoveLeftKeystroke(event)) { - return [MoveViewportAction.create(-this.grid.x, 0)]; + return [MoveViewportAction.create(-offsetX, 0)]; } } return []; } protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowUp') || matchesKeystroke(event, 'ArrowUp', 'alt'); + return matchesKeystroke(event, 'ArrowUp') || matchesKeystroke(event, 'ArrowUp', unsnapModifier()); } protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowDown') || matchesKeystroke(event, 'ArrowDown', 'alt'); + return matchesKeystroke(event, 'ArrowDown') || matchesKeystroke(event, 'ArrowDown', unsnapModifier()); } protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowRight') || matchesKeystroke(event, 'ArrowRight', 'alt'); + return matchesKeystroke(event, 'ArrowRight') || matchesKeystroke(event, 'ArrowRight', unsnapModifier()); } protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowLeft') || matchesKeystroke(event, 'ArrowLeft', 'alt'); + return matchesKeystroke(event, 'ArrowLeft') || matchesKeystroke(event, 'ArrowLeft', unsnapModifier()); } } diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 239472e..cf86680 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -26,12 +26,13 @@ import { configureCommand, configureLayout } from '@eclipse-glsp/sprotty'; +import { PositionSnapper } from '../change-bounds/position-snapper'; import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; import { HBoxLayouterExt } from './hbox-layout'; import { LayouterExt } from './layouter'; import { LocalComputedBoundsCommand } from './local-bounds'; -import { SetBoundsFeebackCommand } from './set-bounds-feedback-command'; +import { SetBoundsFeedbackCommand } from './set-bounds-feedback-command'; import { VBoxLayouterExt } from './vbox-layout'; export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { @@ -42,7 +43,7 @@ export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); configureCommand(context, LocalComputedBoundsCommand); - configureCommand(context, SetBoundsFeebackCommand); + configureCommand(context, SetBoundsFeedbackCommand); bind(TYPES.Layouter).to(LayouterExt).inSingletonScope(); bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); @@ -50,4 +51,6 @@ export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) configureLayout(context, VBoxLayouter.KIND, VBoxLayouterExt); configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); + + bind(PositionSnapper).toSelf(); }); diff --git a/packages/client/src/features/bounds/index.ts b/packages/client/src/features/bounds/index.ts new file mode 100644 index 0000000..b35779b --- /dev/null +++ b/packages/client/src/features/bounds/index.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './bounds-module'; +export * from './freeform-layout'; +export * from './glsp-hidden-bounds-updater'; +export * from './hbox-layout'; +export * from './layouter'; +export * from './local-bounds'; +export * from './set-bounds-feedback-command'; +export * from './vbox-layout'; diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts index 82e6c8a..699e343 100644 --- a/packages/client/src/features/bounds/local-bounds.ts +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -25,7 +25,8 @@ import { GModelRoot, GModelRootSchema, RequestBoundsAction, - TYPES + TYPES, + ViewerOptions } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { ServerAction } from '../../base/model/glsp-model-source'; @@ -63,6 +64,7 @@ export class LocalComputedBoundsCommand extends Command { static readonly KIND: string = ComputedBoundsAction.KIND; @inject(ComputedBoundsApplicator) protected readonly computedBoundsApplicator: ComputedBoundsApplicator; + @inject(TYPES.ViewerOptions) protected readonly viewerOptions: ViewerOptions; constructor(@inject(TYPES.Action) readonly action: ComputedBoundsAction) { super(); @@ -70,10 +72,14 @@ export class LocalComputedBoundsCommand extends Command { override execute(context: CommandExecutionContext): GModelRoot | CommandResult { if (LocalComputedBoundsAction.is(this.action)) { + if (!this.viewerOptions.needsClientLayout) { + return context.root; + } // apply computed bounds from the hidden model and return updated model to render new main model this.computedBoundsApplicator.apply(context.root as unknown as GModelRootSchema, this.action); return context.root; } + // computed bounds action from server -> we do not care and do not trigger any update of the main model return { model: context.root, diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index c8cd331..5f7f2b0 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -45,7 +45,7 @@ export namespace SetBoundsFeedbackAction { } @injectable() -export class SetBoundsFeebackCommand extends SetBoundsCommand implements FeedbackCommand { +export class SetBoundsFeedbackCommand extends SetBoundsCommand implements FeedbackCommand { static override readonly KIND: string = SetBoundsFeedbackAction.KIND; readonly priority: number = 0; diff --git a/packages/client/src/features/change-bounds/index.ts b/packages/client/src/features/change-bounds/index.ts new file mode 100644 index 0000000..85d8b38 --- /dev/null +++ b/packages/client/src/features/change-bounds/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './model'; +export * from './movement-restrictor'; +export * from './point-position-updater'; +export * from './position-snapper'; +export * from './snap'; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index d024241..506af69 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -62,12 +62,28 @@ export class SResizeHandle extends GChildElement implements Hoverable { return feature === hoverFeedbackFeature; } + isNwResize(): boolean { + return this.location === ResizeHandleLocation.TopLeft; + } + + isSeResize(): boolean { + return this.location === ResizeHandleLocation.BottomRight; + } + + isNeResize(): boolean { + return this.location === ResizeHandleLocation.TopRight; + } + + isSwResize(): boolean { + return this.location === ResizeHandleLocation.BottomLeft; + } + isNwSeResize(): boolean { - return this.location === ResizeHandleLocation.TopLeft || this.location === ResizeHandleLocation.BottomRight; + return this.isNwResize() || this.isSeResize(); } isNeSwResize(): boolean { - return this.location === ResizeHandleLocation.TopRight || this.location === ResizeHandleLocation.BottomLeft; + return this.isNeResize() || this.isSwResize(); } } diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts new file mode 100644 index 0000000..244de68 --- /dev/null +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2020-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ +import { GModelElement, ISnapper, Point, Writable } from '@eclipse-glsp/sprotty'; +import { calculateDeltaBetweenPoints } from '../../utils/gmodel-util'; +import { isMouseEvent } from '../../utils/html-utils'; +import { IHelperLineManager } from '../helper-lines/helper-line-manager'; +import { Direction, getDirectionOf } from '../helper-lines/model'; +import { PositionSnapper } from './position-snapper'; +import { useSnap } from './snap'; + +/** + * This class can be used to calculate the current position, when an element is + * moved. This includes node movements, node resizing (resize handle movement) + * or edge routing-point movements. + * + * You can initialize a this class with a optional {@link ISnapper}. If a + * snapper is present, the positions will be snapped to the defined grid. + */ +export class PointPositionUpdater { + protected positionSnapper: PositionSnapper; + protected lastDragPosition?: Point; + protected positionDelta: Writable = { x: 0, y: 0 }; + + constructor(snapper?: PositionSnapper); + constructor(snapper?: ISnapper, helperLineManager?: IHelperLineManager); + constructor(first?: PositionSnapper | ISnapper, helperLineManager?: IHelperLineManager) { + this.positionSnapper = first instanceof PositionSnapper ? first : new PositionSnapper(first, helperLineManager); + } + + /** + * Init the position with the {@link Point} of your mouse cursor. + * This method is normally called in the `mouseDown` event. + * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` + */ + public updateLastDragPosition(mousePosition: Point): void; + public updateLastDragPosition(mouseEvent: MouseEvent): void; + public updateLastDragPosition(first: Point | MouseEvent): void { + this.lastDragPosition = isMouseEvent(first) ? { x: first.pageX, y: first.pageY } : first; + } + + /** + * Check if the mouse is currently not in a drag mode. + * @returns true if the last drag position is undefined + */ + public isLastDragPositionUndefined(): boolean { + return this.lastDragPosition === undefined; + } + + /** + * Reset the updater for new movements. + * This method is normally called in the `mouseUp` event. + */ + public resetPosition(): void { + this.lastDragPosition = undefined; + this.positionDelta = { x: 0, y: 0 }; + } + + /** + * Calculate the current position of your movement. + * This method is normally called in the `mouseMove` event. + * @param target node which is moved around + * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` + * @param useSnap if a snapper is defined you can disable it, e.g when a specific key is pressed `!event.shiftKey` + * @param direction the direction in which the position is updated, will be calculated if not provided + * @returns delta to previous position or undefined if no delta should be applied + */ + public updatePosition(target: GModelElement, mousePosition: Point, useSnap: boolean, direction?: Direction[]): Point | undefined; + public updatePosition(target: GModelElement, mouseEvent: MouseEvent, direction?: Direction[]): Point | undefined; + public updatePosition( + target: GModelElement, + second: Point | MouseEvent, + third?: boolean | Direction[], + fourth?: Direction[] + ): Point | undefined { + if (!this.lastDragPosition) { + return undefined; + } + const mousePosition = isMouseEvent(second) ? { x: second.pageX, y: second.pageY } : second; + const shouldSnap = typeof third === 'boolean' ? third : useSnap(second as MouseEvent); + const direction = typeof third !== 'boolean' ? third : fourth; + + // calculate update to last drag position + const deltaToLastPosition = calculateDeltaBetweenPoints(mousePosition, this.lastDragPosition, target); + this.lastDragPosition = mousePosition; + if (Point.equals(deltaToLastPosition, Point.ORIGIN)) { + return undefined; + } + + // accumulate position delta with latest delta + this.positionDelta.x += deltaToLastPosition.x; + this.positionDelta.y += deltaToLastPosition.y; + + const directions = direction ?? getDirectionOf(this.positionDelta); + + // only send update if the position actually changes + // otherwise accumulate delta until we get to an update + const positionUpdate = this.positionSnapper.snapDelta(this.positionDelta, target, shouldSnap, directions); + if (Point.equals(positionUpdate, Point.ORIGIN)) { + return undefined; + } + // we update our position so we update our delta by the snapped position + this.positionDelta.x -= positionUpdate.x; + this.positionDelta.y -= positionUpdate.y; + return positionUpdate; + } +} diff --git a/packages/client/src/features/change-bounds/position-snapper.ts b/packages/client/src/features/change-bounds/position-snapper.ts new file mode 100644 index 0000000..adaf34b --- /dev/null +++ b/packages/client/src/features/change-bounds/position-snapper.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement, ISnapper, Point, TYPES, Writable } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { IHelperLineManager } from '../helper-lines/helper-line-manager'; +import { Direction, HelperLine, isHelperLine } from '../helper-lines/model'; + +@injectable() +export class PositionSnapper { + constructor( + @optional() @inject(TYPES.ISnapper) protected snapper?: ISnapper, + @optional() @inject(TYPES.IHelperLineManager) protected helperLineManager?: IHelperLineManager + ) {} + + snapPosition(position: Point, element: GModelElement, isSnap: boolean = true): Point { + return isSnap && this.snapper ? this.snapper.snap(position, element) : { x: position.x, y: position.y }; + } + + snapDelta(positionDelta: Point, element: GModelElement, isSnap: boolean, directions: Direction[]): Point { + const delta: Writable = this.snapPosition(positionDelta, element, isSnap); + const minimumDelta = this.getMinimumDelta(element, isSnap, directions); + if (!minimumDelta) { + return delta; + } + delta.x = Math.abs(delta.x) >= minimumDelta.x ? delta.x : 0; + delta.y = Math.abs(delta.y) >= minimumDelta.y ? delta.y : 0; + return delta; + } + + protected getMinimumDelta(target: GModelElement, isSnap: boolean, directions: Direction[]): Point | undefined { + return this.getHelperLineMinimum(target, isSnap, directions); + } + + protected getHelperLineMinimum(target: GModelElement, isSnap: boolean, directions: Direction[]): Point | undefined { + if (!this.helperLineManager) { + return undefined; + } + const helperLines = target.root.children.filter(child => isHelperLine(child)) as HelperLine[]; + if (helperLines.length === 0) { + return undefined; + } + + const minimum: Writable = { x: 0, y: 0 }; + if (directions.includes(Direction.Left) && helperLines.some(line => line.isLeft || line.isCenter)) { + minimum.x = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Left); + } else if (directions.includes(Direction.Right) && helperLines.some(line => line.isRight || line.isCenter)) { + minimum.x = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Right); + } + if (directions.includes(Direction.Up) && helperLines.some(line => line.isTop || line.isMiddle)) { + minimum.y = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Up); + } else if (directions.includes(Direction.Down) && helperLines.some(line => line.isBottom || line.isMiddle)) { + minimum.y = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Down); + } + return minimum; + } +} diff --git a/packages/client/src/features/change-bounds/snap.spec.ts b/packages/client/src/features/change-bounds/snap.spec.ts index ff30aac..ca6a82e 100644 --- a/packages/client/src/features/change-bounds/snap.spec.ts +++ b/packages/client/src/features/change-bounds/snap.spec.ts @@ -14,9 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { expect } from 'chai'; import { GModelElement } from '@eclipse-glsp/sprotty'; -import { GridSnapper, PointPositionUpdater } from './snap'; +import { expect } from 'chai'; +import { PointPositionUpdater } from './point-position-updater'; +import { GridSnapper } from './snap'; describe('GridSnapper', () => { it('snap', () => { diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 7d81c48..36f218c 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ +import { GModelElement, ISnapper, KeyboardModifier, Point } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { ISnapper, Point, GModelElement, Writable, findParentByFeature, isViewport } from '@eclipse-glsp/sprotty'; /** * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. @@ -30,7 +31,7 @@ import { ISnapper, Point, GModelElement, Writable, findParentByFeature, isViewpo export class GridSnapper implements ISnapper { constructor(public grid: { x: number; y: number } = { x: 10, y: 10 }) {} - snap(position: Point, element: GModelElement): Point { + snap(position: Point, _element: GModelElement): Point { return { x: Math.round(position.x / this.grid.x) * this.grid.x, y: Math.round(position.y / this.grid.y) * this.grid.y @@ -38,85 +39,10 @@ export class GridSnapper implements ISnapper { } } -/** - * This class can be used to calculate the current position, when an element is - * moved. This includes node movements, node resizing (resize handle movement) - * or edge routing-point movements. - * - * You can initialize a this class with a optional {@link ISnapper}. If a - * snapper is present, the positions will be snapped to the defined grid. - */ -export class PointPositionUpdater { - protected lastDragPosition?: Point; - protected positionDelta: Writable = { x: 0, y: 0 }; - - constructor(protected snapper?: ISnapper) {} - - /** - * Init the position with the {@link Point} of your mouse cursor. - * This method is normally called in the `mouseDown` event. - * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` - */ - public updateLastDragPosition(mousePosition: Point): void { - this.lastDragPosition = mousePosition; - } - - /** - * Check if the mouse is currently not in a drag mode. - * @returns true if the last drag position is undefined - */ - public isLastDragPositionUndefined(): boolean { - return this.lastDragPosition === undefined; - } - - /** - * Reset the updater for new movements. - * This method is normally called in the `mouseUp` event. - */ - public resetPosition(): void { - this.lastDragPosition = undefined; - this.positionDelta = { x: 0, y: 0 }; - } - - /** - * Calculate the current position of your movement. - * This method is normally called in the `mouseMove` event. - * @param target node which is moved around - * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` - * @param isSnapEnabled if a snapper is defined you can disable it, e.g when a specific key is pressed `!event.altKey` - * @returns current position or undefined if updater has no last drag position initialized - */ - public updatePosition(target: GModelElement, mousePosition: Point, isSnapEnabled: boolean): Point | undefined { - if (this.lastDragPosition) { - const newDragPosition = mousePosition; - - const viewport = findParentByFeature(target, isViewport); - const zoom = viewport?.zoom ?? 1; - const dx = (mousePosition.x - this.lastDragPosition.x) / zoom; - const dy = (mousePosition.y - this.lastDragPosition.y) / zoom; - const deltaToLastPosition = { x: dx, y: dy }; - this.lastDragPosition = newDragPosition; - - // update position delta with latest delta - this.positionDelta.x += deltaToLastPosition.x; - this.positionDelta.y += deltaToLastPosition.y; - - // snap our delta and only send update if the position actually changes - // otherwise accumulate delta until we do snap to an update - const positionUpdate = this.snap(this.positionDelta, target, isSnapEnabled); - if (positionUpdate.x === 0 && positionUpdate.y === 0) { - return undefined; - } - - // we update our position so we update our delta by the snapped position - this.positionDelta.x -= positionUpdate.x; - this.positionDelta.y -= positionUpdate.y; - return positionUpdate; - } - return undefined; - } +export function useSnap(event: MouseEvent | KeyboardEvent): boolean { + return !event.shiftKey; +} - protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { - return isSnap && this.snapper ? this.snapper.snap(position, element) : { x: position.x, y: position.y }; - } +export function unsnapModifier(): KeyboardModifier { + return 'shift'; } diff --git a/packages/client/src/features/command-palette/index.ts b/packages/client/src/features/command-palette/index.ts new file mode 100644 index 0000000..a1ce843 --- /dev/null +++ b/packages/client/src/features/command-palette/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './command-palette-module'; +export * from './command-palette-tool'; +export * from './server-command-palette-provider'; diff --git a/packages/client/src/features/context-menu/index.ts b/packages/client/src/features/context-menu/index.ts new file mode 100644 index 0000000..5cf038b --- /dev/null +++ b/packages/client/src/features/context-menu/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './context-menu-module'; +export * from './delete-element-context-menu'; +export * from './glsp-context-menu-mouse-listener'; +export * from './server-context-menu-provider'; diff --git a/packages/client/src/features/copy-paste/index.ts b/packages/client/src/features/copy-paste/index.ts new file mode 100644 index 0000000..e533d25 --- /dev/null +++ b/packages/client/src/features/copy-paste/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './copy-paste-context-menu'; +export * from './copy-paste-handler'; +export * from './copy-paste-modules'; +export * from './copy-paste-standalone'; diff --git a/packages/client/src/features/decoration/index.ts b/packages/client/src/features/decoration/index.ts new file mode 100644 index 0000000..d994863 --- /dev/null +++ b/packages/client/src/features/decoration/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './decoration-module'; +export * from './decoration-placer'; diff --git a/packages/client/src/features/element-template/index.ts b/packages/client/src/features/element-template/index.ts new file mode 100644 index 0000000..841060f --- /dev/null +++ b/packages/client/src/features/element-template/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './add-template-element'; +export * from './element-template-module'; +export * from './mouse-tracking-element-position-listener'; +export * from './remove-template-element'; diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index a79a410..a137aa7 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, GModelElement, ISnapper, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; +import { Action, Disposable, GModelElement, Locateable, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; @@ -26,9 +26,12 @@ import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; +import { PositionSnapper } from '../change-bounds/position-snapper'; +import { PointPositionUpdater } from '../change-bounds/point-position-updater'; +import { useSnap } from '../change-bounds/snap'; export interface PositioningTool extends Tool { - readonly snapper?: ISnapper; + readonly positionSnapper: PositionSnapper; readonly movementRestrictor?: IMovementRestrictor; registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable; @@ -37,57 +40,61 @@ export interface PositioningTool extends Tool { @injectable() export class MouseTrackingElementPositionListener extends DragAwareMouseListener { - element?: GModelElement; currentPosition?: Point; + protected positionUpdater: PointPositionUpdater; + constructor( protected elementId: string, protected tool: PositioningTool, protected cursorPosition: 'top-left' | 'middle' = 'top-left' ) { super(); + this.positionUpdater = new PointPositionUpdater(this.tool.positionSnapper); } override mouseMove(target: GModelElement, event: MouseEvent): Action[] { super.mouseMove(target, event); const element = target.root.index.getById(this.elementId); - this.element = element; - if (!element) { + if (!element || !isMoveable(element)) { return []; } - - let newPosition = getAbsolutePosition(target, event); - if (this.cursorPosition === 'middle' && isBoundsAware(element)) { - newPosition = Point.subtract(newPosition, { x: element.bounds.width / 2, y: element.bounds.height / 2 }); + let targetPosition = this.getTargetPosition(target, event, element); + if (this.positionUpdater.isLastDragPositionUndefined()) { + this.positionUpdater.updateLastDragPosition(targetPosition); } - newPosition = this.snap(newPosition, element); - - const finished = false; - if (isMoveable(element)) { - newPosition = this.validateMove(this.currentPosition ?? newPosition, newPosition, element, finished); + const delta = this.positionUpdater.updatePosition(element, targetPosition, useSnap(event)); + if (!delta) { + return []; } - this.currentPosition = newPosition; + targetPosition = this.validateMove(this.currentPosition ?? targetPosition, targetPosition, element, false); const moveGhostElement = MoveAction.create( [ { elementId: element.id, - toPosition: newPosition + fromPosition: this.currentPosition, + toPosition: targetPosition } ], - { animate: false, finished } + { animate: false, finished: false } ); + this.currentPosition = targetPosition; this.tool.registerFeedback([moveGhostElement], this); return element.cssClasses?.includes(CSS_HIDDEN) ? [ModifyCSSFeedbackAction.create({ elements: [element.id], remove: [CSS_HIDDEN] })] : []; } - protected snap(position: Point, element: GModelElement, isSnap = true): Point { - if (isSnap && this.tool.snapper) { - return this.tool.snapper.snap(position, element); - } else { - return position; + protected getTargetPosition(target: GModelElement, event: MouseEvent, element: GModelElement & Locateable): Point { + let targetPosition = getAbsolutePosition(target, event); + if (this.cursorPosition === 'middle' && isBoundsAware(element)) { + targetPosition = Point.subtract(targetPosition, { x: element.bounds.width / 2, y: element.bounds.height / 2 }); } + return targetPosition; + } + + protected snap(position: Point, element: GModelElement, isSnap = true): Point { + return this.tool.positionSnapper.snapPosition(position, element, isSnap); } protected validateMove(startPosition: Point, toPosition: Point, element: GModelElement, isFinished: boolean): Point { diff --git a/packages/client/src/features/export/index.ts b/packages/client/src/features/export/index.ts new file mode 100644 index 0000000..378fddb --- /dev/null +++ b/packages/client/src/features/export/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './export-modules'; +export * from './export-svg-action-handler'; +export * from './glsp-svg-exporter'; diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts new file mode 100644 index 0000000..56e6157 --- /dev/null +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -0,0 +1,269 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + Bounds, + CommandExecutionContext, + CommandReturn, + GModelElement, + GModelRoot, + TYPES, + Viewport, + findParentByFeature, + isBoundsAware, + isViewport +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { partition } from 'lodash'; +import '../../../css/helper-lines.css'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { + bottom, + bottomCenter, + bottomLeft, + bottomRight, + center, + isAbove, + isBefore, + left, + middle, + middleLeft, + middleRight, + right, + sortBy, + top, + topCenter, + topLeft, + topRight +} from '../../utils/geometry-util'; +import { BoundsAwareModelElement, findTopLevelElementByFeature, forEachElement, getMatchingElements } from '../../utils/gmodel-util'; +import { getViewportBounds } from '../../utils/viewpoint-util'; +import { HelperLine, HelperLineType, SelectionBounds, isHelperLine, isSelectionBounds } from './model'; + +export type ViewportLineType = typeof HelperLineType.Center | typeof HelperLineType.Middle | string; + +export const ALL_ELEMENT_LINE_TYPES = Object.values(HelperLineType); +export const ALL_VIEWPORT_LINE_TYPES = [HelperLineType.Center, HelperLineType.Middle]; + +export interface DrawHelperLinesFeedbackAction extends Action { + kind: typeof DrawHelperLinesFeedbackAction.KIND; + elementIds: string[]; + elementLines?: HelperLineType[]; + viewportLines?: ViewportLineType[]; + alignmentEpsilon?: number; +} + +export namespace DrawHelperLinesFeedbackAction { + export const KIND = 'drawHelperLines'; + + export function create(options: Omit): DrawHelperLinesFeedbackAction { + return { + kind: KIND, + ...options + }; + } +} + +@injectable() +export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { + static readonly KIND = DrawHelperLinesFeedbackAction.KIND; + + constructor(@inject(TYPES.Action) public action: DrawHelperLinesFeedbackAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + removeHelperLines(context.root); + removeSelectionBounds(context.root); + const boundsAwareElements = getMatchingElements(context.root.index, this.isCompareElement); + const [referenceElements, elements] = partition(boundsAwareElements, element => this.action.elementIds.includes(element.id)); + if (referenceElements.length === 0) { + return context.root; + } + const referenceBounds = this.calcReferenceBounds(referenceElements); + const helperLines = this.calcHelperLines(elements, referenceBounds, context); + if (referenceElements.length > 1) { + context.root.add(new SelectionBounds(referenceBounds)); + } + helperLines.forEach(helperLine => context.root.add(helperLine)); + return context.root; + } + + protected isCompareElement(element: GModelElement): element is BoundsAwareModelElement { + return isBoundsAware(element) && findTopLevelElementByFeature(element, isBoundsAware, isViewport) === element; + } + + protected calcReferenceBounds(referenceElements: BoundsAwareModelElement[]): Bounds { + return referenceElements.map(element => element.bounds).reduce((combined, next) => Bounds.combine(combined, next), Bounds.EMPTY); + } + + protected calcHelperLines(elements: BoundsAwareModelElement[], bounds: Bounds, context: CommandExecutionContext): HelperLine[] { + const helperLines: HelperLine[] = []; + const viewport = findParentByFeature(context.root, isViewport); + if (viewport) { + helperLines.push(...this.calcHelperLinesForViewport(viewport, bounds)); + } + elements.flatMap(element => this.calcHelperLinesForElement(element, bounds)).forEach(line => helperLines.push(line)); + return helperLines; + } + + protected calcHelperLinesForViewport( + root: Viewport & GModelRoot, + bounds: Bounds, + lineTypes: HelperLineType[] = this.action.viewportLines ?? ALL_VIEWPORT_LINE_TYPES + ): HelperLine[] { + const helperLines: HelperLine[] = []; + const viewportBounds = getViewportBounds(root, root.canvasBounds); + if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, viewportBounds, bounds, 2)) { + helperLines.push(new HelperLine(topCenter(viewportBounds), bottomCenter(viewportBounds), HelperLineType.Center)); + } + if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, viewportBounds, bounds, 2)) { + helperLines.push(new HelperLine(middleLeft(viewportBounds), middleRight(viewportBounds), HelperLineType.Middle)); + } + return helperLines; + } + + protected calcHelperLinesForElement( + element: BoundsAwareModelElement, + bounds: Bounds, + lineTypes: HelperLineType[] = this.action.elementLines ?? ALL_ELEMENT_LINE_TYPES + ): HelperLine[] { + return this.calcHelperLinesForBounds(element.bounds, bounds, lineTypes); + } + + protected calcHelperLinesForBounds( + elementBounds: Bounds, + bounds: Bounds, + lineTypes: HelperLineType[] = this.action.elementLines ?? ALL_ELEMENT_LINE_TYPES + ): HelperLine[] { + const helperLines: HelperLine[] = []; + + if (lineTypes.includes(HelperLineType.Left) && this.isAligned(left, elementBounds, bounds)) { + const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(bottomLeft(below), topLeft(above), HelperLineType.Left)); + } + + if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, elementBounds, bounds)) { + const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(topCenter(above), bottomCenter(below), HelperLineType.Center)); + } + + if (lineTypes.includes(HelperLineType.Right) && this.isAligned(right, elementBounds, bounds)) { + const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(bottomRight(below), topRight(above), HelperLineType.Right)); + } + + if (lineTypes.includes(HelperLineType.Bottom) && this.isAligned(bottom, elementBounds, bounds)) { + const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(bottomLeft(before), bottomRight(after), HelperLineType.Bottom)); + } + + if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, elementBounds, bounds)) { + const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(middleLeft(before), middleRight(after), HelperLineType.Middle)); + } + + if (lineTypes.includes(HelperLineType.Top) && this.isAligned(top, elementBounds, bounds)) { + const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(topLeft(before), topRight(after), HelperLineType.Top)); + } + + if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(left(elementBounds), right(bounds), 2)) { + if (isAbove(bounds, elementBounds)) { + helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.RightLeft)); + } else { + helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.RightLeft)); + } + } + + if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(right(elementBounds), left(bounds), 2)) { + if (isAbove(bounds, elementBounds)) { + helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.LeftRight)); + } else { + helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.LeftRight)); + } + } + + if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(top(elementBounds), bottom(bounds), 2)) { + if (isBefore(bounds, elementBounds)) { + helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.BottomTop)); + } else { + helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.BottomTop)); + } + } + + if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(bottom(elementBounds), top(bounds), 2)) { + if (isBefore(bounds, elementBounds)) { + helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.TopBottom)); + } else { + helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.TopBottom)); + } + } + + return helperLines; + } + + protected isAligned( + coordinate: (elem: Bounds) => number, + leftBounds: Bounds, + rightBounds: Bounds, + epsilon = this.action.alignmentEpsilon ?? 1 + ): boolean { + return this.isMatch(coordinate(leftBounds), coordinate(rightBounds), epsilon); + } + + protected isMatch(leftCoordinate: number, rightCoordinate: number, epsilon = this.action.alignmentEpsilon ?? 1): boolean { + return Math.abs(leftCoordinate - rightCoordinate) < epsilon; + } +} + +export interface RemoveHelperLinesFeedbackAction extends Action { + kind: typeof RemoveHelperLinesFeedbackAction.KIND; +} + +export namespace RemoveHelperLinesFeedbackAction { + export const KIND = 'removeHelperLines'; + + export function create(options: Omit = {}): RemoveHelperLinesFeedbackAction { + return { + kind: KIND, + ...options + }; + } +} + +@injectable() +export class RemoveHelperLinesFeedbackCommand extends FeedbackCommand { + static readonly KIND = RemoveHelperLinesFeedbackAction.KIND; + + constructor(@inject(TYPES.Action) public action: RemoveHelperLinesFeedbackAction) { + super(); + } + override execute(context: CommandExecutionContext): CommandReturn { + removeHelperLines(context.root); + removeSelectionBounds(context.root); + return context.root; + } +} + +export function removeHelperLines(root: GModelRoot): void { + forEachElement(root.index, isHelperLine, line => root.remove(line)); +} + +export function removeSelectionBounds(root: GModelRoot): void { + forEachElement(root.index, isSelectionBounds, line => root.remove(line)); +} diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts new file mode 100644 index 0000000..97d371e --- /dev/null +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + DisposableCollection, + GModelElement, + GModelRoot, + IActionHandler, + ISnapper, + MoveAction, + SetBoundsAction, + TYPES +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional, postConstruct } from 'inversify'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { ISelectionListener, SelectionService } from '../../base/selection-service'; +import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; +import { GridSnapper } from '../change-bounds/snap'; +import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; +import { DrawHelperLinesFeedbackAction, RemoveHelperLinesFeedbackAction, ViewportLineType } from './helper-line-feedback'; +import { IHelperLineManager } from './helper-line-manager'; +import { Direction, HelperLineType } from './model'; + +@injectable() +export class HelperLineManager implements IActionHandler, ISelectionListener, IHelperLineManager { + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(SelectionService) protected selectionService: SelectionService; + @optional() @inject(TYPES.ISnapper) protected snapper?: ISnapper; + + protected snapSize = { x: 20, y: 20 }; + protected feedback: DisposableCollection = new DisposableCollection(); + + protected elementLines?: HelperLineType[]; + protected viewportLines?: ViewportLineType[]; + protected alignmentEpsilon?: number; + + @postConstruct() + protected init(): void { + this.selectionService.onSelectionChanged(change => + this.selectionChanged(change.root, change.selectedElements, change.deselectedElements) + ); + if (this.snapper instanceof GridSnapper) { + this.snapSize = { x: this.snapper.grid.x * 2, y: this.snapper.grid.y * 2 }; + } + } + + handle(action: Action): void { + if (MoveInitializedEventAction.is(action)) { + this.handleMoveInitializedAction(action); + } else if (MoveAction.is(action)) { + this.handleMoveAction(action); + } else if (MoveFinishedEventAction.is(action)) { + this.handleMoveFinishedAction(action); + } else if (SetBoundsAction.is(action) || SetBoundsFeedbackAction.is(action)) { + this.handleSetBoundsAction(action); + } + } + + protected handleMoveInitializedAction(_action: MoveInitializedEventAction): void { + this.feedback.dispose(); + const feedback = this.createHelperLineFeedback(this.selectionService.getSelectedElementIDs()); + this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + } + + protected handleMoveFinishedAction(_action: MoveFinishedEventAction): void { + this.feedback.dispose(); + } + + protected handleMoveAction(action: MoveAction): void { + if (!action.finished) { + const elementIds = action.moves.map(move => move.elementId); + const feedback = this.createHelperLineFeedback(elementIds); + this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + } else { + this.feedback.dispose(); + } + } + + protected createHelperLineFeedback(elementIds: string[]): DrawHelperLinesFeedbackAction { + return DrawHelperLinesFeedbackAction.create({ + elementIds, + elementLines: this.elementLines, + viewportLines: this.viewportLines, + alignmentEpsilon: this.alignmentEpsilon + }); + } + + protected handleSetBoundsAction(action: SetBoundsAction | SetBoundsFeedbackAction): void { + const elementIds = action.bounds.map(bound => bound.elementId); + const feedback = this.createHelperLineFeedback(elementIds); + this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + } + + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[] | undefined): void { + this.feedback.dispose(); + } + + getHelperLineSnapping(target: GModelElement, isSnap: boolean, direction: Direction): number { + return direction === Direction.Left || direction === Direction.Right ? this.snapSize.x : this.snapSize.y; + } +} diff --git a/packages/client/src/features/helper-lines/helper-line-manager.ts b/packages/client/src/features/helper-lines/helper-line-manager.ts new file mode 100644 index 0000000..be26092 --- /dev/null +++ b/packages/client/src/features/helper-lines/helper-line-manager.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/sprotty'; +import { Direction } from './model'; + +export interface IHelperLineManager { + getHelperLineSnapping(target: GModelElement, isSnap: boolean, direction: Direction): number; +} diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts new file mode 100644 index 0000000..03436c9 --- /dev/null +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + FeatureModule, + MoveAction, + SetBoundsAction, + TYPES, + bindAsService, + configureActionHandler, + configureCommand, + configureModelElement +} from '@eclipse-glsp/sprotty'; +import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; +import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; +import { DrawHelperLinesFeedbackCommand, RemoveHelperLinesFeedbackCommand } from './helper-line-feedback'; +import { HelperLineManager } from './helper-line-manager-default'; +import { HELPER_LINE, HelperLine, SELECTION_BOUNDS, SelectionBounds } from './model'; +import { HelperLineView, SelectionBoundsView } from './view'; + +export const helperLineModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureModelElement(context, HELPER_LINE, HelperLine, HelperLineView); + configureModelElement(context, SELECTION_BOUNDS, SelectionBounds, SelectionBoundsView); + configureCommand(context, DrawHelperLinesFeedbackCommand); + configureCommand(context, RemoveHelperLinesFeedbackCommand); + + bindAsService(bind, TYPES.IHelperLineManager, HelperLineManager); + configureActionHandler(context, SetBoundsAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, SetBoundsFeedbackAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveInitializedEventAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveFinishedEventAction.KIND, TYPES.IHelperLineManager); +}); diff --git a/packages/client/src/features/helper-lines/index.ts b/packages/client/src/features/helper-lines/index.ts new file mode 100644 index 0000000..7e836a1 --- /dev/null +++ b/packages/client/src/features/helper-lines/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './helper-line-feedback'; +export * from './helper-line-manager-default'; +export * from './helper-line-manager'; +export * from './helper-line-module'; +export * from './model'; +export * from './view'; diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts new file mode 100644 index 0000000..21c13d6 --- /dev/null +++ b/packages/client/src/features/helper-lines/model.ts @@ -0,0 +1,140 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Args, Bounds, GChildElement, GModelElement, GShapeElement, Point } from '@eclipse-glsp/sprotty'; +import { v4 as uuid } from 'uuid'; +import { ArgsAware } from '../../base/args-feature'; +import { ResizeHandleLocation } from '../change-bounds/model'; + +export const HelperLineType = { + Left: 'left', + Right: 'right', + Center: 'center', + Top: 'top', + Bottom: 'bottom', + Middle: 'middle', + LeftRight: 'left-right', + RightLeft: 'right-left', + BottomTop: 'bottom-top', + TopBottom: 'top-bottom' +} as const; + +// allow any string to be set as helper line type to support customization +export type HelperLineType = (typeof HelperLineType)[keyof typeof HelperLineType] | string; + +export const HELPER_LINE = 'helper-line'; + +export class HelperLine extends GChildElement implements ArgsAware { + args?: Args; + + constructor( + readonly startPoint = Point.ORIGIN, + readonly endPoint = Point.ORIGIN, + readonly lineType: HelperLineType = HelperLineType.Left + ) { + super(); + this.id = uuid(); + this.type = HELPER_LINE; + } + + get isLeft(): boolean { + return this.lineType === HelperLineType.Left || this.lineType === HelperLineType.LeftRight; + } + + get isRight(): boolean { + return this.lineType === HelperLineType.Right || this.lineType === HelperLineType.RightLeft; + } + + get isTop(): boolean { + return this.lineType === HelperLineType.Top || this.lineType === HelperLineType.TopBottom; + } + + get isBottom(): boolean { + return this.lineType === HelperLineType.Bottom || this.lineType === HelperLineType.BottomTop; + } + + get isMiddle(): boolean { + return this.lineType === HelperLineType.Middle; + } + + get isCenter(): boolean { + return this.lineType === HelperLineType.Center; + } +} + +export function isHelperLine(element: GModelElement): element is HelperLine { + return element.type === HELPER_LINE; +} + +export const SELECTION_BOUNDS = 'selection-bounds'; + +export class SelectionBounds extends GShapeElement implements ArgsAware { + args?: Args; + + constructor(bounds?: Bounds) { + super(); + this.id = uuid(); + this.type = SELECTION_BOUNDS; + if (bounds) { + this.bounds = bounds; + } + } +} + +export function isSelectionBounds(element: GModelElement): element is SelectionBounds { + return element.type === SELECTION_BOUNDS; +} + +export const Direction = { + Left: 'left', + Right: 'right', + Up: 'up', + Down: 'down' +} as const; + +// allow any string to be set as helper line type to support customization +export type Direction = (typeof Direction)[keyof typeof Direction]; + +export function getDirectionOf(point: Point): Direction[] { + const directions: Direction[] = []; + if (point.x < 0) { + directions.push(Direction.Left); + } else if (point.x > 0) { + directions.push(Direction.Right); + } + if (point.y < 0) { + directions.push(Direction.Up); + } else if (point.y > 0) { + directions.push(Direction.Down); + } + return directions; +} + +export function getDirectionFrom(resize?: ResizeHandleLocation): Direction[] { + if (resize === ResizeHandleLocation.TopLeft) { + return [Direction.Up, Direction.Left]; + } + if (resize === ResizeHandleLocation.TopRight) { + return [Direction.Up, Direction.Right]; + } + if (resize === ResizeHandleLocation.BottomLeft) { + return [Direction.Down, Direction.Left]; + } + if (resize === ResizeHandleLocation.BottomRight) { + return [Direction.Down, Direction.Right]; + } + return []; +} diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx new file mode 100644 index 0000000..b47b6d6 --- /dev/null +++ b/packages/client/src/features/helper-lines/view.tsx @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { IViewArgs, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { HelperLine, SelectionBounds } from './model'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: svg }; + +@injectable() +export class HelperLineView extends ShapeView { + override render(model: HelperLine, _context: RenderingContext): VNode | undefined { + return ( + + + + ); + } +} + +@injectable() +export class SelectionBoundsView extends ShapeView { + override render(model: SelectionBounds, context: RenderingContext, args?: IViewArgs | undefined): VNode | undefined { + if (!this.isVisible(model, context)) { + return undefined; + } + return ( + + + + ); + } +} diff --git a/packages/client/src/features/hints/index.ts b/packages/client/src/features/hints/index.ts new file mode 100644 index 0000000..2effe7c --- /dev/null +++ b/packages/client/src/features/hints/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './model'; +export * from './type-hint-provider'; +export * from './type-hints-module'; diff --git a/packages/client/src/features/hover/index.ts b/packages/client/src/features/hover/index.ts new file mode 100644 index 0000000..948df0b --- /dev/null +++ b/packages/client/src/features/hover/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './hover-module'; +export * from './hover'; diff --git a/packages/client/src/features/index.ts b/packages/client/src/features/index.ts new file mode 100644 index 0000000..e58d15d --- /dev/null +++ b/packages/client/src/features/index.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './accessibility'; +export * from './bounds'; +export * from './change-bounds'; +export * from './command-palette'; +export * from './context-menu'; +export * from './copy-paste'; +export * from './decoration'; +export * from './element-template'; +export * from './export'; +export * from './helper-lines'; +export * from './hints'; +export * from './hover'; +export * from './label-edit'; +export * from './layout'; +export * from './navigation'; +export * from './reconnect'; +export * from './routing'; +export * from './save'; +export * from './select'; +export * from './source-model-watcher'; +export * from './status'; +export * from './svg-metadata'; +export * from './tool-palette'; +export * from './tools'; +export * from './undo-redo'; +export * from './validation'; +export * from './viewport'; diff --git a/packages/client/src/features/label-edit/index.ts b/packages/client/src/features/label-edit/index.ts new file mode 100644 index 0000000..340592d --- /dev/null +++ b/packages/client/src/features/label-edit/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './edit-label-tool'; +export * from './edit-label-validator'; +export * from './label-edit-module'; diff --git a/packages/client/src/features/layout/index.ts b/packages/client/src/features/layout/index.ts new file mode 100644 index 0000000..03966eb --- /dev/null +++ b/packages/client/src/features/layout/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './layout-elements-action'; +export * from './layout-module'; diff --git a/packages/client/src/features/navigation/index.ts b/packages/client/src/features/navigation/index.ts new file mode 100644 index 0000000..93c96cc --- /dev/null +++ b/packages/client/src/features/navigation/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './navigation-action-handler'; +export * from './navigation-module'; +export * from './navigation-target-resolver'; diff --git a/packages/client/src/features/reconnect/index.ts b/packages/client/src/features/reconnect/index.ts new file mode 100644 index 0000000..0886faa --- /dev/null +++ b/packages/client/src/features/reconnect/index.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './model'; diff --git a/packages/client/src/features/routing/index.ts b/packages/client/src/features/routing/index.ts new file mode 100644 index 0000000..452038f --- /dev/null +++ b/packages/client/src/features/routing/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './glsp-manhattan-edge-router'; +export * from './routing-module'; diff --git a/packages/client/src/features/save/index.ts b/packages/client/src/features/save/index.ts new file mode 100644 index 0000000..d9c79be --- /dev/null +++ b/packages/client/src/features/save/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './save-keylistener'; +export * from './save-module'; diff --git a/packages/client/src/features/select/index.ts b/packages/client/src/features/select/index.ts new file mode 100644 index 0000000..0030448 --- /dev/null +++ b/packages/client/src/features/select/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './select-feedback-command'; +export * from './select-module'; +export * from './select-mouse-listener'; diff --git a/packages/client/src/features/source-model-watcher/index.ts b/packages/client/src/features/source-model-watcher/index.ts new file mode 100644 index 0000000..caa3abd --- /dev/null +++ b/packages/client/src/features/source-model-watcher/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './source-model-changed-action-handler'; +export * from './source-model-watcher-module'; diff --git a/packages/client/src/features/source-model-watcher/source-model-wacher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts similarity index 100% rename from packages/client/src/features/source-model-watcher/source-model-wacher-module.ts rename to packages/client/src/features/source-model-watcher/source-model-watcher-module.ts diff --git a/packages/client/src/features/status/index.ts b/packages/client/src/features/status/index.ts new file mode 100644 index 0000000..51e2efe --- /dev/null +++ b/packages/client/src/features/status/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './status-module'; +export * from './status-overlay'; diff --git a/packages/client/src/features/svg-metadata/index.ts b/packages/client/src/features/svg-metadata/index.ts new file mode 100644 index 0000000..db153a7 --- /dev/null +++ b/packages/client/src/features/svg-metadata/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './metadata-placer'; +export * from './svg-metadata-module'; diff --git a/packages/client/src/features/tool-palette/index.ts b/packages/client/src/features/tool-palette/index.ts new file mode 100644 index 0000000..008dc76 --- /dev/null +++ b/packages/client/src/features/tool-palette/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './tool-palette-module'; +export * from './tool-palette'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index cb711d4..f6984c3 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -22,24 +22,24 @@ import { GChildElement, GModelElement, GModelRoot, - MouseListener, MoveAction, Point, TYPES, findParentByFeature, hasStringProp, isMoveable, - isSelectable, - isViewport + isSelectable } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { VNode } from 'snabbdom'; +import { DebouncedFunc, debounce } from 'lodash'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { forEachElement } from '../../../utils/gmodel-util'; import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; +import { PointPositionUpdater } from '../../change-bounds/point-position-updater'; import { ChangeBoundsTool } from './change-bounds-tool'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { @@ -113,6 +113,38 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { } } +export interface MoveInitializedEventAction extends Action { + kind: typeof MoveInitializedEventAction.KIND; +} + +export namespace MoveInitializedEventAction { + export const KIND = 'move-initialized-event'; + + export function is(object: any): object is MoveInitializedEventAction { + return Action.hasKind(object, KIND); + } + + export function create(): MoveInitializedEventAction { + return { kind: KIND }; + } +} + +export interface MoveFinishedEventAction extends Action { + kind: typeof MoveFinishedEventAction.KIND; +} + +export namespace MoveFinishedEventAction { + export const KIND = 'move-finished-event'; + + export function is(object: any): object is MoveFinishedEventAction { + return Action.hasKind(object, KIND); + } + + export function create(): MoveFinishedEventAction { + return { kind: KIND }; + } +} + /** * This mouse listener provides visual feedback for moving by sending client-side * `MoveAction`s while elements are selected and dragged. This will also update @@ -120,45 +152,57 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { * the visual feedback but also the basis for sending the change to the server * (see also `tools/MoveTool`). */ -export class FeedbackMoveMouseListener extends MouseListener implements Disposable { - protected hasDragged = false; +export class FeedbackMoveMouseListener extends DragAwareMouseListener implements Disposable { protected rootElement?: GModelRoot; - protected startDragPosition?: Point; + protected positionUpdater; protected elementId2startPos = new Map(); + protected pendingMoveInitialized?: DebouncedFunc<() => void>; constructor(protected tool: ChangeBoundsTool) { super(); + this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { if (event.button === 0 && !(target instanceof SResizeHandle)) { const moveable = findParentByFeature(target, isMoveable); - if (moveable !== undefined) { - this.startDragPosition = { x: event.pageX, y: event.pageY }; + if (moveable !== undefined && !(target instanceof SResizeHandle)) { + this.positionUpdater.updateLastDragPosition(event); + this.scheduleMoveInitialized(); } else { - this.startDragPosition = undefined; + this.positionUpdater.resetPosition(); } - this.hasDragged = false; + this._isMouseDrag = false; } return []; } + protected scheduleMoveInitialized(): void { + this.pendingMoveInitialized?.cancel(); + this.pendingMoveInitialized = debounce(() => { + this.tool.registerFeedback([MoveInitializedEventAction.create()], this); + this.pendingMoveInitialized = undefined; + }, 750); + this.pendingMoveInitialized(); + } + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { this.mouseUp(target, event); - } else if (this.startDragPosition) { + } else if (!this.positionUpdater.isLastDragPositionUndefined()) { + this.pendingMoveInitialized?.cancel(); if (this.elementId2startPos.size === 0) { this.collectStartPositions(target.root); } - this.hasDragged = true; + this._isMouseDrag = true; const moveAction = this.getElementMoves(target, event, false); if (moveAction) { result.push(moveAction); result.push(cursorFeedbackAction(CursorCSS.MOVE)); + this.tool.registerFeedback(result, this); } } - this.tool.registerFeedback(result, this); return []; } @@ -186,18 +230,11 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab } protected getElementMoves(target: GModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { - if (!this.startDragPosition) { + const delta = this.positionUpdater.updatePosition(target, event); + if (!delta) { return undefined; } - - const viewport = findParentByFeature(target, isViewport); - const zoom = viewport ? viewport.zoom : 1; - const delta = { - x: (event.pageX - this.startDragPosition.x) / zoom, - y: (event.pageY - this.startDragPosition.y) / zoom - }; - - const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, !event.altKey, finished); + const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, finished); if (elementMoves.length > 0) { return MoveAction.create(elementMoves, { animate: false, finished }); } else { @@ -205,35 +242,15 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab } } - protected getElementMovesForDelta( - target: GModelElement, - delta: { x: number; y: number }, - isSnap: boolean, - finished: boolean - ): ElementMove[] { + protected getElementMovesForDelta(target: GModelElement, delta: Point, finished: boolean): ElementMove[] { const elementMoves: ElementMove[] = []; this.elementId2startPos.forEach((startPosition, elementId) => { const element = target.root.index.getById(elementId); if (element) { - let toPosition = this.snap( - { - x: startPosition.x + delta.x, - y: startPosition.y + delta.y - }, - element, - isSnap - ); - if (isMoveable(element)) { - toPosition = this.validateMove(startPosition, toPosition, element, finished); - elementMoves.push({ - elementId: element.id, - fromPosition: { - x: element.position.x, - y: element.position.y - }, - toPosition - }); + const targetPosition = Point.add(element.position, delta); + const toPosition = this.validateMove(startPosition, targetPosition, element, finished); + elementMoves.push({ elementId: element.id, fromPosition: element.position, toPosition }); } } }); @@ -247,7 +264,6 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab let action; if (!valid) { action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); - if (isFinished) { newPosition = startPosition; } @@ -259,24 +275,24 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return newPosition; } - protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { - if (isSnap && this.tool.snapper) { - return this.tool.snapper.snap(position, element); - } else { - return position; - } - } - override mouseEnter(target: GModelElement, event: MouseEvent): Action[] { - if (target instanceof GModelRoot && event.buttons === 0 && !this.startDragPosition) { + if (target instanceof GModelRoot && event.buttons === 0 && this.positionUpdater.isLastDragPositionUndefined()) { this.mouseUp(target, event); } return []; } - override mouseUp(target: GModelElement, event: MouseEvent): Action[] { + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { + this.reset(true); + return []; + } + + override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; - if (this.startDragPosition) { + if (this.positionUpdater.isLastDragPositionUndefined()) { + this.reset(true); + return result; + } else { const moveAction = this.getElementMoves(target, event, true); if (moveAction) { result.push(moveAction); @@ -292,22 +308,36 @@ export class FeedbackMoveMouseListener extends MouseListener implements Disposab return result; } + protected resetMoveFeedback(): ElementMove[] { + const elementMoves: ElementMove[] = []; + this.elementId2startPos.forEach((startPosition, elementId) => { + const element = this.rootElement!.index.getById(elementId); + if (element) { + if (isMoveable(element)) { + elementMoves.push({ elementId: element.id, fromPosition: element.position, toPosition: startPosition }); + } + } + }); + return elementMoves; + } + protected reset(resetFeedback = false): void { + this.pendingMoveInitialized?.cancel(); if (this.rootElement && resetFeedback) { - const elementMoves: ElementMove[] = this.getElementMovesForDelta(this.rootElement, { x: 0, y: 0 }, true, true); - const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); - this.tool.deregisterFeedback(this, [moveAction]); + const elementMoves: ElementMove[] = this.resetMoveFeedback(); + if (elementMoves.length > 0) { + const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); + this.tool.deregisterFeedback(this, [moveAction]); + } + } else if (resetFeedback) { + this.tool.deregisterFeedback(this, [MoveFinishedEventAction.create()]); } - this.hasDragged = false; - this.startDragPosition = undefined; + this.positionUpdater.resetPosition(); + this._isMouseDrag = false; this.rootElement = undefined; this.elementId2startPos.clear(); } - override decorate(vnode: VNode, _element: GModelElement): VNode { - return vnode; - } - dispose(): void { this.reset(true); } diff --git a/packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts similarity index 100% rename from packages/client/src/features/tools/change-bounds/change-boounds-tool-module.ts rename to packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 672c767..6be5e57 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -32,7 +32,6 @@ import { GModelElement, GModelRoot, GParentElement, - ISnapper, MouseListener, Operation, Point, @@ -43,7 +42,7 @@ import { import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; -import { PointPositionUpdater } from '../../../features/change-bounds/snap'; +import { PointPositionUpdater } from '../../../features/change-bounds/point-position-updater'; import { calcElementAndRoutingPoints, forEachElement, @@ -58,6 +57,8 @@ import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; +import { PositionSnapper } from '../../change-bounds/position-snapper'; +import { getDirectionFrom } from '../../helper-lines/model'; import { BaseEditTool } from '../base-tools'; import { FeedbackMoveMouseListener, @@ -84,8 +85,8 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; - @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; + @inject(PositionSnapper) readonly positionSnapper: PositionSnapper; get id(): string { return ChangeBoundsTool.ID; @@ -135,7 +136,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel constructor(protected tool: ChangeBoundsTool) { super(); - this.pointPositionUpdater = new PointPositionUpdater(tool.snapper); + this.pointPositionUpdater = new PointPositionUpdater(tool.positionSnapper); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { @@ -168,7 +169,11 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel cursorFeedbackAction(this.activeResizeHandle.isNwSeResize() ? CursorCSS.RESIZE_NWSE : CursorCSS.RESIZE_NESW), applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE) ]; - const positionUpdate = this.pointPositionUpdater.updatePosition(target, { x: event.pageX, y: event.pageY }, !event.altKey); + const positionUpdate = this.pointPositionUpdater.updatePosition( + target, + event, + getDirectionFrom(this.activeResizeHandle.location) + ); if (positionUpdate) { const resizeActions = this.handleResizeOnClient(positionUpdate); actions.push(...resizeActions); @@ -242,7 +247,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel const newRoutingPoints: ElementAndRoutingPoints[] = []; const routerRegistry = this.tool.edgeRouterRegistry; if (routerRegistry) { - // If client routing is enabled -> delegate routingpoints of connected edges to server + // If client routing is enabled -> delegate routing points of connected edges to server forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { if (element instanceof GConnectableElement) { element.incomingEdges @@ -308,7 +313,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } protected initPosition(event: MouseEvent): void { - this.pointPositionUpdater.updateLastDragPosition({ x: event.pageX, y: event.pageY }); + this.pointPositionUpdater.updateLastDragPosition(event); if (this.activeResizeHandle) { const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); this.initialBounds = { @@ -459,10 +464,6 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return result; } - protected snap(position: Point, element: GModelElement, isSnap: boolean): Point { - return isSnap && this.tool.snapper ? this.tool.snapper.snap(position, element) : { x: position.x, y: position.y }; - } - protected isValidBoundChange(element: GModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { return this.isValidSize(element, newSize) && this.isValidMove(element, newPosition); } diff --git a/packages/client/src/features/tools/change-bounds/index.ts b/packages/client/src/features/tools/change-bounds/index.ts new file mode 100644 index 0000000..8b50ec7 --- /dev/null +++ b/packages/client/src/features/tools/change-bounds/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './change-bounds-tool-feedback'; +export * from './change-bounds-tool-module'; +export * from './change-bounds-tool'; +export * from './view'; diff --git a/packages/client/src/features/tools/deletion/index.ts b/packages/client/src/features/tools/deletion/index.ts new file mode 100644 index 0000000..f33c825 --- /dev/null +++ b/packages/client/src/features/tools/deletion/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './delete-tool'; +export * from './deletion-tool-module'; diff --git a/packages/client/src/features/tools/edge-creation/index.ts b/packages/client/src/features/tools/edge-creation/index.ts new file mode 100644 index 0000000..c16e26d --- /dev/null +++ b/packages/client/src/features/tools/edge-creation/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './dangling-edge-feedback'; +export * from './edege-creation-module'; +export * from './edge-creation-tool-feedback'; +export * from './edge-creation-tool'; +export * from './view'; diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index b7fe227..d32850d 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -13,8 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { VNode } from 'snabbdom'; import { Action, AnchorComputerRegistry, @@ -24,14 +22,13 @@ import { Disposable, EdgeRouterRegistry, ElementMove, - ISnapper, + GConnectableElement, + GModelElement, + GRoutingHandle, MouseListener, MoveAction, Point, PolylineEdgeRouter, - GConnectableElement, - GModelElement, - GRoutingHandle, SwitchEditModeAction, SwitchEditModeCommand, TYPES, @@ -42,14 +39,17 @@ import { isConnectable, isSelected } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { forEachElement, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; -import { PointPositionUpdater } from '../../change-bounds/snap'; +import { PositionSnapper } from '../../change-bounds/position-snapper'; +import { useSnap } from '../../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; import { FeedbackEdgeEnd, feedbackEdgeEndId, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from '../edge-creation/edge-creation-tool-feedback'; +import { PointPositionUpdater } from '../../change-bounds/point-position-updater'; /** * RECONNECT HANDLES FEEDBACK @@ -198,7 +198,10 @@ export class FeedbackEdgeTargetMovingMouseListener extends FeedbackEdgeEndMoving } export class FeedbackEdgeSourceMovingMouseListener extends MouseListener implements Disposable { - constructor(protected anchorRegistry: AnchorComputerRegistry, protected feedbackDispatcher: IFeedbackActionDispatcher) { + constructor( + protected anchorRegistry: AnchorComputerRegistry, + protected feedbackDispatcher: IFeedbackActionDispatcher + ) { super(); } @@ -254,9 +257,12 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { protected pointPositionUpdater: PointPositionUpdater; - constructor(protected edgeRouterRegistry?: EdgeRouterRegistry, protected snapper?: ISnapper) { + constructor( + protected positionSnapper: PositionSnapper, + protected edgeRouterRegistry?: EdgeRouterRegistry + ) { super(); - this.pointPositionUpdater = new PointPositionUpdater(snapper); + this.pointPositionUpdater = new PointPositionUpdater(positionSnapper); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { @@ -265,7 +271,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { const routingHandle = findParentByFeature(target, isRoutingHandle); if (routingHandle !== undefined) { result.push(SwitchRoutingModeAction.create({ elementsToActivate: [target.id] })); - this.pointPositionUpdater.updateLastDragPosition({ x: event.pageX, y: event.pageY }); + this.pointPositionUpdater.updateLastDragPosition(event); } else { this.pointPositionUpdater.resetPosition(); } @@ -278,9 +284,9 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { if (event.buttons === 0) { return this.mouseUp(target, event); } - const positionUpdate = this.pointPositionUpdater.updatePosition(target, { x: event.pageX, y: event.pageY }, !event.altKey); + const positionUpdate = this.pointPositionUpdater.updatePosition(target, event); if (positionUpdate) { - const moveActions = this.handleMoveOnClient(target, positionUpdate, !event.altKey); + const moveActions = this.handleMoveOnClient(target, positionUpdate, useSnap(event)); result.push(...moveActions); } return result; @@ -322,10 +328,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } protected getSnappedHandlePosition(element: GRoutingHandle, point: Point, isSnap: boolean): Point { - if (this.snapper && isSnap) { - return this.snapper.snap(point, element); - } - return point; + return this.positionSnapper?.snapPosition(point, element, isSnap); } protected getHandlePosition(handle: GRoutingHandle): Point | undefined { @@ -345,10 +348,6 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { this.pointPositionUpdater.resetPosition(); return []; } - - override decorate(vnode: VNode, _element: GModelElement): VNode { - return vnode; - } } /** diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index caf9877..018122d 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, AnchorComputerRegistry, @@ -21,22 +20,22 @@ import { Connectable, Disposable, EdgeRouterRegistry, - ISnapper, - ReconnectEdgeOperation, GModelElement, GModelRoot, GRoutableElement, GRoutingHandle, - TYPES, + ReconnectEdgeOperation, canEditRouting, findParentByFeature, isConnectable, isSelected } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; +import { PositionSnapper } from '../../change-bounds/position-snapper'; import { GReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; import { BaseEditTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; @@ -57,7 +56,7 @@ export class EdgeEditTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; - @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; + @inject(PositionSnapper) readonly positionSnapper: PositionSnapper; protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; @@ -74,7 +73,7 @@ export class EdgeEditTool extends BaseEditTool { // install feedback move mouse listener for client-side move updates this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); - this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.edgeRouterRegistry, this.snapper); + this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.positionSnapper, this.edgeRouterRegistry); this.toDisposeOnDisable.push( Disposable.create(() => this.edgeEditListener.reset()), diff --git a/packages/client/src/features/tools/edge-edit/index.ts b/packages/client/src/features/tools/edge-edit/index.ts new file mode 100644 index 0000000..a1bd73e --- /dev/null +++ b/packages/client/src/features/tools/edge-edit/index.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './edge-edit-module'; +export * from './edge-edit-tool-feedback'; +export * from './edge-edit-tool'; diff --git a/packages/client/src/features/tools/index.ts b/packages/client/src/features/tools/index.ts new file mode 100644 index 0000000..9b34389 --- /dev/null +++ b/packages/client/src/features/tools/index.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './base-tools'; +export * from './change-bounds'; +export * from './deletion'; +export * from './edge-creation'; +export * from './edge-edit'; +export * from './marquee-selection'; +export * from './node-creation'; +export * from './tool-focus-loss-module'; diff --git a/packages/client/src/features/tools/marquee-selection/index.ts b/packages/client/src/features/tools/marquee-selection/index.ts new file mode 100644 index 0000000..6eab570 --- /dev/null +++ b/packages/client/src/features/tools/marquee-selection/index.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './marquee-behavior'; +export * from './marquee-mouse-tool'; +export * from './marquee-selection-module'; +export * from './marquee-tool-feedback'; +export * from './marquee-tool'; +export * from './model'; +export * from './view'; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts index 06b2fa1..d52af1f 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-tool.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { Action, KeyListener, GModelElement } from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, KeyListener } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { SelectionService } from '../../../base/selection-service'; import { EnableToolsAction } from '../../../base/tool-manager/tool'; import { BaseEditTool } from '../base-tools'; import { MarqueeMouseTool } from './marquee-mouse-tool'; @@ -23,21 +24,30 @@ import { MarqueeMouseTool } from './marquee-mouse-tool'; export class MarqueeTool extends BaseEditTool { static ID = 'glsp.marquee-tool'; - protected marqueeKeyListener: MarqueeKeyListener = new MarqueeKeyListener(); + @inject(SelectionService) protected selectionService: SelectionService; + + protected marqueeKeyListener: MarqueeKeyListener; get id(): string { return MarqueeTool.ID; } enable(): void { + if (!this.marqueeKeyListener) { + this.marqueeKeyListener = new MarqueeKeyListener(this.selectionService); + } this.toDisposeOnDisable.push(this.keyTool.registerListener(this.marqueeKeyListener)); } } @injectable() export class MarqueeKeyListener extends KeyListener { + constructor(protected selectionService: SelectionService) { + super(); + } + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { - if (event.shiftKey) { + if (event.shiftKey && !this.selectionService.hasSelectedElements()) { return [EnableToolsAction.create([MarqueeMouseTool.ID])]; } return []; diff --git a/packages/client/src/features/tools/node-creation/index.ts b/packages/client/src/features/tools/node-creation/index.ts new file mode 100644 index 0000000..4cf07d2 --- /dev/null +++ b/packages/client/src/features/tools/node-creation/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './node-creation-module'; +export * from './node-creation-tool'; diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index edefe88..bd8a8ed 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -18,7 +18,6 @@ import { CreateNodeOperation, GModelElement, GNode, - ISnapper, Point, TYPES, TriggerNodeCreationAction, @@ -32,9 +31,9 @@ import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from ' import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { getAbsolutePosition } from '../../../utils/viewpoint-util'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; +import { PositionSnapper } from '../../change-bounds/position-snapper'; import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; -import { RemoveTemplateElementsAction } from '../../element-template/remove-template-element'; import { Containable, isContainable } from '../../hints/model'; import { BaseCreationTool } from '../base-tools'; @@ -44,8 +43,8 @@ export class NodeCreationTool extends BaseCreationTool= 0 ? bounds.width * 0.5 : 0); +} + +export function right(bounds: Bounds): number { + return bounds.x + bounds.width; +} + +export function top(bounds: Bounds): number { + return bounds.y; +} + +export function middle(bounds: Bounds): number { + return bounds.y + (bounds.height >= 0 ? bounds.height * 0.5 : 0); +} + +export function bottom(bounds: Bounds): number { + return bounds.y + bounds.height; +} + +export function topLeft(bounds: Bounds): Point { + return { x: left(bounds), y: top(bounds) }; +} + +export function topCenter(bounds: Bounds): Point { + return { x: center(bounds), y: top(bounds) }; +} + +export function topRight(bounds: Bounds): Point { + return { x: right(bounds), y: top(bounds) }; +} + +export function middleLeft(bounds: Bounds): Point { + return { x: left(bounds), y: middle(bounds) }; +} + +export function middleCenter(bounds: Bounds): Point { + return { x: center(bounds), y: middle(bounds) }; +} + +export function middleRight(bounds: Bounds): Point { + return { x: right(bounds), y: middle(bounds) }; +} + +export function bottomLeft(bounds: Bounds): Point { + return { x: left(bounds), y: bottom(bounds) }; +} + +export function bottomCenter(bounds: Bounds): Point { + return { x: center(bounds), y: bottom(bounds) }; +} + +export function bottomRight(bounds: Bounds): Point { + return { x: right(bounds), y: bottom(bounds) }; +} + +export function sortBy(rankFunc: (elem: Bounds) => number, ...points: Bounds[]): Bounds[] { + return points.sort(compareFunction(rankFunc)); +} + +export function compareFunction(rankFunc: (elem: T) => number): (left: T, right: T) => number { + return (elemLeft, elemRight) => rankFunc(elemLeft) - rankFunc(elemRight); +} + +export function isAbove(leftBounds: Bounds, rightBounds: Bounds): boolean { + return top(leftBounds) <= top(rightBounds); +} + +export function isBelow(leftBounds: Bounds, rightBounds: Bounds): boolean { + return top(leftBounds) >= top(rightBounds); +} + +export function isBefore(leftBounds: Bounds, rightBounds: Bounds): boolean { + return left(leftBounds) < left(rightBounds); +} + +export function isAfter(leftBounds: Bounds, rightBounds: Bounds): boolean { + return left(leftBounds) >= left(rightBounds); +} diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index 1b0b1dc..e42e8de 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -15,25 +15,28 @@ ********************************************************************************/ import { BoundsAware, - distinctAdd, EdgeRouterRegistry, ElementAndBounds, ElementAndRoutingPoints, FluentIterable, - isBoundsAware, - isMoveable, - isSelectable, - isSelected, + GChildElement, + GModelElement, + GModelElementSchema, + GRoutableElement, + GRoutingHandle, ModelIndexImpl, Point, - remove, RoutedPoint, Selectable, - GModelElement, - GRoutableElement, - GRoutingHandle, TypeGuard, - GModelElementSchema + distinctAdd, + findParentByFeature, + isBoundsAware, + isMoveable, + isSelectable, + isSelected, + isViewport, + remove } from '@eclipse-glsp/sprotty'; /** @@ -319,3 +322,31 @@ export function getElementTypeId(input: GModelElement | GModelElementSchema | st return input.type; } } + +export function findTopLevelElementByFeature( + element: GModelElement, + predicate: (t: GModelElement) => t is GModelElement & T, + skip: (t: GModelElement) => boolean = _t => false +): (GModelElement & T) | undefined { + let match: (GModelElement & T) | undefined; + let current: GModelElement | undefined = element; + while (current !== undefined) { + if (!skip(current) && predicate(current)) { + match = current; + } + if (current instanceof GChildElement) { + current = current.parent; + } else { + current = undefined; + } + } + return match; +} + +export function calculateDeltaBetweenPoints(target: Point, source: Point, element: GModelElement): Point { + const delta = Point.subtract(target, source); + const viewport = findParentByFeature(element, isViewport); + const zoom = viewport?.zoom ?? 1; + const adaptedDelta = { x: delta.x / zoom, y: delta.y / zoom }; + return adaptedDelta; +} diff --git a/packages/client/src/utils/html-utils.ts b/packages/client/src/utils/html-utils.ts index 7b5c066..801cc49 100644 --- a/packages/client/src/utils/html-utils.ts +++ b/packages/client/src/utils/html-utils.ts @@ -13,6 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { AnyObject, hasNumberProp } from '@eclipse-glsp/sprotty'; + export function createElementFromHTML(html: string): HTMLElement | undefined { const template = document.createElement('template'); html = html.trim(); // Never return a text node of whitespace as the result @@ -23,3 +25,7 @@ export function createElementFromHTML(html: string): HTMLElement | undefined { } return undefined; } + +export function isMouseEvent(object: unknown): object is MouseEvent { + return AnyObject.is(object) && hasNumberProp(object, 'pageX') && hasNumberProp(object, 'pageY'); +} diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts new file mode 100644 index 0000000..ffccc99 --- /dev/null +++ b/packages/client/src/utils/index.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './argument-utils'; +export * from './geometry-util'; +export * from './gmodel-util'; +export * from './html-utils'; +export * from './layout-utils'; +export * from './marker'; +export * from './viewpoint-util'; diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 9df7546..8ec28fe 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -29,6 +29,7 @@ import { isViewport, translateBounds } from '@eclipse-glsp/sprotty'; +import { bottomRight, topLeft } from './geometry-util'; import { BoundsAwareModelElement } from './gmodel-util'; /** @@ -47,6 +48,7 @@ import { BoundsAwareModelElement } from './gmodel-util'; export function getAbsolutePosition(target: GModelElement, mouseEvent: MouseEvent): Point { return getAbsolutePositionByPoint(target, { x: mouseEvent.pageX, y: mouseEvent.pageY }); } + export function getAbsolutePositionByPoint(target: GModelElement, point: Point): Point { let xPos = point.x; let yPos = point.y; @@ -71,6 +73,12 @@ export function getAbsolutePositionByPoint(target: GModelElement, point: Point): }; } +export function getViewportBounds(target: GModelElement, bounds: Bounds): Bounds { + const start = getAbsolutePositionByPoint(target, topLeft(bounds)); + const end = getAbsolutePositionByPoint(target, bottomRight(bounds)); + return { ...start, width: end.x - start.x, height: end.y - start.y }; +} + /** * Translates the bounds of the diagram element (local coordinates) into the diagram coordinates system * (i.e. relative to the Diagram's 0;0 point) diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index 845fd97..a10f749 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,5 +18,5 @@ export * from './compartments'; export * from './gedge-view'; export * from './glsp-projection-view'; export * from './issue-marker-view'; -export * from './rounded-corner'; export * from './rounded-corner-view'; +export * from './rounded-corner'; diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 5f72a68..f24046c 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,310 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -/** - * Selective custom reexport of the sprotty API. Also provides augmentations/adaptions - * of certain type definitions for GLSP. Using this custom reexport allows us to gracefully - * update to newer sprotty versions without hard API breaks. - */ - -/** - * @eclipse-glsp/protocol - */ -export * from '@eclipse-glsp/protocol'; - export * from './action-override'; - -/* - * sprotty - */ -// ------------------ Base ------------------ - -// Exclude definition for labeled actions -// export * from 'sprotty/lib/base/actions/action'; -export * from 'sprotty/lib/base/actions/action-dispatcher'; -export { - ActionHandlerRegistration, - ActionHandlerRegistry, - IActionHandlerInitializer, - configureActionHandler, - onAction -} from 'sprotty/lib/base/actions/action-handler'; -export * from 'sprotty/lib/base/actions/diagram-locker'; - -export * from 'sprotty/lib/base/animations/animation'; -export * from 'sprotty/lib/base/animations/animation-frame-syncer'; -export * from 'sprotty/lib/base/animations/easing'; - -export * from 'sprotty/lib/base/commands/command'; -export * from 'sprotty/lib/base/commands/command-registration'; -export * from 'sprotty/lib/base/commands/command-stack'; -export * from 'sprotty/lib/base/commands/command-stack-options'; - -export * from 'sprotty/lib/base/features/initialize-canvas'; -export * from 'sprotty/lib/base/features/set-model'; -// Exclude SModelElementImpl as it as exported with augmentation module -export { - FeatureSet, - SChildElementImpl as GChildElement, - SModelElementImpl as GModelElement, - SModelRootImpl as GModelRoot, - SParentElementImpl as GParentElement, - IModelIndex, - ModelIndexImpl, - createRandomId, - isParent -} from 'sprotty/lib/base/model/smodel'; - -export { - CustomFeatures, - EMPTY_ROOT, - SModelElementConstructor as GModelElementConstructor, - SModelElementRegistration as GModelElementRegistration, - SModelFactory as GModelFactory, - IModelFactory, - // exported without alias we extend it in glsp-client to `GModelRegistry` - SModelRegistry, - createFeatureSet -} from 'sprotty/lib/base/model/smodel-factory'; -export * from 'sprotty/lib/base/model/smodel-utils'; - -export * from 'sprotty/lib/base/ui-extensions/ui-extension'; -export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; - -export * from 'sprotty/lib/base/views/dom-helper'; -export { KeyTool } from 'sprotty/lib/base/views/key-tool'; -export { MouseEventKind, MousePositionTracker, MouseTool, PopupMouseTool } from 'sprotty/lib/base/views/mouse-tool'; -export * from 'sprotty/lib/base/views/thunk-view'; -export * from 'sprotty/lib/base/views/view'; -export * from 'sprotty/lib/base/views/viewer'; -export * from 'sprotty/lib/base/views/viewer-cache'; -export * from 'sprotty/lib/base/views/viewer-options'; -export * from 'sprotty/lib/base/views/vnode-postprocessor'; -export * from 'sprotty/lib/base/views/vnode-utils'; - -// Exclude sprotty types and export augmented GLSP types instead -// export * from 'sprotty/lib/base/types'; -export * from './types'; - -// ------------------ Features ------------------ -export * from 'sprotty/lib/features/bounds/abstract-layout'; -export * from 'sprotty/lib/features/bounds/bounds-manipulation'; -export * from 'sprotty/lib/features/bounds/hbox-layout'; -export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; -export * from 'sprotty/lib/features/bounds/layout'; -export * from 'sprotty/lib/features/bounds/layout-options'; -export { - Alignable, - BoundsAware, - SShapeElementImpl as GShapeElement, - LayoutContainer, - LayoutableChild, - ModelLayoutOptions, - boundsFeature, - findChildrenAtPosition, - getAbsoluteBounds, - getAbsoluteClientBounds, - isAlignable, - isBoundsAware, - isLayoutContainer, - isLayoutableChild, - isSizeable, - layoutContainerFeature, - layoutableChildFeature -} from 'sprotty/lib/features/bounds/model'; -// exclude stack layout as its not supported in GLSP -// export * from 'sprotty/lib/features/bounds/stack-layout'; -export * from 'sprotty/lib/features/bounds/vbox-layout'; -export * from 'sprotty/lib/features/bounds/views'; - -export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; -export { SButtonImpl as GButton, SButtonSchema as GButtonSchema } from 'sprotty/lib/features/button/model'; - -export { - CommandPaletteActionProviderRegistry, - RevealNamedElementActionProvider -} from 'sprotty/lib/features/command-palette/action-providers'; -export * from 'sprotty/lib/features/command-palette/command-palette'; - -// Exclude menu item. Aready provided by glsp-protocol -export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; -export { ContextMenuProviderRegistry, DeleteContextMenuItemProvider } from 'sprotty/lib/features/context-menu/menu-providers'; -export * from 'sprotty/lib/features/context-menu/mouse-listener'; - -export * from 'sprotty/lib/features/edge-layout/di.config'; -export * from 'sprotty/lib/features/edge-layout/edge-layout'; -export * from 'sprotty/lib/features/edge-layout/model'; -// Exclude client-side creation features (not supported in GLSP) -// export * from 'sprotty/lib/features/edit/create'; -// export * from 'sprotty/lib/features/edit/create-on-drag'; -export * from 'sprotty/lib/features/edit/delete'; -export * from 'sprotty/lib/features/edit/edit-label'; -export * from 'sprotty/lib/features/edit/edit-label-ui'; -export * from 'sprotty/lib/features/edit/edit-routing'; -export * from 'sprotty/lib/features/edit/model'; -// export * from 'sprotty/lib/features/edit/reconnect'; - -export * from 'sprotty/lib/features/expand/expand'; -export * from 'sprotty/lib/features/expand/model'; -export * from 'sprotty/lib/features/expand/views'; -// Exclude RequestExportSvgAction. Already provided by glsp-protocol -export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor } from 'sprotty/lib/features/export/export'; -export * from 'sprotty/lib/features/export/model'; -// Exclude ExportSvgAction. Already provided by glsp-protocol -export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; - -export * from 'sprotty/lib/features/fade/fade'; -export * from 'sprotty/lib/features/fade/model'; - -export * from 'sprotty/lib/features/hover/hover'; -export * from 'sprotty/lib/features/hover/model'; -export * from 'sprotty/lib/features/hover/popup-position-updater'; - -// Alias SModel types -export * from 'sprotty/lib/features/decoration/decoration-placer'; -export { - Decoration, - SDecoration as GDecoration, - SIssue as GIssue, - SIssueSeverity as GIssueSeverity, - // Export as is, we extend it glsp-client to `GIssueMarker` - SIssueMarker -} from 'sprotty/lib/features/decoration/model'; -export * from 'sprotty/lib/features/decoration/views'; - -export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; -export * from 'sprotty/lib/features/edge-intersection/sweepline'; - -export * from 'sprotty/lib/features/move/model'; -export * from 'sprotty/lib/features/move/move'; -export * from 'sprotty/lib/features/move/snap'; - -export * from 'sprotty/lib/features/nameable/model'; - -export * from 'sprotty/lib/features/open/model'; -export * from 'sprotty/lib/features/open/open'; - -export * from 'sprotty/lib/features/projection/model'; -export * from 'sprotty/lib/features/projection/views'; - -export * from 'sprotty/lib/features/routing/abstract-edge-router'; -export * from 'sprotty/lib/features/routing/anchor'; -export * from 'sprotty/lib/features/routing/bezier-anchors'; -export * from 'sprotty/lib/features/routing/bezier-edge-router'; -export * from 'sprotty/lib/features/routing/manhattan-anchors'; -export * from 'sprotty/lib/features/routing/manhattan-edge-router'; -// Alias SModel types -export { - Connectable, - SConnectableElementImpl as GConnectableElement, - SDanglingAnchorImpl as GDanglingAnchor, - SRoutableElementImpl as GRoutableElement, - SRoutingHandleImpl as GRoutingHandle, - RoutingHandleKind, - connectableFeature, - edgeInProgressID, - edgeInProgressTargetHandleID, - getAbsoluteRouteBounds, - getRouteBounds, - isConnectable -} from 'sprotty/lib/features/routing/model'; -export * from 'sprotty/lib/features/routing/polyline-anchors'; -export * from 'sprotty/lib/features/routing/polyline-edge-router'; -export * from 'sprotty/lib/features/routing/routing'; -export * from 'sprotty/lib/features/routing/views'; - -export * from 'sprotty/lib/features/select/model'; -// Alias Select commands with sprotty prefix to avoid clash with glsp-client -export { - GetSelectionCommand, - SelectKeyboardListener, - SelectMouseListener, - SelectAllCommand as SprottySelectAllCommand, - SelectCommand as SprottySelectCommand -} from 'sprotty/lib/features/select/select'; - -// Exclude undo-redo as the client provides its own implementation -// export * from 'sprotty/lib/features/undo-redo/undo-redo'; - -export * from 'sprotty/lib/features/update/model-matching'; -export * from 'sprotty/lib/features/update/update-model'; - -export * from 'sprotty/lib/features/viewport/center-fit'; -export * from 'sprotty/lib/features/viewport/model'; -export * from 'sprotty/lib/features/viewport/scroll'; -// Alias SModel types -export * from 'sprotty/lib/features/viewport/viewport'; -export { ViewportRootElementImpl as GViewportRootElement } from 'sprotty/lib/features/viewport/viewport-root'; -export * from 'sprotty/lib/features/viewport/zoom'; - -export * from 'sprotty/lib/features/zorder/zorder'; - -// ------------------ Graph ------------------ -// Alias SModel types -export { - SCompartmentImpl as GCompartment, - SGraphIndex as GGraphIndex, - SLabelImpl as GLabel, - SNodeImpl as GNode, - SPortImpl as GPort, - // Exported as is, we extend it in glsp-client to GEdge - SEdgeImpl, - // Exported as is, we extend it in glsp-client to GGraph - SGraphImpl -} from 'sprotty/lib/graph/sgraph'; -// Alias SModel types -export { - BezierCurveEdgeView, - SBezierControlHandleView as GBezierControlHandleView, - SBezierCreateHandleView as GBezierCreateHandleView, - SCompartmentView as GCompartmentView, - SGraphView as GGraphView, - SLabelView as GLabelView, - SRoutingHandleView as GRoutingHandleView, - JumpingPolylineEdgeView, - PolylineEdgeView, - PolylineEdgeViewWithGapsOnIntersections -} from 'sprotty/lib/graph/views'; - export * from './feature-modules'; - -// ------------------ Library ------------------ - -export * from 'sprotty/lib/lib/generic-views'; -export * from 'sprotty/lib/lib/html-views'; -export * from 'sprotty/lib/lib/jsx'; -// Alias SModel types -export { - CircularNode, - CircularPort, - DiamondNode, - ForeignObjectElementImpl as GForeignObjectElement, - HtmlRootImpl as GHtmlRoot, - PreRenderedElementImpl as GPreRenderedElement, - ShapedPreRenderedElementImpl as GShapedPreRenderedElement, - RectangularNode, - RectangularPort -} from 'sprotty/lib/lib/model'; -export * from 'sprotty/lib/lib/modules'; -export * from 'sprotty/lib/lib/svg-views'; - -// ------------------ Model Source ------------------ -export * from 'sprotty/lib/model-source/commit-model'; -// Exclude as not supported in GLSP -// export * from 'sprotty/lib/model-source/diagram-server'; -// export * from 'sprotty/lib/model-source/local-model-source'; -export * from 'sprotty/lib/model-source/logging'; -export * from 'sprotty/lib/model-source/model-source'; -// export * from 'sprotty/lib/model-source/websocket'; - -// ------------------ Utilities ------------------ -export * from 'sprotty/lib/utils/browser'; -export * from 'sprotty/lib/utils/codicon'; -export * from 'sprotty/lib/utils/color'; -export * from 'sprotty/lib/utils/geometry'; -export * from 'sprotty/lib/utils/inversify'; -export * from 'sprotty/lib/utils/iterable'; -export * from 'sprotty/lib/utils/keyboard'; -export * from 'sprotty/lib/utils/logging'; -export * from 'sprotty/lib/utils/registry'; +export * from './re-exports'; +export * from './types'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts new file mode 100644 index 0000000..7623566 --- /dev/null +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -0,0 +1,311 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * @eclipse-glsp/protocol + */ +export * from '@eclipse-glsp/protocol'; + +/* + * sprotty + */ +// ------------------ Base ------------------ + +// Exclude definition for labeled actions +// export * from 'sprotty/lib/base/actions/action'; +export * from 'sprotty/lib/base/actions/action-dispatcher'; +export { + ActionHandlerRegistration, + ActionHandlerRegistry, + IActionHandlerInitializer, + configureActionHandler, + onAction +} from 'sprotty/lib/base/actions/action-handler'; +export * from 'sprotty/lib/base/actions/diagram-locker'; + +export * from 'sprotty/lib/base/animations/animation'; +export * from 'sprotty/lib/base/animations/animation-frame-syncer'; +export * from 'sprotty/lib/base/animations/easing'; + +export * from 'sprotty/lib/base/commands/command'; +export * from 'sprotty/lib/base/commands/command-registration'; +export * from 'sprotty/lib/base/commands/command-stack'; +export * from 'sprotty/lib/base/commands/command-stack-options'; + +export * from 'sprotty/lib/base/features/initialize-canvas'; +export * from 'sprotty/lib/base/features/set-model'; +// Exclude SModelElementImpl as it as exported with augmentation module +export { + FeatureSet, + SChildElementImpl as GChildElement, + SModelElementImpl as GModelElement, + SModelRootImpl as GModelRoot, + SParentElementImpl as GParentElement, + IModelIndex, + ModelIndexImpl, + createRandomId, + isParent +} from 'sprotty/lib/base/model/smodel'; + +export { + CustomFeatures, + EMPTY_ROOT, + SModelElementConstructor as GModelElementConstructor, + SModelElementRegistration as GModelElementRegistration, + SModelFactory as GModelFactory, + IModelFactory, + // exported without alias we extend it in glsp-client to `GModelRegistry` + SModelRegistry, + createFeatureSet +} from 'sprotty/lib/base/model/smodel-factory'; +export * from 'sprotty/lib/base/model/smodel-utils'; + +export * from 'sprotty/lib/base/ui-extensions/ui-extension'; +export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; + +export * from 'sprotty/lib/base/views/dom-helper'; +export { KeyTool } from 'sprotty/lib/base/views/key-tool'; +export { MouseEventKind, MousePositionTracker, MouseTool, PopupMouseTool } from 'sprotty/lib/base/views/mouse-tool'; +export * from 'sprotty/lib/base/views/thunk-view'; +export * from 'sprotty/lib/base/views/view'; +export * from 'sprotty/lib/base/views/viewer'; +export * from 'sprotty/lib/base/views/viewer-cache'; +export * from 'sprotty/lib/base/views/viewer-options'; +export * from 'sprotty/lib/base/views/vnode-postprocessor'; +export * from 'sprotty/lib/base/views/vnode-utils'; + +// Exclude sprotty types and export augmented GLSP types instead +// export * from 'sprotty/lib/base/types'; + +// ------------------ Features ------------------ +export * from 'sprotty/lib/features/bounds/abstract-layout'; +export * from 'sprotty/lib/features/bounds/bounds-manipulation'; +export * from 'sprotty/lib/features/bounds/hbox-layout'; +export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; +export * from 'sprotty/lib/features/bounds/layout'; +export * from 'sprotty/lib/features/bounds/layout-options'; +export { + Alignable, + BoundsAware, + SShapeElementImpl as GShapeElement, + LayoutContainer, + LayoutableChild, + ModelLayoutOptions, + boundsFeature, + findChildrenAtPosition, + getAbsoluteBounds, + getAbsoluteClientBounds, + isAlignable, + isBoundsAware, + isLayoutContainer, + isLayoutableChild, + isSizeable, + layoutContainerFeature, + layoutableChildFeature +} from 'sprotty/lib/features/bounds/model'; +// exclude stack layout as its not supported in GLSP +// export * from 'sprotty/lib/features/bounds/stack-layout'; +export * from 'sprotty/lib/features/bounds/vbox-layout'; +export * from 'sprotty/lib/features/bounds/views'; + +export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; +export { SButtonImpl as GButton, SButtonSchema as GButtonSchema } from 'sprotty/lib/features/button/model'; + +export { + CommandPaletteActionProviderRegistry, + RevealNamedElementActionProvider +} from 'sprotty/lib/features/command-palette/action-providers'; +export * from 'sprotty/lib/features/command-palette/command-palette'; + +// Exclude menu item. Aready provided by glsp-protocol +export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; +export { ContextMenuProviderRegistry, DeleteContextMenuItemProvider } from 'sprotty/lib/features/context-menu/menu-providers'; +export * from 'sprotty/lib/features/context-menu/mouse-listener'; + +export * from 'sprotty/lib/features/edge-layout/di.config'; +export * from 'sprotty/lib/features/edge-layout/edge-layout'; +export * from 'sprotty/lib/features/edge-layout/model'; +// Exclude client-side creation features (not supported in GLSP) +// export * from 'sprotty/lib/features/edit/create'; +// export * from 'sprotty/lib/features/edit/create-on-drag'; +export * from 'sprotty/lib/features/edit/delete'; +export * from 'sprotty/lib/features/edit/edit-label'; +export * from 'sprotty/lib/features/edit/edit-label-ui'; +export * from 'sprotty/lib/features/edit/edit-routing'; +export * from 'sprotty/lib/features/edit/model'; +// export * from 'sprotty/lib/features/edit/reconnect'; + +export * from 'sprotty/lib/features/expand/expand'; +export * from 'sprotty/lib/features/expand/model'; +export * from 'sprotty/lib/features/expand/views'; +// Exclude RequestExportSvgAction. Already provided by glsp-protocol +export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor } from 'sprotty/lib/features/export/export'; +export * from 'sprotty/lib/features/export/model'; +// Exclude ExportSvgAction. Already provided by glsp-protocol +export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; + +export * from 'sprotty/lib/features/fade/fade'; +export * from 'sprotty/lib/features/fade/model'; + +export * from 'sprotty/lib/features/hover/hover'; +export * from 'sprotty/lib/features/hover/model'; +export * from 'sprotty/lib/features/hover/popup-position-updater'; + +// Alias SModel types +export * from 'sprotty/lib/features/decoration/decoration-placer'; +export { + Decoration, + SDecoration as GDecoration, + SIssue as GIssue, + SIssueSeverity as GIssueSeverity, + // Export as is, we extend it glsp-client to `GIssueMarker` + SIssueMarker +} from 'sprotty/lib/features/decoration/model'; +export * from 'sprotty/lib/features/decoration/views'; + +export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; +export * from 'sprotty/lib/features/edge-intersection/sweepline'; + +export * from 'sprotty/lib/features/move/model'; +export * from 'sprotty/lib/features/move/move'; +export * from 'sprotty/lib/features/move/snap'; + +export * from 'sprotty/lib/features/nameable/model'; + +export * from 'sprotty/lib/features/open/model'; +export * from 'sprotty/lib/features/open/open'; + +export * from 'sprotty/lib/features/projection/model'; +export * from 'sprotty/lib/features/projection/views'; + +export * from 'sprotty/lib/features/routing/abstract-edge-router'; +export * from 'sprotty/lib/features/routing/anchor'; +export * from 'sprotty/lib/features/routing/bezier-anchors'; +export * from 'sprotty/lib/features/routing/bezier-edge-router'; +export * from 'sprotty/lib/features/routing/manhattan-anchors'; +export * from 'sprotty/lib/features/routing/manhattan-edge-router'; +// Alias SModel types +export { + Connectable, + SConnectableElementImpl as GConnectableElement, + SDanglingAnchorImpl as GDanglingAnchor, + SRoutableElementImpl as GRoutableElement, + SRoutingHandleImpl as GRoutingHandle, + RoutingHandleKind, + connectableFeature, + edgeInProgressID, + edgeInProgressTargetHandleID, + getAbsoluteRouteBounds, + getRouteBounds, + isConnectable +} from 'sprotty/lib/features/routing/model'; +export * from 'sprotty/lib/features/routing/polyline-anchors'; +export * from 'sprotty/lib/features/routing/polyline-edge-router'; +export * from 'sprotty/lib/features/routing/routing'; +export * from 'sprotty/lib/features/routing/views'; + +export * from 'sprotty/lib/features/select/model'; +// Alias Select commands with sprotty prefix to avoid clash with glsp-client +export { + GetSelectionCommand, + SelectKeyboardListener, + SelectMouseListener, + SelectAllCommand as SprottySelectAllCommand, + SelectCommand as SprottySelectCommand +} from 'sprotty/lib/features/select/select'; + +// Exclude undo-redo as the client provides its own implementation +// export * from 'sprotty/lib/features/undo-redo/undo-redo'; + +export * from 'sprotty/lib/features/update/model-matching'; +export * from 'sprotty/lib/features/update/update-model'; + +export * from 'sprotty/lib/features/viewport/center-fit'; +export * from 'sprotty/lib/features/viewport/model'; +export * from 'sprotty/lib/features/viewport/scroll'; +// Alias SModel types +export * from 'sprotty/lib/features/viewport/viewport'; +export { ViewportRootElementImpl as GViewportRootElement } from 'sprotty/lib/features/viewport/viewport-root'; +export * from 'sprotty/lib/features/viewport/zoom'; + +export * from 'sprotty/lib/features/zorder/zorder'; + +// ------------------ Graph ------------------ +// Alias SModel types +export { + SCompartmentImpl as GCompartment, + SGraphIndex as GGraphIndex, + SLabelImpl as GLabel, + SNodeImpl as GNode, + SPortImpl as GPort, + // Exported as is, we extend it in glsp-client to GEdge + SEdgeImpl, + // Exported as is, we extend it in glsp-client to GGraph + SGraphImpl +} from 'sprotty/lib/graph/sgraph'; +// Alias SModel types +export { + BezierCurveEdgeView, + SBezierControlHandleView as GBezierControlHandleView, + SBezierCreateHandleView as GBezierCreateHandleView, + SCompartmentView as GCompartmentView, + SGraphView as GGraphView, + SLabelView as GLabelView, + SRoutingHandleView as GRoutingHandleView, + JumpingPolylineEdgeView, + PolylineEdgeView, + PolylineEdgeViewWithGapsOnIntersections +} from 'sprotty/lib/graph/views'; + +// ------------------ Library ------------------ + +export * from 'sprotty/lib/lib/generic-views'; +export * from 'sprotty/lib/lib/html-views'; +export * from 'sprotty/lib/lib/jsx'; +// Alias SModel types +export { + CircularNode, + CircularPort, + DiamondNode, + ForeignObjectElementImpl as GForeignObjectElement, + HtmlRootImpl as GHtmlRoot, + PreRenderedElementImpl as GPreRenderedElement, + ShapedPreRenderedElementImpl as GShapedPreRenderedElement, + RectangularNode, + RectangularPort +} from 'sprotty/lib/lib/model'; +export * from 'sprotty/lib/lib/modules'; +export * from 'sprotty/lib/lib/svg-views'; + +// ------------------ Model Source ------------------ +export * from 'sprotty/lib/model-source/commit-model'; +// Exclude as not supported in GLSP +// export * from 'sprotty/lib/model-source/diagram-server'; +// export * from 'sprotty/lib/model-source/local-model-source'; +export * from 'sprotty/lib/model-source/logging'; +export * from 'sprotty/lib/model-source/model-source'; +// export * from 'sprotty/lib/model-source/websocket'; + +// ------------------ Utilities ------------------ +export * from 'sprotty/lib/utils/browser'; +export * from 'sprotty/lib/utils/codicon'; +export * from 'sprotty/lib/utils/color'; +export * from 'sprotty/lib/utils/geometry'; +export * from 'sprotty/lib/utils/inversify'; +export * from 'sprotty/lib/utils/iterable'; +export * from 'sprotty/lib/utils/keyboard'; +export * from 'sprotty/lib/utils/logging'; +export * from 'sprotty/lib/utils/registry'; diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index a826755..7937660 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -40,6 +40,7 @@ export const TYPES = { IDefaultTool: Symbol('IDefaultTool'), IEditModeListener: Symbol('IEditModeListener'), IMarqueeBehavior: Symbol('IMarqueeBehavior'), + IHelperLineManager: Symbol('IHelperLineManager'), IElementNavigator: Symbol('IElementNavigator'), ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), diff --git a/packages/protocol/src/client-server-protocol/index.ts b/packages/protocol/src/client-server-protocol/index.ts new file mode 100644 index 0000000..dd8fa48 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './base-glsp-client'; +export * from './glsp-client'; +export * from './glsp-server'; +export * from './jsonrpc'; +export * from './types'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/index.ts b/packages/protocol/src/client-server-protocol/jsonrpc/index.ts new file mode 100644 index 0000000..b466993 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/jsonrpc/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './base-jsonrpc-glsp-client'; +export * from './glsp-jsonrpc-client'; +export * from './glsp-jsonrpc-server'; +export * from './websocket-connection'; +export * from './ws-connection-provider'; diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index d47a1d9..8dc607a 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,29 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -// Partial reexport of sprotty-protocol -export { Viewport } from 'sprotty-protocol/lib/model'; -export * from 'sprotty-protocol/lib/utils/async'; -export * from 'sprotty-protocol/lib/utils/geometry'; -export * from 'sprotty-protocol/lib/utils/json'; -export { applyBounds, cloneModel, findElement, getBasicType, getSubType } from 'sprotty-protocol/lib/utils/model-utils'; -// Default export of @eclipse-glsp/protocol export * from './action-protocol'; -export * from './client-server-protocol/base-glsp-client'; -export * from './client-server-protocol/glsp-client'; -export * from './client-server-protocol/glsp-server'; -export * from './client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; -export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; -export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; -export * from './client-server-protocol/jsonrpc/websocket-connection'; -export * from './client-server-protocol/jsonrpc/ws-connection-provider'; -export * from './client-server-protocol/types'; -export * from './model/default-types'; -export * from './model/model-schema'; +export * from './client-server-protocol'; +export * from './model'; +export * from './re-exports'; export * from './sprotty-actions'; -export * from './utils/array-util'; -export * from './utils/di-util'; -export * from './utils/disposable'; -export * from './utils/event'; -export * from './utils/type-util'; +export * from './utils'; diff --git a/packages/protocol/src/model/index.ts b/packages/protocol/src/model/index.ts new file mode 100644 index 0000000..d372746 --- /dev/null +++ b/packages/protocol/src/model/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './default-types'; +export * from './model-schema'; diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts new file mode 100644 index 0000000..84d91f5 --- /dev/null +++ b/packages/protocol/src/re-exports.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export { Viewport } from 'sprotty-protocol/lib/model'; +export * from 'sprotty-protocol/lib/utils/async'; +export * from 'sprotty-protocol/lib/utils/geometry'; +export * from 'sprotty-protocol/lib/utils/json'; +export { applyBounds, cloneModel, findElement, getBasicType, getSubType } from 'sprotty-protocol/lib/utils/model-utils'; diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 501d524..0d6f826 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Constructor, Primitive } from './type-util'; +import { Constructor, Primitive, TypeGuard } from './type-util'; /** * A union type for for objects that can either be a single element or and array of elements. @@ -180,3 +180,16 @@ export function isStringArray(object: unknown, supportEmpty = false): object is export function isArrayMatching(object: unknown, predicate: (elem: unknown) => boolean, supportEmpty = false): boolean { return Array.isArray(object) && object.every(predicate) && (supportEmpty || object.length > 0); } + +export function partition(source: T[], matchGuard: TypeGuard): { match: T[]; rest: T[] } { + const match: T[] = []; + const rest: T[] = []; + source.forEach(element => { + if (matchGuard(element)) { + match.push(element); + } else { + rest.push(element); + } + }); + return { match, rest }; +} diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts new file mode 100644 index 0000000..0bb9e30 --- /dev/null +++ b/packages/protocol/src/utils/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './array-util'; +export * from './di-util'; +export * from './disposable'; +export * from './event'; +export * from './test-util'; +export * from './type-util'; From 1bf1a4f772c5a5e751a1ee5483ced61b2f9cc0c5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 18 Dec 2023 01:33:26 -0800 Subject: [PATCH 290/566] Add generic args property to all operations (#309) --- .../protocol/src/action-protocol/base-protocol.ts | 11 +++++++++-- packages/protocol/src/action-protocol/clipboard.ts | 9 +++++---- .../protocol/src/action-protocol/edge-modification.ts | 10 +++------- .../protocol/src/action-protocol/element-creation.ts | 9 +++------ .../src/action-protocol/element-text-editing.ts | 2 +- packages/protocol/src/action-protocol/model-layout.ts | 7 ++++--- .../protocol/src/action-protocol/node-modification.ts | 9 +++++++-- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts index 542f1c6..c6643df 100644 --- a/packages/protocol/src/action-protocol/base-protocol.ts +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; import { AnyObject, TypeGuard, hasArrayProp, hasStringProp } from '../utils/type-util'; +import { Args } from './types'; /** * An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. @@ -185,6 +186,11 @@ export interface Operation extends Action { * Discriminator property to make operations distinguishable from plain {@link Action}s. */ isOperation: true; + + /** + * Optional custom arguments. + */ + args?: Args; } export namespace Operation { @@ -223,11 +229,12 @@ export namespace CompoundOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'operationList'); } - export function create(operationList: Operation[]): CompoundOperation { + export function create(operationList: Operation[], options: { args?: Args } = {}): CompoundOperation { return { kind: KIND, isOperation: true, - operationList + operationList, + ...options }; } } diff --git a/packages/protocol/src/action-protocol/clipboard.ts b/packages/protocol/src/action-protocol/clipboard.ts index 3f5399e..36ad9a9 100644 --- a/packages/protocol/src/action-protocol/clipboard.ts +++ b/packages/protocol/src/action-protocol/clipboard.ts @@ -16,7 +16,7 @@ import { hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; -import { EditorContext } from './types'; +import { Args, EditorContext } from './types'; /** * Requests the clipboard data for the current editor context, i.e., the selected elements, in a clipboard-compatible format. @@ -96,11 +96,12 @@ export namespace CutOperation { return Operation.hasKind(object, KIND) && hasObjectProp(object, 'editorContext'); } - export function create(editorContext: EditorContext): CutOperation { + export function create(editorContext: EditorContext, options: { args?: Args } = {}): CutOperation { return { kind: KIND, isOperation: true, - editorContext + editorContext, + ...options }; } } @@ -129,7 +130,7 @@ export namespace PasteOperation { return Operation.hasKind(object, KIND) && hasObjectProp(object, 'clipboardData') && hasObjectProp(object, 'editorContext'); } - export function create(options: { editorContext: EditorContext; clipboardData: ClipboardData }): PasteOperation { + export function create(options: { editorContext: EditorContext; clipboardData: ClipboardData; args?: Args }): PasteOperation { return { kind: KIND, isOperation: true, diff --git a/packages/protocol/src/action-protocol/edge-modification.ts b/packages/protocol/src/action-protocol/edge-modification.ts index 4a32913..cd8f3bb 100644 --- a/packages/protocol/src/action-protocol/edge-modification.ts +++ b/packages/protocol/src/action-protocol/edge-modification.ts @@ -40,11 +40,6 @@ export interface ReconnectEdgeOperation extends Operation { * The (new) target element of the edge. */ targetElementId: string; - - /* - * Additional arguments for custom behavior. - */ - args?: Args; } export namespace ReconnectEdgeOperation { @@ -95,11 +90,12 @@ export namespace ChangeRoutingPointsOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'newRoutingPoints'); } - export function create(newRoutingPoints: ElementAndRoutingPoints[]): ChangeRoutingPointsOperation { + export function create(newRoutingPoints: ElementAndRoutingPoints[], options: { args?: Args } = {}): ChangeRoutingPointsOperation { return { kind: KIND, isOperation: true, - newRoutingPoints + newRoutingPoints, + ...options }; } } diff --git a/packages/protocol/src/action-protocol/element-creation.ts b/packages/protocol/src/action-protocol/element-creation.ts index 0b9c286..56b683c 100644 --- a/packages/protocol/src/action-protocol/element-creation.ts +++ b/packages/protocol/src/action-protocol/element-creation.ts @@ -29,10 +29,6 @@ export interface CreateOperation extends Operation { * The type of the element that should be created. */ elementTypeId: string; - /** - * Optional additional arguments for the server to execute the create operation. - */ - args?: Args; } export namespace CreateOperation { @@ -148,11 +144,12 @@ export namespace DeleteElementOperation { return Operation.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); } - export function create(elementIds: string[]): DeleteElementOperation { + export function create(elementIds: string[], options: { args?: Args } = {}): DeleteElementOperation { return { kind: KIND, isOperation: true, - elementIds + elementIds, + ...options }; } } diff --git a/packages/protocol/src/action-protocol/element-text-editing.ts b/packages/protocol/src/action-protocol/element-text-editing.ts index ce9aa9e..f248756 100644 --- a/packages/protocol/src/action-protocol/element-text-editing.ts +++ b/packages/protocol/src/action-protocol/element-text-editing.ts @@ -129,7 +129,7 @@ export namespace ApplyLabelEditOperation { return Operation.hasKind(object, KIND) && hasStringProp(object, 'labelId') && hasStringProp(object, 'text'); } - export function create(options: { labelId: string; text: string }): ApplyLabelEditOperation { + export function create(options: { labelId: string; text: string; args?: Args }): ApplyLabelEditOperation { return { kind: KIND, isOperation: true, diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index e0470a0..dd190e9 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -17,7 +17,7 @@ import * as sprotty from 'sprotty-protocol/lib/actions'; import { GModelRootSchema } from '..'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; -import { ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints } from './types'; +import { Args, ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints } from './types'; /** * Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can @@ -127,11 +127,12 @@ export namespace LayoutOperation { return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); } - export function create(elementIds?: string[]): LayoutOperation { + export function create(elementIds?: string[], options: { args?: Args } = {}): LayoutOperation { return { kind: KIND, isOperation: true, - elementIds + elementIds, + ...options }; } } diff --git a/packages/protocol/src/action-protocol/node-modification.ts b/packages/protocol/src/action-protocol/node-modification.ts index e80ef3b..a9cff36 100644 --- a/packages/protocol/src/action-protocol/node-modification.ts +++ b/packages/protocol/src/action-protocol/node-modification.ts @@ -17,7 +17,7 @@ import { Point } from 'sprotty-protocol'; import { hasArrayProp, hasStringProp } from '../utils/type-util'; import { Operation } from './base-protocol'; -import { ElementAndBounds } from './types'; +import { Args, ElementAndBounds } from './types'; /** * Triggers the position or size change of elements. This action concerns only the element's graphical size and position. @@ -80,7 +80,12 @@ export namespace ChangeContainerOperation { return Operation.hasKind(object, KIND) && hasStringProp(object, 'elementId') && hasStringProp(object, 'targetContainerId'); } - export function create(options: { elementId: string; targetContainerId: string; location?: Point }): ChangeContainerOperation { + export function create(options: { + elementId: string; + targetContainerId: string; + location?: Point; + args?: Args; + }): ChangeContainerOperation { return { kind: KIND, isOperation: true, From 4b7f5c88f15f13956f0658629ab53375b18019d8 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 19 Jan 2024 09:02:48 +0100 Subject: [PATCH 291/566] Allow optional actions to avoid console errors (#310) --- packages/client/src/base/action-dispatcher.ts | 6 +++- .../src/base/model/glsp-model-source.ts | 28 +++++++++++++++++-- .../change-bounds-tool-feedback.ts | 11 ++++++-- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 74ebb45..f3301f2 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, ActionDispatcher, RequestAction, ResponseAction } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { OptionalAction } from './model/glsp-model-source'; import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @injectable() @@ -76,6 +77,9 @@ export class GLSPActionDispatcher extends ActionDispatcher { action.responseId = ''; } } + if (!this.hasHandler(action) && OptionalAction.is(action)) { + return Promise.resolve(); + } return super.handleAction(action); } diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index 1687bef..bdc2a79 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, preDestroy } from 'inversify'; import { Action, ActionMessage, @@ -29,8 +28,10 @@ import { ModelSource, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, preDestroy } from 'inversify'; import { GLSPActionHandlerRegistry } from '../action-handler-registry'; import { IDiagramOptions } from './diagram-loader'; + /** * A helper interface that allows the client to mark actions that have been received from the server. */ @@ -44,7 +45,7 @@ export namespace ServerAction { } /** - * Mark the given action as {@link ServerAction} by attaching the "_receivedFromServer" property + * Mark the given action as {@link ServerAction} by attaching the "__receivedFromServer" property * @param action The action that should be marked as server action */ export function mark(action: Action): void { @@ -52,6 +53,29 @@ export namespace ServerAction { } } +/** + * A helper interface that allows the client to mark actions that can be considered optional and should not throw an error if + * no handler is available. + */ +export interface OptionalAction extends Action { + __skipErrorIfNoHandler: true; +} + +export namespace OptionalAction { + export function is(object: unknown): object is ServerAction { + return Action.is(object) && '__skipErrorIfNoHandler' in object && object.__skipErrorIfNoHandler === true; + } + + /** + * Mark the given action as {@link OptionalAction} by attaching the "__skipErrorIfNoHandler" property + * @param action The action that should be marked as optional action + */ + export function mark(action: T): T & OptionalAction { + (action as unknown as OptionalAction).__skipErrorIfNoHandler = true; + return action as T & OptionalAction; + } +} + /** * Central component for enabling the client-server action flow with the help of an underlying {@link GLSPClient}. * Handles & forwards actions that are intended for the GLSP server. In addition, it handles {@link ActionMessage}s received diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index f6984c3..f91b03b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -36,6 +36,7 @@ import { DebouncedFunc, debounce } from 'lodash'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; +import { OptionalAction } from '../../../base/model/glsp-model-source'; import { forEachElement } from '../../../utils/gmodel-util'; import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; @@ -125,7 +126,7 @@ export namespace MoveInitializedEventAction { } export function create(): MoveInitializedEventAction { - return { kind: KIND }; + return OptionalAction.mark({ kind: KIND }); } } @@ -141,7 +142,7 @@ export namespace MoveFinishedEventAction { } export function create(): MoveFinishedEventAction { - return { kind: KIND }; + return OptionalAction.mark({ kind: KIND }); } } @@ -157,6 +158,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements protected positionUpdater; protected elementId2startPos = new Map(); protected pendingMoveInitialized?: DebouncedFunc<() => void>; + protected moveInitialized = false; constructor(protected tool: ChangeBoundsTool) { super(); @@ -178,10 +180,12 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements } protected scheduleMoveInitialized(): void { + this.moveInitialized = false; this.pendingMoveInitialized?.cancel(); this.pendingMoveInitialized = debounce(() => { this.tool.registerFeedback([MoveInitializedEventAction.create()], this); this.pendingMoveInitialized = undefined; + this.moveInitialized = true; }, 750); this.pendingMoveInitialized(); } @@ -329,11 +333,12 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); this.tool.deregisterFeedback(this, [moveAction]); } - } else if (resetFeedback) { + } else if (resetFeedback && this.moveInitialized) { this.tool.deregisterFeedback(this, [MoveFinishedEventAction.create()]); } this.positionUpdater.resetPosition(); this._isMouseDrag = false; + this.moveInitialized = false; this.rootElement = undefined; this.elementId2startPos.clear(); } From 5252fdf76bd84f31bd7ad7bc51d177d2c758ce17 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 19 Jan 2024 09:31:06 +0100 Subject: [PATCH 292/566] GLSP-1194: Do not send out selection changes if only the root changed (#313) When we receive a model update and the selection state (ids of selected elements) stays the same, we should not send out a selection changed event. Code interested in model updates can listen to root changes manually. This also prevents Theia from permanently updating the selection if only the root element was changed. Fixes https://github.com/eclipse-glsp/glsp/issues/1194 --- .../client/src/base/selection-service.spec.ts | 16 ++++---- packages/client/src/base/selection-service.ts | 38 +++++++++---------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index cabde1f..eb2ee08 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; import { defaultModule } from './default.module'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @@ -217,13 +217,13 @@ describe('SelectionService', () => { selectionService.updateSelection(root, [], ['node4']); assertListener(listener, root, ['node2', 'node3'], ['node4'], 4); }); - it('A registered listener should be notified of root changes.', () => { + it('A registered listener should NOT be notified of root changes.', () => { selectionService.updateSelection(root, [], []); - assertListener(listener, root, [], [], 1); + assertListener(listener, root, [], [], 0); const newRoot = createRoot('node1', 'newNode2', 'newNode3'); selectionService.updateSelection(newRoot, [], []); - assertListener(listener, newRoot, [], [], 2); + assertListener(listener, newRoot, [], [], 0); }); it('Selecting the same elements consecutively should not trigger a listener update.', () => { selectionService.updateSelection(root, ['node1'], []); @@ -290,8 +290,10 @@ describe('SelectionService', () => { expectedCalled: number ): void { expect(listener.selectionChanged.callCount).to.be.equal(expectedCalled); - expect(listener.selectionChanged.lastCall.args[0]).to.be.deep.equals(expectedRoot); - expect(listener.selectionChanged.lastCall.args[1]).to.be.deep.equals(expectedSelection); - expect(listener.selectionChanged.lastCall.args[2]).to.be.deep.equals(expectedDeselection); + if (expectedCalled > 0) { + expect(listener.selectionChanged.lastCall.args[0]).to.be.deep.equals(expectedRoot); + expect(listener.selectionChanged.lastCall.args[1]).to.be.deep.equals(expectedSelection); + expect(listener.selectionChanged.lastCall.args[2]).to.be.deep.equals(expectedDeselection); + } } }); diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index bb3b154..c0906ef 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -96,41 +96,41 @@ export class SelectionService implements IGModelRootListener, Disposable { this.updateSelection(root, [], []); } - updateSelection(root: Readonly, select: string[], deselect: string[]): void { - if (root === undefined && select.length === 0 && deselect.length === 0) { + updateSelection(newRoot: Readonly, select: string[], deselect: string[]): void { + if (newRoot === undefined && select.length === 0 && deselect.length === 0) { return; } const prevRoot = this.root; const prevSelectedElementIDs = new Set(this.selectedElementIDs); - // update root - this.root = root; + this.root = newRoot; - // update selected element IDs and collect deselected elements - // - select all elements that are not deselected at the same time (no-op) - // - deselect all elements that are not selected at the same time (no-op) but was selected + // We only select elements that are not part of the deselection const toSelect = [...select].filter(selectId => deselect.indexOf(selectId) === -1); + + // We only need to deselect elements that are not part of the selection + // If an element is part of both the select and deselect, it's state is not changed const toDeselect = [...deselect].filter(deselectId => select.indexOf(deselectId) === -1 && this.selectedElementIDs.has(deselectId)); - for (const id of toDeselect) { - this.selectedElementIDs.delete(id); - } - for (const id of toSelect) { - this.selectedElementIDs.add(id); - } + // update selected element ids + toDeselect.forEach(toDeselectId => this.selectedElementIDs.delete(toDeselectId)); + toSelect.forEach(toSelectId => this.selectedElementIDs.add(toSelectId)); + + // check if the newly or previously selected elements still exist in the updated root const deselectedElementIDs = new Set(toDeselect); - // see if selected elements still exist in the updated root for (const id of this.selectedElementIDs) { - const element = root.index.getById(id); + const element = newRoot.index.getById(id); if (element === undefined) { + // element to be selected does not exist in the root... this.selectedElementIDs.delete(id); - if (prevRoot !== undefined && prevRoot.index.getById(id)) { + if (prevRoot?.index.getById(id)) { + // ...but existed in the previous root, so we want to consider it deselected deselectedElementIDs.add(id); } } } - // only send out changes if there actually are changes, i.e., the root or the selected elements changed + // only send out changes if there actually are changes, i.e., any of the selected elements ids has changed const selectionChanged = prevSelectedElementIDs.size !== this.selectedElementIDs.size || ![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value)); @@ -142,10 +142,6 @@ export class SelectionService implements IGModelRootListener, Disposable { deselectedElementsIDs: [...deselectedElementIDs] }) ]); - } - - const rootChanged = prevRoot !== root; - if (rootChanged || selectionChanged) { // notify listeners after the feedback action this.notifyListeners(this.root, this.selectedElementIDs, deselectedElementIDs); } From 34e1de13115e6002e0767e87a25d40c98c1c8afb Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 22 Jan 2024 11:47:30 +0100 Subject: [PATCH 293/566] Make HelperLine module configurable from central options (#312) - Make HelperLine module configurable from central options - Exclude routable elements (edges) from being considered by default - Skip decorations (eg issue markers) by default - Fix handling of containers by using absolute bounds - Add possibility of logging debug output --- .../src/workflow-diagram-module.ts | 16 ++ .../change-bounds/position-snapper.ts | 8 +- .../helper-lines/helper-line-feedback.ts | 172 +++++++++++++----- .../helper-line-manager-default.ts | 88 +++++++-- .../helper-lines/helper-line-manager.ts | 9 +- .../change-bounds-tool-feedback.ts | 2 +- packages/client/src/utils/gmodel-util.ts | 12 ++ packages/glsp-sprotty/src/re-exports.ts | 3 +- packages/glsp-sprotty/src/types.ts | 1 + 9 files changed, 239 insertions(+), 72 deletions(-) diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 5c32db6..8e2d52e 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -16,6 +16,7 @@ import { ConsoleLogger, ContainerConfiguration, + DEFAULT_ALIGNABLE_ELEMENT_FILTER, DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, @@ -27,6 +28,8 @@ import { GLabel, GLabelView, GridSnapper, + IHelperLineOptions, + ISnapper, LogLevel, RectangularNodeView, RevealNamedElementActionProvider, @@ -75,6 +78,19 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); + + bind(TYPES.IHelperLineOptions).toDynamicValue(ctx => { + const options: IHelperLineOptions = {}; + // the user needs to use twice the force (double the distance) to break through a helper line compared to moving on the grid + const snapper = ctx.container.get(TYPES.ISnapper); + if (snapper instanceof GridSnapper) { + options.minimumMoveDelta = { x: snapper.grid.x * 2, y: snapper.grid.y * 2 }; + } + // skip icons for alignment as well as compartments which are only used for structure + options.alignmentElementFilter = element => + DEFAULT_ALIGNABLE_ELEMENT_FILTER(element) && !(element instanceof Icon) && !(element instanceof GCompartment); + return options; + }); }); export function createWorkflowDiagramContainer(...containerConfiguration: ContainerConfiguration): Container { diff --git a/packages/client/src/features/change-bounds/position-snapper.ts b/packages/client/src/features/change-bounds/position-snapper.ts index adaf34b..81c9f07 100644 --- a/packages/client/src/features/change-bounds/position-snapper.ts +++ b/packages/client/src/features/change-bounds/position-snapper.ts @@ -55,14 +55,14 @@ export class PositionSnapper { const minimum: Writable = { x: 0, y: 0 }; if (directions.includes(Direction.Left) && helperLines.some(line => line.isLeft || line.isCenter)) { - minimum.x = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Left); + minimum.x = this.helperLineManager.getMinimumMoveDelta(target, isSnap, Direction.Left); } else if (directions.includes(Direction.Right) && helperLines.some(line => line.isRight || line.isCenter)) { - minimum.x = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Right); + minimum.x = this.helperLineManager.getMinimumMoveDelta(target, isSnap, Direction.Right); } if (directions.includes(Direction.Up) && helperLines.some(line => line.isTop || line.isMiddle)) { - minimum.y = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Up); + minimum.y = this.helperLineManager.getMinimumMoveDelta(target, isSnap, Direction.Up); } else if (directions.includes(Direction.Down) && helperLines.some(line => line.isBottom || line.isMiddle)) { - minimum.y = this.helperLineManager.getHelperLineSnapping(target, isSnap, Direction.Down); + minimum.y = this.helperLineManager.getMinimumMoveDelta(target, isSnap, Direction.Down); } return minimum; } diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index 56e6157..0c18dd5 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -19,12 +19,17 @@ import { Bounds, CommandExecutionContext, CommandReturn, + GChildElement, + GLabel, GModelElement, GModelRoot, + ILogger, + Point, TYPES, Viewport, findParentByFeature, isBoundsAware, + isDecoration, isViewport } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; @@ -50,14 +55,23 @@ import { topLeft, topRight } from '../../utils/geometry-util'; -import { BoundsAwareModelElement, findTopLevelElementByFeature, forEachElement, getMatchingElements } from '../../utils/gmodel-util'; -import { getViewportBounds } from '../../utils/viewpoint-util'; +import { + BoundsAwareModelElement, + findTopLevelElementByFeature, + forEachElement, + getMatchingElements, + isRoutable, + isVisibleOnCanvas +} from '../../utils/gmodel-util'; +import { getViewportBounds, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { HelperLine, HelperLineType, SelectionBounds, isHelperLine, isSelectionBounds } from './model'; export type ViewportLineType = typeof HelperLineType.Center | typeof HelperLineType.Middle | string; -export const ALL_ELEMENT_LINE_TYPES = Object.values(HelperLineType); -export const ALL_VIEWPORT_LINE_TYPES = [HelperLineType.Center, HelperLineType.Middle]; +export type AlignmentElementFilter = (element: BoundsAwareModelElement, referenceElementIds: string[]) => boolean; + +export const isTopLevelBoundsAwareElement: AlignmentElementFilter = element => + findTopLevelElementByFeature(element, isBoundsAware, isViewport) === element; export interface DrawHelperLinesFeedbackAction extends Action { kind: typeof DrawHelperLinesFeedbackAction.KIND; @@ -65,8 +79,20 @@ export interface DrawHelperLinesFeedbackAction extends Action { elementLines?: HelperLineType[]; viewportLines?: ViewportLineType[]; alignmentEpsilon?: number; + alignmentElementFilter?: AlignmentElementFilter; + debug?: boolean; } +export const ALL_ELEMENT_LINE_TYPES = Object.values(HelperLineType); +export const ALL_VIEWPORT_LINE_TYPES = [HelperLineType.Center, HelperLineType.Middle]; + +export const DEFAULT_ELEMENT_LINES = ALL_ELEMENT_LINE_TYPES; +export const DEFAULT_VIEWPORT_LINES = ALL_VIEWPORT_LINE_TYPES; +export const DEFAULT_EPSILON = 1; +export const DEFAULT_ALIGNABLE_ELEMENT_FILTER = (element: BoundsAwareModelElement): boolean => + isVisibleOnCanvas(element) && !isRoutable(element) && !(element instanceof GLabel) && !isDecoration(element); +export const DEFAULT_DEBUG = false; + export namespace DrawHelperLinesFeedbackAction { export const KIND = 'drawHelperLines'; @@ -82,153 +108,200 @@ export namespace DrawHelperLinesFeedbackAction { export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { static readonly KIND = DrawHelperLinesFeedbackAction.KIND; - constructor(@inject(TYPES.Action) public action: DrawHelperLinesFeedbackAction) { + protected elementIds: string[]; + protected elementLines: HelperLineType[]; + protected viewportLines: ViewportLineType[]; + protected alignmentEpsilon: number; + protected alignableElementFilter: AlignmentElementFilter; + protected isAlignableElementPredicate: (element: GModelElement) => element is BoundsAwareModelElement; + protected debug: boolean; + + constructor( + @inject(TYPES.Action) action: DrawHelperLinesFeedbackAction, + @inject(TYPES.ILogger) protected logger: ILogger + ) { super(); + this.elementIds = action.elementIds; + this.elementLines = action.elementLines ?? DEFAULT_ELEMENT_LINES; + this.viewportLines = action.viewportLines ?? DEFAULT_VIEWPORT_LINES; + this.alignmentEpsilon = action.alignmentEpsilon ?? DEFAULT_EPSILON; + this.alignableElementFilter = action.alignmentElementFilter ?? DEFAULT_ALIGNABLE_ELEMENT_FILTER; + this.isAlignableElementPredicate = this.isAlignableElement.bind(this); + this.debug = action.debug ?? DEFAULT_DEBUG; } execute(context: CommandExecutionContext): CommandReturn { removeHelperLines(context.root); removeSelectionBounds(context.root); - const boundsAwareElements = getMatchingElements(context.root.index, this.isCompareElement); - const [referenceElements, elements] = partition(boundsAwareElements, element => this.action.elementIds.includes(element.id)); + const alignableElements = getMatchingElements(context.root.index, this.isAlignableElementPredicate); + this.log('All alignable elements: ', alignableElements); + const [referenceElements, elements] = partition(alignableElements, element => this.elementIds.includes(element.id)); + this.log('Split alignable elements into reference elements and other elements: ', referenceElements, elements); if (referenceElements.length === 0) { + this.log('--> No helper lines as we do not have any reference elements.'); return context.root; } const referenceBounds = this.calcReferenceBounds(referenceElements); + this.log('Bounds encompasing all reference elements: ', referenceBounds); const helperLines = this.calcHelperLines(elements, referenceBounds, context); if (referenceElements.length > 1) { context.root.add(new SelectionBounds(referenceBounds)); + this.log('Render selection bounds for more than one reference element:', referenceBounds); } helperLines.forEach(helperLine => context.root.add(helperLine)); + if (helperLines.length > 0) { + this.log(`--> Add ${helperLines.length} helper lines to root:`, helperLines); + } else { + this.log('--> Add no helper lines to root.'); + } return context.root; } - protected isCompareElement(element: GModelElement): element is BoundsAwareModelElement { - return isBoundsAware(element) && findTopLevelElementByFeature(element, isBoundsAware, isViewport) === element; + protected isAlignableElement(element: GModelElement): element is BoundsAwareModelElement { + return isBoundsAware(element) && this.alignableElementFilter(element, this.elementIds); } protected calcReferenceBounds(referenceElements: BoundsAwareModelElement[]): Bounds { - return referenceElements.map(element => element.bounds).reduce((combined, next) => Bounds.combine(combined, next), Bounds.EMPTY); + return referenceElements.map(element => this.calcBounds(element)).reduce(Bounds.combine, Bounds.EMPTY); + } + + protected calcBounds(element: BoundsAwareModelElement): Bounds { + return toAbsoluteBounds(element); } protected calcHelperLines(elements: BoundsAwareModelElement[], bounds: Bounds, context: CommandExecutionContext): HelperLine[] { const helperLines: HelperLine[] = []; const viewport = findParentByFeature(context.root, isViewport); if (viewport) { - helperLines.push(...this.calcHelperLinesForViewport(viewport, bounds)); + helperLines.push(...this.calcHelperLinesForViewport(viewport, bounds, this.viewportLines)); } - elements.flatMap(element => this.calcHelperLinesForElement(element, bounds)).forEach(line => helperLines.push(line)); + elements + .flatMap(element => this.calcHelperLinesForElement(element, bounds, this.elementLines)) + .forEach(line => helperLines.push(line)); return helperLines; } - protected calcHelperLinesForViewport( - root: Viewport & GModelRoot, - bounds: Bounds, - lineTypes: HelperLineType[] = this.action.viewportLines ?? ALL_VIEWPORT_LINE_TYPES - ): HelperLine[] { + protected calcHelperLinesForViewport(root: Viewport & GModelRoot, bounds: Bounds, lineTypes: HelperLineType[]): HelperLine[] { const helperLines: HelperLine[] = []; + this.log('Find helperlines for viewport:', root); const viewportBounds = getViewportBounds(root, root.canvasBounds); if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, viewportBounds, bounds, 2)) { helperLines.push(new HelperLine(topCenter(viewportBounds), bottomCenter(viewportBounds), HelperLineType.Center)); + this.log('- Reference bounds center align with viewport.', viewportBounds); } if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, viewportBounds, bounds, 2)) { helperLines.push(new HelperLine(middleLeft(viewportBounds), middleRight(viewportBounds), HelperLineType.Middle)); + this.log('- Reference bounds middle align with viewport.', viewportBounds); + } + if (helperLines.length > 0) { + this.log(`--> Add ${helperLines.length} helperlines for viewport:`, helperLines); } return helperLines; } - protected calcHelperLinesForElement( - element: BoundsAwareModelElement, - bounds: Bounds, - lineTypes: HelperLineType[] = this.action.elementLines ?? ALL_ELEMENT_LINE_TYPES - ): HelperLine[] { - return this.calcHelperLinesForBounds(element.bounds, bounds, lineTypes); + protected calcHelperLinesForElement(element: BoundsAwareModelElement, bounds: Bounds, lineTypes: HelperLineType[]): HelperLine[] { + this.log('Find helperlines for element:', element); + return this.calcHelperLinesForBounds(this.calcBounds(element), bounds, lineTypes); } - protected calcHelperLinesForBounds( - elementBounds: Bounds, - bounds: Bounds, - lineTypes: HelperLineType[] = this.action.elementLines ?? ALL_ELEMENT_LINE_TYPES - ): HelperLine[] { + protected calcHelperLinesForBounds(elementBounds: Bounds, bounds: Bounds, lineTypes: HelperLineType[]): HelperLine[] { const helperLines: HelperLine[] = []; - if (lineTypes.includes(HelperLineType.Left) && this.isAligned(left, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Left) && this.isAligned(left, elementBounds, bounds, this.alignmentEpsilon)) { const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower helperLines.push(new HelperLine(bottomLeft(below), topLeft(above), HelperLineType.Left)); + this.log('- Reference bounds left align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, elementBounds, bounds, this.alignmentEpsilon)) { const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower helperLines.push(new HelperLine(topCenter(above), bottomCenter(below), HelperLineType.Center)); + this.log('- Reference bounds center align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Right) && this.isAligned(right, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Right) && this.isAligned(right, elementBounds, bounds, this.alignmentEpsilon)) { const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower helperLines.push(new HelperLine(bottomRight(below), topRight(above), HelperLineType.Right)); + this.log('- Reference bounds right align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Bottom) && this.isAligned(bottom, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Bottom) && this.isAligned(bottom, elementBounds, bounds, this.alignmentEpsilon)) { const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right helperLines.push(new HelperLine(bottomLeft(before), bottomRight(after), HelperLineType.Bottom)); + this.log('- Reference bounds bottom align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, elementBounds, bounds, this.alignmentEpsilon)) { const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right helperLines.push(new HelperLine(middleLeft(before), middleRight(after), HelperLineType.Middle)); + this.log('- Reference bounds middle align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Top) && this.isAligned(top, elementBounds, bounds)) { + if (lineTypes.includes(HelperLineType.Top) && this.isAligned(top, elementBounds, bounds, this.alignmentEpsilon)) { const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right helperLines.push(new HelperLine(topLeft(before), topRight(after), HelperLineType.Top)); + this.log('- Reference bounds top align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(left(elementBounds), right(bounds), 2)) { + if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(left(elementBounds), right(bounds), this.alignmentEpsilon)) { if (isAbove(bounds, elementBounds)) { helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.RightLeft)); + this.log('- Reference bounds right aligns with element left', elementBounds); } else { helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.RightLeft)); + this.log('- Reference bounds right aligns with element left', elementBounds); } } - if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(right(elementBounds), left(bounds), 2)) { + if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(right(elementBounds), left(bounds), this.alignmentEpsilon)) { if (isAbove(bounds, elementBounds)) { helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.LeftRight)); + this.log('- Reference bounds left aligns with element right', elementBounds); } else { helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.LeftRight)); + this.log('- Reference bounds left aligns with element right', elementBounds); } } - if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(top(elementBounds), bottom(bounds), 2)) { + if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(top(elementBounds), bottom(bounds), this.alignmentEpsilon)) { if (isBefore(bounds, elementBounds)) { helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.BottomTop)); + this.log('- Reference bounds bottom aligns with element top', elementBounds); } else { helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.BottomTop)); + this.log('- Reference bounds bottom aligns with element top', elementBounds); } } - if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(bottom(elementBounds), top(bounds), 2)) { + if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(bottom(elementBounds), top(bounds), this.alignmentEpsilon)) { if (isBefore(bounds, elementBounds)) { helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.TopBottom)); + this.log('- Reference bounds top aligns with element bottom', elementBounds); } else { helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.TopBottom)); + this.log('- Reference bounds top aligns with element bottom', elementBounds); } } + if (helperLines.length > 0) { + this.log(`--> Add ${helperLines.length} helperlines for element:`, helperLines); + } return helperLines; } - protected isAligned( - coordinate: (elem: Bounds) => number, - leftBounds: Bounds, - rightBounds: Bounds, - epsilon = this.action.alignmentEpsilon ?? 1 - ): boolean { + protected isAligned(coordinate: (elem: Bounds) => number, leftBounds: Bounds, rightBounds: Bounds, epsilon: number): boolean { return this.isMatch(coordinate(leftBounds), coordinate(rightBounds), epsilon); } - protected isMatch(leftCoordinate: number, rightCoordinate: number, epsilon = this.action.alignmentEpsilon ?? 1): boolean { + protected isMatch(leftCoordinate: number, rightCoordinate: number, epsilon: number): boolean { return Math.abs(leftCoordinate - rightCoordinate) < epsilon; } + + protected log(message: string, ...params: any[]): void { + if (this.debug) { + this.logger.log(this, message, params); + } + } } export interface RemoveHelperLinesFeedbackAction extends Action { @@ -253,6 +326,7 @@ export class RemoveHelperLinesFeedbackCommand extends FeedbackCommand { constructor(@inject(TYPES.Action) public action: RemoveHelperLinesFeedbackAction) { super(); } + override execute(context: CommandExecutionContext): CommandReturn { removeHelperLines(context.root); removeSelectionBounds(context.root); @@ -267,3 +341,11 @@ export function removeHelperLines(root: GModelRoot): void { export function removeSelectionBounds(root: GModelRoot): void { forEachElement(root.index, isSelectionBounds, line => root.remove(line)); } + +export function boundsInViewport(element: GModelElement, bounds: Bounds | Point): Bounds | Point { + if (element instanceof GChildElement && !isViewport(element.parent)) { + return boundsInViewport(element.parent, element.parent.localToParent(bounds) as Bounds); + } else { + return bounds; + } +} diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 97d371e..873117b 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -19,8 +19,8 @@ import { GModelElement, GModelRoot, IActionHandler, - ISnapper, MoveAction, + Point, SetBoundsAction, TYPES } from '@eclipse-glsp/sprotty'; @@ -28,33 +28,81 @@ import { inject, injectable, optional, postConstruct } from 'inversify'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; -import { GridSnapper } from '../change-bounds/snap'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; -import { DrawHelperLinesFeedbackAction, RemoveHelperLinesFeedbackAction, ViewportLineType } from './helper-line-feedback'; +import { + AlignmentElementFilter, + DEFAULT_ALIGNABLE_ELEMENT_FILTER, + DEFAULT_DEBUG, + DEFAULT_ELEMENT_LINES, + DEFAULT_EPSILON, + DEFAULT_VIEWPORT_LINES, + DrawHelperLinesFeedbackAction, + RemoveHelperLinesFeedbackAction, + ViewportLineType +} from './helper-line-feedback'; import { IHelperLineManager } from './helper-line-manager'; import { Direction, HelperLineType } from './model'; +export interface IHelperLineOptions { + /** + * A list of helper line types that should be rendered when elements are aligned. + * Defaults to all possible alignments. + */ + elementLines?: HelperLineType[]; + /** + * A list of helper line types that should be rendered when an element is aligned with the viewport. + * Defaults to middle and center alignment. + */ + viewportLines?: ViewportLineType[]; + /** + * The minimum difference between two coordinates + * Defaults to 1. + */ + alignmentEpsilon?: number; + /** + * A filter that is applied to determine on which elements the alignment calculation is performed. + * By default all top-level bounds-aware, non-routable elements that are visible on the canvas are considered. + */ + alignmentElementFilter?: AlignmentElementFilter; + /** + * The minimum move delta that is necessary for an element to break through a helper line. + * Defaults to { x: 1, y: 1 } whereas the x represents the horizontal distance and y represents the vertical distance. + */ + minimumMoveDelta?: Point; + + /** + * Produces debug output. + * Defaults to false. + */ + debug?: boolean; +} + +export const DEFAULT_MOVE_DELTA = { x: 1, y: 1 }; + +export const DEFAULT_HELPER_LINE_OPTIONS: Required = { + elementLines: DEFAULT_ELEMENT_LINES, + viewportLines: DEFAULT_VIEWPORT_LINES, + alignmentEpsilon: DEFAULT_EPSILON, + alignmentElementFilter: DEFAULT_ALIGNABLE_ELEMENT_FILTER, + minimumMoveDelta: DEFAULT_MOVE_DELTA, + debug: DEFAULT_DEBUG +}; + @injectable() export class HelperLineManager implements IActionHandler, ISelectionListener, IHelperLineManager { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(SelectionService) protected selectionService: SelectionService; - @optional() @inject(TYPES.ISnapper) protected snapper?: ISnapper; + @optional() @inject(TYPES.IHelperLineOptions) protected userOptions?: IHelperLineOptions; - protected snapSize = { x: 20, y: 20 }; + protected options: Required; protected feedback: DisposableCollection = new DisposableCollection(); - protected elementLines?: HelperLineType[]; - protected viewportLines?: ViewportLineType[]; - protected alignmentEpsilon?: number; - @postConstruct() protected init(): void { + this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...this.userOptions }; this.selectionService.onSelectionChanged(change => this.selectionChanged(change.root, change.selectedElements, change.deselectedElements) ); - if (this.snapper instanceof GridSnapper) { - this.snapSize = { x: this.snapper.grid.x * 2, y: this.snapper.grid.y * 2 }; - } } handle(action: Action): void { @@ -90,12 +138,7 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH } protected createHelperLineFeedback(elementIds: string[]): DrawHelperLinesFeedbackAction { - return DrawHelperLinesFeedbackAction.create({ - elementIds, - elementLines: this.elementLines, - viewportLines: this.viewportLines, - alignmentEpsilon: this.alignmentEpsilon - }); + return DrawHelperLinesFeedbackAction.create({ elementIds, ...this.options }); } protected handleSetBoundsAction(action: SetBoundsAction | SetBoundsFeedbackAction): void { @@ -108,7 +151,12 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH this.feedback.dispose(); } - getHelperLineSnapping(target: GModelElement, isSnap: boolean, direction: Direction): number { - return direction === Direction.Left || direction === Direction.Right ? this.snapSize.x : this.snapSize.y; + getMinimumMoveDelta(element: GModelElement, isSnap: boolean, direction: Direction): number { + if (!isSnap) { + return 0; + } + return direction === Direction.Left || direction === Direction.Right + ? this.options.minimumMoveDelta.x + : this.options.minimumMoveDelta.y; } } diff --git a/packages/client/src/features/helper-lines/helper-line-manager.ts b/packages/client/src/features/helper-lines/helper-line-manager.ts index be26092..f74266b 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager.ts @@ -17,5 +17,12 @@ import { GModelElement } from '@eclipse-glsp/sprotty'; import { Direction } from './model'; export interface IHelperLineManager { - getHelperLineSnapping(target: GModelElement, isSnap: boolean, direction: Direction): number; + /** + * Calculates the minimum move delta that is necessary to break through a helper line. + * + * @param element element that is being moved + * @param isSnap whether snapping is active or not + * @param direction direction in which the target element is moving + */ + getMinimumMoveDelta(element: GModelElement, isSnap: boolean, direction: Direction): number; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index f91b03b..e33067b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -195,13 +195,13 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements if (event.buttons === 0) { this.mouseUp(target, event); } else if (!this.positionUpdater.isLastDragPositionUndefined()) { - this.pendingMoveInitialized?.cancel(); if (this.elementId2startPos.size === 0) { this.collectStartPositions(target.root); } this._isMouseDrag = true; const moveAction = this.getElementMoves(target, event, false); if (moveAction) { + this.pendingMoveInitialized?.cancel(); result.push(moveAction); result.push(cursorFeedbackAction(CursorCSS.MOVE)); this.tool.registerFeedback(result, this); diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index e42e8de..7842427 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -31,6 +31,7 @@ import { TypeGuard, distinctAdd, findParentByFeature, + getAbsoluteBounds, isBoundsAware, isMoveable, isSelectable, @@ -350,3 +351,14 @@ export function calculateDeltaBetweenPoints(target: Point, source: Point, elemen const adaptedDelta = { x: delta.x / zoom, y: delta.y / zoom }; return adaptedDelta; } + +export function isVisibleOnCanvas(model: BoundsAwareModelElement): boolean { + const modelBounds = getAbsoluteBounds(model); + const canvasBounds = model.root.canvasBounds; + return ( + modelBounds.x <= canvasBounds.width && + modelBounds.x + modelBounds.width >= 0 && + modelBounds.y <= canvasBounds.height && + modelBounds.y + modelBounds.height >= 0 + ); +} diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 7623566..af6027f 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -172,7 +172,8 @@ export { SIssue as GIssue, SIssueSeverity as GIssueSeverity, // Export as is, we extend it glsp-client to `GIssueMarker` - SIssueMarker + SIssueMarker, + isDecoration } from 'sprotty/lib/features/decoration/model'; export * from 'sprotty/lib/features/decoration/views'; diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 7937660..8c763eb 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -41,6 +41,7 @@ export const TYPES = { IEditModeListener: Symbol('IEditModeListener'), IMarqueeBehavior: Symbol('IMarqueeBehavior'), IHelperLineManager: Symbol('IHelperLineManager'), + IHelperLineOptions: Symbol('IHelperLineOptions'), IElementNavigator: Symbol('IElementNavigator'), ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), From 0765cfa92299f7a2bf3abe31e1953d881f091fad Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 23 Jan 2024 12:55:40 +0000 Subject: [PATCH 294/566] v2.1.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index ca847da..ff7909b 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.1.0-next", + "version": "2.1.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -43,7 +43,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.1.0-next", + "@eclipse-glsp/client": "2.1.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 3065b99..daaa1e0 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.1.0-next", + "version": "2.1.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -31,8 +31,8 @@ "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.1.0-next", - "@eclipse-glsp/client": "2.1.0-next" + "@eclipse-glsp-examples/workflow-glsp": "2.1.0", + "@eclipse-glsp/client": "2.1.0" }, "devDependencies": { "@types/shelljs": "0.8.12", diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..5bdb000 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.1.0" } diff --git a/packages/client/package.json b/packages/client/package.json index 7da1f4b..bdc8f95 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.1.0-next", + "version": "2.1.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -47,7 +47,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.1.0-next", + "@eclipse-glsp/sprotty": "2.1.0", "autocompleter": "^9.1.0", "file-saver": "^2.0.5", "lodash": "4.17.21" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 8c360af..0a52212 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.1.0-next", + "version": "2.1.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -34,7 +34,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.1.0-next", + "@eclipse-glsp/protocol": "2.1.0", "autocompleter": "^9.1.0", "snabbdom": "^3.5.1", "sprotty": "1.0.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index d694fe1..a2381ab 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.1.0-next", + "version": "2.1.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 89faeb469837346e519e065b792de0b84d9f12f9 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 23 Jan 2024 14:03:17 +0100 Subject: [PATCH 295/566] Switch to 2.2.0-next versions Prepare the repository for the release iteration --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index ff7909b..9743cc7 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.1.0", + "version": "2.2.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -43,7 +43,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.1.0", + "@eclipse-glsp/client": "^2.2.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index daaa1e0..9e70b7f 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.1.0", + "version": "2.2.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -31,8 +31,8 @@ "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.1.0", - "@eclipse-glsp/client": "2.1.0" + "@eclipse-glsp-examples/workflow-glsp": "^2.2.0-next", + "@eclipse-glsp/client": "^2.2.0-next" }, "devDependencies": { "@types/shelljs": "0.8.12", diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index 5bdb000..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.1.0" + "version": "next" } diff --git a/packages/client/package.json b/packages/client/package.json index bdc8f95..706a82c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.1.0", + "version": "2.2.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -47,7 +47,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.1.0", + "@eclipse-glsp/sprotty": "^2.2.0-next", "autocompleter": "^9.1.0", "file-saver": "^2.0.5", "lodash": "4.17.21" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 0a52212..e132f79 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.1.0", + "version": "2.2.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -34,7 +34,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.1.0", + "@eclipse-glsp/protocol": "^2.2.0-next", "autocompleter": "^9.1.0", "snabbdom": "^3.5.1", "sprotty": "1.0.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index a2381ab..4e31edc 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.1.0", + "version": "2.2.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From f1673ccb91024926e9bc5d38ee9147e4c1934dde Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 23 Jan 2024 15:04:13 +0100 Subject: [PATCH 296/566] v2.1.0 (#72) --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index c5de4ca..aba6552 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.1.0-next", + "version": "2.1.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index d71e1e9..6f1b03d 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.1.0-next", + "version": "2.1.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.1.0-next", - "@eclipse-glsp/server": "2.1.0-next", + "@eclipse-glsp/layout-elk": "2.1.0", + "@eclipse-glsp/server": "2.1.0", "inversify": "^6.0.1" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 8e4346e..53c4bca 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.1.0-next", + "version": "2.1.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.1.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 541c75d..e81b606 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.1.0-next", + "version": "2.1.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.1.0-next", + "@eclipse-glsp/server": "2.1.0", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 73cf765..c522975 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.1.0-next", + "version": "2.1.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -59,8 +59,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.1.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.1.0", + "@eclipse-glsp/protocol": "2.1.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 3d23cc823b887112978e62da894b455663b44997 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 24 Jan 2024 10:33:00 +0100 Subject: [PATCH 297/566] Switch to 2.2.0-next versions (#73) --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index aba6552..10a213e 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.1.0", + "version": "2.2.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 6f1b03d..d2eae87 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.1.0", + "version": "2.2.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.1.0", - "@eclipse-glsp/server": "2.1.0", + "@eclipse-glsp/layout-elk": "2.2.0-next", + "@eclipse-glsp/server": "2.2.0-next", "inversify": "^6.0.1" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 53c4bca..c0c5e19 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.1.0", + "version": "2.2.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.1.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index e81b606..5500d87 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.1.0", + "version": "2.2.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.1.0", + "@eclipse-glsp/server": "2.2.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index c522975..793d5b5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.1.0", + "version": "2.2.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -59,8 +59,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.1.0", - "@eclipse-glsp/protocol": "2.1.0", + "@eclipse-glsp/graph": "^2.2.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 49aea45caf6c7447ac9847a029b8f0ce1d900003 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 2 Feb 2024 15:13:53 +0100 Subject: [PATCH 298/566] Fix release CLI tool (#1226) - Adapt script for updating the server version used in the standalone client example - Update lerna version in releng docker to 7.x --- .../cli/src/commands/release/common.ts | 10 ++------ .../src/commands/release/release-client.ts | 25 ++++++++----------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index 604befd..ef1b9d5 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -43,7 +43,7 @@ export interface ReleaseOptions { export type ReleaseRepository = 'npm' | 'm2' | 'm2&p2' | 'p2'; export interface Component { - type: typeof Component.CLI_CHOICES[number]; + type: (typeof Component.CLI_CHOICES)[number]; releaseRepo: ReleaseRepository; githubRepo: string; directory: string; @@ -114,7 +114,7 @@ export namespace ReleaseType { export const CLI_CHOICES = ['major', 'minor', 'patch', 'rc', 'custom'] as const; } -export type ReleaseType = typeof ReleaseType.CLI_CHOICES[number]; +export type ReleaseType = (typeof ReleaseType.CLI_CHOICES)[number]; export function checkoutAndCd(options: ReleaseOptions): string { const ghUrl = options.component.githubRepo; @@ -244,12 +244,6 @@ export function updateVersion(...packages: { name: string; version: string }[]): }); } -export function updateServerConfig(configFile: string, version: string, isSnapShot: boolean): void { - LOGGER.info('Update example server download config'); - sh.exec(`jq '.isSnapShot=${isSnapShot}| .version="${version}"' ${configFile} > temp.json`, getShellConfig()); - sh.exec(`mv temp.json ${configFile}`, getShellConfig()); -} - export function asMvnVersion(version: string): string { LOGGER.debug(`Convert to maven conform version: ${version}`); const mavenVersion = version.replace('-', '.'); diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index 5af918e..f3b9f71 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -14,17 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sh from 'shelljs'; +import { getShellConfig } from '../../util/command-util'; import { LOGGER } from '../../util/logger'; -import { - checkoutAndCd, - commitAndTag, - lernaSetVersion, - publish, - ReleaseOptions, - updateLernaForDryRun, - updateVersion, - yarnInstall -} from './common'; +import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common'; let REPO_ROOT: string; @@ -32,7 +24,7 @@ export async function releaseClient(options: ReleaseOptions): Promise { LOGGER.info('Prepare glsp-client release'); LOGGER.debug('Release options: ', options.version); REPO_ROOT = checkoutAndCd(options); - updateExternalGLSPDependencies(options.version); + updateDownloadServerScript(options.version); generateChangeLog(); lernaSetVersion(REPO_ROOT, options.version); build(); @@ -43,10 +35,13 @@ export async function releaseClient(options: ReleaseOptions): Promise { publish(REPO_ROOT, options); } -function updateExternalGLSPDependencies(version: string): void { - LOGGER.info('Update external GLSP dependencies (workflow example server)'); - sh.cd(REPO_ROOT); - updateVersion({ name: '@eclipse-glsp-examples/workflow-server-bundled', version }); +async function updateDownloadServerScript(version: string): Promise { + LOGGER.info('Update example server download config'); + sh.cd(`${REPO_ROOT}/examples/workflow-standalone/scripts`); + const configFile = 'config.json'; + LOGGER.info('Update example server download config'); + sh.exec(`jq '.version="${version}"' ${configFile} > temp.json`, getShellConfig()); + sh.exec(`mv temp.json ${configFile}`, getShellConfig()); } function generateChangeLog(): void { From bb6533b30a35f330aa2fd699df0323b7380eb605 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 2 Feb 2024 15:33:55 +0100 Subject: [PATCH 299/566] Update README.md (#1233) * Update README.md * Update dev-packages/README.md Co-authored-by: Martin Fleck --------- Co-authored-by: Martin Fleck --- dev-packages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/README.md b/dev-packages/README.md index f038e5c..767908f 100644 --- a/dev-packages/README.md +++ b/dev-packages/README.md @@ -1,5 +1,4 @@ -# Eclipse GLSP - Dev Packages [![build-status](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp/job/master)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) [![build-status-server](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/deploy-npm-glsp-config/&label=publish)](https://ci.eclipse.org/glsp/job/deploy-npm-glsp-config/) - +# Eclipse GLSP - Dev Packages [![build-status](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp/job/master)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) Common shared development packages for Eclipse GLSP components that are implemented with Typescript. ## Components @@ -13,6 +12,7 @@ Common shared development packages for Eclipse GLSP components that are implemen - [`@eclipse-glsp/config-test`](./config-test//README.md): Provides a meta package that exports common test configuration object on top of `@eclipse-glsp/config`: - [Mocha](https://www.npmjs.com/package/@eclipse-glsp/mocha-config) (`.mocharc`) - [NYC](https://www.npmjs.com/package/@eclipse-glsp/nyc-config): (`.nycrc`) +- [`@eclipse-glsp/dev`](./dev//README.md): Provides an all-in-one meta package that includes `@eclipse-glsp/cli`, `@eclipse-glsp/config` and `@eclipse-glsp/config-dev` The packages are available via npm and are used by all GLSP components implemented with Typescript. From cf9e8232f48c2dff21d76ef65b98833a01d99c9b Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 2 Feb 2024 15:45:44 +0100 Subject: [PATCH 300/566] Update README.md (#1234) Remove wrong mocha entry from "config" Co-authored-by: Tobias Ortmayr --- dev-packages/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/README.md b/dev-packages/README.md index 767908f..e331619 100644 --- a/dev-packages/README.md +++ b/dev-packages/README.md @@ -8,7 +8,6 @@ Common shared development packages for Eclipse GLSP components that are implemen - [Typescript](https://www.typescriptlang.org/) (`tsconfig.json`) - [ESLint](https://eslint.org/) (`.eslintrc`) - [Prettier](https://prettier.io/) (`.prettierrc`). - - [Mocha](https://mochajs.org/) (`.mocharc.json`). - [`@eclipse-glsp/config-test`](./config-test//README.md): Provides a meta package that exports common test configuration object on top of `@eclipse-glsp/config`: - [Mocha](https://www.npmjs.com/package/@eclipse-glsp/mocha-config) (`.mocharc`) - [NYC](https://www.npmjs.com/package/@eclipse-glsp/nyc-config): (`.nycrc`) From b9a705100c434c5f983dbd3ec111918d7882692d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 31 Jan 2024 16:40:10 +0100 Subject: [PATCH 301/566] GLSP-1227: Ensure that model update is correct when using autolayout In the `submmitModelDirectly` method of the `ModelSubmissionHandler` the gmodel serialization was invoked before the layout engine was executed (in automatic layouting mode). As a consequence the model sent to the server reflected the unlayouted state. Fixes https://github.com/eclipse-glsp/glsp/issues/1227 --- .../src/common/features/model/model-submission-handler.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index 4b97e94..ccd78f9 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -133,11 +133,12 @@ export class ModelSubmissionHandler { * @returns A list of actions to be processed in order to submit the model. */ async submitModelDirectly(reason?: DirtyStateChangeReason): Promise { - const root = this.serializeGModel(); - if (this.diagramConfiguration.layoutKind === ServerLayoutKind.AUTOMATIC && this.layoutEngine) { await this.layoutEngine.layout(); } + + const root = this.serializeGModel(); + const result: Action[] = []; result.push( this.requestModelAction From 72b939ef909e70381214db4a908a3465fdc39548 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Thu, 22 Feb 2024 14:35:14 +0100 Subject: [PATCH 302/566] GLSP-1266 Fix handling of zero values in builder functions (#76) * GLSP-1266 Fix handling of zero values in builder functions - Fix handling of zero values in GBoundsAwareBuilder functions - Fix handling of zero values in GAlignableBuilder function Resolves eclipse-glsp/glsp/issues/1266 * GLSP-1266 Add unit tests for GBoundsAware and GAlignable --- packages/graph/src/galignable.spec.ts | 74 ++++++++++++++++ packages/graph/src/galignable.ts | 8 +- packages/graph/src/gbounds-aware.spec.ts | 105 +++++++++++++++++++++++ packages/graph/src/gbounds-aware.ts | 14 ++- 4 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 packages/graph/src/galignable.spec.ts create mode 100644 packages/graph/src/gbounds-aware.spec.ts diff --git a/packages/graph/src/galignable.spec.ts b/packages/graph/src/galignable.spec.ts new file mode 100644 index 0000000..c37df9b --- /dev/null +++ b/packages/graph/src/galignable.spec.ts @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import { GAlignableBuilder, isGAlignable } from './galignable'; +import { GLabel, GLabelBuilder } from './glabel'; +import { GNode, GNodeBuilder } from './gnode'; + +describe('GAlignable Tests', () => { + describe('isGAlignable function', () => { + it('should return true for a GAlignable element', () => { + const gLabelElement = new GLabelBuilder(GLabel) // + .type(DefaultTypes.LABEL) // + .build(); + + const result = isGAlignable(gLabelElement); + expect(result).to.be.true; + }); + + it('should return false for a non-GAlignable element', () => { + const gNodeElement = new GNodeBuilder(GNode) // + .position(100, 50) // + .size(50, 15) // + .type(DefaultTypes.NODE) // + .build(); + + const result = isGAlignable(gNodeElement); + expect(result).to.be.false; + }); + }); + + describe('GAlignableBuilder alignment function', () => { + it('should set alignment using a point object', () => { + const builder = new GLabelBuilder(GLabel); + GAlignableBuilder.alignment(builder, { x: 45, y: 90 }); + expect(builder['proxy'].alignment).to.deep.equal({ x: 45, y: 90 }); + }); + + it('should set alignment using x and y parameters', () => { + let builder = new GLabelBuilder(GLabel); + GAlignableBuilder.alignment(builder, 8, 16); + expect(builder['proxy'].alignment).to.deep.equal({ x: 8, y: 16 }); + + builder = new GLabelBuilder(GLabel); + GAlignableBuilder.alignment(builder, 12, 0); + expect(builder['proxy'].alignment).to.deep.equal({ x: 12, y: 0 }); + }); + + it('should set alignment from point object if y is provided too', () => { + const builder = new GLabelBuilder(GLabel); + GAlignableBuilder.alignment(builder, { x: 17, y: 71 }, 15); + expect(builder['proxy'].alignment).to.deep.equal({ x: 17, y: 71 }); + }); + + it('should default y to 0 if y is not provided', () => { + const builder = new GLabelBuilder(GLabel); + GAlignableBuilder.alignment(builder, 77); + expect(builder['proxy'].alignment).to.deep.equal({ x: 77, y: 0 }); + }); + }); +}); diff --git a/packages/graph/src/galignable.ts b/packages/graph/src/galignable.ts index 214a098..ead156c 100644 --- a/packages/graph/src/galignable.ts +++ b/packages/graph/src/galignable.ts @@ -34,8 +34,14 @@ export namespace GAlignableBuilder { const proxy = builder['proxy']; if (typeof pointOrX === 'object') { proxy.alignment = pointOrX; - } else if (y) { + } else if (y !== undefined) { proxy.alignment = { x: pointOrX, y }; + } else { + // Optionally handle cases where y is not provided + proxy.alignment = { x: pointOrX, y: 0 }; + console.warn( + `Incomplete parameters for GAlignableBuilder.alignment function. Setting alignment to ${JSON.stringify(proxy.alignment)}` + ); } return builder; } diff --git a/packages/graph/src/gbounds-aware.spec.ts b/packages/graph/src/gbounds-aware.spec.ts new file mode 100644 index 0000000..bc2a861 --- /dev/null +++ b/packages/graph/src/gbounds-aware.spec.ts @@ -0,0 +1,105 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import { GBoundsAwareBuilder, isGBoundsAware } from './gbounds-aware'; +import { GEdge, GEdgeBuilder } from './gedge'; +import { GNode, GNodeBuilder } from './gnode'; +import { GShapeElementBuilder } from './gshape-element'; + +describe('GBoundsAware Tests', () => { + describe('isGBoundsAware function', () => { + it('should return true for a GBoundsAware element', () => { + const gNodeElement = new GNodeBuilder(GNode) // + .position(100, 50) // + .size(50, 15) // + .type(DefaultTypes.NODE) // + .build(); + + const result = isGBoundsAware(gNodeElement); + expect(result).to.be.true; + }); + + it('should return false for a non-GBoundsAware element', () => { + const gEdgeElement = new GEdgeBuilder(GEdge) // + .type(DefaultTypes.EDGE) // + .build(); + + const result = isGBoundsAware(gEdgeElement); + expect(result).to.be.false; + }); + }); + + describe('GBoundsAwareBuilder position function', () => { + it('should set position using a point object', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.position(builder, { x: 5, y: 10 }); + expect(builder['proxy'].position).to.deep.equal({ x: 5, y: 10 }); + }); + + it('should set position using x and y parameters', () => { + let builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.position(builder, 5, 10); + expect(builder['proxy'].position).to.deep.equal({ x: 5, y: 10 }); + + builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.position(builder, 5, 0); + expect(builder['proxy'].position).to.deep.equal({ x: 5, y: 0 }); + }); + + it('should set position from point object if y is provided too', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.position(builder, { x: 17, y: 71 }, 15); + expect(builder['proxy'].position).to.deep.equal({ x: 17, y: 71 }); + }); + + it('should default y to 0 if y is not provided', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.position(builder, 35); + expect(builder['proxy'].position).to.deep.equal({ x: 35, y: 0 }); + }); + }); + + describe('GBoundsAwareBuilder size function', () => { + it('should set size using a dimension object', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.size(builder, { width: 55, height: 15 }); + expect(builder['proxy'].size).to.deep.equal({ width: 55, height: 15 }); + }); + + it('should set size using width and height parameters', () => { + let builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.size(builder, 50, 35); + expect(builder['proxy'].size).to.deep.equal({ width: 50, height: 35 }); + + builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.size(builder, 70, 0); + expect(builder['proxy'].size).to.deep.equal({ width: 70, height: 0 }); + }); + + it('should set size from dimension object if height is provided too', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.size(builder, { width: 11, height: 33 }, 15); + expect(builder['proxy'].size).to.deep.equal({ width: 11, height: 33 }); + }); + + it('should default height to 0 if height is not provided', () => { + const builder = new GShapeElementBuilder(GNode); + GBoundsAwareBuilder.size(builder, 60); + expect(builder['proxy'].size).to.deep.equal({ width: 60, height: 0 }); + }); + }); +}); diff --git a/packages/graph/src/gbounds-aware.ts b/packages/graph/src/gbounds-aware.ts index bec116a..ba992bb 100644 --- a/packages/graph/src/gbounds-aware.ts +++ b/packages/graph/src/gbounds-aware.ts @@ -35,8 +35,14 @@ export namespace GBoundsAwareBuilder { const proxy = builder['proxy']; if (typeof pointOrX === 'object') { proxy.position = pointOrX; - } else if (y) { + } else if (y !== undefined) { proxy.position = { x: pointOrX, y }; + } else { + // Optionally handle cases where y is not provided + proxy.position = { x: pointOrX, y: 0 }; + console.warn( + `Incomplete parameters for GBoundsAwareBuilder.position function. Setting position to ${JSON.stringify(proxy.position)}` + ); } return builder; } @@ -45,8 +51,12 @@ export namespace GBoundsAwareBuilder { const proxy = builder['proxy']; if (typeof sizeOrWidth === 'object') { proxy.size = sizeOrWidth; - } else if (height) { + } else if (height !== undefined) { proxy.size = { width: sizeOrWidth, height }; + } else { + // Optionally handle cases where height is not provided + proxy.size = { width: sizeOrWidth, height: 0 }; + console.warn(`Incomplete parameters for GBoundsAwareBuilder.size function. Setting size to ${JSON.stringify(proxy.size)}`); } return builder; } From 47105cd3141ea60819f7a76fb8f93f03e3d8bf9c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sat, 24 Feb 2024 17:26:45 +0100 Subject: [PATCH 303/566] Pin sprotty protocol version to 1.0.0 --- packages/protocol/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 4e31edc..d8bdfb7 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "0.15.0-next.044bba2.13", + "sprotty-protocol": "1.0.0", "uuid": "7.0.3", "vscode-jsonrpc": "^8.0.2" }, From 59356216478d2e43207fa8bc2800750f3dcda617 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 27 Feb 2024 10:14:58 +0100 Subject: [PATCH 304/566] Avoid dependency on 'chai' through exported index Fixes https://github.com/eclipse-glsp/glsp/issues/1269 --- packages/protocol/src/utils/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index 0bb9e30..9ba54bb 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -17,5 +17,7 @@ export * from './array-util'; export * from './di-util'; export * from './disposable'; export * from './event'; -export * from './test-util'; +// we do not export test-util to avoid a dependency on test frameworks such as chai +// however, adopters can still access the file by accessing it through the complete path +// export * from './test-util'; export * from './type-util'; From b7fa2b1d2565bcf1f817a8175e6abc4111dcba92 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 27 Feb 2024 16:35:43 +0100 Subject: [PATCH 305/566] Fix helper line feedback matching - Use <= as comparison operator - Do not match for feedback edge Fixes https://github.com/eclipse-glsp/glsp/issues/1257 --- .../src/features/helper-lines/helper-line-feedback.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index 0c18dd5..5240a50 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -64,6 +64,7 @@ import { isVisibleOnCanvas } from '../../utils/gmodel-util'; import { getViewportBounds, toAbsoluteBounds } from '../../utils/viewpoint-util'; +import { feedbackEdgeEndId, feedbackEdgeId } from '../tools'; import { HelperLine, HelperLineType, SelectionBounds, isHelperLine, isSelectionBounds } from './model'; export type ViewportLineType = typeof HelperLineType.Center | typeof HelperLineType.Middle | string; @@ -90,7 +91,12 @@ export const DEFAULT_ELEMENT_LINES = ALL_ELEMENT_LINE_TYPES; export const DEFAULT_VIEWPORT_LINES = ALL_VIEWPORT_LINE_TYPES; export const DEFAULT_EPSILON = 1; export const DEFAULT_ALIGNABLE_ELEMENT_FILTER = (element: BoundsAwareModelElement): boolean => - isVisibleOnCanvas(element) && !isRoutable(element) && !(element instanceof GLabel) && !isDecoration(element); + isVisibleOnCanvas(element) && + !isRoutable(element) && + !(element instanceof GLabel) && + !(element.id === feedbackEdgeId(element.root)) && + !(element.id === feedbackEdgeEndId(element.root)) && + !isDecoration(element); export const DEFAULT_DEBUG = false; export namespace DrawHelperLinesFeedbackAction { @@ -294,7 +300,7 @@ export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { } protected isMatch(leftCoordinate: number, rightCoordinate: number, epsilon: number): boolean { - return Math.abs(leftCoordinate - rightCoordinate) < epsilon; + return Math.abs(leftCoordinate - rightCoordinate) <= epsilon; } protected log(message: string, ...params: any[]): void { From efda9fd8a817e5040ebe500c0815f33a45e82911 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 2 Feb 2024 16:15:11 +0100 Subject: [PATCH 306/566] GLSP-1223 Align sprotty-protocol version Ensure that 1.0.0 of sprotty-protocol is used in all client pacakges Part of https://github.com/eclipse-glsp/glsp/issues/1223 --- examples/workflow-glsp/package.json | 2 +- examples/workflow-standalone/package.json | 4 ++-- packages/client/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 9743cc7..2cea66a 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -43,7 +43,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "^2.2.0-next", + "@eclipse-glsp/client": "2.2.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 9e70b7f..70c4ccc 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -31,8 +31,8 @@ "watch": "tsc -w -p ./tsconfig.json" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "^2.2.0-next", - "@eclipse-glsp/client": "^2.2.0-next" + "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", + "@eclipse-glsp/client": "2.2.0-next" }, "devDependencies": { "@types/shelljs": "0.8.12", diff --git a/packages/client/package.json b/packages/client/package.json index 706a82c..7cb6575 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -47,7 +47,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "^2.2.0-next", + "@eclipse-glsp/sprotty": "2.2.0-next", "autocompleter": "^9.1.0", "file-saver": "^2.0.5", "lodash": "4.17.21" From d8fc300691df6cb067b12d98b667c68c4de7dda5 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Sun, 10 Mar 2024 12:04:20 +0000 Subject: [PATCH 307/566] Ensure we do not return an invalid anchor point during edge routing (#325) * Ensure we do not return an invalid anchor point during edge routing Fixes https://github.com/eclipse-glsp/glsp/issues/1270 --- packages/glsp-sprotty/src/index.ts | 1 + packages/glsp-sprotty/src/re-exports.ts | 22 ++++- packages/glsp-sprotty/src/routing-override.ts | 88 +++++++++++++++++++ packages/protocol/src/index.ts | 1 + .../protocol/src/sprotty-geometry-point.ts | 33 +++++++ 5 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 packages/glsp-sprotty/src/routing-override.ts create mode 100644 packages/protocol/src/sprotty-geometry-point.ts diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index f24046c..9d5b71b 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -16,4 +16,5 @@ export * from './action-override'; export * from './feature-modules'; export * from './re-exports'; +export * from './routing-override'; export * from './types'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index af6027f..2ce6fd9 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -192,12 +192,26 @@ export * from 'sprotty/lib/features/open/open'; export * from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; -export * from 'sprotty/lib/features/routing/abstract-edge-router'; +export { + DefaultAnchors, + LinearRouteOptions, + Side, + AbstractEdgeRouter as SprottyAbstractEdgeRouter +} from 'sprotty/lib/features/routing/abstract-edge-router'; export * from 'sprotty/lib/features/routing/anchor'; export * from 'sprotty/lib/features/routing/bezier-anchors'; -export * from 'sprotty/lib/features/routing/bezier-edge-router'; +export { + AddRemoveBezierSegmentAction, + AddRemoveBezierSegmentCommand, + BezierMouseListener, + BezierEdgeRouter as SprottyBezierEdgeRouter +} from 'sprotty/lib/features/routing/bezier-edge-router'; export * from 'sprotty/lib/features/routing/manhattan-anchors'; -export * from 'sprotty/lib/features/routing/manhattan-edge-router'; +export { + ManhattanRouterOptions, + ManhattanEdgeRouter as SprottyManhattanEdgeRouter +} from 'sprotty/lib/features/routing/manhattan-edge-router'; + // Alias SModel types export { Connectable, @@ -214,7 +228,7 @@ export { isConnectable } from 'sprotty/lib/features/routing/model'; export * from 'sprotty/lib/features/routing/polyline-anchors'; -export * from 'sprotty/lib/features/routing/polyline-edge-router'; +export { PolylineRouteOptions, PolylineEdgeRouter as SprottyPolylineEdgeRouter } from 'sprotty/lib/features/routing/polyline-edge-router'; export * from 'sprotty/lib/features/routing/routing'; export * from 'sprotty/lib/features/routing/views'; diff --git a/packages/glsp-sprotty/src/routing-override.ts b/packages/glsp-sprotty/src/routing-override.ts new file mode 100644 index 0000000..8bde30c --- /dev/null +++ b/packages/glsp-sprotty/src/routing-override.ts @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Point } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { + SConnectableElementImpl, + SParentElementImpl, + SRoutableElementImpl, + AbstractEdgeRouter as SprottyAbstractEdgeRouter, + BezierEdgeRouter as SprottyBezierEdgeRouter, + ManhattanEdgeRouter as SprottyManhattanEdgeRouter, + PolylineEdgeRouter as SprottyPolylineEdgeRouter +} from 'sprotty'; +import { GConnectableElement, GParentElement, GRoutableElement } from './re-exports'; + +@injectable() +export abstract class AbstractEdgeRouter extends SprottyAbstractEdgeRouter { + override getTranslatedAnchor( + connectable: SConnectableElementImpl, + refPoint: Point, + refContainer: SParentElementImpl, + edge: SRoutableElementImpl, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} + +@injectable() +export class PolylineEdgeRouter extends SprottyPolylineEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} + +@injectable() +export class ManhattanEdgeRouter extends SprottyManhattanEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} + +@injectable() +export class BezierEdgeRouter extends SprottyBezierEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 8dc607a..f3c0e22 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -18,4 +18,5 @@ export * from './client-server-protocol'; export * from './model'; export * from './re-exports'; export * from './sprotty-actions'; +export * from './sprotty-geometry-point'; export * from './utils'; diff --git a/packages/protocol/src/sprotty-geometry-point.ts b/packages/protocol/src/sprotty-geometry-point.ts new file mode 100644 index 0000000..e1090b7 --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-point.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ + +import { Point } from 'sprotty-protocol/lib/utils/geometry'; + +declare module 'sprotty-protocol/lib/utils/geometry' { + namespace Point { + /** + * Type guard to check if a point is valid. For a point to be valid it needs to be defined and have valid x and y coordinates. + * + * @param point the point to be checked for validity + */ + function isValid(point?: Point): point is Point; + } +} + +Point.isValid = (point?: Point): point is Point => point !== undefined && !isNaN(point.x) && !isNaN(point.y); + +export { Point }; From 259a89483b3ee5fcb174e76b4cd2584912f4136f Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 27 Feb 2024 16:40:39 +0100 Subject: [PATCH 308/566] Ensure hidden bounds calculation works in all browser - Do not use 'display: none' for hidden getBBox() calculation - Only re-calculate the bounds for certain elements -- Extend LocalRequestBoundsAction with element IDs -- Restrict calculation to given set of elements if IDs are present - Minor: Fix re-export for decorationModule Fixes https://github.com/eclipse-glsp/glsp/issues/1258 --- packages/client/css/ghost-element.css | 4 ++- packages/client/css/glsp-sprotty.css | 5 ---- .../bounds/glsp-hidden-bounds-updater.ts | 30 ++++++++++++++++--- .../src/features/bounds/local-bounds.ts | 29 ++++++++++++++---- .../bounds/set-bounds-feedback-command.ts | 3 +- .../element-template/add-template-element.ts | 9 +++--- packages/client/src/utils/gmodel-util.ts | 19 +++++++++--- packages/glsp-sprotty/src/re-exports.ts | 1 + 8 files changed, 75 insertions(+), 25 deletions(-) diff --git a/packages/client/css/ghost-element.css b/packages/client/css/ghost-element.css index 2ced756..a9acb53 100644 --- a/packages/client/css/ghost-element.css +++ b/packages/client/css/ghost-element.css @@ -21,5 +21,7 @@ } .ghost-element.hidden { - display: none; + width: 0; + height: 0; + visibility: hidden; } diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 8d17f5c..f5c3862 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -73,11 +73,6 @@ fill: #1d80d1; } -.sprotty-hidden .sprotty-resize-handle { - /** resize handles should not be considered as part of the elements bounds */ - display: none; -} - .sprotty-edge { fill: none; stroke-width: 1.5px; diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 115776e..963ae16 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -16,20 +16,21 @@ import { Action, + BoundsData, ComputedBoundsAction, Deferred, EdgeRouterRegistry, ElementAndRoutingPoints, GModelElement, - GRoutableElement, HiddenBoundsUpdater, IActionDispatcher, + ModelIndexImpl, RequestAction, ResponseAction } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; -import { calcElementAndRoute, isRoutable } from '../../utils/gmodel-util'; +import { BoundsAwareModelElement, calcElementAndRoute, getDescendantIds, isRoutable } from '../../utils/gmodel-util'; import { LocalComputedBoundsAction, LocalRequestBoundsAction } from './local-bounds'; /** @@ -42,8 +43,10 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { @inject(EdgeRouterRegistry) @optional() protected readonly edgeRouterRegistry?: EdgeRouterRegistry; protected element2route: ElementAndRoutingPoints[] = []; - protected edges: GRoutableElement[] = []; - protected nodes: VNode[] = []; + + protected getElement2BoundsData(): Map { + return this['element2boundsData']; + } override decorate(vnode: VNode, element: GModelElement): VNode { super.decorate(vnode, element); @@ -54,6 +57,9 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { } override postUpdate(cause?: Action): void { + if (LocalRequestBoundsAction.is(cause) && cause.elementIDs) { + this.focusOnElements(cause.elementIDs); + } const actions = this.captureActions(() => super.postUpdate(cause)); actions .filter(action => ComputedBoundsAction.is(action)) @@ -61,6 +67,22 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { this.element2route = []; } + protected focusOnElements(elementIDs: string[]): void { + const data = this.getElement2BoundsData(); + if (data.size > 0) { + // expand given IDs to their descendent element IDs as we need their bounding boxes as well + const index = [...data.keys()][0].index; + const relevantIds = new Set(elementIDs.flatMap(elementId => this.expandElementId(elementId, index, elementIDs))); + + // ensure we only keep the bounds of the elements we are interested in + data.forEach((_bounds, element) => !relevantIds.has(element.id) && data.delete(element)); + } + } + + protected expandElementId(id: string, index: ModelIndexImpl, elementIDs: string[]): string[] { + return getDescendantIds(index.getById(id)); + } + protected captureActions(call: () => void): Action[] { const capturingActionDispatcher = new CapturingActionDispatcher(); const actualActionDispatcher = this.actionDispatcher; diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts index 699e343..f02c9ae 100644 --- a/packages/client/src/features/bounds/local-bounds.ts +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -26,21 +26,38 @@ import { GModelRootSchema, RequestBoundsAction, TYPES, - ViewerOptions + ViewerOptions, + hasArrayProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { ServerAction } from '../../base/model/glsp-model-source'; +export interface LocalRequestBoundsAction extends RequestBoundsAction { + elementIDs?: string[]; +} + export namespace LocalRequestBoundsAction { - export function is(object: unknown): object is RequestBoundsAction { - return RequestBoundsAction.is(object) && !ServerAction.is(object); + export function is(object: unknown): object is LocalRequestBoundsAction { + return RequestBoundsAction.is(object) && !ServerAction.is(object) && hasArrayProp(object, 'elementIDs', true); + } + + export function create(newRoot: GModelRootSchema, elementIDs?: string[]): LocalRequestBoundsAction { + return { + ...RequestBoundsAction.create(newRoot), + elementIDs + }; } - export function fromCommand(context: CommandExecutionContext, actionDispatcher: ActionDispatcher, cause?: Action): CommandResult { + export function fromCommand( + { root }: CommandExecutionContext, + actionDispatcher: ActionDispatcher, + cause?: Action, + elementIDs?: string[] + ): CommandResult { // do not modify the main model (modelChanged = false) but request local bounds calculation on hidden model - actionDispatcher.dispatch(RequestBoundsAction.create(context.root as unknown as GModelRootSchema)); + actionDispatcher.dispatch(LocalRequestBoundsAction.create(root as unknown as GModelRootSchema, elementIDs)); return { - model: context.root, + model: root, modelChanged: false, cause }; diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index 5f7f2b0..62b20e4 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -65,6 +65,7 @@ export class SetBoundsFeedbackCommand extends SetBoundsCommand implements Feedba element.layoutOptions = options; } }); - return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action); + const elementIDs = this.action.bounds.map(bounds => bounds.elementId); + return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action, elementIDs); } } diff --git a/packages/client/src/features/element-template/add-template-element.ts b/packages/client/src/features/element-template/add-template-element.ts index 7bc81ba..e897269 100644 --- a/packages/client/src/features/element-template/add-template-element.ts +++ b/packages/client/src/features/element-template/add-template-element.ts @@ -64,13 +64,14 @@ export class AddTemplateElementsFeedbackCommand extends FeedbackCommand { } override execute(context: CommandExecutionContext): CommandResult { - this.action.templates + const templateElements = this.action.templates .map(template => templateToSchema(template, context)) .filter(isNotUndefined) .map(schema => context.modelFactory.createElement(schema)) - .map(element => this.applyRootCssClasses(element, this.action.addClasses, this.action.removeClasses)) - .forEach(templateElement => context.root.add(templateElement)); - return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action); + .map(element => this.applyRootCssClasses(element, this.action.addClasses, this.action.removeClasses)); + templateElements.forEach(templateElement => context.root.add(templateElement)); + const templateElementIDs = templateElements.map(element => element.id); + return LocalRequestBoundsAction.fromCommand(context, this.actionDispatcher, this.action, templateElementIDs); } protected applyRootCssClasses(element: GChildElement, addClasses?: string[], removeClasses?: string[]): GChildElement { diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index 7842427..8e7c25e 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -22,6 +22,7 @@ import { GChildElement, GModelElement, GModelElementSchema, + GParentElement, GRoutableElement, GRoutingHandle, ModelIndexImpl, @@ -30,13 +31,12 @@ import { Selectable, TypeGuard, distinctAdd, - findParentByFeature, getAbsoluteBounds, + getZoom, isBoundsAware, isMoveable, isSelectable, isSelected, - isViewport, remove } from '@eclipse-glsp/sprotty'; @@ -346,8 +346,7 @@ export function findTopLevelElementByFeature( export function calculateDeltaBetweenPoints(target: Point, source: Point, element: GModelElement): Point { const delta = Point.subtract(target, source); - const viewport = findParentByFeature(element, isViewport); - const zoom = viewport?.zoom ?? 1; + const zoom = getZoom(element); const adaptedDelta = { x: delta.x / zoom, y: delta.y / zoom }; return adaptedDelta; } @@ -362,3 +361,15 @@ export function isVisibleOnCanvas(model: BoundsAwareModelElement): boolean { modelBounds.y + modelBounds.height >= 0 ); } + +export function getDescendantIds(element?: GModelElement, skip?: (t: GModelElement) => boolean): string[] { + if (!element || skip?.(element)) { + return []; + } + const parent = element; + const ids = [parent.id]; + if (parent instanceof GParentElement) { + ids.push(...parent.children.flatMap(child => getDescendantIds(child, skip))); + } + return ids; +} diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 2ce6fd9..8180be0 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -173,6 +173,7 @@ export { SIssueSeverity as GIssueSeverity, // Export as is, we extend it glsp-client to `GIssueMarker` SIssueMarker, + decorationFeature, isDecoration } from 'sprotty/lib/features/decoration/model'; export * from 'sprotty/lib/features/decoration/views'; From 39ea986df84b67dbf4d430f493775f2e7e3a6785 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 12 Mar 2024 07:39:41 +0000 Subject: [PATCH 309/566] Ensure we apply feedback actions already on SetModel (#322) - Ensure we apply feedback actions already on SetModel - Avoid code duplication by moving function into feedback dispatcher - Use 'rank' instead of 'priority' for feedback commands - unify - ContributionProvider to replace @multiInject to better time lazy init Fixes https://github.com/eclipse-glsp/glsp/issues/1239 --- .../src/base/action-handler-registry.ts | 43 +++++++++++- packages/client/src/base/default.module.ts | 7 ++ .../feedback/feedback-action-dispatcher.ts | 50 +++++++++++++- .../src/base/feedback/feedback-command.ts | 19 ++++-- packages/client/src/base/feedback/index.ts | 2 + .../src/base/feedback/set-model-command.ts | 45 +++++++++++++ .../src/base/feedback/update-model-command.ts | 53 +++------------ packages/client/src/base/ranked.ts | 14 ++++ .../client/src/base/selection-service.spec.ts | 8 ++- .../bounds/set-bounds-feedback-command.ts | 3 +- .../src/features/hints/type-hint-provider.ts | 10 +-- .../client/src/utils/contribution-provider.ts | 65 +++++++++++++++++++ packages/glsp-sprotty/src/types.ts | 4 +- 13 files changed, 263 insertions(+), 60 deletions(-) create mode 100644 packages/client/src/base/feedback/set-model-command.ts create mode 100644 packages/client/src/utils/contribution-provider.ts diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index 7b13032..b063fb8 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -14,17 +14,56 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { ActionHandlerRegistry } from '@eclipse-glsp/sprotty'; +import { ActionHandlerRegistration, ActionHandlerRegistry, IActionHandler, IActionHandlerInitializer, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, named } from 'inversify'; +import { IContributionProvider } from '../utils/contribution-provider'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { + @inject(TYPES.IContributionProvider) + @named(TYPES.ActionHandlerRegistration) + protected readonly registrations: IContributionProvider; + + @inject(TYPES.IContributionProvider) + @named(TYPES.IActionHandlerInitializer) + protected readonly initializers: IContributionProvider; + + protected initialized = false; + + constructor() { + super([], []); + } + + protected init(): void { + if (!this.initialized) { + this.initialized = true; + this.registrations.getContributions().forEach(registration => this.register(registration.actionKind, registration.factory())); + this.initializers.getContributions().forEach(initializer => this.initializeActionHandler(initializer)); + } + } + + override register(key: string, instance: IActionHandler): void { + this.init(); + super.register(key, instance); + } + + override get(key: string): IActionHandler[] { + this.init(); + return super.get(key); + } + + override initializeActionHandler(initializer: IActionHandlerInitializer): void { + this.init(); + super.initializeActionHandler(initializer); + } + /** * Retrieve a set of all action kinds for which (at least) one * handler is registered * @returns the set of handled action kinds */ getHandledActionKinds(): string[] { + this.init(); return Array.from(this.elements.keys()); } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 6690f86..6317f5c 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -22,6 +22,7 @@ import { MoveCommand, SetDirtyStateAction, SetEditModeAction, + SetModelCommand, TYPES, bindAsService, bindOrRebind, @@ -31,10 +32,12 @@ import { } from '@eclipse-glsp/sprotty'; import '@vscode/codicons/dist/codicon.css'; import '../../css/glsp-sprotty.css'; +import { bindContributionProvider } from '../utils/contribution-provider'; import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; +import { FeedbackAwareSetModelCommand } from './feedback'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; @@ -73,6 +76,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . // Model update initialization ------------------------------------ bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand(context, FeedbackAwareUpdateModelCommand); + rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); bind(GLSPMouseTool).toSelf().inSingletonScope(); bindOrRebind(context, MouseTool).toService(GLSPMouseTool); @@ -84,6 +88,9 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + + bindContributionProvider(bind, TYPES.ActionHandlerRegistration); + bindContributionProvider(bind, TYPES.IActionHandlerInitializer); bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index 708b2bb..1dcd22d 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -13,8 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + Action, + ActionHandlerRegistry, + Command, + CommandActionHandler, + CommandExecutionContext, + Disposable, + IActionDispatcher, + ICommand, + ILogger, + TYPES, + toTypeGuard +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Action, Disposable, IActionDispatcher, ILogger, TYPES } from '@eclipse-glsp/sprotty'; +import { getFeedbackRank } from './feedback-command'; export interface IFeedbackEmitter {} @@ -57,6 +70,16 @@ export interface IFeedbackActionDispatcher { * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. */ getRegisteredFeedback(): Action[]; + + /** + * Retrieves all commands based on the registered feedback actions, ordered by their rank (lowest rank first). + */ + getFeedbackCommands(): Command[]; + + /** + * Applies all current feedback commands to the given command execution context. + */ + applyFeedbackCommands(context: CommandExecutionContext): Promise; } @injectable() @@ -66,6 +89,8 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; @inject(TYPES.ILogger) protected logger: ILogger; + @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable { if (feedbackActions.length > 0) { this.registeredFeedback.set(feedbackEmitter, feedbackActions); @@ -97,6 +122,29 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { return result; } + getFeedbackCommands(): Command[] { + return this.getRegisteredFeedback() + .flatMap(action => this.actionToCommands(action)) + .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise { + const feedbackCommands = this.getFeedbackCommands() ?? []; + if (feedbackCommands?.length > 0) { + const results = feedbackCommands.map(command => command.execute(context)); + await Promise.all(results); + } + } + + protected actionToCommands(action: Action): ICommand[] { + return ( + this.actionHandlerRegistry + ?.get(action.kind) + .filter(toTypeGuard(CommandActionHandler)) + .map(handler => handler.handle(action)) ?? [] + ); + } + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { try { const actionDispatcher = await this.actionDispatcher(); diff --git a/packages/client/src/base/feedback/feedback-command.ts b/packages/client/src/base/feedback/feedback-command.ts index 1cd192a..2d03398 100644 --- a/packages/client/src/base/feedback/feedback-command.ts +++ b/packages/client/src/base/feedback/feedback-command.ts @@ -13,11 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Command, CommandExecutionContext, CommandReturn } from '@eclipse-glsp/sprotty'; +/* eslint-disable deprecation/deprecation */ +import { Command, CommandExecutionContext, CommandReturn, ICommand } from '@eclipse-glsp/sprotty'; +import { Ranked } from '../ranked'; -export abstract class FeedbackCommand extends Command { - // used by the `FeedbackAwareUpdateModelCommand` - readonly priority: number = 0; +export abstract class FeedbackCommand extends Command implements Ranked { + /** @deprecated Use rank instead. Please note that a lower rank implies higher priority, so the order is reversed. */ + readonly priority?: number = 0; + + // backwards compatibility: convert any existing priority to an equivalent rank + readonly rank: number = this.priority ? -this.priority : Ranked.DEFAULT_RANK; undo(context: CommandExecutionContext): CommandReturn { return context.root; @@ -27,3 +32,9 @@ export abstract class FeedbackCommand extends Command { return context.root; } } + +/** Used for backwards compatibility, otherwise use Ranked.getRank or Ranked sort functions. */ +export function getFeedbackRank(command: ICommand): number { + const feedbackCommand = command as Partial; + return feedbackCommand?.priority ? -feedbackCommand.priority : feedbackCommand.rank ?? Ranked.DEFAULT_RANK; +} diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 9a753e0..0ba61ca 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -16,4 +16,6 @@ export * from './css-feedback'; export * from './feedback-action-dispatcher'; export * from './feedback-command'; +export * from './set-model-command'; export * from './update-model-command'; + diff --git a/packages/client/src/base/feedback/set-model-command.ts b/packages/client/src/base/feedback/set-model-command.ts new file mode 100644 index 0000000..71c37d2 --- /dev/null +++ b/packages/client/src/base/feedback/set-model-command.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CommandExecutionContext, GModelRoot, ILogger, SetModelAction, SetModelCommand, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; + +@injectable() +export class FeedbackAwareSetModelCommand extends SetModelCommand { + @inject(TYPES.ILogger) + protected logger: ILogger; + + @inject(TYPES.IFeedbackActionDispatcher) + @optional() + protected feedbackActionDispatcher?: IFeedbackActionDispatcher; + + constructor(@inject(TYPES.Action) action: SetModelAction) { + super(action); + } + + override execute(context: CommandExecutionContext): GModelRoot { + const root = super.execute(context); + this.applyFeedback(root, context); + return root; + } + + protected applyFeedback(newRoot: GModelRoot, context: CommandExecutionContext): void { + // Create a temporary context which defines the `newRoot` as `root` + // This way we do not corrupt the redo/undo behavior of the super class + const tempContext: CommandExecutionContext = { ...context, root: newRoot }; + this.feedbackActionDispatcher?.applyFeedbackCommands(tempContext); + } +} diff --git a/packages/client/src/base/feedback/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts index 18a8f05..d766678 100644 --- a/packages/client/src/base/feedback/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -13,25 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { - ActionHandlerRegistry, Animation, - Command, - CommandActionHandler, CommandExecutionContext, CommandReturn, + GModelRoot, ILogger, MorphEdgesAnimation, - GModelRoot, TYPES, UpdateAnimationData, UpdateModelAction, - UpdateModelCommand, - toTypeGuard + UpdateModelCommand } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { IFeedbackActionDispatcher } from './feedback-action-dispatcher'; -import { FeedbackCommand } from './feedback-command'; /** * A special {@link UpdateModelCommand} that retrieves all registered {@link Action}s from the {@link IFeedbackActionDispatcher} @@ -47,50 +42,18 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { @optional() protected feedbackActionDispatcher: IFeedbackActionDispatcher; - protected actionHandlerRegistry?: ActionHandlerRegistry; - - constructor( - @inject(TYPES.Action) action: UpdateModelAction, - @inject(TYPES.ActionHandlerRegistryProvider) - actionHandlerRegistryProvider: () => Promise - ) { + constructor(@inject(TYPES.Action) action: UpdateModelAction) { super({ animate: true, ...action }); - actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } protected override performUpdate(oldRoot: GModelRoot, newRoot: GModelRoot, context: CommandExecutionContext): CommandReturn { - if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { - // Create a temporary context which defines the `newRoot` as `root` - // This way we do not corrupt the redo/undo behavior of the super class - const tempContext: CommandExecutionContext = { - ...context, - root: newRoot - }; - - const feedbackCommands = this.getFeedbackCommands(this.actionHandlerRegistry); - feedbackCommands.forEach(command => command.execute(tempContext)); - } - + // Create a temporary context which defines the `newRoot` as `root` + // This way we do not corrupt the redo/undo behavior of the super class + const tempContext: CommandExecutionContext = { ...context, root: newRoot }; + this.feedbackActionDispatcher?.applyFeedbackCommands(tempContext); return super.performUpdate(oldRoot, newRoot, context); } - protected getFeedbackCommands(registry: ActionHandlerRegistry): Command[] { - const result: Command[] = []; - this.feedbackActionDispatcher.getRegisteredFeedback().forEach(action => { - const commands = registry - .get(action.kind) - .filter(toTypeGuard(CommandActionHandler)) - .map(handler => handler.handle(action)); - result.push(...commands); - }); - // sort commands descanting by priority - return result.sort((a, b) => this.getPriority(b) - this.getPriority(a)); - } - - protected getPriority(command: Partial): number { - return command.priority ?? 0; - } - // Override the `createAnimations` implementation and remove the animation for edge morphing. Otherwise routing & reconnect // handles flicker after each server update. protected override createAnimations(data: UpdateAnimationData, root: GModelRoot, context: CommandExecutionContext): Animation[] { diff --git a/packages/client/src/base/ranked.ts b/packages/client/src/base/ranked.ts index f1914fb..759236b 100644 --- a/packages/client/src/base/ranked.ts +++ b/packages/client/src/base/ranked.ts @@ -21,11 +21,16 @@ import { AnyObject, hasNumberProp } from '@eclipse-glsp/sprotty'; * orderable by a type or rank/priority. */ export interface Ranked { + /** + * A rank implies the position of this element within a sequence of other ranked elements. + * A lower rank implies a position earlier in the list. + */ rank: number; } export namespace Ranked { export const DEFAULT_RANK = 0; + export function is(object: unknown): object is Ranked { return AnyObject.is(object) && hasNumberProp(object, 'rank'); } @@ -39,4 +44,13 @@ export namespace Ranked { export function getRank(object: unknown): number { return is(object) ? object.rank : DEFAULT_RANK; } + + /** Sort function for lowest rank first. */ + export const sortAsc = (left: unknown, right: unknown): number => getRank(left) - getRank(right); + + /** Sort function for highest rank first. */ + export const sortDesc = (left: unknown, right: unknown): number => getRank(right) - getRank(left); + + /** Default sort function for rank: Lowest rank first */ + export const sort = sortAsc; } diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index eb2ee08..d33bb00 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; +import { Action, Command, CommandExecutionContext, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; @@ -44,6 +44,12 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { const actions = this.getRegisteredFeedback(); return actions.length === 1 ? (actions[0] as SelectFeedbackAction) : undefined; } + + getFeedbackCommands(): Command[] { + return []; + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise {} } class MockSelectionListener implements ISelectionListener { diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index 62b20e4..de64247 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -24,6 +24,7 @@ import { isLayoutContainer } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { Ranked } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { LocalRequestBoundsAction } from './local-bounds'; @@ -48,7 +49,7 @@ export namespace SetBoundsFeedbackAction { export class SetBoundsFeedbackCommand extends SetBoundsCommand implements FeedbackCommand { static override readonly KIND: string = SetBoundsFeedbackAction.KIND; - readonly priority: number = 0; + readonly rank: number = Ranked.DEFAULT_RANK; @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 093298d..772ffc3 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -13,19 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, CommandExecutionContext, Connectable, EdgeTypeHint, - IActionHandler, - RequestTypeHintsAction, GModelElement, GModelElementSchema, GModelRoot, GRoutableElement, GShapeElement, + IActionHandler, + RequestTypeHintsAction, SetTypeHintsAction, ShapeTypeHint, TYPES, @@ -36,16 +35,17 @@ import { isConnectable, moveFeature } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { GEdge } from '../../model'; import { getElementTypeId } from '../../utils/gmodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; import { containerFeature, isContainable, reparentFeature } from './model'; -import { GEdge } from '../../model'; /** * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server @@ -78,7 +78,7 @@ type CanConnectFn = Connectable['canConnect']; @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { public static KIND = ApplyTypeHintsAction.KIND; - public override readonly priority = 10; + public override readonly rank: number = -10; @inject(TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; diff --git a/packages/client/src/utils/contribution-provider.ts b/packages/client/src/utils/contribution-provider.ts new file mode 100644 index 0000000..a489727 --- /dev/null +++ b/packages/client/src/utils/contribution-provider.ts @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (C) 2017 TypeFox and others. + * Modifications: (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 + *******************************************************************************/ +// eslint-disable-next-line max-len +// based on https://github.com/eclipse-theia/theia/blob/9ff0cedff1d591b0eb4be97a05f6d992789d0a24/packages/core/src/common/contribution-provider.ts + +import { BindingContext, TYPES } from '@eclipse-glsp/sprotty'; +import { interfaces } from 'inversify'; + +export interface IContributionProvider { + /** + * @param recursive `true` if the contributions should be collected from the parent containers as well. Otherwise, `false`. + * It is `false` by default. + */ + getContributions(recursive?: boolean): T[]; +} + +class ContainerBasedContributionProvider implements IContributionProvider { + protected services: T[] | undefined; + + constructor( + protected readonly serviceIdentifier: interfaces.ServiceIdentifier, + protected readonly container: interfaces.Container + ) {} + + getContributions(recursive?: boolean): T[] { + if (this.services === undefined) { + const currentServices: T[] = []; + let currentContainer: interfaces.Container | undefined = this.container; + while (currentContainer !== undefined) { + if (currentContainer.isBound(this.serviceIdentifier)) { + try { + currentServices.push(...currentContainer.getAll(this.serviceIdentifier)); + } catch (error) { + console.error(error); + } + } + currentContainer = recursive === true && currentContainer.parent ? currentContainer.parent : undefined; + } + this.services = currentServices; + } + return this.services; + } +} + +export function bindContributionProvider(context: Pick | interfaces.Bind, id: symbol): void { + const bind = typeof context === 'object' ? context.bind.bind(context) : context; + bind(TYPES.IContributionProvider) + .toDynamicValue(ctx => new ContainerBasedContributionProvider(id, ctx.container)) + .inSingletonScope() + .whenTargetNamed(id); +} diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 8c763eb..2bb7cbe 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { TYPES as SprottyTYPES } from 'sprotty'; + /** * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service * identifiers. @@ -46,5 +47,6 @@ export const TYPES = { ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), - IToolManager: Symbol('IToolManager') + IToolManager: Symbol('IToolManager'), + IContributionProvider: Symbol('IContributionProvider') }; From 46e51ceb96ce339aa634d539d6bad93f65b60f80 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 18 Mar 2024 18:38:24 +0100 Subject: [PATCH 310/566] Ensure that markers are properly reinitialize on update Use separate emitters per reason to apply validation feedback actions. --- .../src/features/validation/validate.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index f67fe05..cc56379 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -13,26 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, CommandExecutionContext, CommandReturn, DeleteMarkersAction, + GParentElement, IActionDispatcher, IActionHandler, ICommand, Marker, - GParentElement, SetMarkersAction, TYPES, hasArrayProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { removeCssClasses } from '../../utils/gmodel-util'; -import { GIssueMarker, createGIssue, getOrCreateGIssueMarker, getGIssueMarker, getSeverity } from './issue-marker'; +import { GIssueMarker, createGIssue, getGIssueMarker, getOrCreateGIssueMarker, getSeverity } from './issue-marker'; /** * Feedback emitter sending actions for visualizing model validation feedback and @@ -42,9 +42,7 @@ import { GIssueMarker, createGIssue, getOrCreateGIssueMarker, getGIssueMarker, g export class ValidationFeedbackEmitter implements IFeedbackEmitter { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - - protected registeredFeedbackByReason: Map = new Map(); + protected registeredFeedbackByReason: Map = new Map(); /** * Register the action that should be emitted for visualizing validation feedback. @@ -54,15 +52,24 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { registerValidationFeedbackAction(action: ApplyMarkersAction, reason = ''): void { // De-register feedback and clear existing markers with the same reason const previousFeedbackWithSameReason = this.registeredFeedbackByReason.get(reason); + let emitter: IFeedbackEmitter | undefined; if (previousFeedbackWithSameReason) { - this.feedbackActionDispatcher.deregisterFeedback(this, [previousFeedbackWithSameReason]); - const deleteMarkersAction = DeleteMarkersAction.create(previousFeedbackWithSameReason.markers); - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(deleteMarkersAction)); + emitter = previousFeedbackWithSameReason.emitter; + const deleteMarkersAction = DeleteMarkersAction.create(previousFeedbackWithSameReason.action.markers); + this.feedbackActionDispatcher.deregisterFeedback(emitter, [deleteMarkersAction]); + } + + if (!emitter) { + emitter = this.createEmitter(reason); } // Register new action responsible for applying markers and re-applying them when the model is updated - this.registeredFeedbackByReason.set(reason, action); - this.feedbackActionDispatcher.registerFeedback(this, [...this.registeredFeedbackByReason.values()]); + this.registeredFeedbackByReason.set(reason, { action, emitter }); + this.feedbackActionDispatcher.registerFeedback(emitter, [action]); + } + + protected createEmitter(reason: string): IFeedbackEmitter { + return { id: 'validationFeedbackEmitter_' + reason }; } } From 6df71a3f41ab5f71348f31bd57b8dc60f3c8dfb2 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 17 Mar 2024 18:25:08 +0100 Subject: [PATCH 311/566] Fix copyright header violations --- examples/workflow-glsp/src/workflow-diagram-module.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- packages/client/src/base/action-dispatcher.ts | 2 +- packages/client/src/base/action-handler-registry.ts | 2 +- packages/client/src/base/default.module.ts | 2 +- packages/client/src/base/feedback/feedback-action-dispatcher.ts | 2 +- packages/client/src/base/feedback/feedback-command.ts | 2 +- packages/client/src/base/feedback/index.ts | 2 +- packages/client/src/base/feedback/update-model-command.ts | 2 +- packages/client/src/base/model/glsp-model-source.ts | 2 +- packages/client/src/base/ranked.ts | 2 +- packages/client/src/base/selection-service.spec.ts | 2 +- packages/client/src/base/selection-service.ts | 2 +- .../client/src/features/bounds/glsp-hidden-bounds-updater.ts | 2 +- packages/client/src/features/bounds/local-bounds.ts | 2 +- .../client/src/features/bounds/set-bounds-feedback-command.ts | 2 +- packages/client/src/features/change-bounds/position-snapper.ts | 2 +- .../src/features/element-template/add-template-element.ts | 2 +- .../client/src/features/helper-lines/helper-line-feedback.ts | 2 +- .../src/features/helper-lines/helper-line-manager-default.ts | 2 +- .../client/src/features/helper-lines/helper-line-manager.ts | 2 +- packages/client/src/features/hints/type-hint-provider.ts | 2 +- .../features/tools/change-bounds/change-bounds-tool-feedback.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.spec.ts | 2 +- packages/client/src/utils/contribution-provider.ts | 2 +- packages/client/src/utils/gmodel-util.ts | 2 +- packages/client/src/utils/html-utils.ts | 2 +- packages/glsp-sprotty/src/index.ts | 2 +- packages/glsp-sprotty/src/re-exports.ts | 2 +- packages/glsp-sprotty/src/types.ts | 2 +- packages/protocol/src/action-protocol/base-protocol.spec.ts | 2 +- packages/protocol/src/action-protocol/clipboard.ts | 2 +- packages/protocol/src/action-protocol/contexts.ts | 2 +- packages/protocol/src/action-protocol/edge-modification.ts | 2 +- packages/protocol/src/action-protocol/element-creation.ts | 2 +- packages/protocol/src/action-protocol/element-hover.ts | 2 +- packages/protocol/src/action-protocol/element-navigation.ts | 2 +- packages/protocol/src/action-protocol/element-selection.spec.ts | 2 +- packages/protocol/src/action-protocol/element-selection.ts | 2 +- .../protocol/src/action-protocol/element-type-hints.spec.ts | 2 +- packages/protocol/src/action-protocol/element-type-hints.ts | 2 +- packages/protocol/src/action-protocol/model-data.ts | 2 +- packages/protocol/src/action-protocol/model-edit-mode.ts | 2 +- packages/protocol/src/action-protocol/model-saving.ts | 2 +- packages/protocol/src/action-protocol/node-modification.spec.ts | 2 +- packages/protocol/src/action-protocol/node-modification.ts | 2 +- packages/protocol/src/action-protocol/tool-palette.ts | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- packages/protocol/src/action-protocol/undo-redo.ts | 2 +- packages/protocol/src/action-protocol/viewport.ts | 2 +- packages/protocol/src/index.ts | 2 +- packages/protocol/src/utils/index.ts | 2 +- 52 files changed, 52 insertions(+), 52 deletions(-) diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 8e2d52e..807d27c 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index ffdf448..dfebe6e 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index f3301f2..bb56b5e 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index b063fb8..63e7ca1 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 6317f5c..cc0a7bb 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index 1dcd22d..a80d9c8 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/feedback-command.ts b/packages/client/src/base/feedback/feedback-command.ts index 2d03398..ca4aa58 100644 --- a/packages/client/src/base/feedback/feedback-command.ts +++ b/packages/client/src/base/feedback/feedback-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 0ba61ca..8053f4b 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/update-model-command.ts b/packages/client/src/base/feedback/update-model-command.ts index d766678..873b313 100644 --- a/packages/client/src/base/feedback/update-model-command.ts +++ b/packages/client/src/base/feedback/update-model-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/glsp-model-source.ts b/packages/client/src/base/model/glsp-model-source.ts index bdc2a79..1eae4f7 100644 --- a/packages/client/src/base/model/glsp-model-source.ts +++ b/packages/client/src/base/model/glsp-model-source.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/ranked.ts b/packages/client/src/base/ranked.ts index 759236b..799c189 100644 --- a/packages/client/src/base/ranked.ts +++ b/packages/client/src/base/ranked.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index d33bb00..f88d5cc 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index c0906ef..548a48c 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 963ae16..36453de 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts index f02c9ae..101968d 100644 --- a/packages/client/src/features/bounds/local-bounds.ts +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index de64247..604a84d 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/position-snapper.ts b/packages/client/src/features/change-bounds/position-snapper.ts index 81c9f07..573c49c 100644 --- a/packages/client/src/features/change-bounds/position-snapper.ts +++ b/packages/client/src/features/change-bounds/position-snapper.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/add-template-element.ts b/packages/client/src/features/element-template/add-template-element.ts index e897269..6cbf20c 100644 --- a/packages/client/src/features/element-template/add-template-element.ts +++ b/packages/client/src/features/element-template/add-template-element.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index 5240a50..aa78bf9 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 873117b..5dbcf22 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-manager.ts b/packages/client/src/features/helper-lines/helper-line-manager.ts index f74266b..18f7a57 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 772ffc3..2e092db 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index e33067b..056ea2b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index 838dd74..d1dc414 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/contribution-provider.ts b/packages/client/src/utils/contribution-provider.ts index a489727..b10716b 100644 --- a/packages/client/src/utils/contribution-provider.ts +++ b/packages/client/src/utils/contribution-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2017 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * Modifications: (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index 8e7c25e..a2b4c8c 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/html-utils.ts b/packages/client/src/utils/html-utils.ts index 801cc49..ca1502b 100644 --- a/packages/client/src/utils/html-utils.ts +++ b/packages/client/src/utils/html-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2020 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 9d5b71b..7b20b9e 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 8180be0..67336e6 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 2bb7cbe..20142d7 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/base-protocol.spec.ts b/packages/protocol/src/action-protocol/base-protocol.spec.ts index 3cfe713..9b3f3d2 100644 --- a/packages/protocol/src/action-protocol/base-protocol.spec.ts +++ b/packages/protocol/src/action-protocol/base-protocol.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/clipboard.ts b/packages/protocol/src/action-protocol/clipboard.ts index 36ad9a9..8e7f292 100644 --- a/packages/protocol/src/action-protocol/clipboard.ts +++ b/packages/protocol/src/action-protocol/clipboard.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/contexts.ts b/packages/protocol/src/action-protocol/contexts.ts index bf737e7..0b68e55 100644 --- a/packages/protocol/src/action-protocol/contexts.ts +++ b/packages/protocol/src/action-protocol/contexts.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/edge-modification.ts b/packages/protocol/src/action-protocol/edge-modification.ts index cd8f3bb..d4b22b1 100644 --- a/packages/protocol/src/action-protocol/edge-modification.ts +++ b/packages/protocol/src/action-protocol/edge-modification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-creation.ts b/packages/protocol/src/action-protocol/element-creation.ts index 56b683c..80b892f 100644 --- a/packages/protocol/src/action-protocol/element-creation.ts +++ b/packages/protocol/src/action-protocol/element-creation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-hover.ts b/packages/protocol/src/action-protocol/element-hover.ts index 8cc86ac..6aeda51 100644 --- a/packages/protocol/src/action-protocol/element-hover.ts +++ b/packages/protocol/src/action-protocol/element-hover.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-navigation.ts b/packages/protocol/src/action-protocol/element-navigation.ts index 706448b..92c5e0e 100644 --- a/packages/protocol/src/action-protocol/element-navigation.ts +++ b/packages/protocol/src/action-protocol/element-navigation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-selection.spec.ts b/packages/protocol/src/action-protocol/element-selection.spec.ts index 36ff7a6..43944fb 100644 --- a/packages/protocol/src/action-protocol/element-selection.spec.ts +++ b/packages/protocol/src/action-protocol/element-selection.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts index 20262ed..e10fb89 100644 --- a/packages/protocol/src/action-protocol/element-selection.ts +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-type-hints.spec.ts b/packages/protocol/src/action-protocol/element-type-hints.spec.ts index a5752b2..83d2109 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.spec.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-type-hints.ts b/packages/protocol/src/action-protocol/element-type-hints.ts index 6af66ff..478b38d 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2022 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-data.ts b/packages/protocol/src/action-protocol/model-data.ts index 0b87c28..5187339 100644 --- a/packages/protocol/src/action-protocol/model-data.ts +++ b/packages/protocol/src/action-protocol/model-data.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-edit-mode.ts b/packages/protocol/src/action-protocol/model-edit-mode.ts index f21a1d0..4c0fa08 100644 --- a/packages/protocol/src/action-protocol/model-edit-mode.ts +++ b/packages/protocol/src/action-protocol/model-edit-mode.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2022 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index 8f877ab..6d225fa 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/node-modification.spec.ts b/packages/protocol/src/action-protocol/node-modification.spec.ts index c9f0eb1..8d950fd 100644 --- a/packages/protocol/src/action-protocol/node-modification.spec.ts +++ b/packages/protocol/src/action-protocol/node-modification.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/node-modification.ts b/packages/protocol/src/action-protocol/node-modification.ts index a9cff36..d09d80c 100644 --- a/packages/protocol/src/action-protocol/node-modification.ts +++ b/packages/protocol/src/action-protocol/node-modification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/tool-palette.ts b/packages/protocol/src/action-protocol/tool-palette.ts index 80f8a69..41d29e9 100644 --- a/packages/protocol/src/action-protocol/tool-palette.ts +++ b/packages/protocol/src/action-protocol/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index a2486a0..a614bc3 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/undo-redo.ts b/packages/protocol/src/action-protocol/undo-redo.ts index a219fec..b217605 100644 --- a/packages/protocol/src/action-protocol/undo-redo.ts +++ b/packages/protocol/src/action-protocol/undo-redo.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index 8a677cc..5c895d2 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2022 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index f3c0e22..39539de 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index 9ba54bb..bacbee1 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 29f3fbcf4edb5c284659b663f079b83effe737d4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 17 Mar 2024 00:52:38 +0100 Subject: [PATCH 312/566] GLSP-1179 Switch to composite ts build - Use composite ts build instead of streaming the build command to each package - Execute linting from root instead of streaming lint command to each package - Execute watch from root instead of streaming watch command to each package Part of https://github.com/eclipse-glsp/glsp/issues/1179 --- examples/workflow-glsp/package.json | 4 +--- examples/workflow-glsp/tsconfig.json | 8 +++++++- examples/workflow-standalone/package.json | 11 ++++++----- examples/workflow-standalone/tsconfig.json | 11 ++++++++++- packages/client/package.json | 4 +--- packages/client/tsconfig.json | 10 +++++++++- packages/glsp-sprotty/package.json | 4 +--- packages/glsp-sprotty/tsconfig.json | 9 ++++++++- packages/protocol/package.json | 4 +--- packages/protocol/tsconfig.json | 1 + 10 files changed, 45 insertions(+), 21 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 2cea66a..d49cee7 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -36,10 +36,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf tsconfig.tsbuildinfo lib", + "clean": "rimraf *.tsbuildinfo lib", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build ", "watch": "tsc -w" }, "dependencies": { diff --git a/examples/workflow-glsp/tsconfig.json b/examples/workflow-glsp/tsconfig.json index b4e6fe3..6227c1e 100644 --- a/examples/workflow-glsp/tsconfig.json +++ b/examples/workflow-glsp/tsconfig.json @@ -1,11 +1,17 @@ { "extends": "@eclipse-glsp/ts-config", "compilerOptions": { + "composite": true, "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../../packages/client" + } + ] } diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 70c4ccc..1ea206d 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -22,13 +22,14 @@ "main": "lib", "types": "lib", "scripts": { - "build": "tsc -b && webpack", - "clean": "rimraf lib tsconfig.tsbuildinfo app/bundle.js app/bundle.js.map app/css", + "build": "yarn compile && yarn bundle", + "bundle": "webpack", + "clean": "rimraf lib *.tsbuildinfo app/bundle.js app/bundle.js.map app/css", + "compile": "tsc -b", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "start:exampleServer": "yarn ts-node ./scripts/start-example-server.ts", - "watch": "tsc -w -p ./tsconfig.json" + "watch": "tsc -w && yarn watch:bundle", + "watch:bundle": "webpack -w" }, "dependencies": { "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json index c5b2db8..edcac2a 100644 --- a/examples/workflow-standalone/tsconfig.json +++ b/examples/workflow-standalone/tsconfig.json @@ -1,8 +1,17 @@ { "extends": "@eclipse-glsp/ts-config/tsconfig.json", "compilerOptions": { + "composite": true, "rootDir": "src", "outDir": "lib" }, - "include": ["src", "lib", "css", "app"] + "include": ["src", "lib", "css", "app"], + "references": [ + { + "path": "../../packages/client" + }, + { + "path": "../workflow-glsp" + } + ] } diff --git a/packages/client/package.json b/packages/client/package.json index 7cb6575..568b005 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -37,10 +37,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "test:coverage": "nyc yarn test", diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 1f6d5ad..e2fdc87 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,8 +1,16 @@ { "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { + "composite": true, + "rootDir": "src", + "resolveJsonModule": true, "outDir": "lib", "reactNamespace": "JSX" }, - "include": ["src"] + "include": ["src", "src/**/*.json"], + "references": [ + { + "path": "../glsp-sprotty" + } + ] } diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index e132f79..7eb46e7 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -27,10 +27,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo", + "clean": "rimraf lib *.tsbuildinfo", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "watch": "tsc -w" }, "dependencies": { diff --git a/packages/glsp-sprotty/tsconfig.json b/packages/glsp-sprotty/tsconfig.json index f3d8c20..95cc815 100644 --- a/packages/glsp-sprotty/tsconfig.json +++ b/packages/glsp-sprotty/tsconfig.json @@ -1,8 +1,15 @@ { "extends": "@eclipse-glsp/ts-config", "compilerOptions": { + "composite": true, + "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX" }, - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../protocol" + } + ] } diff --git a/packages/protocol/package.json b/packages/protocol/package.json index d8bdfb7..4ee1d19 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -36,10 +36,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output ", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output ", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "test:coverage": "nyc yarn test", diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index db53a4c..07b938b 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { + "composite": true, "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX" From 2d42029f70682e55b891c8cdbfb42d9b3a2008e5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 22 Mar 2024 07:52:39 +0100 Subject: [PATCH 313/566] Refactor `check-header` script (#1289) Refactor check header script to only consider the `endYear` in copyrigh headers. Previously we also had a mechanism in place to validate the start year of a copyright range. However, this approach was flawed because - It could not consider files that predate the initial inception of the repository - had a hard time with determining the correct start year for files that have been renamed/moved So in the end we opted for ignoring startYear validations anyways and only considered endYear errors. Therefore this PR removes the entire `startYear` validation and we only check for single year or end year violations. This makes the whole check process faster and less error prone. In addition, there was an issue with files that had multiple copyright years defined (e.g. `contribution-provider` in glsp-client). This is now fixed. In addition, align version numbers with the other GLSP projects so that the dev-packages can be included in the Release train. Also: Minor fix for `release-vscode-integration` --- dev-packages/cli/package.json | 4 +- dev-packages/cli/src/commands/check-header.ts | 167 +++++------------- .../release/release-vscode-integration.ts | 3 +- dev-packages/cli/src/util/git-util.ts | 54 ------ dev-packages/config-test/package.json | 6 +- dev-packages/config/package.json | 8 +- dev-packages/dev/package.json | 8 +- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 12 files changed, 61 insertions(+), 199 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 004cc72..4e5559a 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.0.0", + "version": "2.2.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "~2.0.0", + "@eclipse-glsp/config": "2.2.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/cli/src/commands/check-header.ts b/dev-packages/cli/src/commands/check-header.ts index 75c8aad..d1829a2 100644 --- a/dev-packages/cli/src/commands/check-header.ts +++ b/dev-packages/cli/src/commands/check-header.ts @@ -21,14 +21,7 @@ import * as minimatch from 'minimatch'; import * as readline from 'readline-sync'; import * as sh from 'shelljs'; import { baseCommand, configureShell, getShellConfig } from '../util/command-util'; -import { - getChangesOfLastCommit, - getFirstCommit, - getFirstModificationDate, - getInitialCommit, - getLastModificationDate, - getUncommittedChanges -} from '../util/git-util'; +import { getChangesOfLastCommit, getLastModificationDate, getUncommittedChanges } from '../util/git-util'; import { LOGGER } from '../util/logger'; import { validateGitDirectory } from '../util/validation-util'; @@ -40,24 +33,19 @@ export interface HeaderCheckOptions { json: boolean; excludeDefaults: boolean; autoFix: boolean; - severity: Severity; } const checkTypes = ['full', 'changes', 'lastCommit'] as const; -type CheckType = typeof checkTypes[number]; - -const severityTypes = ['error', 'warn', 'ok'] as const; - -type Severity = typeof severityTypes[number]; +type CheckType = (typeof checkTypes)[number]; const DEFAULT_EXCLUDES = ['**/@(node_modules|lib|dist|bundle)/**']; -const YEAR_RANGE_REGEX = /\d{4}(?:-d{4})?/g; -const HEADER_PATTERN = 'Copyright \\([cC]\\) \\d{4}(-d{4})?'; +const YEAR_RANGE_REGEX = /\d{4}/g; +const HEADER_PATTERN = 'Copyright \\([cC]\\) \\d{4}'; const AUTO_FIX_MESSAGE = 'Fix copyright header violations'; export const CheckHeaderCommand = baseCommand() // .name('checkHeaders') - .description('Validates the copyright year range of license header files') + .description('Validates the copyright year range (end year) of license header files') .argument('', 'The starting directory for the check', validateGitDirectory) .addOption( new Option( @@ -80,11 +68,6 @@ export const CheckHeaderCommand = baseCommand() // 'Disables the default excludes patterns. Only explicitly passed exclude patterns (-e, --exclude) are considered' ) .option('-j, --json', 'Also persist validation results as json file', false) - .addOption( - new Option('-s, --severity ', 'The severity of validation results that should be printed.') - .choices(severityTypes) - .default('error', '"error" (only)') - ) .option('-a, --autoFix', 'Auto apply & commit fixes without prompting the user', false) .action(checkHeaders); @@ -127,17 +110,11 @@ function getFiles(rootDir: string, options: HeaderCheckOptions): string[] { } function validate(rootDir: string, files: string[], options: HeaderCheckOptions): ValidationResult[] { - // Derives all files with valid headers, their copyright years and all files with no or invalid headers + // Derives all files with valid headers and all files with no or invalid headers const filesWithHeader = sh.grep('-l', HEADER_PATTERN, files).stdout.trim().split('\n'); - const copyrightYears = sh - .grep(HEADER_PATTERN, files) - .stdout.trim() - .split('\n') - .map(line => line.match(YEAR_RANGE_REGEX)!.map(string => Number.parseInt(string, 10))); const noHeaders = files.filter(file => !filesWithHeader.includes(file)); const results: ValidationResult[] = []; - const allFilesLength = files.length; // Create validation results for all files with no or invalid headers @@ -147,7 +124,7 @@ function validate(rootDir: string, files: string[], options: HeaderCheckOptions) } noHeaders.forEach((file, i) => { printFileProgress(i + 1, allFilesLength, `Validating ${file}`); - results.push({ file: path.resolve(rootDir, file), violation: 'noOrMissingHeader', severity: 'error' }); + results.push({ file: path.resolve(rootDir, file), violation: 'noOrMissingHeader' }); }); // Performance optimization: avoid retrieving the dates for each individual file by precalculating the endYear if possible. @@ -161,22 +138,20 @@ function validate(rootDir: string, files: string[], options: HeaderCheckOptions) // Create validation results for all files with valid headers filesWithHeader.forEach((file, i) => { printFileProgress(i + 1 + noHeadersLength, allFilesLength, `Validating ${file}`); - + const copyrightLine = sh.head({ '-n': 2 }, file).stdout.trim().split('\n')[1]; + const copyRightYears = copyrightLine.match(YEAR_RANGE_REGEX)!; + const currentStartYear = Number.parseInt(copyRightYears[0], 10); + const currentEndYear = copyRightYears[1] ? Number.parseInt(copyRightYears[1], 10) : undefined; const result: DateValidationResult = { - currentStartYear: copyrightYears[i].shift()!, - expectedStartYear: getFirstModificationDate(file, rootDir, AUTO_FIX_MESSAGE)!.getFullYear(), - currentEndYear: copyrightYears[i].shift(), + currentStartYear, + currentEndYear, expectedEndYear: defaultEndYear ?? getLastModificationDate(file, rootDir, AUTO_FIX_MESSAGE)!.getFullYear(), file, - severity: 'ok', violation: 'none' }; - if (result.expectedStartYear === result.expectedEndYear) { - validateSingleYear(result); - } else { - validateTimePeriod(result); - } + validateEndYear(result); + results.push(result); }); @@ -186,57 +161,16 @@ function validate(rootDir: string, files: string[], options: HeaderCheckOptions) return results; } -function validateSingleYear(result: DateValidationResult): void { - const { currentStartYear, expectedStartYear, currentEndYear } = result; - result.violation = 'invalidCopyrightYear'; - result.severity = 'error'; - - if (!currentEndYear) { - if (currentStartYear === expectedStartYear) { - result.violation = 'none'; - result.severity = 'ok'; - } - return; - } - - // Cornercase: For files of the initial contribution the copyright header predates the first git modification date. - // => declare as warning if not part of the initial contribution. - if (expectedStartYear === currentEndYear && currentStartYear < expectedStartYear) { - if (getFirstCommit(result.file) === getInitialCommit()) { - result.violation = 'none'; - result.severity = 'ok'; - } else { - result.severity = 'warn'; - } - } -} - -function validateTimePeriod(result: DateValidationResult): void { - const { currentStartYear, expectedStartYear, expectedEndYear, currentEndYear } = result; +function validateEndYear(result: DateValidationResult): void { + const { currentStartYear, expectedEndYear, currentEndYear } = result; + result.violation = 'invalidEndYear'; - result.violation = 'incorrectCopyrightPeriod'; - result.severity = 'error'; - if (!currentEndYear) { - result.severity = 'error'; - return; - } + const valid = currentEndYear ? currentEndYear === expectedEndYear : currentStartYear === expectedEndYear; - if (currentStartYear === expectedStartYear && currentEndYear === expectedEndYear) { + if (valid) { result.violation = 'none'; - result.severity = 'ok'; return; } - - // Cornercase: For files of the initial contribution the copyright header predates the first git modification date. - // => declare as warning if not part of the initial contribution. - if (currentEndYear === expectedEndYear && currentStartYear < expectedEndYear) { - if (getFirstCommit(result.file) === getInitialCommit()) { - result.violation = 'none'; - result.severity = 'ok'; - } else { - result.severity = 'warn'; - } - } } function printFileProgress(currentFileCount: number, maxFileCount: number, message: string, clear = true): void { @@ -253,14 +187,9 @@ function printFileProgress(currentFileCount: number, maxFileCount: number, messa export function handleValidationResults(rootDir: string, results: ValidationResult[], options: HeaderCheckOptions): void { LOGGER.newLine(); LOGGER.info(`Header validation for ${results.length} files completed`); - const violations = results.filter(result => result.severity === 'error'); + const violations = results.filter(result => result.violation !== 'none'); // Adjust results to print based on configured severity level - let toPrint = results; - if (options.severity === 'error') { - toPrint = violations; - } else if (options.severity === 'warn') { - toPrint = results.filter(result => result.severity !== 'ok'); - } + const toPrint = violations; LOGGER.info(`Found ${toPrint.length} copyright header violations:`); LOGGER.newLine(); @@ -274,9 +203,7 @@ export function handleValidationResults(rootDir: string, results: ValidationResu } if (violations.length > 0 && (options.autoFix || readline.keyInYN('Do you want automatically fix copyright year range violations?'))) { - const toFix = violations.filter( - violation => violation.severity === 'error' && isDateValidationResult(violation) - ) as DateValidationResult[]; + const toFix = violations.filter(violation => isDateValidationResult(violation)) as DateValidationResult[]; fixViolations(rootDir, toFix, options); } @@ -284,43 +211,33 @@ export function handleValidationResults(rootDir: string, results: ValidationResu } function toPrintMessage(result: ValidationResult): string { - const colors: Record = { - error: '\x1b[31m', - warn: '\x1b[33m', - ok: '\x1b[32m' - } as const; - - if ( - isDateValidationResult(result) && - (result.violation === 'incorrectCopyrightPeriod' || result.violation === 'invalidCopyrightYear') - ) { - const expected = - result.expectedStartYear !== result.expectedEndYear - ? `${result.expectedStartYear}-${result.expectedEndYear}` - : result.expectedStartYear.toString(); - const actual = result.currentEndYear ? `${result.currentStartYear}-${result.currentEndYear}` : result.currentStartYear.toString(); - const message = result.violation === 'incorrectCopyrightPeriod' ? 'Invalid copyright period' : 'Invalid copyright year'; - return `${colors[result.severity]} ${message}! Expected '${expected}' but is '${actual}'`; + const error = '\x1b[31m'; + const info = '\x1b[32m'; + + if (isDateValidationResult(result) && result.violation === 'invalidEndYear') { + const expected = result.expectedEndYear.toString(); + const actual = result.currentEndYear + ? `${result.currentEndYear} (${result.currentStartYear}-${result.currentEndYear})` + : result.currentStartYear.toString(); + const message = 'Invalid copyright end year'; + return `${error} ${message}! Expected end year '${expected}' but is '${actual}'`; } else if (result.violation === 'noOrMissingHeader') { - return `${colors[result.severity]} No or invalid copyright header!`; + return `${error} No or invalid copyright header!`; } - return `${colors[result.severity]} OK`; + return `${info} OK`; } function fixViolations(rootDir: string, violations: DateValidationResult[], options: HeaderCheckOptions): void { LOGGER.newLine(); violations.forEach((violation, i) => { printFileProgress(i + 1, violations.length, `Fix ${violation.file}`, false); - const fixedStartYear = - violation.currentStartYear < violation.expectedStartYear ? violation.currentStartYear : violation.expectedStartYear; const currentRange = `${violation.currentStartYear}${violation.currentEndYear ? '-' + violation.currentEndYear : ''}`; - - let fixedRange = `${fixedStartYear}`; - if (violation.expectedEndYear !== violation.expectedStartYear || fixedStartYear !== violation.expectedStartYear) { - fixedRange = `${fixedStartYear}-${violation.expectedEndYear}`; - } + const fixedRange = + violation.currentEndYear || violation.currentStartYear < violation.expectedEndYear + ? `${violation.currentStartYear}-${violation.expectedEndYear}` + : `${violation.expectedEndYear}`; sh.sed('-i', RegExp('Copyright \\([cC]\\) ' + currentRange), `Copyright (c) ${fixedRange}`, violation.file); }); @@ -337,19 +254,17 @@ function fixViolations(rootDir: string, violations: DateValidationResult[], opti // Helper types interface ValidationResult { file: string; - severity: Severity; violation: Violation; } interface DateValidationResult extends ValidationResult { currentStartYear: number; - expectedStartYear: number; currentEndYear?: number; expectedEndYear: number; } function isDateValidationResult(object: ValidationResult): object is DateValidationResult { - return 'currentStartYear' in object && 'expectedStartYear' in object && 'expectedEndYear' in object; + return 'currentStartYear' in object && 'expectedEndYear' in object; } -type Violation = 'none' | 'noOrMissingHeader' | 'incorrectCopyrightPeriod' | 'invalidCopyrightYear'; +type Violation = 'none' | 'noOrMissingHeader' | 'invalidEndYear'; diff --git a/dev-packages/cli/src/commands/release/release-vscode-integration.ts b/dev-packages/cli/src/commands/release/release-vscode-integration.ts index 0dee142..86d400a 100644 --- a/dev-packages/cli/src/commands/release/release-vscode-integration.ts +++ b/dev-packages/cli/src/commands/release/release-vscode-integration.ts @@ -51,7 +51,8 @@ function updateExternalGLSPDependencies(version: string): void { { name: '@eclipse-glsp/protocol', version }, { name: '@eclipse-glsp/client', version }, { name: '@eclipse-glsp-examples/workflow-glsp', version }, - { name: '@eclipse-glsp-examples/workflow-server', version } + { name: '@eclipse-glsp-examples/workflow-server', version }, + { name: '@eclipse-glsp-examples/workflow-server-bundled', version } ); } diff --git a/dev-packages/cli/src/util/git-util.ts b/dev-packages/cli/src/util/git-util.ts index d8748df..13a1f07 100644 --- a/dev-packages/cli/src/util/git-util.ts +++ b/dev-packages/cli/src/util/git-util.ts @@ -84,28 +84,6 @@ export function getLastModificationDate(filePath?: string, repoRoot?: string, ex } return new Date(result.stdout.trim()); } -/** - * Returns the last modification date of a file in a git repo. - * @param filePath The file - * @param repoRoot The path to the repo root. If undefined the current working directory is used. - * @param excludeMessage Only consider commits that don`t match the excludeMessage - * @returns The date or undefined if the file is outside of the git repo. - */ -export function getFirstModificationDate(filePath: string, repoRoot?: string, excludeMessage?: string): Date | undefined { - cdIfPresent(repoRoot); - const additionalArgs = excludeMessage ? `--grep="${excludeMessage}" --invert-grep` : ''; - const result = sh.exec(`git log ${additionalArgs} --pretty="format:%ci" --follow ${filePath}`, getShellConfig()); - if (result.code !== 0) { - return undefined; - } - const datesString = result.stdout.trim(); - if (datesString.length === 0) { - return new Date(); - } - - const date = datesString.split('\n').pop(); - return date ? new Date(date) : undefined; -} export function getFilesOfCommit(commitHash: string, repoRoot?: string): string[] { cdIfPresent(repoRoot); @@ -117,38 +95,6 @@ export function getFilesOfCommit(commitHash: string, repoRoot?: string): string[ return result.stdout.trim().split('\n'); } -/** - * Returns the commit hash of the initial commit of the given repository - * @param repoRoot The path to the repo root. If undefined the current working directory is used. - * @returns The commit hash or undefined if something went wrong. - */ -export function getInitialCommit(repoRoot?: string): string | undefined { - cdIfPresent(repoRoot); - const result = sh.exec('git log --pretty=oneline --reverse', getShellConfig()); - if (result.code !== 0) { - return undefined; - } - const commits = result.stdout.trim(); - if (commits.length === 0) { - return undefined; - } - return commits.substring(0, commits.indexOf(' ')); -} - -/** - * Returns the commit hash of the first commit for a given file (across renames). - * @param repoRoot The path to the repo root. If undefined the current working directory is used. - * @returns The commit hash or undefined if something went wrong. - */ -export function getFirstCommit(filePath: string, repoRoot?: string): string | undefined { - cdIfPresent(repoRoot); - const result = sh.exec(`git log --follow --pretty=format:"%H" ${filePath}`, getShellConfig()); - if (result.code !== 0) { - return undefined; - } - return result.stdout.trim().split('\n').pop(); -} - export function getLatestGithubRelease(path?: string): string { cdIfPresent(path); const release = sh.exec('gh release list --exclude-drafts -L 1', getShellConfig()).stdout.trim().split('\t'); diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 87f0ca7..2a3cd65 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "~2.0.0", - "@eclipse-glsp/nyc-config": "~2.0.0", + "@eclipse-glsp/mocha-config": "2.2.0-next", + "@eclipse-glsp/nyc-config": "2.2.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index b79c090..035bb91 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "~2.0.0", - "@eclipse-glsp/prettier-config": "~2.0.0", - "@eclipse-glsp/ts-config": "~2.0.0", + "@eclipse-glsp/eslint-config": "2.2.0-next", + "@eclipse-glsp/prettier-config": "2.2.0-next", + "@eclipse-glsp/ts-config": "2.2.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 6234f9d..8d1dcbc 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.0.0", + "version": "2.2.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "~2.0.0", - "@eclipse-glsp/config": "~2.0.0", - "@eclipse-glsp/config-test": "~2.0.0" + "@eclipse-glsp/cli": "2.2.0-next", + "@eclipse-glsp/config": "2.2.0-next", + "@eclipse-glsp/config-test": "2.2.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 6165b4d..b2a2af3 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 4eb4961..426c423 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 784f40a..9a6c4f7 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index b86feab..3f40489 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 6eef59e..9014dbf 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.0.0", + "version": "2.2.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From b016b6cb4e61d545503dee8c0be48c2ad1ca6d58 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 12 Apr 2024 16:56:27 +0200 Subject: [PATCH 314/566] GLSP-1298: Add `updateNext` command (#1299) * GLSP-1298: Add `updateNext` command Add `updateNext` command that serves as a replacement for the (currently broken) update:next scripts in GLSP repos. It automatically scanns all workspace pacakges for `next` dependencies, resolves the latest `next` version for these dependencies and then updates the `yarn.lock` by temorary adding a `resolutions` block to the root package.json to enforece an update. Fixes https://github.com/eclipse-glsp/glsp/issues/1298 * Address review feedback --- dev-packages/cli/README.md | 16 +++ dev-packages/cli/package.json | 1 + dev-packages/cli/src/app.ts | 4 +- .../cli/src/commands/release/release.ts | 2 +- dev-packages/cli/src/commands/update-next.ts | 128 ++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 dev-packages/cli/src/commands/update-next.ts diff --git a/dev-packages/cli/README.md b/dev-packages/cli/README.md index 17b0b2b..0204281 100644 --- a/dev-packages/cli/README.md +++ b/dev-packages/cli/README.md @@ -83,6 +83,22 @@ Options: -h, --help display help for command ``` +## updateNext + +```console +$ glsp updateNext -h +Usage: glsp updateNext|u [options] [rootDir] + +Updates all `next` dependencies in GLSP project to the latest version + +Arguments: + rootDir The repository root (default: "") + +Options: + -v, --verbose Enable verbose (debug) log output (default: false) + -h, --help display help for command +``` + ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 4e5559a..8aa8f9c 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -44,6 +44,7 @@ "commander": "^10.0.1", "glob": "^10.3.10", "node-fetch": "^2.6.11", + "node-jq": "^4.3.1", "readline-sync": "^1.4.10", "semver": "^7.5.1", "shelljs": "^0.8.5" diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index c373b1a..73605a5 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -17,6 +17,7 @@ import { CheckHeaderCommand } from './commands/check-header'; import { CoverageReportCommand } from './commands/coverage-report'; import { ReleaseCommand } from './commands/release/release'; +import { UpdateNextCommand } from './commands/update-next'; import { baseCommand } from './util/command-util'; export const COMMAND_VERSION = '1.1.0-next'; @@ -26,6 +27,7 @@ const app = baseCommand() // .name('glsp') .addCommand(CoverageReportCommand) .addCommand(ReleaseCommand) - .addCommand(CheckHeaderCommand); + .addCommand(CheckHeaderCommand) + .addCommand(UpdateNextCommand); app.parse(process.argv); diff --git a/dev-packages/cli/src/commands/release/release.ts b/dev-packages/cli/src/commands/release/release.ts index 16df74a..de71ee1 100644 --- a/dev-packages/cli/src/commands/release/release.ts +++ b/dev-packages/cli/src/commands/release/release.ts @@ -77,9 +77,9 @@ export async function release( cliOptions: ReleaseCmdOptions ): Promise { try { + configureLogger(cliOptions.verbose); LOGGER.debug('Cli options:', cliOptions); configureShell({ silent: !cliOptions.verbose }); - configureLogger(cliOptions.verbose); checkGHCli(); const version = deriveVersion(releaseType, customVersion); const options: ReleaseOptions = { ...cliOptions, component, releaseType, version }; diff --git a/dev-packages/cli/src/commands/update-next.ts b/dev-packages/cli/src/commands/update-next.ts new file mode 100644 index 0000000..4b270dc --- /dev/null +++ b/dev-packages/cli/src/commands/update-next.ts @@ -0,0 +1,128 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as fs from 'fs'; +import { glob } from 'glob'; +import * as jq from 'node-jq'; +import * as path from 'path'; +import * as sh from 'shelljs'; +import { baseCommand, configureShell } from '../util/command-util'; +import { getUncommittedChanges } from '../util/git-util'; +import { LOGGER, configureLogger } from '../util/logger'; +import { validateGitDirectory } from '../util/validation-util'; +export const UpdateNextCommand = baseCommand() // + .name('updateNext') + .alias('u') + .description('Updates all `next` dependencies in GLSP project to the latest version') + .argument('[rootDir]', 'The repository root', validateGitDirectory, process.cwd()) + .option('-v, --verbose', 'Enable verbose (debug) log output', false) + .action(updateNext); + +export async function updateNext(rootDir: string, options: { verbose: boolean }): Promise { + configureLogger(options.verbose); + configureShell({ silent: true, fatal: true }); + + const rootPackage = path.join(rootDir, 'package.json'); + if (getUncommittedChanges(rootDir).includes(rootPackage)) { + LOGGER.warn('Uncommitted changes in root `package.json`. Please commit or stash them before running this command.'); + return; + } + + configureShell({ silent: false, fatal: true }); + + LOGGER.info('Updating next dependencies ...'); + rootDir = path.resolve(rootDir); + const packages = await getWorkspacePackages(rootDir); + LOGGER.debug(`Scanning ${packages.length} packages to derive resolutions`, packages); + const resolutions = await getResolutions(packages); + if (Object.keys(resolutions).length === 0) { + LOGGER.info('No next dependencies found'); + return; + } + LOGGER.info('Upgrade and rebuild packages ...'); + const packageJson = fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'); + LOGGER.debug('Updating package.json with resolutions', resolutions); + fs.writeFileSync(path.join(rootDir, 'package.json'), JSON.stringify({ ...JSON.parse(packageJson), resolutions }, undefined, 2)); + LOGGER.debug('Running yarn install'); + sh.exec('yarn install --ignore-scripts'); + LOGGER.debug('Reverting package.json'); + sh.exec('git checkout HEAD -- package.json'); + LOGGER.debug('Rebuild to update yarn.lock'); + sh.exec('yarn'); + LOGGER.info('Upgrade successfully completed'); +} + +async function getWorkspacePackages(rootDir: string): Promise { + const rootPackage = path.join(rootDir, 'package.json'); + const packages = [rootPackage]; + if (!fs.existsSync(rootPackage)) { + LOGGER.error('No package.json found in root directory'); + process.exit(1); + } + const workspaces = await getWorkspaceConfig(rootPackage); + if (workspaces) { + workspaces + .map(workspace => `${workspace}/**/package.json`) + .forEach(pattern => { + glob.sync(pattern, { + cwd: rootDir, + ignore: ['**/node_modules/**'] + }).forEach(packageJson => packages.push(path.join(rootDir, packageJson))); + }); + } + + return [...new Set(packages)]; +} + +async function getResolutions(packages: string[]): Promise> { + let dependencies: string[] = []; + for (const pkg of packages) { + const deps = await jq.run( + '.dependencies //{} + .devDependencies + .peerDependencies | with_entries(select(.value == "next")) | keys', + pkg, + { + output: 'json' + } + ); + if (Array.isArray(deps)) { + dependencies.push(...deps); + } + } + dependencies = [...new Set(dependencies)]; + LOGGER.debug(`Found ${dependencies.length} 'next' dependencies`, dependencies); + LOGGER.info('Retrieve next versions ... '); + const resolutions: Record = {}; + [...new Set(dependencies)].forEach(dep => { + LOGGER.info(`Retrieving next version for ${dep}`); + const version = sh.exec(`npm view ${dep}@next version`, { silent: true }).stdout.trim(); + resolutions[`**/${dep}`] = version; + }); + return resolutions; +} +async function getWorkspaceConfig(rootPackage: string): Promise { + const result = await jq.run('.workspaces', rootPackage, { output: 'json' }); + if (!result) { + return undefined; + } + if (Array.isArray(result)) { + return result; + } + if (typeof result === 'object' && 'packages' in result && Array.isArray(result.packages)) { + return result.packages; + } + + return undefined; +} From 8c325b30870786885c5ad91e2352a873f3bb7501 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 22 Mar 2024 06:43:06 +0100 Subject: [PATCH 315/566] GLSP-1225: Avoid circular dependency issues in multi services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduce `LazyInjector` that allow deferred retrial of services from the container - Introduce a new `preDiagramLoad` hook for `IDiagramStartup` this hook is calle directly after `DiagramLoader.load` is invoked. This is the central entry point to initialize/activate lazily injected services - Migrate retrieval of multiInjected services to use the `LazyInjector` instead - Defer registration/initialization of multiInjected services from ´postConstruct` to `preDiagramLoad` (where possible) - Migrate `IGModelListener` API from commandstack to the `EditorContextService`. This serves two purposes: - The `EditorContextService` becomes the central component for managing model related context information - The `Commandstack` is removed from the dependency chain for editor context listeners. This should avoid circular dependency issues related to injecting the action provider. - Restructure `di-util` of protocol package into `di` sub directory and add tests Note that base sprotty still uses multi inject for some services (ViewRegistration, ModelElementRegistrations, IVnode post processors etc.) Currently, these bindings don't seem to cause any dependency issues. If we encounter additional issues in the future we might consider fully replacing all sprotty multiinjections with contribution providers Also - Add `inversify` peerDependency to client and glsp-sprotty package. Adding this dependency explicitly allows the TS-LSP to properly resolve inversify for auto-importing and quick fixing of imports. - Add option for advanced inversify logs to standalone example. If the example is opened with `?inversifyLog=true` extensive infos about the inversify resolution tree will be logged - Fix visibility of projection bars on hidden div - Fix test launch configs Fixes https://github.com/eclipse-glsp/glsp/issues/1225 --- examples/workflow-standalone/package.json | 4 +- examples/workflow-standalone/src/di.config.ts | 45 +++- .../workflow-standalone/webpack.config.js | 8 +- packages/client/css/glsp-sprotty.css | 4 + packages/client/package.json | 3 + .../client/src/base/action-dispatcher.spec.ts | 9 +- packages/client/src/base/action-dispatcher.ts | 22 +- .../src/base/action-handler-registry.ts | 71 ++--- packages/client/src/base/command-stack.ts | 46 ++-- packages/client/src/base/default.module.ts | 30 +-- .../client/src/base/editor-context-service.ts | 77 ++++-- .../feedback/feedback-action-dispatcher.ts | 2 +- packages/client/src/base/index.ts | 3 +- .../client/src/base/model/diagram-loader.ts | 35 ++- packages/client/src/base/selection-service.ts | 41 +-- .../src/base/tool-manager/tool-manager.ts | 50 ++-- .../client/src/base/ui-extension-registry.ts | 44 ++++ packages/client/src/base/view/key-tool.ts | 31 ++- packages/client/src/base/view/mouse-tool.ts | 44 +++- .../navigation/navigation-action-handler.ts | 4 +- .../src/features/validation/validate.ts | 2 +- .../client/src/utils/contribution-provider.ts | 65 ----- packages/glsp-sprotty/package.json | 3 + packages/glsp-sprotty/src/types.ts | 4 +- .../src/di/container-configuration.spec.ts | 67 +++++ .../src/di/container-configuration.ts | 82 ++++++ .../protocol/src/di/feature-module.spec.ts | 111 ++++++++ packages/protocol/src/di/feature-module.ts | 87 +++++++ packages/protocol/src/di/index.ts | 19 ++ packages/protocol/src/di/inversify-util.ts | 111 ++++++++ .../protocol/src/di/lazy-injector.spec.ts | 121 +++++++++ packages/protocol/src/di/lazy-injector.ts | 131 ++++++++++ packages/protocol/src/index.ts | 2 + packages/protocol/src/utils/di-util.spec.ts | 114 --------- packages/protocol/src/utils/di-util.ts | 242 ------------------ packages/protocol/src/utils/index.ts | 1 - 36 files changed, 1131 insertions(+), 604 deletions(-) create mode 100644 packages/client/src/base/ui-extension-registry.ts delete mode 100644 packages/client/src/utils/contribution-provider.ts create mode 100644 packages/protocol/src/di/container-configuration.spec.ts create mode 100644 packages/protocol/src/di/container-configuration.ts create mode 100644 packages/protocol/src/di/feature-module.spec.ts create mode 100644 packages/protocol/src/di/feature-module.ts create mode 100644 packages/protocol/src/di/index.ts create mode 100644 packages/protocol/src/di/inversify-util.ts create mode 100644 packages/protocol/src/di/lazy-injector.spec.ts create mode 100644 packages/protocol/src/di/lazy-injector.ts delete mode 100644 packages/protocol/src/utils/di-util.spec.ts delete mode 100644 packages/protocol/src/utils/di-util.ts diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 1ea206d..3d8a93b 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", - "@eclipse-glsp/client": "2.2.0-next" + "@eclipse-glsp/client": "2.2.0-next", + "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { "@types/shelljs": "0.8.12", @@ -42,6 +43,7 @@ "css-loader": "^6.7.1", "inversify": "^6.0.1", "path-browserify": "^1.0.1", + "process": "^0.11.10", "source-map-loader": "^4.0.0", "style-loader": "^3.3.1", "webpack": "^5.74.0", diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 2e1c8ff..6321b0f 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,18 +15,20 @@ ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; import { - accessibilityModule, - bindOrRebind, ConsoleLogger, - createDiagramOptionsModule, IDiagramOptions, LogLevel, STANDALONE_MODULE_CONFIG, TYPES, + accessibilityModule, + bindOrRebind, + createDiagramOptionsModule, toolPaletteModule } from '@eclipse-glsp/client'; import { Container } from 'inversify'; +import { makeLoggerMiddleware } from 'inversify-logger-middleware'; import '../css/diagram.css'; +import { getParameters } from './url-parameters'; export default function createContainer(options: IDiagramOptions): Container { const container = createWorkflowDiagramContainer( createDiagramOptionsModule(options), @@ -39,5 +41,40 @@ export default function createContainer(options: IDiagramOptions): Container { bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); + configureInversifyLogger(container); return container; } + +function configureInversifyLogger(container: Container): void { + const parameters = getParameters(); + if (!parameters.inversifyLog) { + return; + } + const logOptions = { + request: { + bindings: { + activated: true, + cache: false, + constraint: false, + dynamicValue: false, + factory: false, + implementationType: true, + onActivation: false, + provider: false, + scope: true, + serviceIdentifier: true, + type: false + }, + serviceIdentifier: true, + target: { + metadata: true, + name: false, + serviceIdentifier: true + } + }, + time: true + }; + + const logger = makeLoggerMiddleware(logOptions); + container.applyMiddleware(logger); +} diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index 2483ba6..306821e 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2022 TypeFox & others + * Copyright (c) 2019-2024 EclipseSource & others * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,6 +20,9 @@ const buildRoot = path.resolve(__dirname, 'lib'); const appRoot = path.resolve(__dirname, 'app'); var CircularDependencyPlugin = require('circular-dependency-plugin'); +/** + * @type {import('webpack').Configuration} + */ module.exports = { entry: [path.resolve(buildRoot, 'app')], output: { @@ -64,6 +67,9 @@ module.exports = { exclude: /(node_modules|examples)\/./, failOnError: false }), + new webpack.ProvidePlugin({ + process: 'process/browser' + }), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }) ] }; diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index f5c3862..b678936 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -230,6 +230,10 @@ opacity: 1; } +.sprotty.sprotty-hidden .sprotty-projection-bar { + visibility: hidden; +} + .sprotty-projection-bar.vertical.bordered-projection-bar { position: absolute; top: 0; diff --git a/packages/client/package.json b/packages/client/package.json index 568b005..a384982 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -54,6 +54,9 @@ "@types/file-saver": "^2.0.5", "@types/lodash": "4.14.191" }, + "peerDependencies": { + "inversify": "^6.0.1" + }, "publishConfig": { "access": "public" } diff --git a/packages/client/src/base/action-dispatcher.spec.ts b/packages/client/src/base/action-dispatcher.spec.ts index b12a0b2..248cf71 100644 --- a/packages/client/src/base/action-dispatcher.spec.ts +++ b/packages/client/src/base/action-dispatcher.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,19 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ActionHandlerRegistry, IActionHandler, RequestAction, ResponseAction, TYPES } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; import { Container } from 'inversify'; -import { ActionHandlerRegistry, IActionHandler, RequestAction, ResponseAction, TYPES } from '@eclipse-glsp/sprotty'; -import { IDiagramOptions } from '..'; + import { GLSPActionDispatcher } from './action-dispatcher'; import { defaultModule } from './default.module'; +import { IDiagramOptions } from './model'; const container = new Container(); container.load(defaultModule); container.bind(TYPES.IDiagramOptions).toConstantValue(({ clientId: 'client1', diagramType: 'diagramType', - glspClientProvider: async () => ({} as any) + glspClientProvider: async () => ({}) as any })); const registry = container.get(ActionHandlerRegistry); const actionDispatcher = container.get(GLSPActionDispatcher); diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index bb56b5e..c873058 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ActionDispatcher, RequestAction, ResponseAction } from '@eclipse-glsp/sprotty'; +import { Action, ActionDispatcher, EMPTY_ROOT, RequestAction, ResponseAction, SetModelAction } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { OptionalAction } from './model/glsp-model-source'; import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @@ -27,10 +28,25 @@ export class GLSPActionDispatcher extends ActionDispatcher { protected initializationConstraint: ModelInitializationConstraint; override initialize(): Promise { - return super.initialize().then(() => this.startModelInitialization()); + if (!this.initialized) { + this.initialized = this.doInitialize(); + } + return this.initialized; + } + + protected async doInitialize(): Promise { + const registry = await this.actionHandlerRegistryProvider(); + this.actionHandlerRegistry = registry; + if (registry instanceof GLSPActionHandlerRegistry) { + registry.initialize(); + } + this.handleAction(SetModelAction.create(EMPTY_ROOT)).catch(() => { + /* Logged in handleAction method */ + }); + this.startModelInitialization(); } - startModelInitialization(): void { + protected startModelInitialization(): void { if (!this.initializedConstraint) { this.logger.log(this, 'Starting model initialization mode'); this.initializationConstraint.onInitialized(() => this.logger.log(this, 'Model initialization completed')); diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index 63e7ca1..d5f4391 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -14,56 +14,57 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionHandlerRegistration, ActionHandlerRegistry, IActionHandler, IActionHandlerInitializer, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, named } from 'inversify'; -import { IContributionProvider } from '../utils/contribution-provider'; +import { + ActionHandlerRegistration, + ActionHandlerRegistry, + BindingContext, + IActionHandlerInitializer, + LazyInjector, + TYPES, + bindOrRebind +} from '@eclipse-glsp/sprotty'; +import { decorate, inject, injectable, unmanaged } from 'inversify'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { - @inject(TYPES.IContributionProvider) - @named(TYPES.ActionHandlerRegistration) - protected readonly registrations: IContributionProvider; - - @inject(TYPES.IContributionProvider) - @named(TYPES.IActionHandlerInitializer) - protected readonly initializers: IContributionProvider; + @inject(LazyInjector) + protected lazyInjector: LazyInjector; protected initialized = false; constructor() { super([], []); } - - protected init(): void { - if (!this.initialized) { - this.initialized = true; - this.registrations.getContributions().forEach(registration => this.register(registration.actionKind, registration.factory())); - this.initializers.getContributions().forEach(initializer => this.initializeActionHandler(initializer)); - } - } - - override register(key: string, instance: IActionHandler): void { - this.init(); - super.register(key, instance); - } - - override get(key: string): IActionHandler[] { - this.init(); - return super.get(key); - } - - override initializeActionHandler(initializer: IActionHandlerInitializer): void { - this.init(); - super.initializeActionHandler(initializer); - } - /** * Retrieve a set of all action kinds for which (at least) one * handler is registered * @returns the set of handled action kinds */ getHandledActionKinds(): string[] { - this.init(); return Array.from(this.elements.keys()); } + + initialize(): void { + if (this.initialized) { + return; + } + + this.lazyInjector + .getAll(TYPES.ActionHandlerRegistration) + .forEach(registration => this.register(registration.actionKind, registration.factory())); + this.lazyInjector + .getAll(TYPES.IActionHandlerInitializer) + .forEach(initializer => this.initializeActionHandler(initializer)); + } +} + +let baseClassDecorated = false; +export function bindActionHandlerRegistry(context: Omit): void { + context.bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); + bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); + if (!baseClassDecorated) { + decorate(unmanaged(), ActionHandlerRegistry, 0); + decorate(unmanaged(), ActionHandlerRegistry, 1); + baseClassDecorated = true; + } } diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 8a9e559..86604fd 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,50 +17,38 @@ import { CommandStack, Disposable, DisposableCollection, - Emitter, Event, GModelRoot, ICommand, + LazyInjector, SetModelCommand, - TYPES, UpdateModelCommand } from '@eclipse-glsp/sprotty'; -import { injectable, multiInject, optional, preDestroy } from 'inversify'; - -/** - * A hook to listen for model root changes. Will be called after a server update - * has been processed - */ -export interface IGModelRootListener { - modelRootChanged(root: Readonly): void; -} - -/** - * @deprecated Use {@link IGModelRootListener} instead - */ -export type ISModelRootListener = IGModelRootListener; +import { inject, injectable, preDestroy } from 'inversify'; +import { EditorContextService } from './editor-context-service'; @injectable() export class GLSPCommandStack extends CommandStack implements Disposable { - @multiInject(TYPES.IGModelRootListener) - @optional() - protected modelRootListeners: IGModelRootListener[] = []; + @inject(LazyInjector) + protected lazyInjector: LazyInjector; protected toDispose = new DisposableCollection(); - protected override initialize(): void { - super.initialize(); - this.toDispose.push(this.onModelRootChangedEmitter); - this.modelRootListeners.forEach(listener => this.onModelRootChanged(root => listener.modelRootChanged(root))); - } - @preDestroy() dispose(): void { this.toDispose.dispose(); } - protected onModelRootChangedEmitter = new Emitter>(); + // Use lazyInjector to resolve circular dependency + // GLSPActionDispatcher --> GLSPCommandStack --> EditorContextService --> GLSPActionDispatcher + get editorContext(): EditorContextService { + return this.lazyInjector.get(EditorContextService); + } + + /** + * @deprecated Use the `EditorContext.onModelRootChanged` event instead + */ get onModelRootChanged(): Event> { - return this.onModelRootChangedEmitter.event; + return this.editorContext.onModelRootChanged; } override undo(): Promise { @@ -88,6 +76,6 @@ export class GLSPCommandStack extends CommandStack implements Disposable { } protected notifyListeners(root: Readonly): void { - this.onModelRootChangedEmitter.fire(root); + this.editorContext.notifyModelRootChanged(root, this); } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index cc0a7bb..597ac3b 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -14,17 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - ActionHandlerRegistry, FeatureModule, - KeyTool, LocationPostprocessor, - MouseTool, MoveCommand, SetDirtyStateAction, SetEditModeAction, SetModelCommand, TYPES, bindAsService, + bindLazyInjector, bindOrRebind, configureActionHandler, configureCommand, @@ -32,9 +30,8 @@ import { } from '@eclipse-glsp/sprotty'; import '@vscode/codicons/dist/codicon.css'; import '../../css/glsp-sprotty.css'; -import { bindContributionProvider } from '../utils/contribution-provider'; import { GLSPActionDispatcher } from './action-dispatcher'; -import { GLSPActionHandlerRegistry } from './action-handler-registry'; +import { bindActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; import { FeedbackAwareSetModelCommand } from './feedback'; @@ -51,8 +48,9 @@ import { SelectionClearingMouseListener } from './selection-clearing-mouse-liste import { SelectionService } from './selection-service'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; -import { GLSPKeyTool } from './view/key-tool'; -import { GLSPMouseTool } from './view/mouse-tool'; +import { bindUIExtensionRegistry } from './ui-extension-registry'; +import { bindKeyTool } from './view/key-tool'; +import { bindMouseTool } from './view/mouse-tool'; import { GViewRegistry } from './view/view-registry'; /** @@ -64,7 +62,10 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . sprottyDefaultModule.registry(bind, unbind, isBound, rebind, ...rest); const context = { bind, unbind, isBound, rebind }; + bindLazyInjector(context); + bind(EditorContextService).toSelf().inSingletonScope(); + bind(TYPES.IDiagramStartup).toService(EditorContextService); bind(TYPES.IEditorContextServiceProvider).toProvider(ctx => async () => ctx.container.get(EditorContextService)); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); @@ -78,10 +79,8 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . configureCommand(context, FeedbackAwareUpdateModelCommand); rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); - bind(GLSPMouseTool).toSelf().inSingletonScope(); - bindOrRebind(context, MouseTool).toService(GLSPMouseTool); - bind(GLSPKeyTool).toSelf().inSingletonScope(); - bindOrRebind(context, KeyTool).toService(GLSPKeyTool); + bindMouseTool(context); + bindKeyTool(context); bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); @@ -89,10 +88,7 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); - bindContributionProvider(bind, TYPES.ActionHandlerRegistration); - bindContributionProvider(bind, TYPES.IActionHandlerInitializer); - bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); - bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); + bindActionHandlerRegistry(context); bindAsService(context, TYPES.ModelSource, GLSPModelSource); bind(DiagramLoader).toSelf().inSingletonScope(); @@ -103,6 +99,8 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); bind(SelectionService).toSelf().inSingletonScope(); + bind(TYPES.IGModelRootListener).toService(SelectionService); + bind(TYPES.IDiagramStartup).toService(SelectionService); // Feedback Support ------------------------------------ // Generic re-usable feedback modifying css classes @@ -115,9 +113,11 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . // Tool manager initialization ------------------------------------ bind(TYPES.IToolManager).to(ToolManager).inSingletonScope(); + bind(TYPES.IDiagramStartup).toService(TYPES.IToolManager); bind(DefaultToolsEnablingKeyListener).toSelf().inSingletonScope(); bind(TYPES.KeyListener).toService(DefaultToolsEnablingKeyListener); bind(ToolManagerActionHandler).toSelf().inSingletonScope(); configureActionHandler(context, EnableDefaultToolsAction.KIND, ToolManagerActionHandler); configureActionHandler(context, EnableToolsAction.KIND, ToolManagerActionHandler); + bindUIExtensionRegistry(context); }); diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 290fd02..e8308f7 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,30 +13,50 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; import { Action, + AnyObject, Args, + CommandStack, Disposable, DisposableCollection, EditMode, EditorContext, Emitter, Event, + GModelElement, + GModelRoot, IActionHandler, + LazyInjector, MaybePromise, MousePositionTracker, - GModelElement, - GModelRoot, SetDirtyStateAction, SetEditModeAction, TYPES, ValueChange } from '@eclipse-glsp/sprotty'; +import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { GLSPActionDispatcher } from './action-dispatcher'; import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; import { SelectionService } from './selection-service'; +/** + * A hook to listen for model root changes. Will be called after a server update + * has been processed + */ +export interface IGModelRootListener { + modelRootChanged(root: Readonly): void; +} + +/** + * @deprecated Use {@link IGModelRootListener} instead + */ +export type ISModelRootListener = IGModelRootListener; + +/** + * A hook to listen for edit mode changes. Will be after the {@link EditorContextService} + * has handled the {@link SetEditModeAction}. + */ export interface IEditModeListener { editModeChanged(newValue: string, oldValue: string): void; } @@ -62,13 +82,12 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + @inject(TYPES.IDiagramOptions) protected diagramOptions: IDiagramOptions; - @multiInject(TYPES.IEditModeListener) - @optional() - protected editModeListeners: IEditModeListener[] = []; - @inject(GLSPActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @@ -84,15 +103,27 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this.onDirtyStateChangedEmitter.event; } + protected _modelRoot?: Readonly; + protected onModelRootChangedEmitter = new Emitter>(); + get onModelRootChanged(): Event> { + return this.onModelRootChangedEmitter.event; + } + protected toDispose = new DisposableCollection(); @postConstruct() protected initialize(): void { this._editMode = this.diagramOptions.editMode ?? EditMode.EDITABLE; this.toDispose.push(this.onEditModeChangedEmitter, this.onDirtyStateChangedEmitter); - this.editModeListeners.forEach(listener => - this.onEditModeChanged(change => listener.editModeChanged(change.newValue, change.oldValue)) - ); + } + + preLoadDiagram(): MaybePromise { + this.lazyInjector.getAll(TYPES.IGModelRootListener).forEach(listener => { + this.onModelRootChanged(event => listener.modelRootChanged(event)); + }); + this.lazyInjector.getAll(TYPES.IEditModeListener).forEach(listener => { + this.onEditModeChanged(event => listener.editModeChanged(event.newValue, event.oldValue)); + }); } @preDestroy() @@ -116,6 +147,21 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra }; } + /** + * Notifies the service about a model root change. This method should not be called + * directly. It is called by the `CommandStack` after a model update has been processed. + * @throws an error if the notifier is not a `CommandStack` + * @param root the new model root + * @param notifier the object that triggered the model root change + */ + notifyModelRootChanged(root: Readonly, notifier: AnyObject): void { + if (!(notifier instanceof CommandStack)) { + throw new Error('Invalid model root change notification. Notifier is not an instance of `CommandStack`.'); + } + this._modelRoot = root; + this.onModelRootChangedEmitter.fire(root); + } + handle(action: Action): void { if (SetEditModeAction.is(action)) { this.handleSetEditModeAction(action); @@ -154,7 +200,10 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra } get modelRoot(): Readonly { - return this.selectionService.getModelRoot(); + if (!this._modelRoot) { + throw new Error('Model root not available yet'); + } + return this._modelRoot; } get selectedElements(): Readonly[] { @@ -168,10 +217,6 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra get isDirty(): boolean { return this._isDirty; } - - postRequestModel(): MaybePromise { - this.actionDispatcher.dispatch(SetEditModeAction.create(this.editMode)); - } } export type EditorContextServiceProvider = () => Promise; diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index a80d9c8..b7f8491 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -139,7 +139,7 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { protected actionToCommands(action: Action): ICommand[] { return ( this.actionHandlerRegistry - ?.get(action.kind) + .get(action.kind) .filter(toTypeGuard(CommandActionHandler)) .map(handler => handler.handle(action)) ?? [] ); diff --git a/packages/client/src/base/index.ts b/packages/client/src/base/index.ts index 66976fb..b38ba91 100644 --- a/packages/client/src/base/index.ts +++ b/packages/client/src/base/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,4 +28,5 @@ export * from './ranked'; export * from './selection-clearing-mouse-listener'; export * from './selection-service'; export * from './tool-manager'; +export * from './ui-extension-registry'; export * from './view'; diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 926a568..e9255d0 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,6 +21,7 @@ import { EMPTY_ROOT, GLSPClient, InitializeParameters, + LazyInjector, MaybePromise, RequestModelAction, SetModelAction, @@ -28,7 +29,7 @@ import { TYPES, hasNumberProp } from '@eclipse-glsp/sprotty'; -import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; import { GLSPModelSource } from './glsp-model-source'; @@ -69,22 +70,27 @@ export interface IDiagramOptions { */ export interface IDiagramStartup extends Partial { /** - * Hook for services that should be executed before the underlying GLSP client is configured and the server is initialized. + * Hook for services that want to execute code before the diagram loading routine is started. This is the + * first hook that is invoked directly after {@link DiagramLoader.load} is called. + */ + preLoadDiagram?(): MaybePromise; + /** + * Hook for services that want to execute code before the underlying GLSP client is configured and the server is initialized. */ preInitialize?(): MaybePromise; /** - * Hook for services that should be executed before the initial model loading request (i.e. `RequestModelAction`) but + * Hook for services that want to execute code before the initial model loading request (i.e. `RequestModelAction`) but * after the underlying GLSP client has been configured and the server is initialized. */ preRequestModel?(): MaybePromise; /** - * Hook for services that should be executed after the initial model loading request (i.e. `RequestModelAction`). + * Hook for services that want to execute code after the initial model loading request (i.e. `RequestModelAction`). * Note that this hook is invoked directly after the `RequestModelAction` has been dispatched. It does not necessarily wait * until the client-server update roundtrip is completed. If you need to wait until the diagram is fully initialized use the * {@link postModelInitialization} hook. */ postRequestModel?(): MaybePromise; - /* Hook for services that should be executed after the diagram model is fully initialized + /* Hook for services that want to execute code after the diagram model is fully initialized * (i.e. `ModelInitializationConstraint` is completed). */ postModelInitialization?(): MaybePromise; @@ -95,7 +101,8 @@ export namespace IDiagramStartup { return ( AnyObject.is(object) && hasNumberProp(object, 'rank', true) && - ('preInitialize' in object || + ('preLoadDiagram' in object || + 'preInitialize' in object || 'preRequestModel' in object || 'postRequestModel' in object || 'postModelInitialization' in object) @@ -144,22 +151,22 @@ export class DiagramLoader { @inject(GLSPActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; - @multiInject(TYPES.IDiagramStartup) - @optional() - protected diagramStartups: IDiagramStartup[] = []; - @inject(GLSPModelSource) protected modelSource: GLSPModelSource; @inject(ModelInitializationConstraint) protected modelInitializationConstraint: ModelInitializationConstraint; - @postConstruct() - protected postConstruct(): void { - this.diagramStartups.sort((a, b) => Ranked.getRank(a) - Ranked.getRank(b)); + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + + get diagramStartups(): IDiagramStartup[] { + return this.lazyInjector.getAll(TYPES.IDiagramStartup); } async load(options: DiagramLoadingOptions = {}): Promise { + this.diagramStartups.sort(Ranked.sort); + await this.invokeStartupHook('preLoadDiagram'); const resolvedOptions: ResolvedDiagramLoadingOptions = { requestModelOptions: { sourceUri: this.options.sourceUri ?? '', diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index 548a48c..a1e55ce 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -25,6 +25,7 @@ import { GModelElement, GModelRoot, ILogger, + LazyInjector, SelectAction, SelectAllAction, Selectable, @@ -35,10 +36,11 @@ import { isSelectable, pluck } from '@eclipse-glsp/sprotty'; -import { inject, injectable, multiInject, optional, postConstruct, preDestroy } from 'inversify'; +import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { getElements, getMatchingElements } from '../utils/gmodel-util'; -import { GLSPCommandStack, IGModelRootListener } from './command-stack'; +import { IGModelRootListener } from './editor-context-service'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; +import { IDiagramStartup } from './model'; export interface ISelectionListener { selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; @@ -51,35 +53,34 @@ export interface SelectionChange { } @injectable() -export class SelectionService implements IGModelRootListener, Disposable { - protected root: Readonly; - protected selectedElementIDs: Set = new Set(); - +export class SelectionService implements IGModelRootListener, Disposable, IDiagramStartup { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + @inject(TYPES.ILogger) protected logger: ILogger; - @inject(TYPES.ICommandStack) - protected commandStack: GLSPCommandStack; - - @multiInject(TYPES.ISelectionListener) - @optional() - protected selectionListeners: ISelectionListener[] = []; + protected root: Readonly; + protected selectedElementIDs: Set = new Set(); protected toDispose = new DisposableCollection(); @postConstruct() protected initialize(): void { - this.toDispose.push( - this.onSelectionChangedEmitter, - this.commandStack.onModelRootChanged(root => this.modelRootChanged(root)) - ); - - this.selectionListeners.forEach(listener => - this.onSelectionChanged(change => listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements)) - ); + this.toDispose.push(this.onSelectionChangedEmitter); + } + + preLoadDiagram(): void { + this.lazyInjector + .getAll('selectionListener') + .forEach(listener => + this.onSelectionChanged(change => + listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements) + ) + ); } @preDestroy() diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts index 4f7a6e9..b6169cd 100644 --- a/packages/client/src/base/tool-manager/tool-manager.ts +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,19 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, multiInject, optional } from 'inversify'; import { Action, EditMode, + GModelElement, IActionHandler, ICommand, KeyListener, - GModelElement, + LazyInjector, + MaybePromise, TYPES, distinctAdd, matchesKeystroke } from '@eclipse-glsp/sprotty'; -import { EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; +import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../editor-context-service'; +import { IDiagramStartup } from '../model'; +import { Ranked } from '../ranked'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from './tool'; /** * A tool manager coordinates the state of tools in the context of an editor. @@ -75,27 +79,23 @@ export interface IToolManager { * registration of tools via Dependency Injection. */ @injectable() -export class ToolManager implements IToolManager { - readonly actives: Tool[] = []; +export class ToolManager implements IToolManager, IDiagramStartup { + @inject(EditorContextService) + protected editorContext: EditorContextService; + + @inject(LazyInjector) + protected readonly lazyInjector: LazyInjector; - protected editorContext?: EditorContextService; - - constructor( - @multiInject(TYPES.ITool) - @optional() - readonly tools: Tool[] = [], - @multiInject(TYPES.IDefaultTool) - @optional() - readonly defaultTools: Tool[], - @inject(TYPES.IEditorContextServiceProvider) protected contextServiceProvider: EditorContextServiceProvider - ) { - this.registerTools(...this.tools); - this.registerDefaultTools(...this.defaultTools); + readonly actives: Tool[] = []; + readonly tools: Tool[] = []; + readonly defaultTools: Tool[] = []; + + preLoadDiagram(): MaybePromise { + const tools: Tool[] = this.lazyInjector.getAll(TYPES.ITool); + const defaultTools: Tool[] = this.lazyInjector.getAll(TYPES.IDefaultTool); + this.registerTools(...tools); + this.registerDefaultTools(...defaultTools); this.enableDefaultTools(); - this.contextServiceProvider().then(editorContext => { - editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); - this.editorContext = editorContext; - }); } get managedTools(): Tool[] { @@ -106,6 +106,10 @@ export class ToolManager implements IToolManager { return this.actives; } + get rank(): number { + return Ranked.DEFAULT_RANK - 100; + } + registerDefaultTools(...tools: Tool[]): void { distinctAdd(this.defaultTools, ...tools); } diff --git a/packages/client/src/base/ui-extension-registry.ts b/packages/client/src/base/ui-extension-registry.ts new file mode 100644 index 0000000..03ab1ab --- /dev/null +++ b/packages/client/src/base/ui-extension-registry.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { BindingContext, IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry, bindOrRebind } from '@eclipse-glsp/sprotty'; +import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { IDiagramStartup } from './model'; + +@injectable() +export class GLSPUIExtensionRegistry extends UIExtensionRegistry implements IDiagramStartup { + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + + constructor() { + super([]); + } + + preLoadDiagram(): MaybePromise { + this.lazyInjector.getAll(TYPES.IUIExtension).forEach(extension => this.register(extension.id(), extension)); + } +} + +let baseClassDecorated = false; +export function bindUIExtensionRegistry(context: Omit): void { + context.bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); + bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); + context.bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); + if (!baseClassDecorated) { + decorate(unmanaged(), UIExtensionRegistry, 0); + baseClassDecorated = true; + } +} diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts index 9f29998..cacdd5e 100644 --- a/packages/client/src/base/view/key-tool.ts +++ b/packages/client/src/base/view/key-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,13 +13,36 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { Disposable, KeyListener, KeyTool } from '@eclipse-glsp/sprotty'; +import { BindingContext, Disposable, KeyListener, KeyTool, LazyInjector, MaybePromise, TYPES, bindOrRebind } from '@eclipse-glsp/sprotty'; +import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { IDiagramStartup } from '../model'; @injectable() -export class GLSPKeyTool extends KeyTool { +export class GLSPKeyTool extends KeyTool implements IDiagramStartup { + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + + constructor() { + super([]); + } + registerListener(keyListener: KeyListener): Disposable { super.register(keyListener); return Disposable.create(() => this.deregister(keyListener)); } + + preLoadDiagram(): MaybePromise { + this.lazyInjector.getAll(TYPES.KeyListener).forEach(listener => this.register(listener)); + } +} + +let baseClassDecorated = false; +export function bindKeyTool(context: Omit): void { + context.bind(GLSPKeyTool).toSelf().inSingletonScope(); + bindOrRebind(context, KeyTool).toService(GLSPKeyTool); + context.bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); + if (!baseClassDecorated) { + decorate(unmanaged(), KeyTool, 0); + baseClassDecorated = true; + } } diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index d7e595c..dcb4724 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, multiInject, optional } from 'inversify'; -import { Action, Disposable, MouseListener, MouseTool, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; +import { + Action, + BindingContext, + Disposable, + GModelElement, + GModelRoot, + LazyInjector, + MaybePromise, + MouseListener, + MouseTool, + TYPES, + bindOrRebind +} from '@eclipse-glsp/sprotty'; +import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { IDiagramStartup } from '../model'; import { Ranked } from '../ranked'; /** @@ -24,12 +37,18 @@ import { Ranked } from '../ranked'; type MouseListenerMethods = keyof Omit; @injectable() -export class GLSPMouseTool extends MouseTool { +export class GLSPMouseTool extends MouseTool implements IDiagramStartup { + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + protected rankedMouseListeners: Map; - constructor(@multiInject(TYPES.MouseListener) @optional() protected override mouseListeners: MouseListener[] = []) { - super(mouseListeners); - this.rankedMouseListeners = groupBy(mouseListeners, listener => Ranked.getRank(listener)); + constructor() { + super([]); + } + + preLoadDiagram(): MaybePromise { + this.lazyInjector.getAll(TYPES.MouseListener).forEach(listener => this.register(listener)); } override register(mouseListener: MouseListener): void { @@ -88,6 +107,17 @@ export class GLSPMouseTool extends MouseTool { } } +let baseClassDecorated = false; +export function bindMouseTool(context: Omit): void { + context.bind(GLSPMouseTool).toSelf().inSingletonScope(); + bindOrRebind(context, MouseTool).toService(GLSPMouseTool); + context.bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); + if (!baseClassDecorated) { + decorate(unmanaged(), MouseTool, 0); + baseClassDecorated = true; + } +} + function groupBy(array: Array, keyFunction: (x: T) => K): Map { const unsortedMap = array.reduce((result: Map, item: T) => { const key = keyFunction(item); diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index 2f31af2..b255d33 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, ActionHandlerRegistry, @@ -38,6 +37,7 @@ import { hasObjectProp, hasStringProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; import { NavigationTargetResolver } from './navigation-target-resolver'; diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index cc56379..612ef89 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/contribution-provider.ts b/packages/client/src/utils/contribution-provider.ts deleted file mode 100644 index b10716b..0000000 --- a/packages/client/src/utils/contribution-provider.ts +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017-2024 TypeFox and others. - * Modifications: (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 - *******************************************************************************/ -// eslint-disable-next-line max-len -// based on https://github.com/eclipse-theia/theia/blob/9ff0cedff1d591b0eb4be97a05f6d992789d0a24/packages/core/src/common/contribution-provider.ts - -import { BindingContext, TYPES } from '@eclipse-glsp/sprotty'; -import { interfaces } from 'inversify'; - -export interface IContributionProvider { - /** - * @param recursive `true` if the contributions should be collected from the parent containers as well. Otherwise, `false`. - * It is `false` by default. - */ - getContributions(recursive?: boolean): T[]; -} - -class ContainerBasedContributionProvider implements IContributionProvider { - protected services: T[] | undefined; - - constructor( - protected readonly serviceIdentifier: interfaces.ServiceIdentifier, - protected readonly container: interfaces.Container - ) {} - - getContributions(recursive?: boolean): T[] { - if (this.services === undefined) { - const currentServices: T[] = []; - let currentContainer: interfaces.Container | undefined = this.container; - while (currentContainer !== undefined) { - if (currentContainer.isBound(this.serviceIdentifier)) { - try { - currentServices.push(...currentContainer.getAll(this.serviceIdentifier)); - } catch (error) { - console.error(error); - } - } - currentContainer = recursive === true && currentContainer.parent ? currentContainer.parent : undefined; - } - this.services = currentServices; - } - return this.services; - } -} - -export function bindContributionProvider(context: Pick | interfaces.Bind, id: symbol): void { - const bind = typeof context === 'object' ? context.bind.bind(context) : context; - bind(TYPES.IContributionProvider) - .toDynamicValue(ctx => new ContainerBasedContributionProvider(id, ctx.container)) - .inSingletonScope() - .whenTargetNamed(id); -} diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 7eb46e7..c76d025 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -43,6 +43,9 @@ "@vscode/codicons": "^0.0.25", "snabbdom-to-html": "^7.1.0" }, + "peerDependencies": { + "inversify": "^6.0.1" + }, "publishConfig": { "access": "public" } diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 20142d7..1c8bb45 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -48,5 +48,7 @@ export const TYPES = { IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), IToolManager: Symbol('IToolManager'), - IContributionProvider: Symbol('IContributionProvider') + IContributionProvider: Symbol('IContributionProvider'), + IContributionInitializer: Symbol('IContributionInitializer'), + IServiceProvider: Symbol('IServiceProvider') }; diff --git a/packages/protocol/src/di/container-configuration.spec.ts b/packages/protocol/src/di/container-configuration.spec.ts new file mode 100644 index 0000000..178b72c --- /dev/null +++ b/packages/protocol/src/di/container-configuration.spec.ts @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { initializeContainer } from './container-configuration'; +import { FeatureModule } from './feature-module'; + +const sandbox = sinon.createSandbox(); +const container = new Container(); +const loadSpy = sandbox.spy(container, 'load'); +container.snapshot(); + +const moduleA = new FeatureModule(() => {}); +const moduleB = new FeatureModule(() => {}); +const moduleC = new FeatureModule(() => {}); + +describe('Container configuration', () => { + describe('initializeContainer', () => { + beforeEach(() => { + sandbox.reset(); + container.restore(); + container.snapshot(); + }); + it('should load the given container modules', () => { + initializeContainer(container, moduleA, moduleB, moduleC); + expect(loadSpy.calledOnce).to.equal(true); + expect(loadSpy.firstCall.args).to.deep.equal([moduleA, moduleB, moduleC]); + }); + it('should load the same container module only once', () => { + initializeContainer(container, moduleA, moduleA); + expect(loadSpy.calledOnce).to.equal(true); + expect(loadSpy.firstCall.args).to.deep.equal([moduleA]); + }); + it('should load the given container modules and add configurations', () => { + initializeContainer(container, moduleA, { add: [moduleB, moduleC] }); + expect(loadSpy.calledOnce).to.equal(true); + expect(loadSpy.firstCall.args).to.deep.equal([moduleA, moduleB, moduleC]); + }); + it('should load the given container modules/add configurations and not load modules from remove configurations', () => { + initializeContainer(container, moduleA, { + add: [moduleB, moduleC], + remove: moduleA + }); + expect(loadSpy.calledOnce).to.equal(true); + expect(loadSpy.firstCall.args).to.deep.equal([moduleB, moduleC]); + }); + it('should load a module from a remove configuration if it is added later again', () => { + initializeContainer(container, moduleA, { remove: moduleA }, moduleA); + expect(loadSpy.calledOnce).to.equal(true); + expect(loadSpy.firstCall.args).to.deep.equal([moduleA]); + }); + }); +}); diff --git a/packages/protocol/src/di/container-configuration.ts b/packages/protocol/src/di/container-configuration.ts new file mode 100644 index 0000000..ed3ff45 --- /dev/null +++ b/packages/protocol/src/di/container-configuration.ts @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Container, ContainerModule } from 'inversify'; +import { MaybeArray, asArray, distinctAdd, hasFunctionProp, hasNumberProp, remove } from '../utils'; + +/** + * Initializes a container with the given {@link ContainerConfiguration}. The container configuration + * consists of the set of {@link ContainerModule}s that should be loaded in the container. + * In addition, for more fine-grained control {@link ModuleConfiguration}s can be passed as part fo the container configuration + * Module loading is distinct,this means each module will only get loaded once even if it is configured multiple times. + @param containerConfigurations + * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. + @returns The initialized container. + */ +export function initializeContainer(container: Container, ...containerConfigurations: ContainerConfiguration): Container { + const modules = resolveContainerConfiguration(...containerConfigurations); + container.load(...modules); + return container; +} + +/** + * Processes the given container configurations and returns the corresponding set of {@link ContainerModule}s. + * Container configurations are processed in the order they are passed. If a module is configured to be removed + * it can be added again in a later configuration. + * @param containerConfigurations The container configurations to resolves + * @returns an Array of resolved container modules + */ +export function resolveContainerConfiguration(...containerConfigurations: ContainerConfiguration): ContainerModule[] { + const modules: ContainerModule[] = []; + containerConfigurations.forEach(config => { + if (isContainerModule(config)) { + distinctAdd(modules, config); + } else { + if (config.remove) { + remove(modules, ...asArray(config.remove)); + } + if (config.add) { + distinctAdd(modules, ...asArray(config.add)); + } + } + }); + return modules; +} +/** + * The container modules might originate form different inversify contexts (e.g. `inversify` vs. `@theia/core/shared/inversify`). + * If this is the case an instanceof check can return false negative. + * => use a simple typeguard instead. + */ +function isContainerModule(config: ContainerModule | ModuleConfiguration): config is ContainerModule { + return hasNumberProp(config, 'id') && hasFunctionProp(config, 'registry'); +} + +/** + * Union type for the set of {@link ContainerModule}s and addition {@link ModuleConfiguration}s + * used to configure a DI container. + */ +export type ContainerConfiguration = Array; + +/** + * Can be passed to create DI container utility functions to configure additional modules or + * remove (i.e. not load) default modules. + */ +export interface ModuleConfiguration { + /** Set of modules that should be loaded into the container. Loading */ + add?: MaybeArray; + /** Set of modules that should be loaded into the container */ + remove?: MaybeArray; +} diff --git a/packages/protocol/src/di/feature-module.spec.ts b/packages/protocol/src/di/feature-module.spec.ts new file mode 100644 index 0000000..0c562ea --- /dev/null +++ b/packages/protocol/src/di/feature-module.spec.ts @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import { FeatureModule } from './feature-module'; +describe('FeatureModule', () => { + const container = new Container(); + + const moduleA = new FeatureModule(() => {}); + const moduleB = new FeatureModule(() => {}); + + afterEach(() => { + container.unbindAll(); + }); + + describe('No or single required module', () => { + it('Should load a feature module with no required module', () => { + const moduleWithNoRequirements = new FeatureModule(bind => { + bind('Foo').toConstantValue('Foo'); + }); + container.load(moduleWithNoRequirements); + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; + expect(container.isBound('Foo')).to.be.true; + }); + it('Should load a feature module with met required module', () => { + const moduleWithNoRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleA, moduleWithNoRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; + expect(container.isBound('Foo')).to.be.true; + }); + it('Should not load a feature module if required module is loaded afterwards', () => { + const moduleWithNoRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleWithNoRequirements, moduleA); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + it('Should not load a feature module with missing required module', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: moduleA } + ); + container.load(moduleWithUnmetRequirements); + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + }); + describe('Multiple required modules', () => { + it('Should load feature module with loaded required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleA, moduleWithUnmetRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + }); + it('Should not load a feature module with missing required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleWithUnmetRequirements); + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); + it('Should not load a feature module with partially loaded required modules', () => { + const moduleWithUnmetRequirements = new FeatureModule( + bind => { + bind('Foo').toConstantValue('Foo'); + }, + { requires: [moduleA, moduleB] } + ); + container.load(moduleA, moduleWithUnmetRequirements); + expect(container.isBound(moduleA.featureId)).to.be.true; + expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; + expect(container.isBound('Foo')).to.be.false; + }); +}); diff --git a/packages/protocol/src/di/feature-module.ts b/packages/protocol/src/di/feature-module.ts new file mode 100644 index 0000000..8139b08 --- /dev/null +++ b/packages/protocol/src/di/feature-module.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule, interfaces } from 'inversify'; +import { MaybeArray, asArray } from '../utils'; +import { BindingContext } from './inversify-util'; + +/** + * Optional constructor options for {@link FeatureModule}s. + */ +export interface FeatureModuleOptions { + /** + * The set of feature modules that is required in order for this module to load. + */ + requires?: MaybeArray; + /** + * Optional `featureId` that should be used. If omitted an id will be autogenerated + */ + featureId?: symbol; +} +/** + * A `FeatureModule` is a specialized {@link ContainerModule} that can declare dependencies to other {@link FeatureModule}. + * A feature module will only be loaded into a container if all of its required modules haven been loaded before. T + * Each feature module binds its `featureId` be default. This enables querying of existing container to check wether a + * feature module has been loaded into this container. + */ + +export class FeatureModule extends ContainerModule { + readonly featureId: symbol; + + readonly requires?: MaybeArray; + + constructor(registry: interfaces.ContainerModuleCallBack, options: FeatureModuleOptions = {}) { + super((bind, unbind, isBound, ...rest) => { + if (this.configure(bind, isBound)) { + registry(bind, unbind, isBound, ...rest); + } + }); + this.featureId = options.featureId ?? this.createFeatureId(); + this.requires = options.requires; + } + + protected createFeatureId(): symbol { + return Symbol(this.id); + } + + /** + * Configures the feature module i.e. checks if the requirements are met. + * If this is the case the {@link FeatureModule.featureId} will be bound and the module will be loaded + * @param bind container bind function + * @param isBound container isBound function + * @returns `true` if all requirements are met and the module is loaded. `false` otherwise + */ + configure(bind: interfaces.Bind, isBound: interfaces.IsBound): boolean { + if (this.checkRequirements(isBound)) { + bind(this.featureId).toConstantValue(this.featureId); + return true; + } + return false; + } + + /** + * Checks if all required {@link FeatureModule}s are already loaded/bound in the container. + * @param isBound The `isBound` property of the module callback. Used to check the required modules. + * @returns `true` if all requirements are met, `false` otherwise + */ + protected checkRequirements(isBound: interfaces.IsBound): boolean { + return this.requires ? asArray(this.requires).every(module => isBound(module.featureId)) : true; + } + + isLoaded(context: Pick): boolean { + return context.isBound(this.featureId); + } +} diff --git a/packages/protocol/src/di/index.ts b/packages/protocol/src/di/index.ts new file mode 100644 index 0000000..dadc469 --- /dev/null +++ b/packages/protocol/src/di/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './container-configuration'; +export * from './feature-module'; +export * from './inversify-util'; +export * from './lazy-injector'; diff --git a/packages/protocol/src/di/inversify-util.ts b/packages/protocol/src/di/inversify-util.ts new file mode 100644 index 0000000..27c084b --- /dev/null +++ b/packages/protocol/src/di/inversify-util.ts @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { interfaces } from 'inversify'; + +/** + * A wrapper interface to get access to the binding related functions + * for an inversify container. + */ +export interface BindingContext { + bind: interfaces.Bind; + unbind: interfaces.Unbind; + isBound: interfaces.IsBound; + rebind: interfaces.Rebind; +} + +/** + * Checks wether the given service identifier is already bound in the given context + * then either calls the `bind` or `rebind` function respectively. + * + * As this is just a convenience function + * ```ts + * bindOrRebind({bind,isBound,rebind}, MyService).to(SomeOtherService); + * ``` + * is equivalent to: + * ``` + * if (isBound(MyService)) { + * rebind(MyService).to(SomeOtherService); + * } else { + * bind(MyService).to(SomeOtherService); + * } + * ``` + * @param context The binding context + * @param serviceIdentifier The service identifier + * @returns The result of the `bind` or `rebind` function + */ +export function bindOrRebind( + context: Omit, + serviceIdentifier: interfaces.ServiceIdentifier +): interfaces.BindingToSyntax { + if (context.isBound(serviceIdentifier)) { + return context.rebind(serviceIdentifier); + } + return context.bind(serviceIdentifier); +} + +/** + * Only binds the given service identifier if it's not already bound in the given context. + * + * As this is just a convenience function + * ```ts + * lazyBind({bind,isBound}, MyService)?.to(SomeOtherService); + * ``` + * is equivalent to: + * ``` + * if (!isBound(MyService)) { + * bind(MyService).to(SomeOtherService); + * } + * ``` + * @param context The binding context + * @param serviceIdentifier The service identifier + * @returns The result of the `bind` function or `undefined` if the service was already bound + */ +export function lazyBind( + context: Pick, + serviceIdentifier: interfaces.ServiceIdentifier +): interfaces.BindingToSyntax | undefined { + if (context.isBound(serviceIdentifier)) { + return undefined; + } + return context.bind(serviceIdentifier); +} + +/** + * Binds the given service identifier to the given target service in the given context. + * In addition, the target service is bound to itself in singleton scope. This ensures + * that services can be rebound individually even if they are multi-injected. + * + * As this is just a convenience function + * ```ts + * bindAsService(bind,SomeOtherService,MyServiceImpl); + * ``` + * is equivalent to: + * ```ts + * bind(MyServiceImpl).toSelf.inSingletonScope(): + * bind(SomeOtherService).toService(MyServiceImpl); + * ``` + * @param serviceIdentifier + * @param toServiceIdentifier + */ +export function bindAsService( + context: Pick | interfaces.Bind, + serviceIdentifier: interfaces.ServiceIdentifier, + targetService: interfaces.ServiceIdentifier +): void { + const bind = typeof context === 'object' ? context.bind.bind(context) : context; + bind(targetService).toSelf().inSingletonScope(); + bind(serviceIdentifier).toService(targetService); +} diff --git a/packages/protocol/src/di/lazy-injector.spec.ts b/packages/protocol/src/di/lazy-injector.spec.ts new file mode 100644 index 0000000..08b8edd --- /dev/null +++ b/packages/protocol/src/di/lazy-injector.spec.ts @@ -0,0 +1,121 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container } from 'inversify'; +import { LazyInjector, bindLazyInjector } from './lazy-injector'; + +class ServiceA {} +class ServiceB {} +class MultiService {} + +const container = new Container(); +bindLazyInjector(container); +container + .bind(ServiceA) + .toDynamicValue(() => new ServiceA()) + .inSingletonScope(); +container.bind(ServiceB).toDynamicValue(() => new ServiceB()); +container.bind(MultiService).toDynamicValue(() => new MultiService()); +container.bind(MultiService).toDynamicValue(() => new MultiService()); +container.snapshot(); + +describe('LazyInjector', () => { + let lazyInjector: LazyInjector; + beforeEach(() => { + container.restore(); + container.snapshot(); + lazyInjector = container.get(LazyInjector); + }); + describe('get', () => { + it('should return the service for a bound id', () => { + const service = lazyInjector.get(ServiceA); + expect(service).to.not.be.undefined; + expect(service instanceof ServiceA).to.be.true; + }); + it('should throw an error if the service id is not bound', () => { + expect(() => lazyInjector.get('UnboundService')).to.throw(); + }); + it('should throw an error if multiple services are bound to the given id', () => { + container.bind(ServiceA).toConstantValue('ServiceA'); + expect(() => lazyInjector.get(ServiceA)).to.throw(); + }); + it('should return the same service instance for a bound id on subsequent calls', () => { + const serviceA1 = lazyInjector.get(ServiceA); + const serviceA2 = lazyInjector.get(ServiceA); + const serviceB1 = lazyInjector.get(ServiceB); + const serviceB2 = lazyInjector.get(ServiceB); + expect(serviceA1).to.equal(serviceA2); + expect(serviceB1).to.equal(serviceB2); + }); + }); + describe('getOptional', () => { + it('should return the service for a bound id', () => { + const service = lazyInjector.getOptional(ServiceA); + expect(service).to.not.be.undefined; + expect(service instanceof ServiceA).to.be.true; + }); + it('should return undefined if the service id is not bound', () => { + const service = lazyInjector.getOptional('UnboundService'); + expect(service).to.be.undefined; + }); + it('should throw an error if multiple services are bound to the given id', () => { + container.bind(ServiceA).toConstantValue('ServiceA'); + expect(() => lazyInjector.get(ServiceA)).to.throw(); + }); + it('should return the same service instance for a bound id on subsequent calls', () => { + const serviceA1 = lazyInjector.getOptional(ServiceA); + const serviceA2 = lazyInjector.getOptional(ServiceA); + const serviceB1 = lazyInjector.getOptional(ServiceB); + const serviceB2 = lazyInjector.getOptional(ServiceB); + expect(serviceA1).to.not.be.undefined; + expect(serviceA1).to.equal(serviceA2); + expect(serviceB1).to.not.be.undefined; + expect(serviceB1).to.equal(serviceB2); + }); + it('should return undefined if the service id was initially not bound but is bound on subsequent calls', () => { + const serviceC1 = lazyInjector.getOptional('ServiceC'); + container.bind('ServiceC').toConstantValue('ServiceC'); + const serviceC2 = lazyInjector.getOptional('ServiceC'); + expect(serviceC1).to.be.undefined; + expect(serviceC2).to.be.undefined; + }); + }); + + describe('getAll', () => { + it('should return all services for a multi bound id', () => { + const services = lazyInjector.getAll(MultiService); + expect(services).to.have.lengthOf(2); + expect(services[0] instanceof MultiService).to.be.true; + expect(services[1] instanceof MultiService).to.be.true; + }); + it('should return the service for a single bound id', () => { + const services = lazyInjector.getAll(ServiceA); + expect(services).to.have.lengthOf(1); + expect(services[0] instanceof ServiceA).to.be.true; + }); + it('should return an empty array if the service id is not bound', () => { + const services = lazyInjector.getAll('UnboundService'); + expect(services).to.be.empty; + }); + it('should return an empty array if the service id was initially not bound but is bound on subsequent calls', () => { + const services1 = lazyInjector.getAll('ServiceC'); + container.bind('ServiceC').toConstantValue('ServiceC'); + const services2 = lazyInjector.getAll('ServiceC'); + expect(services1).to.be.empty; + expect(services2).to.be.empty; + }); + }); +}); diff --git a/packages/protocol/src/di/lazy-injector.ts b/packages/protocol/src/di/lazy-injector.ts new file mode 100644 index 0000000..469d451 --- /dev/null +++ b/packages/protocol/src/di/lazy-injector.ts @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { getServiceIdentifierAsString, interfaces } from 'inversify'; +import { NOT_REGISTERED } from 'inversify/lib/constants/error_msgs'; +import { AnyObject, MaybeArray } from '../utils'; +import { BindingContext } from './inversify-util'; + +/** + * The lazy injector can be used to retrieve services from the container in deferred fashion. + * Instead of directly injecting the service, the service provider can be injected and used to retrieve the service + * at a later point when it is actually needed. Services are cached after the first retrieval. + * + * The lazy injector bound in transient scope. This means each injecting class gets its own instance. + * + * This can be used in places where eager injection via decorator would lead to circular dependency issues. + * For example if injecting the following service causes a circular dependency: + * ```ts + * @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; + * ``` + * You can use the lazy injector to resolve the dependency issue as follows: + * + * ```ts + * @inject(LazyInjector) protected lazyInjector: LazyInjector; + * get actionDispatcher(): IActionDispatcher { + * return this.lazyInjector.get(TYPES.IActionDispatcher); + * } + * ``` + * + * Use the lazy injector with caution. In general direct constructor or property injection is always preferred. + * The lazy injector should only be used in cases where direct injection is not possible due to circular dependencies. + * Also keep in mind that the lazy injector should not be accessed in the constructor or `@postConstruct` method of a class. + * This would defeat the purpose of the lazy injector and lead to the same circular dependency issues. + */ +export interface LazyInjector { + /** + * Retrieves the service for the given identifier from the container. + * Use this method to retrieve services that you would annotate with `@inject(...)` when using normal eager injection. + * @throws An error if the service is not bound in or multiple services are bound to the identifier. + * @param serviceIdentifier The service identifier to retrieve the service for. + */ + get(serviceIdentifier: interfaces.ServiceIdentifier): T; + /** + * Retrieves the service for the given identifier from the container. If the service is not bound `undefined` will be returned + * Use this method to retrieve services that you would annotate with `@inject(...)@optional` when using normal eager injection. + * @throws An error if multiple services are bound to the identifier. + * @param serviceIdentifier The service identifier to retrieve the service for. + */ + getOptional(serviceIdentifier: interfaces.ServiceIdentifier): T | undefined; + + /** + * Retrieves all services for the given service identifier from the container. If the service is not bound an empty array + * will be returned. + * Use this method to retrieve services that you would annotate with `@multiInject(...)@optional()` when using normal eager injection. + * @param serviceIdentifier The service identifier to retrieve the services for. + */ + getAll(serviceIdentifier: interfaces.ServiceIdentifier): T[]; +} +export const LazyInjector = Symbol('LazyInjector'); + +/** + * Default implementation of the {@link LazyInjector} interface. This implementation + * will be bound when using the {@link bindLazyInjector} function. + */ + +export class DefaultLazyInjector implements LazyInjector { + protected cache = new Map, MaybeArray | undefined>(); + + constructor(protected readonly container: interfaces.Container) {} + + get(serviceIdentifier: interfaces.ServiceIdentifier): T { + const service = this.getOptional(serviceIdentifier); + if (service === undefined) { + throw new Error(NOT_REGISTERED + getServiceIdentifierAsString(serviceIdentifier)); + } + return service; + } + + getOptional(serviceIdentifier: interfaces.ServiceIdentifier): T | undefined { + if (this.cache.has(serviceIdentifier)) { + return this.cache.get(serviceIdentifier) as T | undefined; + } + + const service = this.container.isBound(serviceIdentifier) ? this.container.get(serviceIdentifier) : undefined; + this.cache.set(serviceIdentifier, service); + return service; + } + + getAll(serviceIdentifier: interfaces.ServiceIdentifier): T[] { + if (this.cache.has(serviceIdentifier)) { + return this.cache.get(serviceIdentifier) as T[]; + } + const services = this.container.isBound(serviceIdentifier) ? this.container.getAll(serviceIdentifier) : []; + this.cache.set(serviceIdentifier, services); + return services; + } +} + +export function bindLazyInjector(context: Pick | interfaces.Bind): void { + const bind = typeof context === 'object' ? context.bind.bind(context) : context; + bind(LazyInjector).toDynamicValue(ctx => new DefaultLazyInjector(ctx.container)); +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 39539de..e625a46 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -15,8 +15,10 @@ ********************************************************************************/ export * from './action-protocol'; export * from './client-server-protocol'; +export * from './di'; export * from './model'; export * from './re-exports'; export * from './sprotty-actions'; export * from './sprotty-geometry-point'; export * from './utils'; + diff --git a/packages/protocol/src/utils/di-util.spec.ts b/packages/protocol/src/utils/di-util.spec.ts deleted file mode 100644 index 8781a7a..0000000 --- a/packages/protocol/src/utils/di-util.spec.ts +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { expect } from 'chai'; -import { Container } from 'inversify'; -import { FeatureModule } from './di-util'; -describe('DI Utils', () => { - describe('FeatureModule', () => { - const container = new Container(); - - const moduleA = new FeatureModule(() => {}); - const moduleB = new FeatureModule(() => {}); - - afterEach(() => { - container.unbindAll(); - }); - - describe('No or single required module', () => { - it('Should load a feature module with no required module', () => { - const moduleWithNoRequirements = new FeatureModule(bind => { - bind('Foo').toConstantValue('Foo'); - }); - container.load(moduleWithNoRequirements); - expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; - expect(container.isBound('Foo')).to.be.true; - }); - it('Should load a feature module with met required module', () => { - const moduleWithNoRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: moduleA } - ); - container.load(moduleA, moduleWithNoRequirements); - expect(container.isBound(moduleA.featureId)).to.be.true; - expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.true; - expect(container.isBound('Foo')).to.be.true; - }); - it('Should not load a feature module if required module is loaded afterwards', () => { - const moduleWithNoRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: moduleA } - ); - container.load(moduleWithNoRequirements, moduleA); - expect(container.isBound(moduleA.featureId)).to.be.true; - expect(container.isBound(moduleWithNoRequirements.featureId)).to.be.false; - expect(container.isBound('Foo')).to.be.false; - }); - it('Should not load a feature module with missing required module', () => { - const moduleWithUnmetRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: moduleA } - ); - container.load(moduleWithUnmetRequirements); - expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; - expect(container.isBound('Foo')).to.be.false; - }); - }); - describe('Multiple required modules', () => { - it('Should load feature module with loaded required modules', () => { - const moduleWithUnmetRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: [moduleA, moduleB] } - ); - container.load(moduleA, moduleWithUnmetRequirements); - expect(container.isBound(moduleA.featureId)).to.be.true; - expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; - expect(container.isBound('Foo')).to.be.false; - }); - }); - it('Should not load a feature module with missing required modules', () => { - const moduleWithUnmetRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: [moduleA, moduleB] } - ); - container.load(moduleWithUnmetRequirements); - expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; - expect(container.isBound('Foo')).to.be.false; - }); - it('Should not load a feature module with partially loaded required modules', () => { - const moduleWithUnmetRequirements = new FeatureModule( - bind => { - bind('Foo').toConstantValue('Foo'); - }, - { requires: [moduleA, moduleB] } - ); - container.load(moduleA, moduleWithUnmetRequirements); - expect(container.isBound(moduleA.featureId)).to.be.true; - expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; - expect(container.isBound('Foo')).to.be.false; - }); - }); -}); diff --git a/packages/protocol/src/utils/di-util.ts b/packages/protocol/src/utils/di-util.ts deleted file mode 100644 index 81f9e2f..0000000 --- a/packages/protocol/src/utils/di-util.ts +++ /dev/null @@ -1,242 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { Container, ContainerModule, interfaces } from 'inversify'; -import { MaybeArray, asArray, distinctAdd, remove } from './array-util'; -import { hasFunctionProp, hasNumberProp } from './type-util'; - -/** - * A wrapper interface to get access to the binding related functions - * for an inversify container. - */ -export interface BindingContext { - bind: interfaces.Bind; - unbind: interfaces.Unbind; - isBound: interfaces.IsBound; - rebind: interfaces.Rebind; -} - -/** - * Initializes a container with the given {@link ContainerConfiguration}. The container configuration - * consists of the set of {@link ContainerModule}s that should be loaded in the container. - * In addition, for more fine-grained control {@link ModuleConfiguration}s can be passed as part fo the container configuration - * Module loading is distinct,this means each module will only get loaded once even if it is configured multiple times. - @param containerConfigurations - * Custom modules to be loaded in addition to the default modules and/or default modules that should be excluded. - @returns The initialized container. - */ -export function initializeContainer(container: Container, ...containerConfigurations: ContainerConfiguration): Container { - const modules = resolveContainerConfiguration(...containerConfigurations); - container.load(...modules); - return container; -} - -/** - * Processes the given container configurations and returns the corresponding set of {@link ContainerModule}s. - * @param containerConfigurations The container configurations to resolves - * @returns an Array of resolved container modules - */ -export function resolveContainerConfiguration(...containerConfigurations: ContainerConfiguration): ContainerModule[] { - const modules: ContainerModule[] = []; - containerConfigurations.forEach(config => { - if (isContainerModule(config)) { - distinctAdd(modules, config); - } else { - if (config.remove) { - remove(modules, ...asArray(config.remove)); - } - if (config.add) { - distinctAdd(modules, ...asArray(config.add)); - } - } - }); - return modules; -} -/** - * The container modules might originate form different inversify contexts (e.g. `inversify` vs. `@theia/core/shared/inversify`). - * If this is the case an instanceof check can return false negative. - * => use a simple typeguard instead. - */ -function isContainerModule(config: ContainerModule | ModuleConfiguration): config is ContainerModule { - return hasNumberProp(config, 'id') && hasFunctionProp(config, 'registry'); -} - -/** - * Union type for the set of {@link ContainerModule}s and addition {@link ModuleConfiguration}s - * used to configure a DI container. - */ -export type ContainerConfiguration = Array; - -/** - * Can be passed to create DI container utility functions to configure additional modules or - * remove (i.e. not load) default modules. - */ -export interface ModuleConfiguration { - /** Set of modules that should be loaded into the container. Loading */ - add?: MaybeArray; - /** Set of modules that should be loaded into the container */ - remove?: MaybeArray; -} - -/** - * Optional constructor options for {@link FeatureModule}s. - */ -export interface FeatureModuleOptions { - /** - * The set of feature modules that is required in order for this module to load. - */ - requires?: MaybeArray; - /** - * Optional `featureId` that should be used. If omitted an id will be autogenerated - */ - featureId?: symbol; -} -/** - * A `FeatureModule` is a specialized {@link ContainerModule} that can declare dependencies to other {@link FeatureModule}. - * A feature module will only be loaded into a container if all of its required modules haven been loaded before. T - * Each feature module binds its `featureId` be default. This enables querying of existing container to check wether a - * feature module has been loaded into this container. - */ - -export class FeatureModule extends ContainerModule { - readonly featureId: symbol; - - readonly requires?: MaybeArray; - - constructor(registry: interfaces.ContainerModuleCallBack, options: FeatureModuleOptions = {}) { - super((bind, unbind, isBound, ...rest) => { - if (this.configure(bind, isBound)) { - registry(bind, unbind, isBound, ...rest); - } - }); - this.featureId = options.featureId ?? this.createFeatureId(); - this.requires = options.requires; - } - - protected createFeatureId(): symbol { - return Symbol(this.id); - } - - /** - * Configures the feature module i.e. checks if the requirements are met. - * If this is the case the {@link FeatureModule.featureId} will be bound and the module will be loaded - * @param bind container bind function - * @param isBound container isBound function - * @returns `true` if all requirements are met and the module is loaded. `false` otherwise - */ - configure(bind: interfaces.Bind, isBound: interfaces.IsBound): boolean { - if (this.checkRequirements(isBound)) { - bind(this.featureId).toConstantValue(this.featureId); - return true; - } - return false; - } - - /** - * Checks if all required {@link FeatureModule}s are already loaded/bound in the container. - * @param isBound The `isBound` property of the module callback. Used to check the required modules. - * @returns `true` if all requirements are met, `false` otherwise - */ - protected checkRequirements(isBound: interfaces.IsBound): boolean { - return this.requires ? asArray(this.requires).every(module => isBound(module.featureId)) : true; - } - - isLoaded(context: Pick): boolean { - return context.isBound(this.featureId); - } -} -/** - * Checks wether the given service identifier is already bound in the given context - * then either calls the `bind` or `rebind` function respectively. - * - * As this is just a convenience function - * ```ts - * bindOrRebind({bind,isBound,rebind}, MyService).to(SomeOtherService); - * ``` - * is equivalent to: - * ``` - * if (isBound(MyService)) { - * rebind(MyService).to(SomeOtherService); - * } else { - * bind(MyService).to(SomeOtherService); - * } - * ``` - * @param context The binding context - * @param serviceIdentifier The service identifier - * @returns The result of the `bind` or `rebind` function - */ -export function bindOrRebind( - context: Omit, - serviceIdentifier: interfaces.ServiceIdentifier -): interfaces.BindingToSyntax { - if (context.isBound(serviceIdentifier)) { - return context.rebind(serviceIdentifier); - } - return context.bind(serviceIdentifier); -} - -/** - * Only binds the given service identifier if it's not already bound in the given context. - * - * As this is just a convenience function - * ```ts - * lazyBind({bind,isBound}, MyService)?.to(SomeOtherService); - * ``` - * is equivalent to: - * ``` - * if (!isBound(MyService)) { - * bind(MyService).to(SomeOtherService); - * } - * ``` - * @param context The binding context - * @param serviceIdentifier The service identifier - * @returns The result of the `bind` function or `undefined` if the service was already bound - */ -export function lazyBind( - context: Pick, - serviceIdentifier: interfaces.ServiceIdentifier -): interfaces.BindingToSyntax | undefined { - if (context.isBound(serviceIdentifier)) { - return undefined; - } - return context.bind(serviceIdentifier); -} - -/** - * Binds the given service identifier to the given target service in the given context. - * In addition, the target service is bound to itself in singleton scope. This ensures - * that services can be rebound individually even if they are multi-injected. - * - * As this is just a convenience function - * ```ts - * bindAsService(bind,SomeOtherService,MyServiceImpl); - * ``` - * is equivalent to: - * ```ts - * bind(MyServiceImpl).toSelf.inSingletonScope(): - * bind(SomeOtherService).toService(MyServiceImpl); - * ``` - * @param serviceIdentifier - * @param toServiceIdentifier - */ -export function bindAsService( - context: Pick | interfaces.Bind, - serviceIdentifier: interfaces.ServiceIdentifier, - targetService: interfaces.ServiceIdentifier -): void { - const bind = typeof context === 'object' ? context.bind.bind(context) : context; - bind(targetService).toSelf().inSingletonScope(); - bind(serviceIdentifier).toService(targetService); -} diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index bacbee1..c47e71f 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './array-util'; -export * from './di-util'; export * from './disposable'; export * from './event'; // we do not export test-util to avoid a dependency on test frameworks such as chai From 51a2d9de3e91cad7ad5e0e5885ae27d9c821ec27 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sat, 13 Apr 2024 18:58:49 +0200 Subject: [PATCH 316/566] Address review feedback --- examples/workflow-standalone/src/di.config.ts | 13 +++++---- .../src/base/action-handler-registry.ts | 27 +++-------------- packages/client/src/base/default.module.ts | 29 +++++++++++++------ .../src/base/tool-manager/tool-manager.ts | 4 +-- .../client/src/base/ui-extension-registry.ts | 19 +++--------- packages/client/src/base/view/key-tool.ts | 19 +++--------- packages/client/src/base/view/mouse-tool.ts | 21 +++----------- packages/glsp-sprotty/src/types.ts | 5 +--- 8 files changed, 47 insertions(+), 90 deletions(-) diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 6321b0f..1c0652d 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -16,6 +16,7 @@ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; import { ConsoleLogger, + EditMode, IDiagramOptions, LogLevel, STANDALONE_MODULE_CONFIG, @@ -30,6 +31,10 @@ import { makeLoggerMiddleware } from 'inversify-logger-middleware'; import '../css/diagram.css'; import { getParameters } from './url-parameters'; export default function createContainer(options: IDiagramOptions): Container { + const parameters = getParameters(); + if (parameters.readonly) { + options.editMode = EditMode.READONLY; + } const container = createWorkflowDiagramContainer( createDiagramOptionsModule(options), { @@ -41,15 +46,13 @@ export default function createContainer(options: IDiagramOptions): Container { bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); - configureInversifyLogger(container); + if (parameters.inversifyLog) { + configureInversifyLogger(container); + } return container; } function configureInversifyLogger(container: Container): void { - const parameters = getParameters(); - if (!parameters.inversifyLog) { - return; - } const logOptions = { request: { bindings: { diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index d5f4391..da7ee5d 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -14,16 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - ActionHandlerRegistration, - ActionHandlerRegistry, - BindingContext, - IActionHandlerInitializer, - LazyInjector, - TYPES, - bindOrRebind -} from '@eclipse-glsp/sprotty'; -import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { ActionHandlerRegistration, ActionHandlerRegistry, IActionHandlerInitializer, LazyInjector, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { @@ -32,8 +24,8 @@ export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { protected initialized = false; - constructor() { - super([], []); + constructor(@optional() registrations: ActionHandlerRegistration[] = [], @optional() initializers: IActionHandlerInitializer[] = []) { + super(registrations, initializers); } /** * Retrieve a set of all action kinds for which (at least) one @@ -57,14 +49,3 @@ export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { .forEach(initializer => this.initializeActionHandler(initializer)); } } - -let baseClassDecorated = false; -export function bindActionHandlerRegistry(context: Omit): void { - context.bind(GLSPActionHandlerRegistry).toSelf().inSingletonScope(); - bindOrRebind(context, ActionHandlerRegistry).toService(GLSPActionHandlerRegistry); - if (!baseClassDecorated) { - decorate(unmanaged(), ActionHandlerRegistry, 0); - decorate(unmanaged(), ActionHandlerRegistry, 1); - baseClassDecorated = true; - } -} diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 597ac3b..dc9ae0b 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -14,8 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + ActionHandlerRegistry, FeatureModule, + KeyTool, LocationPostprocessor, + MouseTool, MoveCommand, SetDirtyStateAction, SetEditModeAction, @@ -31,7 +34,7 @@ import { import '@vscode/codicons/dist/codicon.css'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; -import { bindActionHandlerRegistry } from './action-handler-registry'; +import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; import { FeedbackAwareSetModelCommand } from './feedback'; @@ -48,9 +51,9 @@ import { SelectionClearingMouseListener } from './selection-clearing-mouse-liste import { SelectionService } from './selection-service'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; -import { bindUIExtensionRegistry } from './ui-extension-registry'; -import { bindKeyTool } from './view/key-tool'; -import { bindMouseTool } from './view/mouse-tool'; +import { GLSPUIExtensionRegistry } from './ui-extension-registry'; +import { GLSPKeyTool } from './view/key-tool'; +import { GLSPMouseTool } from './view/mouse-tool'; import { GViewRegistry } from './view/view-registry'; /** @@ -79,16 +82,20 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . configureCommand(context, FeedbackAwareUpdateModelCommand); rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); - bindMouseTool(context); - bindKeyTool(context); + bind(GLSPMouseTool).toSelf().inSingletonScope(); + bindOrRebind(context, MouseTool).toService(GLSPMouseTool); + bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); - bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); + bind(GLSPKeyTool).toSelf().inSingletonScope(); + bindOrRebind(context, KeyTool).toService(GLSPKeyTool); + bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); + bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); - bindActionHandlerRegistry(context); + bindOrRebind(context, ActionHandlerRegistry).to(GLSPActionHandlerRegistry).inSingletonScope(); bindAsService(context, TYPES.ModelSource, GLSPModelSource); bind(DiagramLoader).toSelf().inSingletonScope(); @@ -114,10 +121,14 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . // Tool manager initialization ------------------------------------ bind(TYPES.IToolManager).to(ToolManager).inSingletonScope(); bind(TYPES.IDiagramStartup).toService(TYPES.IToolManager); + bind(TYPES.IEditModeListener).toService(TYPES.IToolManager); bind(DefaultToolsEnablingKeyListener).toSelf().inSingletonScope(); bind(TYPES.KeyListener).toService(DefaultToolsEnablingKeyListener); bind(ToolManagerActionHandler).toSelf().inSingletonScope(); configureActionHandler(context, EnableDefaultToolsAction.KIND, ToolManagerActionHandler); configureActionHandler(context, EnableToolsAction.KIND, ToolManagerActionHandler); - bindUIExtensionRegistry(context); + + bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); + bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); + bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); }); diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts index b6169cd..b3755b1 100644 --- a/packages/client/src/base/tool-manager/tool-manager.ts +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -28,7 +28,7 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EditorContextService } from '../editor-context-service'; +import { EditorContextService, IEditModeListener } from '../editor-context-service'; import { IDiagramStartup } from '../model'; import { Ranked } from '../ranked'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from './tool'; @@ -79,7 +79,7 @@ export interface IToolManager { * registration of tools via Dependency Injection. */ @injectable() -export class ToolManager implements IToolManager, IDiagramStartup { +export class ToolManager implements IToolManager, IDiagramStartup, IEditModeListener { @inject(EditorContextService) protected editorContext: EditorContextService; diff --git a/packages/client/src/base/ui-extension-registry.ts b/packages/client/src/base/ui-extension-registry.ts index 03ab1ab..e65deb1 100644 --- a/packages/client/src/base/ui-extension-registry.ts +++ b/packages/client/src/base/ui-extension-registry.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry, bindOrRebind } from '@eclipse-glsp/sprotty'; -import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { IDiagramStartup } from './model'; @injectable() @@ -23,22 +23,11 @@ export class GLSPUIExtensionRegistry extends UIExtensionRegistry implements IDia @inject(LazyInjector) protected lazyInjector: LazyInjector; - constructor() { - super([]); + constructor(@optional() extensions: IUIExtension[] = []) { + super(extensions); } preLoadDiagram(): MaybePromise { this.lazyInjector.getAll(TYPES.IUIExtension).forEach(extension => this.register(extension.id(), extension)); } } - -let baseClassDecorated = false; -export function bindUIExtensionRegistry(context: Omit): void { - context.bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); - bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); - context.bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); - if (!baseClassDecorated) { - decorate(unmanaged(), UIExtensionRegistry, 0); - baseClassDecorated = true; - } -} diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts index cacdd5e..10e4a07 100644 --- a/packages/client/src/base/view/key-tool.ts +++ b/packages/client/src/base/view/key-tool.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, Disposable, KeyListener, KeyTool, LazyInjector, MaybePromise, TYPES, bindOrRebind } from '@eclipse-glsp/sprotty'; -import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { Disposable, KeyListener, KeyTool, LazyInjector, MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { IDiagramStartup } from '../model'; @injectable() @@ -22,8 +22,8 @@ export class GLSPKeyTool extends KeyTool implements IDiagramStartup { @inject(LazyInjector) protected lazyInjector: LazyInjector; - constructor() { - super([]); + constructor(@optional() keyListeners: KeyListener[] = []) { + super(keyListeners); } registerListener(keyListener: KeyListener): Disposable { @@ -35,14 +35,3 @@ export class GLSPKeyTool extends KeyTool implements IDiagramStartup { this.lazyInjector.getAll(TYPES.KeyListener).forEach(listener => this.register(listener)); } } - -let baseClassDecorated = false; -export function bindKeyTool(context: Omit): void { - context.bind(GLSPKeyTool).toSelf().inSingletonScope(); - bindOrRebind(context, KeyTool).toService(GLSPKeyTool); - context.bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); - if (!baseClassDecorated) { - decorate(unmanaged(), KeyTool, 0); - baseClassDecorated = true; - } -} diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index dcb4724..f6739ba 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { Action, - BindingContext, Disposable, GModelElement, GModelRoot, @@ -23,10 +22,9 @@ import { MaybePromise, MouseListener, MouseTool, - TYPES, - bindOrRebind + TYPES } from '@eclipse-glsp/sprotty'; -import { decorate, inject, injectable, unmanaged } from 'inversify'; +import { inject, injectable, optional } from 'inversify'; import { IDiagramStartup } from '../model'; import { Ranked } from '../ranked'; @@ -43,8 +41,8 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup { protected rankedMouseListeners: Map; - constructor() { - super([]); + constructor(@optional() mouseListeners: MouseListener[] = []) { + super(mouseListeners); } preLoadDiagram(): MaybePromise { @@ -107,17 +105,6 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup { } } -let baseClassDecorated = false; -export function bindMouseTool(context: Omit): void { - context.bind(GLSPMouseTool).toSelf().inSingletonScope(); - bindOrRebind(context, MouseTool).toService(GLSPMouseTool); - context.bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); - if (!baseClassDecorated) { - decorate(unmanaged(), MouseTool, 0); - baseClassDecorated = true; - } -} - function groupBy(array: Array, keyFunction: (x: T) => K): Map { const unsortedMap = array.reduce((result: Map, item: T) => { const key = keyFunction(item); diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 1c8bb45..bb796d5 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -47,8 +47,5 @@ export const TYPES = { ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), - IToolManager: Symbol('IToolManager'), - IContributionProvider: Symbol('IContributionProvider'), - IContributionInitializer: Symbol('IContributionInitializer'), - IServiceProvider: Symbol('IServiceProvider') + IToolManager: Symbol('IToolManager') }; From ac6eb792627fc2ca45badf5c933b96adfbcf67e5 Mon Sep 17 00:00:00 2001 From: Andreas Hell <44035624+Sakrafux@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:05:21 +0200 Subject: [PATCH 317/566] Fixed webworker startup bc elkjs hidden dependency (#78) Fixes https://github.com/eclipse-glsp/glsp/issues/1018 --- examples/workflow-server-bundled/package.json | 2 +- packages/layout-elk/src/di.config.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 10a213e..e291068 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -41,7 +41,7 @@ "wf-glsp-server-node.js.map" ], "scripts": { - "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map", + "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map wf-glsp-server-webworker.js wf-glsp-server-webworker.js.map", "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 41954b4..24fbcdb 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -82,7 +82,11 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo const elkFactory: ElkFactory = () => new ElkConstructor({ algorithms: options.algorithms, - defaultLayoutOptions: options.defaultLayoutOptions + defaultLayoutOptions: options.defaultLayoutOptions, + // The node implementation relied on elkjs' `FakeWorker` to set the `workerFactory`. + // However, since the required file is dynamically loaded and not available in a web-worker context, + // it needs to be mocked manually. + workerFactory: () => ({ postMessage: () => {} }) as unknown as Worker }); bind(ElkFactory).toConstantValue(elkFactory); From 881737fcbb260aee85608885fe745c87fc011932 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 17 Apr 2024 08:44:58 +0000 Subject: [PATCH 318/566] Add 'generate-index' function to GLSP CLI (#1197) * Add 'generate-index' function to GLSP CLI - Turn CLI package into ES Module so we can easily use 'globby' - Minor fix in 'check-headers' if second line does not contain year --- dev-packages/cli/.eslintrc.cjs | 10 ++ dev-packages/cli/README.md | 24 +++ dev-packages/cli/bin/glsp | 2 +- dev-packages/cli/package.json | 2 + dev-packages/cli/src/app.ts | 14 +- dev-packages/cli/src/commands/check-header.ts | 52 +++--- .../cli/src/commands/coverage-report.ts | 8 +- .../cli/src/commands/generate-index.ts | 154 ++++++++++++++++++ .../cli/src/commands/release/common.ts | 10 +- .../src/commands/release/release-client.ts | 6 +- .../release/release-eclipse-integration.ts | 12 +- .../commands/release/release-java-server.ts | 8 +- .../commands/release/release-server-node.ts | 6 +- .../release/release-theia-integration.ts | 6 +- .../release/release-vscode-integration.ts | 6 +- .../cli/src/commands/release/release.ts | 22 +-- dev-packages/cli/src/commands/update-next.ts | 11 +- dev-packages/cli/src/util/command-util.ts | 2 +- dev-packages/cli/src/util/git-util.ts | 4 +- dev-packages/cli/src/util/validation-util.ts | 5 +- dev-packages/cli/tsconfig.json | 3 +- 21 files changed, 285 insertions(+), 82 deletions(-) create mode 100644 dev-packages/cli/.eslintrc.cjs create mode 100644 dev-packages/cli/src/commands/generate-index.ts diff --git a/dev-packages/cli/.eslintrc.cjs b/dev-packages/cli/.eslintrc.cjs new file mode 100644 index 0000000..2f9f8f4 --- /dev/null +++ b/dev-packages/cli/.eslintrc.cjs @@ -0,0 +1,10 @@ +/* eslint-disable header/header */ +/* eslint-disable no-undef */ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '@eclipse-glsp', + rules: { + // turn import issues off as eslint cannot handle ES modules easily + 'import/no-unresolved': 'off' + } +}; diff --git a/dev-packages/cli/README.md b/dev-packages/cli/README.md index 0204281..6ca2665 100644 --- a/dev-packages/cli/README.md +++ b/dev-packages/cli/README.md @@ -99,6 +99,30 @@ Options: -h, --help display help for command ``` +## generateIndex + +Use this command to create an index file of all sources for a given directory and all it's sub directories. + +```console +$ glsp generateIndex -h +Usage: glsp generateIndex [options] + +Generate index files in a given source directory. + +Arguments: + rootDir The source directory for index generation. + +Options: + -s, --singleIndex Generate a single index file in the source directory instead of indices in each sub-directory (default: false) + -f, --forceOverwrite Overwrite existing index files and remove them if there are no entries (default: false) + -m, --match [match patterns...] File patterns to consider during indexing (default: ["**/*.ts","**/*.tsx"]) + -i, --ignore [ignore patterns...] File patterns to ignore during indexing (default: ["**/*.spec.ts","**/*.spec.tsx","**/*.d.ts"]) + -s, --style Import Style (choices: "commonjs", "esm", default: "commonjs") + --ignoreFile The file that is used to specify patterns that should be ignored during indexing (default: ".indexignore") + -v, --verbose Generate verbose output during generation (default: false) + -h, --help display help for command +``` + ## More information For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). diff --git a/dev-packages/cli/bin/glsp b/dev-packages/cli/bin/glsp index 8933e61..51a0e4e 100755 --- a/dev-packages/cli/bin/glsp +++ b/dev-packages/cli/bin/glsp @@ -1,2 +1,2 @@ #!/usr/bin/env node -require('../lib/app.js'); +import('../lib/app.js'); diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 8aa8f9c..2ca2138 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -23,6 +23,7 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], + "type": "module", "bin": { "glsp": "bin/glsp" }, @@ -43,6 +44,7 @@ "dependencies": { "commander": "^10.0.1", "glob": "^10.3.10", + "globby": "13.2.2", "node-fetch": "^2.6.11", "node-jq": "^4.3.1", "readline-sync": "^1.4.10", diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index 73605a5..1c1f70e 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -14,11 +14,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CheckHeaderCommand } from './commands/check-header'; -import { CoverageReportCommand } from './commands/coverage-report'; -import { ReleaseCommand } from './commands/release/release'; -import { UpdateNextCommand } from './commands/update-next'; -import { baseCommand } from './util/command-util'; +import { CheckHeaderCommand } from './commands/check-header.js'; +import { CoverageReportCommand } from './commands/coverage-report.js'; +import { GenerateIndex } from './commands/generate-index.js'; +import { ReleaseCommand } from './commands/release/release.js'; +import { UpdateNextCommand } from './commands/update-next.js'; +import { baseCommand } from './util/command-util.js'; export const COMMAND_VERSION = '1.1.0-next'; @@ -28,6 +29,7 @@ const app = baseCommand() // .addCommand(CoverageReportCommand) .addCommand(ReleaseCommand) .addCommand(CheckHeaderCommand) - .addCommand(UpdateNextCommand); + .addCommand(UpdateNextCommand) + .addCommand(GenerateIndex); app.parse(process.argv); diff --git a/dev-packages/cli/src/commands/check-header.ts b/dev-packages/cli/src/commands/check-header.ts index d1829a2..4ee8afc 100644 --- a/dev-packages/cli/src/commands/check-header.ts +++ b/dev-packages/cli/src/commands/check-header.ts @@ -19,13 +19,14 @@ import * as fs from 'fs'; import { glob } from 'glob'; import * as minimatch from 'minimatch'; import * as readline from 'readline-sync'; -import * as sh from 'shelljs'; -import { baseCommand, configureShell, getShellConfig } from '../util/command-util'; -import { getChangesOfLastCommit, getLastModificationDate, getUncommittedChanges } from '../util/git-util'; +import sh from 'shelljs'; +import { baseCommand, configureShell, getShellConfig } from '../util/command-util.js'; +import { getChangesOfLastCommit, getLastModificationDate, getUncommittedChanges } from '../util/git-util.js'; + +import * as path from 'path'; +import { LOGGER } from '../util/logger.js'; +import { validateGitDirectory } from '../util/validation-util.js'; -import { LOGGER } from '../util/logger'; -import { validateGitDirectory } from '../util/validation-util'; -import path = require('path'); export interface HeaderCheckOptions { type: CheckType; exclude: string[]; @@ -140,19 +141,22 @@ function validate(rootDir: string, files: string[], options: HeaderCheckOptions) printFileProgress(i + 1 + noHeadersLength, allFilesLength, `Validating ${file}`); const copyrightLine = sh.head({ '-n': 2 }, file).stdout.trim().split('\n')[1]; const copyRightYears = copyrightLine.match(YEAR_RANGE_REGEX)!; - const currentStartYear = Number.parseInt(copyRightYears[0], 10); - const currentEndYear = copyRightYears[1] ? Number.parseInt(copyRightYears[1], 10) : undefined; - const result: DateValidationResult = { - currentStartYear, - currentEndYear, - expectedEndYear: defaultEndYear ?? getLastModificationDate(file, rootDir, AUTO_FIX_MESSAGE)!.getFullYear(), - file, - violation: 'none' - }; - - validateEndYear(result); - - results.push(result); + if (!copyRightYears) { + const result: ValidationResult = { file, violation: 'noYear', line: copyrightLine }; + results.push(result); + } else { + const currentStartYear = Number.parseInt(copyRightYears[0], 10); + const currentEndYear = copyRightYears[1] ? Number.parseInt(copyRightYears[1], 10) : undefined; + const result: DateValidationResult = { + currentStartYear, + currentEndYear, + expectedEndYear: defaultEndYear ?? getLastModificationDate(file, rootDir, AUTO_FIX_MESSAGE)!.getFullYear(), + file, + violation: 'none' + }; + validateEndYear(result); + results.push(result); + } }); results.sort((a, b) => a.file.localeCompare(b.file)); @@ -202,7 +206,10 @@ export function handleValidationResults(rootDir: string, results: ValidationResu fs.writeFileSync(path.join(rootDir, 'headerCheck.json'), JSON.stringify(results, undefined, 2)); } - if (violations.length > 0 && (options.autoFix || readline.keyInYN('Do you want automatically fix copyright year range violations?'))) { + if ( + violations.length > 0 && + (options.autoFix || readline.keyInYN('Do you want to automatically fix copyright year range violations?')) + ) { const toFix = violations.filter(violation => isDateValidationResult(violation)) as DateValidationResult[]; fixViolations(rootDir, toFix, options); } @@ -223,6 +230,8 @@ function toPrintMessage(result: ValidationResult): string { return `${error} ${message}! Expected end year '${expected}' but is '${actual}'`; } else if (result.violation === 'noOrMissingHeader') { return `${error} No or invalid copyright header!`; + } else if (result.violation === 'noYear') { + return `${error} No year found!${result.line ? ' (line: ' + result.line + ')' : ''}`; } return `${info} OK`; @@ -255,6 +264,7 @@ function fixViolations(rootDir: string, violations: DateValidationResult[], opti interface ValidationResult { file: string; violation: Violation; + line?: string; } interface DateValidationResult extends ValidationResult { @@ -267,4 +277,4 @@ function isDateValidationResult(object: ValidationResult): object is DateValidat return 'currentStartYear' in object && 'expectedEndYear' in object; } -type Violation = 'none' | 'noOrMissingHeader' | 'invalidEndYear'; +type Violation = 'none' | 'noOrMissingHeader' | 'invalidEndYear' | 'noYear'; diff --git a/dev-packages/cli/src/commands/coverage-report.ts b/dev-packages/cli/src/commands/coverage-report.ts index f1096f2..7615a4d 100644 --- a/dev-packages/cli/src/commands/coverage-report.ts +++ b/dev-packages/cli/src/commands/coverage-report.ts @@ -16,10 +16,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as sh from 'shelljs'; -import { baseCommand, fatalExec, getShellConfig } from '../util/command-util'; -import { LOGGER } from '../util/logger'; -import { validateDirectory } from '../util/validation-util'; +import sh from 'shelljs'; +import { baseCommand, fatalExec, getShellConfig } from '../util/command-util.js'; +import { LOGGER } from '../util/logger.js'; +import { validateDirectory } from '../util/validation-util.js'; export interface CoverageCmdOptions { coverageScript: string; diff --git a/dev-packages/cli/src/commands/generate-index.ts b/dev-packages/cli/src/commands/generate-index.ts new file mode 100644 index 0000000..aa71c0c --- /dev/null +++ b/dev-packages/cli/src/commands/generate-index.ts @@ -0,0 +1,154 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { createOption } from 'commander'; +import * as fs from 'fs'; +import { Options as GlobbyOptions, globbySync } from 'globby'; +import * as os from 'os'; +import * as path from 'path'; +import sh from 'shelljs'; +import { baseCommand } from '../util/command-util.js'; +import { LOGGER, configureLogger } from '../util/logger.js'; +import { validateDirectory } from '../util/validation-util.js'; + +export interface GenerateIndexCmdOptions { + singleIndex: boolean; + forceOverwrite: boolean; + match: string[] | boolean; + ignore: string[] | boolean; + ignoreFile: string; + style: 'commonjs' | 'esm'; + verbose: boolean; +} + +export const GenerateIndex = baseCommand() // + .name('generateIndex') + .description('Generate index files in a given source directory.') + .argument('', 'The source directory for index generation.') + .option('-s, --singleIndex', 'Generate a single index file in the source directory instead of indices in each sub-directory', false) + .option('-f, --forceOverwrite', 'Overwrite existing index files and remove them if there are no entries', false) + .option('-m, --match [match patterns...]', 'File patterns to consider during indexing', ['**/*.ts', '**/*.tsx']) + .option('-i, --ignore [ignore patterns...]', 'File patterns to ignore during indexing', ['**/*.spec.ts', '**/*.spec.tsx', '**/*.d.ts']) + .addOption(createOption('-s, --style ', 'Import Style').choices(['commonjs', 'esm']).default('commonjs')) + .option('--ignoreFile ', 'The file that is used to specify patterns that should be ignored during indexing', '.indexignore') + .option('-v, --verbose', 'Generate verbose output during generation', false) + .action(generateIndices); + +export async function generateIndices(rootDirs: string[], options: GenerateIndexCmdOptions): Promise { + const dirs = rootDirs.map(rootDir => validateDirectory(path.resolve(rootDir))); + dirs.forEach(dir => generateIndex(dir, options)); +} + +export async function generateIndex(rootDir: string, options: GenerateIndexCmdOptions): Promise { + configureLogger(options.verbose); + LOGGER.debug('Run generateIndex for', rootDir, 'with the following options:', options); + sh.cd(rootDir); + const cwd = process.cwd(); + + // we want to match all given patterns and subdirectories and ignore all given patterns and (generated) index files + const pattern = typeof options.match === 'boolean' ? ['**/'] : [...options.match, '**/']; + const ignore = typeof options.ignore === 'boolean' ? ['**/index.ts'] : [...options.ignore, '**/index.ts']; + const globbyOptions: GlobbyOptions = { + ignore, + cwd, + onlyFiles: options.singleIndex, + markDirectories: true, // directories have '/' at the end + ignoreFiles: '**/' + options.ignoreFile // users can add this file in their directories to ignore files for indexing + }; + LOGGER.debug('Search for children using the following globby options', globbyOptions); + const files = globbySync(pattern, globbyOptions); + LOGGER.debug('All children considered in the input directory', files); + + const relativeRootDirectory = ''; + if (options.singleIndex) { + writeIndex(relativeRootDirectory, files.filter(isFile), options); + } else { + // sort by length so we deal with sub-directories before we deal with their parents to determine whether they are empty + const directories = [...files.filter(isDirectory), relativeRootDirectory].sort((left, right) => right.length - left.length); + const directoryChildren = new Map(); + for (const directory of directories) { + const children = files.filter(file => isDirectChild(directory, file, () => !!directoryChildren.get(file)?.length)); + directoryChildren.set(directory, children); + writeIndex(directory, children, options); + } + } +} + +export function isDirectChild(parent: string, child: string, childHasChildren: () => boolean): boolean { + return isChildFile(parent, child) || (isChildDirectory(parent, child) && childHasChildren()); +} + +export function isDirectory(file: string): boolean { + return file.endsWith('/'); +} + +export function isFile(file: string): boolean { + return !isDirectory(file); +} + +export function getLevel(file: string): number { + return file.split('/').length; +} + +export function isChild(parent: string, child: string): boolean { + return child.startsWith(parent); +} + +export function isChildDirectory(parent: string, child: string): boolean { + return isDirectory(child) && isChild(parent, child) && getLevel(child) === getLevel(parent) + 1; +} + +export function isChildFile(parent: string, child: string): boolean { + return isFile(child) && isChild(parent, child) && getLevel(child) === getLevel(parent); +} + +export function writeIndex(directory: string, exports: string[], options: GenerateIndexCmdOptions): void { + const indexFile = path.join(process.cwd(), directory, 'index.ts'); + if (exports.length === 0) { + if (options.forceOverwrite && fs.existsSync(indexFile)) { + LOGGER.info('Remove index file', indexFile); + fs.rmSync(indexFile); + } + return; + } + const exists = fs.existsSync(indexFile); + if (exists && !options.forceOverwrite) { + LOGGER.info("Do not overwrite existing index file. Use '-f' to force an overwrite.", indexFile); + return; + } + + const headerContent = exists ? extractReusableContent(fs.readFileSync(indexFile, { encoding: 'utf-8' })) : ''; + const exportContent = exports.map(exported => createExport(directory, exported, options)).sort(); + const content = headerContent + exportContent.join(os.EOL) + os.EOL; // end with an empty line + LOGGER.info((exists ? 'Overwrite' : 'Write') + ' index file', indexFile); + LOGGER.debug(' ' + content.split(os.EOL).join(os.EOL + ' ')); + fs.writeFileSync(indexFile, content, { flag: 'w' }); +} + +export function createExport(directory: string, relativePath: string, options: GenerateIndexCmdOptions): string { + // remove directory prefix, file extension and directory ending '/' + const parentPrefix = directory.length; + const suffix = isFile(relativePath) ? path.extname(relativePath).length : 1; + const relativeName = relativePath.substring(parentPrefix, relativePath.length - suffix); + const exportName = options.style === 'esm' && isFile(relativePath) ? relativeName + '.js' : relativeName; + const exportLine = `export * from './${exportName}';`; + return exportLine; +} + +export function extractReusableContent(fileContent: string): string { + // all code before any actual export lines are considered re-usable + return fileContent.match(/^(.*?)(?=^export)/ms)?.[0] ?? ''; +} diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index ef1b9d5..a626023 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -19,11 +19,11 @@ import fetch from 'node-fetch'; import { resolve } from 'path'; import * as readline from 'readline-sync'; import * as semver from 'semver'; -import * as sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util'; -import { getLatestGithubRelease, getLatestTag, hasGitChanges, isGitRepository } from '../../util/git-util'; -import { LOGGER } from '../../util/logger'; -import { validateVersion } from '../../util/validation-util'; +import sh from 'shelljs'; +import { fatalExec, getShellConfig } from '../../util/command-util.js'; +import { getLatestGithubRelease, getLatestTag, hasGitChanges, isGitRepository } from '../../util/git-util.js'; +import { LOGGER } from '../../util/logger.js'; +import { validateVersion } from '../../util/validation-util.js'; export const VERDACCIO_REGISTRY = 'http://localhost:4873/'; diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index f3b9f71..fb2e4c8 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sh from 'shelljs'; -import { getShellConfig } from '../../util/command-util'; -import { LOGGER } from '../../util/logger'; -import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common'; +import { getShellConfig } from '../../util/command-util.js'; +import { LOGGER } from '../../util/logger.js'; +import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-eclipse-integration.ts b/dev-packages/cli/src/commands/release/release-eclipse-integration.ts index 02411ec..77f7c6e 100644 --- a/dev-packages/cli/src/commands/release/release-eclipse-integration.ts +++ b/dev-packages/cli/src/commands/release/release-eclipse-integration.ts @@ -14,22 +14,22 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util'; -import { LOGGER } from '../../util/logger'; +import sh from 'shelljs'; +import { fatalExec, getShellConfig } from '../../util/command-util.js'; +import { LOGGER } from '../../util/logger.js'; import { + ReleaseOptions, + ReleaseType, asMvnVersion, checkJavaServerVersion, checkoutAndCd, commitAndTag, lernaSetVersion, publish, - ReleaseOptions, - ReleaseType, updateLernaForDryRun, updateVersion, yarnInstall -} from './common'; +} from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-java-server.ts b/dev-packages/cli/src/commands/release/release-java-server.ts index 70bbb50..1bc71a9 100644 --- a/dev-packages/cli/src/commands/release/release-java-server.ts +++ b/dev-packages/cli/src/commands/release/release-java-server.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util'; -import { LOGGER } from '../../util/logger'; -import { asMvnVersion, checkoutAndCd, commitAndTag, publish, ReleaseOptions } from './common'; +import sh from 'shelljs'; +import { fatalExec, getShellConfig } from '../../util/command-util.js'; +import { LOGGER } from '../../util/logger.js'; +import { ReleaseOptions, asMvnVersion, checkoutAndCd, commitAndTag, publish } from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-server-node.ts b/dev-packages/cli/src/commands/release/release-server-node.ts index 84363eb..9937c76 100644 --- a/dev-packages/cli/src/commands/release/release-server-node.ts +++ b/dev-packages/cli/src/commands/release/release-server-node.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; -import { LOGGER } from '../../util/logger'; +import sh from 'shelljs'; +import { LOGGER } from '../../util/logger.js'; import { checkoutAndCd, commitAndTag, @@ -25,7 +25,7 @@ import { updateLernaForDryRun, updateVersion, yarnInstall -} from './common'; +} from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-theia-integration.ts b/dev-packages/cli/src/commands/release/release-theia-integration.ts index 4403c00..26c366b 100644 --- a/dev-packages/cli/src/commands/release/release-theia-integration.ts +++ b/dev-packages/cli/src/commands/release/release-theia-integration.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; -import { LOGGER } from '../../util/logger'; +import sh from 'shelljs'; +import { LOGGER } from '../../util/logger.js'; import { checkoutAndCd, commitAndTag, @@ -25,7 +25,7 @@ import { updateLernaForDryRun, updateVersion, yarnInstall -} from './common'; +} from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-vscode-integration.ts b/dev-packages/cli/src/commands/release/release-vscode-integration.ts index 86d400a..2271da1 100644 --- a/dev-packages/cli/src/commands/release/release-vscode-integration.ts +++ b/dev-packages/cli/src/commands/release/release-vscode-integration.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; -import { LOGGER } from '../../util/logger'; +import sh from 'shelljs'; +import { LOGGER } from '../../util/logger.js'; import { checkoutAndCd, commitAndTag, @@ -25,7 +25,7 @@ import { updateLernaForDryRun, updateVersion, yarnInstall -} from './common'; +} from './common.js'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release.ts b/dev-packages/cli/src/commands/release/release.ts index de71ee1..42ad454 100644 --- a/dev-packages/cli/src/commands/release/release.ts +++ b/dev-packages/cli/src/commands/release/release.ts @@ -19,10 +19,10 @@ import { exit } from 'process'; import { createInterface } from 'readline'; import * as readline from 'readline-sync'; import * as semver from 'semver'; -import * as sh from 'shelljs'; -import { baseCommand, configureShell, fatalExec, getShellConfig } from '../../util/command-util'; -import { LOGGER, configureLogger } from '../../util/logger'; -import { validateDirectory, validateVersion } from '../../util/validation-util'; +import sh from 'shelljs'; +import { baseCommand, configureShell, fatalExec, getShellConfig } from '../../util/command-util.js'; +import { LOGGER, configureLogger } from '../../util/logger.js'; +import { validateDirectory, validateVersion } from '../../util/validation-util.js'; import { Component, ReleaseOptions, @@ -31,13 +31,13 @@ import { asMvnVersion, checkIfMavenVersionExists, checkIfNpmVersionIsNew -} from './common'; -import { releaseClient } from './release-client'; -import { releaseEclipseIntegration } from './release-eclipse-integration'; -import { releaseJavaServer } from './release-java-server'; -import { releaseServerNode } from './release-server-node'; -import { releaseTheiaIntegration } from './release-theia-integration'; -import { releaseVscodeIntegration } from './release-vscode-integration'; +} from './common.js'; +import { releaseClient } from './release-client.js'; +import { releaseEclipseIntegration } from './release-eclipse-integration.js'; +import { releaseJavaServer } from './release-java-server.js'; +import { releaseServerNode } from './release-server-node.js'; +import { releaseTheiaIntegration } from './release-theia-integration.js'; +import { releaseVscodeIntegration } from './release-vscode-integration.js'; interface ReleaseCmdOptions { checkoutDir: string; diff --git a/dev-packages/cli/src/commands/update-next.ts b/dev-packages/cli/src/commands/update-next.ts index 4b270dc..be471f3 100644 --- a/dev-packages/cli/src/commands/update-next.ts +++ b/dev-packages/cli/src/commands/update-next.ts @@ -19,11 +19,12 @@ import { glob } from 'glob'; import * as jq from 'node-jq'; import * as path from 'path'; import * as sh from 'shelljs'; -import { baseCommand, configureShell } from '../util/command-util'; -import { getUncommittedChanges } from '../util/git-util'; -import { LOGGER, configureLogger } from '../util/logger'; -import { validateGitDirectory } from '../util/validation-util'; -export const UpdateNextCommand = baseCommand() // +import { baseCommand, configureShell } from '../util/command-util.js'; +import { getUncommittedChanges } from '../util/git-util.js'; +import { LOGGER, configureLogger } from '../util/logger.js'; +import { validateGitDirectory } from '../util/validation-util.js'; + +export const UpdateNextCommand = baseCommand() .name('updateNext') .alias('u') .description('Updates all `next` dependencies in GLSP project to the latest version') diff --git a/dev-packages/cli/src/util/command-util.ts b/dev-packages/cli/src/util/command-util.ts index 943a32f..357ae3d 100644 --- a/dev-packages/cli/src/util/command-util.ts +++ b/dev-packages/cli/src/util/command-util.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Command } from 'commander'; -import * as sh from 'shelljs'; +import sh from 'shelljs'; // Commander.js utils export function baseCommand(cmd = new Command()): Command { diff --git a/dev-packages/cli/src/util/git-util.ts b/dev-packages/cli/src/util/git-util.ts index 13a1f07..eb1f838 100644 --- a/dev-packages/cli/src/util/git-util.ts +++ b/dev-packages/cli/src/util/git-util.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { resolve } from 'path'; -import * as sh from 'shelljs'; -import { getShellConfig } from './command-util'; +import sh from 'shelljs'; +import { getShellConfig } from './command-util.js'; export function isGitRepository(path?: string): boolean { cdIfPresent(path); diff --git a/dev-packages/cli/src/util/validation-util.ts b/dev-packages/cli/src/util/validation-util.ts index 2871b2d..8229268 100644 --- a/dev-packages/cli/src/util/validation-util.ts +++ b/dev-packages/cli/src/util/validation-util.ts @@ -17,9 +17,8 @@ import { InvalidArgumentError } from 'commander'; import * as fs from 'fs'; import { resolve } from 'path'; import * as semver from 'semver'; -import { getGitRoot, isGitRepository } from './git-util'; -import { LOGGER } from './logger'; -export const COMMAND_VERSION = '1.1.0-next'; +import { getGitRoot, isGitRepository } from './git-util.js'; +import { LOGGER } from './logger.js'; export function validateDirectory(rootDir: string): string { const path = resolve(rootDir); diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index 8157757..1986af2 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -4,7 +4,8 @@ "rootDir": "src", "outDir": "lib", "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "module": "ESNext" }, "include": ["src"] } From 03f1f1fe8a1bbedc39f4cd82e067b75fb4c87b5a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 17 Apr 2024 11:28:05 +0200 Subject: [PATCH 319/566] GLSP-1179 Switch to composite ts build (#79) + update to lates dev package & new update script Part of https://github.com/eclipse-glsp/glsp/issues/1179 --- examples/workflow-server-bundled/README.md | 2 +- examples/workflow-server/package.json | 7 +++---- examples/workflow-server/tsconfig.json | 11 ++++++++++- packages/graph/package.json | 4 +--- packages/graph/tsconfig.json | 1 + packages/layout-elk/package.json | 4 +--- packages/layout-elk/tsconfig.json | 9 +++++++-- packages/server/package.json | 6 ++---- packages/server/tsconfig.json | 10 ++++++++-- 9 files changed, 34 insertions(+), 20 deletions(-) diff --git a/examples/workflow-server-bundled/README.md b/examples/workflow-server-bundled/README.md index 2f51bf4..05404c3 100644 --- a/examples/workflow-server-bundled/README.md +++ b/examples/workflow-server-bundled/README.md @@ -1,7 +1,7 @@ # Eclipse GLSP - Workflow Example Server (bundled) Provides a bundled version of the Workflow example server for node. This example server is used as dev example in GLSP projects. -Note that this package provides the bundled file. If you are looking for the unbundled source code with type definitons check out +Note that this package provides the bundled file. If you are looking for the unbundled source code with type definitions check out [`@eclipse-glsp-examples/workflow-server`](https://www.npmjs.com/package/@eclipse-glsp-examples/workflow-server). ## Workflow Diagram Example diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index d2eae87..a169e47 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -49,11 +49,10 @@ "build": "tsc -b && yarn bundle", "bundle": "webpack", "bundle:browser": "webpack --env target=webworker ", - "clean": "rimraf lib tsconfig.tsbuildinfo bundle", + "clean": "rimraf lib *.tsbuildinfo", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", - "watch": "tsc -w" + "watch": "tsc -w", + "watch:bundle": "webpack -w" }, "dependencies": { "@eclipse-glsp/layout-elk": "2.2.0-next", diff --git a/examples/workflow-server/tsconfig.json b/examples/workflow-server/tsconfig.json index 919b4c0..f1c4e83 100644 --- a/examples/workflow-server/tsconfig.json +++ b/examples/workflow-server/tsconfig.json @@ -3,8 +3,17 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", + "composite": true, "esModuleInterop": true, "resolveJsonModule": true }, - "include": ["src"] + "include": ["src", "src/**/*.json"], + "references": [ + { + "path": "../../packages/server" + }, + { + "path": "../../packages/layout-elk" + } + ] } diff --git a/packages/graph/package.json b/packages/graph/package.json index c0c5e19..cff58ca 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -40,10 +40,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "test:coverage": "nyc yarn test", diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json index db53a4c..5e765d0 100644 --- a/packages/graph/tsconfig.json +++ b/packages/graph/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", + "composite": true, "reactNamespace": "JSX" }, "include": ["src"] diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 5500d87..cd73185 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -40,10 +40,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "test:coverage": "nyc yarn test", diff --git a/packages/layout-elk/tsconfig.json b/packages/layout-elk/tsconfig.json index 0cac18e..56ca413 100644 --- a/packages/layout-elk/tsconfig.json +++ b/packages/layout-elk/tsconfig.json @@ -4,8 +4,13 @@ "rootDir": "src", "outDir": "lib", "reactNamespace": "JSX", - "types": ["node", "mocha", "reflect-metadata"] + "composite": true }, "exclude": ["**/*.spec.ts"], - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../server" + } + ] } diff --git a/packages/server/package.json b/packages/server/package.json index 793d5b5..d4b78eb 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -49,17 +49,15 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib tsconfig.tsbuildinfo coverage .nyc_output", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", "lint": "eslint --ext .ts,.tsx ./src", - "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "test:coverage": "nyc yarn test:ci", "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "^2.2.0-next", + "@eclipse-glsp/graph": "2.2.0-next", "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 1e4a6e5..3736ccd 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -2,8 +2,14 @@ "extends": "@eclipse-glsp/ts-config/mocha", "compilerOptions": { "rootDir": "src", - "outDir": "lib" + "outDir": "lib", + "composite": true }, "exclude": ["**/*.spec.ts", "src/common/test/mock-util.ts"], - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../graph" + } + ] } From 7aac21940c557ec299bf3ac1dafbaa702bd90b1a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 17 Apr 2024 12:01:43 +0200 Subject: [PATCH 320/566] Fix shell.js imports (#1319) Fix `* as sh from 'shelljs'` imports as they seem to fail when used in an es module --- dev-packages/cli/src/commands/release/release-client.ts | 2 +- dev-packages/cli/src/commands/update-next.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index fb2e4c8..c6266bc 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as sh from 'shelljs'; +import sh from 'shelljs'; import { getShellConfig } from '../../util/command-util.js'; import { LOGGER } from '../../util/logger.js'; import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common.js'; diff --git a/dev-packages/cli/src/commands/update-next.ts b/dev-packages/cli/src/commands/update-next.ts index be471f3..6b7471e 100644 --- a/dev-packages/cli/src/commands/update-next.ts +++ b/dev-packages/cli/src/commands/update-next.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import { glob } from 'glob'; import * as jq from 'node-jq'; import * as path from 'path'; -import * as sh from 'shelljs'; +import sh from 'shelljs'; import { baseCommand, configureShell } from '../util/command-util.js'; import { getUncommittedChanges } from '../util/git-util.js'; import { LOGGER, configureLogger } from '../util/logger.js'; From 5a6732e37fdf9cf3e3c87ffc26e39841a1efd1e2 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 17 Apr 2024 15:57:28 +0000 Subject: [PATCH 321/566] Improve UI Extension mechanism for easier re-use of HTML elements (#333) - Allow more fine-grained definition of container and parent - Allow more fine-grained definition of container within parent - Replace hard-coded styles with 'hidden' class Fixes https://github.com/eclipse-glsp/glsp/issues/1246 Fixes https://github.com/eclipse-glsp/glsp/issues/1318 --- packages/client/src/default-modules.ts | 2 +- .../command-palette/command-palette-module.ts | 5 +- .../command-palette/command-palette.ts | 34 +++++ .../src/features/command-palette/index.ts | 1 + .../label-edit-ui/label-edit-ui-module.ts | 24 ++++ .../features/label-edit-ui/label-edit-ui.ts | 33 +++++ packages/glsp-sprotty/css/ui-extension.css | 20 +++ packages/glsp-sprotty/src/feature-modules.ts | 10 +- packages/glsp-sprotty/src/index.ts | 1 + packages/glsp-sprotty/src/re-exports.ts | 2 +- .../glsp-sprotty/src/ui-extension-override.ts | 118 ++++++++++++++++++ 11 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/features/command-palette/command-palette.ts create mode 100644 packages/client/src/features/label-edit-ui/label-edit-ui-module.ts create mode 100644 packages/client/src/features/label-edit-ui/label-edit-ui.ts create mode 100644 packages/glsp-sprotty/css/ui-extension.css create mode 100644 packages/glsp-sprotty/src/ui-extension-override.ts diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index e260d15..ae684ff 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -26,7 +26,6 @@ import { edgeLayoutModule, expandModule, fadeModule, - labelEditUiModule, modelSourceModule, resolveContainerConfiguration, zorderModule @@ -61,6 +60,7 @@ import { nodeCreationToolModule } from './features/tools/node-creation/node-crea import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; import { markerNavigatorModule, validationModule } from './features/validation/validation-modules'; import { viewportModule } from './features/viewport/viewport-modules'; +import { labelEditUiModule } from './features/label-edit-ui/label-edit-ui-module'; export const DEFAULT_MODULES = [ defaultModule, diff --git a/packages/client/src/features/command-palette/command-palette-module.ts b/packages/client/src/features/command-palette/command-palette-module.ts index bbe19a3..8b88490 100644 --- a/packages/client/src/features/command-palette/command-palette-module.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -13,13 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CommandPalette, CommandPaletteActionProviderRegistry, FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; +import { CommandPaletteActionProviderRegistry, FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import '../../../css/command-palette.css'; +import { GlspCommandPalette } from './command-palette'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; export const commandPaletteModule = new FeatureModule(bind => { - bindAsService(bind, TYPES.IUIExtension, CommandPalette); + bindAsService(bind, TYPES.IUIExtension, GlspCommandPalette); bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); bindAsService(bind, TYPES.IDefaultTool, CommandPaletteTool); diff --git a/packages/client/src/features/command-palette/command-palette.ts b/packages/client/src/features/command-palette/command-palette.ts new file mode 100644 index 0000000..1f02630 --- /dev/null +++ b/packages/client/src/features/command-palette/command-palette.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS, CommandPalette } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; + +@injectable() +export class GlspCommandPalette extends CommandPalette { + protected override initializeContents(containerElement: HTMLElement): void { + super.initializeContents(containerElement); + containerElement.classList.add(CSS_UI_EXTENSION_CLASS); + } + + protected override setContainerVisible(visible: boolean): void { + if (visible) { + this.containerElement?.classList.remove(CSS_HIDDEN_EXTENSION_CLASS); + } else { + this.containerElement?.classList.add(CSS_HIDDEN_EXTENSION_CLASS); + } + } +} diff --git a/packages/client/src/features/command-palette/index.ts b/packages/client/src/features/command-palette/index.ts index a1ce843..e818d6b 100644 --- a/packages/client/src/features/command-palette/index.ts +++ b/packages/client/src/features/command-palette/index.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './command-palette'; export * from './command-palette-module'; export * from './command-palette-tool'; export * from './server-command-palette-provider'; diff --git a/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts b/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts new file mode 100644 index 0000000..3b204b1 --- /dev/null +++ b/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { EditLabelAction, EditLabelActionHandler, FeatureModule, TYPES, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { GlspEditLabelUI } from './label-edit-ui'; + +export const labelEditUiModule = new FeatureModule((bind, unbind, isBound, rebind, ...rest) => { + const context = { bind, unbind, isBound, rebind }; + configureActionHandler(context, EditLabelAction.KIND, EditLabelActionHandler); + bind(GlspEditLabelUI).toSelf().inSingletonScope(); + bind(TYPES.IUIExtension).toService(GlspEditLabelUI); +}); diff --git a/packages/client/src/features/label-edit-ui/label-edit-ui.ts b/packages/client/src/features/label-edit-ui/label-edit-ui.ts new file mode 100644 index 0000000..e6de9eb --- /dev/null +++ b/packages/client/src/features/label-edit-ui/label-edit-ui.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS, EditLabelUI } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; + +@injectable() +export class GlspEditLabelUI extends EditLabelUI { + protected override initializeContents(containerElement: HTMLElement): void { + super.initializeContents(containerElement); + containerElement.classList.add(CSS_UI_EXTENSION_CLASS); + } + + protected override setContainerVisible(visible: boolean): void { + if (visible) { + this.containerElement?.classList.remove(CSS_HIDDEN_EXTENSION_CLASS); + } else { + this.containerElement?.classList.add(CSS_HIDDEN_EXTENSION_CLASS); + } + } +} diff --git a/packages/glsp-sprotty/css/ui-extension.css b/packages/glsp-sprotty/css/ui-extension.css new file mode 100644 index 0000000..e5a90d8 --- /dev/null +++ b/packages/glsp-sprotty/css/ui-extension.css @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.ui-extension.hidden { + display: none; + opacity: 0; +} diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts index db64bf2..fb7433d 100644 --- a/packages/glsp-sprotty/src/feature-modules.ts +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -16,21 +16,18 @@ /* eslint-disable no-restricted-imports */ import { FeatureModule } from '@eclipse-glsp/protocol'; +import sprottyDefaultModule from 'sprotty/lib/base/di.config'; import sprottyButtonModule from 'sprotty/lib/features/button/di.config'; import sprottyEdgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; import sprottyEdgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; -import { - edgeEditModule as sprottyEdgeEditModule, - labelEditUiModule as sprottyLabelEditUiModule -} from 'sprotty/lib/features/edit/di.config'; +import { edgeEditModule as sprottyEdgeEditModule } from 'sprotty/lib/features/edit/di.config'; import sprottyExpandModule from 'sprotty/lib/features/expand/di.config'; import sprottyFadeModule from 'sprotty/lib/features/fade/di.config'; import sprottyMoveModule from 'sprotty/lib/features/move/di.config'; +import sprottyOpenModule from 'sprotty/lib/features/open/di.config'; import sprottyUpdateModule from 'sprotty/lib/features/update/di.config'; import sprottyZorderModule from 'sprotty/lib/features/zorder/di.config'; import sprottyModelSourceModule from 'sprotty/lib/model-source/di.config'; -import sprottyDefaultModule from 'sprotty/lib/base/di.config'; -import sprottyOpenModule from 'sprotty/lib/features/open/di.config'; export const buttonModule = new FeatureModule(sprottyButtonModule.registry); export const edgeEditModule = new FeatureModule(sprottyEdgeEditModule.registry); @@ -38,7 +35,6 @@ export const edgeIntersectionModule = new FeatureModule(sprottyEdgeIntersectionM export const edgeLayoutModule = new FeatureModule(sprottyEdgeLayoutModule.registry); export const expandModule = new FeatureModule(sprottyExpandModule.registry); export const fadeModule = new FeatureModule(sprottyFadeModule.registry); -export const labelEditUiModule = new FeatureModule(sprottyLabelEditUiModule.registry); export const modelSourceModule = new FeatureModule(sprottyModelSourceModule.registry); export const moveModule = new FeatureModule(sprottyMoveModule.registry); export const openModule = new FeatureModule(sprottyOpenModule.registry); diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 7b20b9e..ff6e959 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -18,3 +18,4 @@ export * from './feature-modules'; export * from './re-exports'; export * from './routing-override'; export * from './types'; +export * from './ui-extension-override'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 67336e6..1752135 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -73,7 +73,7 @@ export { } from 'sprotty/lib/base/model/smodel-factory'; export * from 'sprotty/lib/base/model/smodel-utils'; -export * from 'sprotty/lib/base/ui-extensions/ui-extension'; +export { IUIExtension, isUIExtension } from 'sprotty/lib/base/ui-extensions/ui-extension'; export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; export * from 'sprotty/lib/base/views/dom-helper'; diff --git a/packages/glsp-sprotty/src/ui-extension-override.ts b/packages/glsp-sprotty/src/ui-extension-override.ts new file mode 100644 index 0000000..448a88d --- /dev/null +++ b/packages/glsp-sprotty/src/ui-extension-override.ts @@ -0,0 +1,118 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import '../css/ui-extension.css'; + +import { injectable } from 'inversify'; +import { AbstractUIExtension as SprottyAbstractUIExtension } from 'sprotty'; + +export const CSS_UI_EXTENSION_CLASS = 'ui-extension'; +export const CSS_HIDDEN_EXTENSION_CLASS = 'hidden'; + +// An override to enables more fine-grained control of the container structure for the UI extension. + +@injectable() +export abstract class AbstractUIExtension extends SprottyAbstractUIExtension { + protected get diagramContainerId(): string { + return this.options.baseDiv; + } + + protected get parentContainerSelector(): string { + return '#' + this.diagramContainerId; + } + + protected get containerSelector(): string { + return '#' + this.id(); + } + + protected get initialized(): boolean { + return !!this.containerElement; + } + + protected override initialize(): boolean { + if (this.initialized) { + return true; + } + try { + this.containerElement = this.getOrCreateContainer(); + this.initializeContainer(this.containerElement); + this.initializeContents(this.containerElement); + } catch (error) { + const msg = error instanceof Error ? error.message : `Could not retrieve container element for UI extension ${this.id}`; + this.logger.error(this, msg); + return false; + } + return true; + } + + protected override getOrCreateContainer(): HTMLElement { + if (this.containerElement) { + return this.containerElement; + } + // check if the container already exists, independent from any potential parent container + // this allows us to use existing elements defined anywhere in the document + const existingContainer = this.getContainer(); + if (existingContainer) { + return existingContainer; + } + // to create a container the parent container + const parent = this.getParentContainer(); + if (!parent?.isConnected) { + throw new Error(`Could not obtain attached parent for initializing UI extension ${this.id}`); + } + const container = this.createContainer(parent); + this.insertContainerIntoParent(container, parent); + return container; + } + + protected getContainer(): HTMLElement | null { + return document.querySelector(this.containerSelector); + } + + protected createContainer(parent: HTMLElement): HTMLElement { + const container = document.createElement('div'); + container.id = parent.id + '_' + this.id(); + return container; + } + + protected initializeContainer(container: HTMLElement): void { + container.classList.add(CSS_UI_EXTENSION_CLASS, this.containerClass()); + } + + protected getParentContainer(): HTMLElement | null { + return document.querySelector(this.parentContainerSelector)!; + } + + protected insertContainerIntoParent(container: HTMLElement, parent: HTMLElement): void { + parent.insertBefore(container, parent.firstChild); + } + + protected override setContainerVisible(visible: boolean): void { + // the parent class simply sets the style directly, however classes provide more fine-grained control + if (visible) { + this.containerElement?.classList.remove(CSS_HIDDEN_EXTENSION_CLASS); + } else { + this.containerElement?.classList.add(CSS_HIDDEN_EXTENSION_CLASS); + } + } + + protected isContainerVisible(): boolean { + return !this.containerElement?.classList.contains(CSS_HIDDEN_EXTENSION_CLASS); + } + + protected toggleContainerVisible(): void { + this.setContainerVisible(!this.isContainerVisible()); + } +} From a47d562f7bc56b2d69c9888c9a4e00ca31fd6fbb Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Apr 2024 14:27:32 +0200 Subject: [PATCH 322/566] GLSP-1320: Update to latest glsp dev config (#334) - Updates to latest version of @eclipse-glsp/dev - Add generate:index utility script - Regenerate index files - Fix codeActionsOnSave in vscode settings - Refactor upgrade:next script - Reformat code base with prettier - Fix copyright headers (by running a full check with glsp checkHeaders . -t full) - Add resolutions for snabbdom to fix https://github.com/eclipse-sprotty/sprotty/pull/429 Can be removed once https://github.com/eclipse-glsp/glsp/issues/1253 is resolved Part of https://github.com/eclipse-glsp/glsp/issues/1320 Fixes https://github.com/eclipse-glsp/glsp/issues/1286 --- examples/workflow-glsp/package.json | 5 ++--- .../src/direct-task-editing/index.ts | 17 +++++++++++++++++ examples/workflow-glsp/src/index.ts | 4 ++-- examples/workflow-standalone/css/diagram.css | 2 +- packages/client/css/command-palette.css | 8 ++++++-- packages/client/css/key-shortcut.css | 8 ++++++-- packages/client/css/keyboard-tool-palette.css | 4 +++- packages/client/css/keyboard.css | 4 +++- packages/client/css/tool-palette.css | 8 ++++++-- packages/client/package.json | 1 + packages/client/src/base/feedback/index.ts | 1 - packages/client/src/base/tool-manager/index.ts | 4 ++-- packages/client/src/default-modules.ts | 2 +- .../accessibility/key-shortcut/index.ts | 4 ++-- .../accessibility/keyboard-grid/index.ts | 4 ++-- .../accessibility/keyboard-pointer/index.ts | 4 ++-- .../keyboard-pointer-listener.ts | 7 +++++-- .../keyboard-tool-palette/index.ts | 4 ++-- .../src/features/accessibility/search/index.ts | 4 ++-- .../command-palette/command-palette-module.ts | 2 +- .../src/features/command-palette/index.ts | 2 +- .../client/src/features/helper-lines/index.ts | 4 ++-- packages/client/src/features/hover/index.ts | 4 ++-- packages/client/src/features/index.ts | 3 ++- .../client/src/features/label-edit-ui/index.ts | 17 +++++++++++++++++ .../client/src/features/tool-palette/index.ts | 4 ++-- .../src/features/tools/change-bounds/index.ts | 4 ++-- .../edge-creation-tool-feedback.ts | 7 +++++-- .../src/features/tools/edge-creation/index.ts | 4 ++-- .../src/features/tools/edge-edit/index.ts | 4 ++-- .../features/tools/marquee-selection/index.ts | 4 ++-- packages/client/src/views/index.ts | 4 ++-- packages/client/src/views/rounded-corner.ts | 7 +++++-- packages/client/tsconfig.json | 1 + packages/glsp-sprotty/package.json | 1 + packages/glsp-sprotty/src/feature-modules.ts | 5 ++--- packages/glsp-sprotty/src/re-exports.ts | 2 ++ packages/protocol/package.json | 3 ++- packages/protocol/src/.indexignore | 6 ++++++ .../jsonrpc/ws-connection-provider.ts | 7 +++++-- packages/protocol/src/index.ts | 2 -- packages/protocol/src/utils/index.ts | 3 --- packages/protocol/tsconfig.json | 3 ++- 43 files changed, 133 insertions(+), 65 deletions(-) create mode 100644 examples/workflow-glsp/src/direct-task-editing/index.ts create mode 100644 packages/client/src/features/label-edit-ui/index.ts create mode 100644 packages/protocol/src/.indexignore diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index d49cee7..6295352 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -30,13 +30,12 @@ "files": [ "lib", "src", - "build", - "css", - "scripts" + "css" ], "scripts": { "build": "tsc -b", "clean": "rimraf *.tsbuildinfo lib", + "generate:index": "glsp generateIndex src -f", "lint": "eslint --ext .ts,.tsx ./src", "watch": "tsc -w" }, diff --git a/examples/workflow-glsp/src/direct-task-editing/index.ts b/examples/workflow-glsp/src/direct-task-editing/index.ts new file mode 100644 index 0000000..48791ce --- /dev/null +++ b/examples/workflow-glsp/src/direct-task-editing/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './di.config'; +export * from './direct-task-editor'; diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index 60e33f8..d67a397 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './direct-task-editing/direct-task-editor'; +export * from './direct-task-editing'; export * from './model'; export * from './workflow-diagram-module'; export * from './workflow-views'; diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 0f21caf..bc379b9 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -82,4 +82,4 @@ polygon.sprotty-node { .projection-scroll-bar { background-color: #555555; -} \ No newline at end of file +} diff --git a/packages/client/css/command-palette.css b/packages/client/css/command-palette.css index 8b14674..9e17a90 100644 --- a/packages/client/css/command-palette.css +++ b/packages/client/css/command-palette.css @@ -16,7 +16,9 @@ .command-palette { transition: opacity 0.3s linear; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .command-palette-suggestions { @@ -25,7 +27,9 @@ overflow: auto; box-sizing: border-box; border: 1px solid rgba(60, 60, 60, 0.6); - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .command-palette-suggestions .icon { diff --git a/packages/client/css/key-shortcut.css b/packages/client/css/key-shortcut.css index 796ed1c..12c02a8 100644 --- a/packages/client/css/key-shortcut.css +++ b/packages/client/css/key-shortcut.css @@ -30,7 +30,9 @@ z-index: 9999; width: 400px; max-height: 512px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .keyboard-shortcuts-menu h3 { @@ -45,7 +47,9 @@ background-color: #e4e1e1; border: 1px solid #ccc; border-radius: 3px; - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.5); + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.2), + inset 0 0 0 1px rgba(255, 255, 255, 0.5); color: #333; display: inline-block; font-size: 0.85em; diff --git a/packages/client/css/keyboard-tool-palette.css b/packages/client/css/keyboard-tool-palette.css index 28bf3a3..2ce9fa8 100644 --- a/packages/client/css/keyboard-tool-palette.css +++ b/packages/client/css/keyboard-tool-palette.css @@ -39,7 +39,9 @@ color: black; text-align: center; - font: bold 0.8rem Arial, sans-serif; + font: + bold 0.8rem Arial, + sans-serif; line-height: 0.5rem; } diff --git a/packages/client/css/keyboard.css b/packages/client/css/keyboard.css index 1d4db6c..49360a5 100644 --- a/packages/client/css/keyboard.css +++ b/packages/client/css/keyboard.css @@ -46,5 +46,7 @@ color: black; text-align: center; - font: bold 1.3rem Arial, sans-serif; + font: + bold 1.3rem Arial, + sans-serif; } diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css index 1a35e2c..d92f468 100644 --- a/packages/client/css/tool-palette.css +++ b/packages/client/css/tool-palette.css @@ -47,7 +47,9 @@ text-align: left; background: #cccccc; border: 1px solid rgba(60, 60, 60, 0.6); - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); display: flex; align-items: center; justify-content: space-between; @@ -88,7 +90,9 @@ .palette-body { background: rgba(100, 100, 100, 0.2); border: 1px solid rgba(60, 60, 60, 0.6); - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .tool-group { diff --git a/packages/client/package.json b/packages/client/package.json index a384982..0e94333 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -38,6 +38,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", + "generate:index": "glsp generateIndex src -f", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 8053f4b..5f9f73b 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -18,4 +18,3 @@ export * from './feedback-action-dispatcher'; export * from './feedback-command'; export * from './set-model-command'; export * from './update-model-command'; - diff --git a/packages/client/src/base/tool-manager/index.ts b/packages/client/src/base/tool-manager/index.ts index b903332..1a8a894 100644 --- a/packages/client/src/base/tool-manager/index.ts +++ b/packages/client/src/base/tool-manager/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './tool-manager'; export * from './tool'; +export * from './tool-manager'; diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index ae684ff..d4b1d2a 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/index.ts b/packages/client/src/features/accessibility/key-shortcut/index.ts index 8ff23f3..43df88f 100644 --- a/packages/client/src/features/accessibility/key-shortcut/index.ts +++ b/packages/client/src/features/accessibility/key-shortcut/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './accessible-key-shortcut-tool'; export * from './accessible-key-shortcut'; +export * from './accessible-key-shortcut-tool'; export * from './di.config'; diff --git a/packages/client/src/features/accessibility/keyboard-grid/index.ts b/packages/client/src/features/accessibility/keyboard-grid/index.ts index d3b3f2e..7b6e618 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/index.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,6 +15,6 @@ ********************************************************************************/ export * from './action'; export * from './constants'; -export * from './keyboard-grid-search-palette'; export * from './keyboard-grid'; +export * from './keyboard-grid-search-palette'; export * from './keyboard-node-grid'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/index.ts b/packages/client/src/features/accessibility/keyboard-pointer/index.ts index 6ec6851..d6f812e 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/index.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ export * from './actions'; export * from './constants'; +export * from './keyboard-pointer'; export * from './keyboard-pointer-listener'; export * from './keyboard-pointer-module'; export * from './keyboard-pointer-position'; -export * from './keyboard-pointer'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts index 33d6d0a..e46cc11 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -31,7 +31,10 @@ export class KeyboardPointerKeyboardListener { return this.keyboardPointer.getPosition; } - constructor(protected readonly keyboardPointer: KeyboardPointer, protected readonly actionDispatcher: IActionDispatcher) {} + constructor( + protected readonly keyboardPointer: KeyboardPointer, + protected readonly actionDispatcher: IActionDispatcher + ) {} keyDown(event: KeyboardEvent): void { this.moveIfArrows(event); diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts index f49dcc4..b8b1621 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './keyboard-tool-palette-module'; export * from './keyboard-tool-palette'; +export * from './keyboard-tool-palette-module'; diff --git a/packages/client/src/features/accessibility/search/index.ts b/packages/client/src/features/accessibility/search/index.ts index 7cf637d..30b4bf1 100644 --- a/packages/client/src/features/accessibility/search/index.ts +++ b/packages/client/src/features/accessibility/search/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './search-palette-module'; export * from './search-palette'; +export * from './search-palette-module'; export * from './search-tool'; diff --git a/packages/client/src/features/command-palette/command-palette-module.ts b/packages/client/src/features/command-palette/command-palette-module.ts index 8b88490..3bbecd8 100644 --- a/packages/client/src/features/command-palette/command-palette-module.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/command-palette/index.ts b/packages/client/src/features/command-palette/index.ts index e818d6b..312a6c5 100644 --- a/packages/client/src/features/command-palette/index.ts +++ b/packages/client/src/features/command-palette/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/index.ts b/packages/client/src/features/helper-lines/index.ts index 7e836a1..18e60b0 100644 --- a/packages/client/src/features/helper-lines/index.ts +++ b/packages/client/src/features/helper-lines/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './helper-line-feedback'; -export * from './helper-line-manager-default'; export * from './helper-line-manager'; +export * from './helper-line-manager-default'; export * from './helper-line-module'; export * from './model'; export * from './view'; diff --git a/packages/client/src/features/hover/index.ts b/packages/client/src/features/hover/index.ts index 948df0b..869f1f1 100644 --- a/packages/client/src/features/hover/index.ts +++ b/packages/client/src/features/hover/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './hover-module'; export * from './hover'; +export * from './hover-module'; diff --git a/packages/client/src/features/index.ts b/packages/client/src/features/index.ts index e58d15d..2afb595 100644 --- a/packages/client/src/features/index.ts +++ b/packages/client/src/features/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,6 +26,7 @@ export * from './helper-lines'; export * from './hints'; export * from './hover'; export * from './label-edit'; +export * from './label-edit-ui'; export * from './layout'; export * from './navigation'; export * from './reconnect'; diff --git a/packages/client/src/features/label-edit-ui/index.ts b/packages/client/src/features/label-edit-ui/index.ts new file mode 100644 index 0000000..58af763 --- /dev/null +++ b/packages/client/src/features/label-edit-ui/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './label-edit-ui'; +export * from './label-edit-ui-module'; diff --git a/packages/client/src/features/tool-palette/index.ts b/packages/client/src/features/tool-palette/index.ts index 008dc76..de20986 100644 --- a/packages/client/src/features/tool-palette/index.ts +++ b/packages/client/src/features/tool-palette/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './tool-palette-module'; export * from './tool-palette'; +export * from './tool-palette-module'; diff --git a/packages/client/src/features/tools/change-bounds/index.ts b/packages/client/src/features/tools/change-bounds/index.ts index 8b50ec7..434437f 100644 --- a/packages/client/src/features/tools/change-bounds/index.ts +++ b/packages/client/src/features/tools/change-bounds/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './change-bounds-tool'; export * from './change-bounds-tool-feedback'; export * from './change-bounds-tool-module'; -export * from './change-bounds-tool'; export * from './view'; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts index d468b94..ed8c6c7 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -34,7 +34,10 @@ import { absoluteToParent, getAbsolutePosition, toAbsoluteBounds } from '../../. import { FeedbackEdgeEnd, feedbackEdgeEndId } from './dangling-edge-feedback'; export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements Disposable { - constructor(protected anchorRegistry: AnchorComputerRegistry, protected feedbackDispatcher: IFeedbackActionDispatcher) { + constructor( + protected anchorRegistry: AnchorComputerRegistry, + protected feedbackDispatcher: IFeedbackActionDispatcher + ) { super(); } diff --git a/packages/client/src/features/tools/edge-creation/index.ts b/packages/client/src/features/tools/edge-creation/index.ts index c16e26d..21b73ae 100644 --- a/packages/client/src/features/tools/edge-creation/index.ts +++ b/packages/client/src/features/tools/edge-creation/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,6 +15,6 @@ ********************************************************************************/ export * from './dangling-edge-feedback'; export * from './edege-creation-module'; -export * from './edge-creation-tool-feedback'; export * from './edge-creation-tool'; +export * from './edge-creation-tool-feedback'; export * from './view'; diff --git a/packages/client/src/features/tools/edge-edit/index.ts b/packages/client/src/features/tools/edge-edit/index.ts index a1bd73e..5e69905 100644 --- a/packages/client/src/features/tools/edge-edit/index.ts +++ b/packages/client/src/features/tools/edge-edit/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,5 +14,5 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './edge-edit-module'; -export * from './edge-edit-tool-feedback'; export * from './edge-edit-tool'; +export * from './edge-edit-tool-feedback'; diff --git a/packages/client/src/features/tools/marquee-selection/index.ts b/packages/client/src/features/tools/marquee-selection/index.ts index 6eab570..548217a 100644 --- a/packages/client/src/features/tools/marquee-selection/index.ts +++ b/packages/client/src/features/tools/marquee-selection/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ export * from './marquee-behavior'; export * from './marquee-mouse-tool'; export * from './marquee-selection-module'; -export * from './marquee-tool-feedback'; export * from './marquee-tool'; +export * from './marquee-tool-feedback'; export * from './model'; export * from './view'; diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index a10f749..97690b3 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,5 +18,5 @@ export * from './compartments'; export * from './gedge-view'; export * from './glsp-projection-view'; export * from './issue-marker-view'; -export * from './rounded-corner-view'; export * from './rounded-corner'; +export * from './rounded-corner-view'; diff --git a/packages/client/src/views/rounded-corner.ts b/packages/client/src/views/rounded-corner.ts index b70ba28..f60c5ce 100644 --- a/packages/client/src/views/rounded-corner.ts +++ b/packages/client/src/views/rounded-corner.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,7 +27,10 @@ export class RoundedCornerWrapper { protected _bottomRightCorner: RoundedCorner; protected _bottomLeftCorner: RoundedCorner; - constructor(public readonly element: GShapeElement & Hoverable & Selectable, public readonly cornerRadius: CornerRadius) {} + constructor( + public readonly element: GShapeElement & Hoverable & Selectable, + public readonly cornerRadius: CornerRadius + ) {} get size(): Dimension { return this.element.size; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index e2fdc87..074058a 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -8,6 +8,7 @@ "reactNamespace": "JSX" }, "include": ["src", "src/**/*.json"], + "exclude": ["**/*.spec.ts"], "references": [ { "path": "../glsp-sprotty" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index c76d025..1ae3902 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -28,6 +28,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo", + "generate:index": "glsp generateIndex src -f", "lint": "eslint --ext .ts,.tsx ./src", "watch": "tsc -w" }, diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts index fb7433d..aed1524 100644 --- a/packages/glsp-sprotty/src/feature-modules.ts +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable no-restricted-imports */ - -import { FeatureModule } from '@eclipse-glsp/protocol'; +import { FeatureModule } from '@eclipse-glsp/protocol/lib/di'; import sprottyDefaultModule from 'sprotty/lib/base/di.config'; import sprottyButtonModule from 'sprotty/lib/features/button/di.config'; import sprottyEdgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 1752135..e08cd14 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -18,6 +18,7 @@ * @eclipse-glsp/protocol */ export * from '@eclipse-glsp/protocol'; +export * from '@eclipse-glsp/protocol/lib/di'; /* * sprotty @@ -325,3 +326,4 @@ export * from 'sprotty/lib/utils/iterable'; export * from 'sprotty/lib/utils/keyboard'; export * from 'sprotty/lib/utils/logging'; export * from 'sprotty/lib/utils/registry'; + diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 4ee1d19..ddf9cb0 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -36,7 +36,8 @@ ], "scripts": { "build": "tsc -b", - "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output ", + "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", + "generate:index": "glsp generateIndex src -f", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/protocol/src/.indexignore b/packages/protocol/src/.indexignore new file mode 100644 index 0000000..ea64672 --- /dev/null +++ b/packages/protocol/src/.indexignore @@ -0,0 +1,6 @@ +# do not export test-util in index to avoid a necessary dependency on test frameworks such as chai +# adopters can still use the functionality by importing it through the complete path +utils/test-util.ts +# do not export the DI utilities to avoid a necessary dependency on reflect-metadata/inversify +# This is reexported in @eclipse-glsp/client & @eclipse-glsp/server so that adopters can still use it +di \ No newline at end of file diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts index 55ad960..73ad42d 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -55,7 +55,10 @@ export class GLSPWebSocketProvider { reconnectDelay: 1000 }; - constructor(protected url: string, options?: GLSPWebSocketOptions) { + constructor( + protected url: string, + options?: GLSPWebSocketOptions + ) { this.options = Object.assign(this.options, options); } diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e625a46..39539de 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -15,10 +15,8 @@ ********************************************************************************/ export * from './action-protocol'; export * from './client-server-protocol'; -export * from './di'; export * from './model'; export * from './re-exports'; export * from './sprotty-actions'; export * from './sprotty-geometry-point'; export * from './utils'; - diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index c47e71f..d606c75 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -16,7 +16,4 @@ export * from './array-util'; export * from './disposable'; export * from './event'; -// we do not export test-util to avoid a dependency on test frameworks such as chai -// however, adopters can still access the file by accessing it through the complete path -// export * from './test-util'; export * from './type-util'; diff --git a/packages/protocol/tsconfig.json b/packages/protocol/tsconfig.json index 07b938b..19cd381 100644 --- a/packages/protocol/tsconfig.json +++ b/packages/protocol/tsconfig.json @@ -6,5 +6,6 @@ "outDir": "lib", "reactNamespace": "JSX" }, - "include": ["src"] + "include": ["src"], + "exclude": ["**/*.spec.ts"] } From ec6f0205cc2668a67687cc1533b5f2c6473c4a28 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 29 Apr 2024 10:12:22 +0000 Subject: [PATCH 323/566] Provide Geometry utility classes (#341) - Extend functions for Bounds, Dimensions, and Point - Provide types and functions for Vector and Movement Fixes https://github.com/eclipse-glsp/glsp/issues/1322 --- .../src/workflow-diagram-module.ts | 3 +- examples/workflow-glsp/src/workflow-views.tsx | 14 +- .../accessibility/move-zoom/move-handler.ts | 2 +- .../change-bounds/movement-restrictor.ts | 21 +- ...ouse-tracking-element-position-listener.ts | 16 +- .../helper-lines/helper-line-feedback.ts | 107 +++---- .../client/src/features/helper-lines/model.ts | 31 +- .../features/layout/layout-elements-action.ts | 17 +- .../src/features/viewport/reposition.ts | 25 +- packages/client/src/utils/geometry-util.ts | 109 +++---- packages/client/src/utils/gmodel-util.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 31 +- packages/protocol/src/index.ts | 2 + .../src/sprotty-geometry-bounds.spec.ts | 287 ++++++++++++++++++ .../protocol/src/sprotty-geometry-bounds.ts | 284 +++++++++++++++++ .../src/sprotty-geometry-dimension.spec.ts | 87 ++++++ .../src/sprotty-geometry-dimension.ts | 94 ++++++ .../src/sprotty-geometry-point.spec.ts | 116 +++++++ .../protocol/src/sprotty-geometry-point.ts | 105 +++++++ .../src/utils/geometry-movement.spec.ts | 119 ++++++++ .../protocol/src/utils/geometry-movement.ts | 104 +++++++ packages/protocol/src/utils/geometry-util.ts | 26 ++ .../src/utils/geometry-vector.spec.ts | 183 +++++++++++ .../protocol/src/utils/geometry-vector.ts | 235 ++++++++++++++ packages/protocol/src/utils/index.ts | 3 + 25 files changed, 1802 insertions(+), 221 deletions(-) create mode 100644 packages/protocol/src/sprotty-geometry-bounds.spec.ts create mode 100644 packages/protocol/src/sprotty-geometry-bounds.ts create mode 100644 packages/protocol/src/sprotty-geometry-dimension.spec.ts create mode 100644 packages/protocol/src/sprotty-geometry-dimension.ts create mode 100644 packages/protocol/src/sprotty-geometry-point.spec.ts create mode 100644 packages/protocol/src/utils/geometry-movement.spec.ts create mode 100644 packages/protocol/src/utils/geometry-movement.ts create mode 100644 packages/protocol/src/utils/geometry-util.ts create mode 100644 packages/protocol/src/utils/geometry-vector.spec.ts create mode 100644 packages/protocol/src/utils/geometry-vector.ts diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 807d27c..70bab38 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -31,6 +31,7 @@ import { IHelperLineOptions, ISnapper, LogLevel, + Point, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, @@ -84,7 +85,7 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, // the user needs to use twice the force (double the distance) to break through a helper line compared to moving on the grid const snapper = ctx.container.get(TYPES.ISnapper); if (snapper instanceof GridSnapper) { - options.minimumMoveDelta = { x: snapper.grid.x * 2, y: snapper.grid.y * 2 }; + options.minimumMoveDelta = Point.multiplyScalar(snapper.grid, 2); } // skip icons for alignment as well as compartments which are only used for structure options.alignmentElementFilter = element => diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index dfebe6e..e014969 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -14,15 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - angleOfPoint, - findParentByFeature, - getSubType, + GEdge, Point, PolylineEdgeViewWithGapsOnIntersections, RenderingContext, - GEdge, - setAttr, ShapeView, + angleOfPoint, + findParentByFeature, + getSubType, + setAttr, svg, toDegrees } from '@eclipse-glsp/client'; @@ -44,9 +44,7 @@ export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { class-sprotty-edge={true} class-arrow={true} d='M 1,0 L 10,-4 L 10,4 Z' - transform={`rotate(${toDegrees(angleOfPoint({ x: p1.x - p2.x, y: p1.y - p2.y }))} ${p2.x} ${p2.y}) translate(${p2.x} ${ - p2.y - })`} + transform={`rotate(${toDegrees(angleOfPoint(Point.subtract(p1, p2)))} ${p2.x} ${p2.y}) translate(${p2.x} ${p2.y})`} /> ); additionals.push(arrow); diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index 56b6ba4..077e6f0 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -205,7 +205,7 @@ export class MoveElementHandler implements IActionHandler { } protected getTargetBounds(element: SelectableBoundsAware, action: MoveElementAction): Point { - let position = { x: element.bounds.x + action.moveX, y: element.bounds.y + action.moveY }; + let position = Point.add(element.bounds, { x: action.moveX, y: action.moveY }); if (this.snapper && action.snap) { position = this.snapper.snap(position, element); } diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 55e1b9a..b2c5d3b 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Bounds, BoundsAware, GModelElement, GNode, GParentElement, Point } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { BoundsAware, Point, GModelElement, GNode, GParentElement } from '@eclipse-glsp/sprotty'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { SResizeHandle, isBoundsAwareMoveable } from './model'; @@ -69,24 +69,7 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { } protected areOverlapping(element1: GModelElement & BoundsAware, element2: GModelElement & BoundsAware): boolean { - const b1 = toAbsoluteBounds(element1); - const b2 = toAbsoluteBounds(element2); - const r1TopLeft: Point = b1; - const r1BottomRight = { x: b1.x + b1.width, y: b1.y + b1.height }; - const r2TopLeft: Point = b2; - const r2BottomRight = { x: b2.x + b2.width, y: b2.y + b2.height }; - - // If one rectangle is on left side of other - if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) { - return false; - } - - // If one rectangle is above other - if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) { - return false; - } - - return true; + return Bounds.overlap(toAbsoluteBounds(element1), toAbsoluteBounds(element2)); } } diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index a137aa7..49e68f5 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -14,7 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, GModelElement, Locateable, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; +import { + Action, + Dimension, + Disposable, + GModelElement, + Locateable, + MoveAction, + Point, + isBoundsAware, + isMoveable +} from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; @@ -26,8 +36,8 @@ import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; -import { PositionSnapper } from '../change-bounds/position-snapper'; import { PointPositionUpdater } from '../change-bounds/point-position-updater'; +import { PositionSnapper } from '../change-bounds/position-snapper'; import { useSnap } from '../change-bounds/snap'; export interface PositioningTool extends Tool { @@ -88,7 +98,7 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener protected getTargetPosition(target: GModelElement, event: MouseEvent, element: GModelElement & Locateable): Point { let targetPosition = getAbsolutePosition(target, event); if (this.cursorPosition === 'middle' && isBoundsAware(element)) { - targetPosition = Point.subtract(targetPosition, { x: element.bounds.width / 2, y: element.bounds.height / 2 }); + targetPosition = Point.subtract(targetPosition, Dimension.center(element.bounds)); } return targetPosition; } diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index aa78bf9..d4b1b9d 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -36,25 +36,6 @@ import { inject, injectable } from 'inversify'; import { partition } from 'lodash'; import '../../../css/helper-lines.css'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; -import { - bottom, - bottomCenter, - bottomLeft, - bottomRight, - center, - isAbove, - isBefore, - left, - middle, - middleLeft, - middleRight, - right, - sortBy, - top, - topCenter, - topLeft, - topRight -} from '../../utils/geometry-util'; import { BoundsAwareModelElement, findTopLevelElementByFeature, @@ -191,12 +172,12 @@ export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { const helperLines: HelperLine[] = []; this.log('Find helperlines for viewport:', root); const viewportBounds = getViewportBounds(root, root.canvasBounds); - if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, viewportBounds, bounds, 2)) { - helperLines.push(new HelperLine(topCenter(viewportBounds), bottomCenter(viewportBounds), HelperLineType.Center)); + if (lineTypes.includes(HelperLineType.Center) && this.isAligned(Bounds.centerX, viewportBounds, bounds, 2)) { + helperLines.push(new HelperLine(Bounds.topCenter(viewportBounds), Bounds.bottomCenter(viewportBounds), HelperLineType.Center)); this.log('- Reference bounds center align with viewport.', viewportBounds); } - if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, viewportBounds, bounds, 2)) { - helperLines.push(new HelperLine(middleLeft(viewportBounds), middleRight(viewportBounds), HelperLineType.Middle)); + if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(Bounds.middle, viewportBounds, bounds, 2)) { + helperLines.push(new HelperLine(Bounds.middleLeft(viewportBounds), Bounds.middleRight(viewportBounds), HelperLineType.Middle)); this.log('- Reference bounds middle align with viewport.', viewportBounds); } if (helperLines.length > 0) { @@ -213,78 +194,90 @@ export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { protected calcHelperLinesForBounds(elementBounds: Bounds, bounds: Bounds, lineTypes: HelperLineType[]): HelperLine[] { const helperLines: HelperLine[] = []; - if (lineTypes.includes(HelperLineType.Left) && this.isAligned(left, elementBounds, bounds, this.alignmentEpsilon)) { - const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower - helperLines.push(new HelperLine(bottomLeft(below), topLeft(above), HelperLineType.Left)); + if (lineTypes.includes(HelperLineType.Left) && this.isAligned(Bounds.left, elementBounds, bounds, this.alignmentEpsilon)) { + const [above, below] = Bounds.sortBy(Bounds.top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(Bounds.bottomLeft(below), Bounds.topLeft(above), HelperLineType.Left)); this.log('- Reference bounds left align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Center) && this.isAligned(center, elementBounds, bounds, this.alignmentEpsilon)) { - const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower - helperLines.push(new HelperLine(topCenter(above), bottomCenter(below), HelperLineType.Center)); + if (lineTypes.includes(HelperLineType.Center) && this.isAligned(Bounds.centerX, elementBounds, bounds, this.alignmentEpsilon)) { + const [above, below] = Bounds.sortBy(Bounds.top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(Bounds.topCenter(above), Bounds.bottomCenter(below), HelperLineType.Center)); this.log('- Reference bounds center align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Right) && this.isAligned(right, elementBounds, bounds, this.alignmentEpsilon)) { - const [above, below] = sortBy(top, elementBounds, bounds); // higher top-value ==> lower - helperLines.push(new HelperLine(bottomRight(below), topRight(above), HelperLineType.Right)); + if (lineTypes.includes(HelperLineType.Right) && this.isAligned(Bounds.right, elementBounds, bounds, this.alignmentEpsilon)) { + const [above, below] = Bounds.sortBy(Bounds.top, elementBounds, bounds); // higher top-value ==> lower + helperLines.push(new HelperLine(Bounds.bottomRight(below), Bounds.topRight(above), HelperLineType.Right)); this.log('- Reference bounds right align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Bottom) && this.isAligned(bottom, elementBounds, bounds, this.alignmentEpsilon)) { - const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right - helperLines.push(new HelperLine(bottomLeft(before), bottomRight(after), HelperLineType.Bottom)); + if (lineTypes.includes(HelperLineType.Bottom) && this.isAligned(Bounds.bottom, elementBounds, bounds, this.alignmentEpsilon)) { + const [before, after] = Bounds.sortBy(Bounds.left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(Bounds.bottomLeft(before), Bounds.bottomRight(after), HelperLineType.Bottom)); this.log('- Reference bounds bottom align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(middle, elementBounds, bounds, this.alignmentEpsilon)) { - const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right - helperLines.push(new HelperLine(middleLeft(before), middleRight(after), HelperLineType.Middle)); + if (lineTypes.includes(HelperLineType.Middle) && this.isAligned(Bounds.centerY, elementBounds, bounds, this.alignmentEpsilon)) { + const [before, after] = Bounds.sortBy(Bounds.left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(Bounds.middleLeft(before), Bounds.middleRight(after), HelperLineType.Middle)); this.log('- Reference bounds middle align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.Top) && this.isAligned(top, elementBounds, bounds, this.alignmentEpsilon)) { - const [before, after] = sortBy(left, elementBounds, bounds); // higher left-value ==> more to the right - helperLines.push(new HelperLine(topLeft(before), topRight(after), HelperLineType.Top)); + if (lineTypes.includes(HelperLineType.Top) && this.isAligned(Bounds.top, elementBounds, bounds, this.alignmentEpsilon)) { + const [before, after] = Bounds.sortBy(Bounds.left, elementBounds, bounds); // higher left-value ==> more to the right + helperLines.push(new HelperLine(Bounds.topLeft(before), Bounds.topRight(after), HelperLineType.Top)); this.log('- Reference bounds top align with element', elementBounds); } - if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(left(elementBounds), right(bounds), this.alignmentEpsilon)) { - if (isAbove(bounds, elementBounds)) { - helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.RightLeft)); + if ( + lineTypes.includes(HelperLineType.LeftRight) && + this.isMatch(Bounds.left(elementBounds), Bounds.right(bounds), this.alignmentEpsilon) + ) { + if (Bounds.isAbove(bounds, elementBounds)) { + helperLines.push(new HelperLine(Bounds.bottomLeft(elementBounds), Bounds.topRight(bounds), HelperLineType.RightLeft)); this.log('- Reference bounds right aligns with element left', elementBounds); } else { - helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.RightLeft)); + helperLines.push(new HelperLine(Bounds.topLeft(elementBounds), Bounds.bottomRight(bounds), HelperLineType.RightLeft)); this.log('- Reference bounds right aligns with element left', elementBounds); } } - if (lineTypes.includes(HelperLineType.LeftRight) && this.isMatch(right(elementBounds), left(bounds), this.alignmentEpsilon)) { - if (isAbove(bounds, elementBounds)) { - helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.LeftRight)); + if ( + lineTypes.includes(HelperLineType.LeftRight) && + this.isMatch(Bounds.right(elementBounds), Bounds.left(bounds), this.alignmentEpsilon) + ) { + if (Bounds.isAbove(bounds, elementBounds)) { + helperLines.push(new HelperLine(Bounds.bottomRight(elementBounds), Bounds.topLeft(bounds), HelperLineType.LeftRight)); this.log('- Reference bounds left aligns with element right', elementBounds); } else { - helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.LeftRight)); + helperLines.push(new HelperLine(Bounds.topRight(elementBounds), Bounds.bottomLeft(bounds), HelperLineType.LeftRight)); this.log('- Reference bounds left aligns with element right', elementBounds); } } - if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(top(elementBounds), bottom(bounds), this.alignmentEpsilon)) { - if (isBefore(bounds, elementBounds)) { - helperLines.push(new HelperLine(topRight(elementBounds), bottomLeft(bounds), HelperLineType.BottomTop)); + if ( + lineTypes.includes(HelperLineType.TopBottom) && + this.isMatch(Bounds.top(elementBounds), Bounds.bottom(bounds), this.alignmentEpsilon) + ) { + if (Bounds.isBefore(bounds, elementBounds)) { + helperLines.push(new HelperLine(Bounds.topRight(elementBounds), Bounds.bottomLeft(bounds), HelperLineType.BottomTop)); this.log('- Reference bounds bottom aligns with element top', elementBounds); } else { - helperLines.push(new HelperLine(topLeft(elementBounds), bottomRight(bounds), HelperLineType.BottomTop)); + helperLines.push(new HelperLine(Bounds.topLeft(elementBounds), Bounds.bottomRight(bounds), HelperLineType.BottomTop)); this.log('- Reference bounds bottom aligns with element top', elementBounds); } } - if (lineTypes.includes(HelperLineType.TopBottom) && this.isMatch(bottom(elementBounds), top(bounds), this.alignmentEpsilon)) { - if (isBefore(bounds, elementBounds)) { - helperLines.push(new HelperLine(bottomRight(elementBounds), topLeft(bounds), HelperLineType.TopBottom)); + if ( + lineTypes.includes(HelperLineType.TopBottom) && + this.isMatch(Bounds.bottom(elementBounds), Bounds.top(bounds), this.alignmentEpsilon) + ) { + if (Bounds.isBefore(bounds, elementBounds)) { + helperLines.push(new HelperLine(Bounds.bottomRight(elementBounds), Bounds.topLeft(bounds), HelperLineType.TopBottom)); this.log('- Reference bounds top aligns with element bottom', elementBounds); } else { - helperLines.push(new HelperLine(bottomLeft(elementBounds), topRight(bounds), HelperLineType.TopBottom)); + helperLines.push(new HelperLine(Bounds.bottomLeft(elementBounds), Bounds.topRight(bounds), HelperLineType.TopBottom)); this.log('- Reference bounds top aligns with element bottom', elementBounds); } } diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index 21c13d6..7b70cd6 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, Bounds, GChildElement, GModelElement, GShapeElement, Point } from '@eclipse-glsp/sprotty'; +import { Args, Bounds, Direction, GChildElement, GModelElement, GShapeElement, Point, Vector } from '@eclipse-glsp/sprotty'; import { v4 as uuid } from 'uuid'; import { ArgsAware } from '../../base/args-feature'; import { ResizeHandleLocation } from '../change-bounds/model'; @@ -98,30 +98,8 @@ export function isSelectionBounds(element: GModelElement): element is SelectionB return element.type === SELECTION_BOUNDS; } -export const Direction = { - Left: 'left', - Right: 'right', - Up: 'up', - Down: 'down' -} as const; - -// allow any string to be set as helper line type to support customization -export type Direction = (typeof Direction)[keyof typeof Direction]; - -export function getDirectionOf(point: Point): Direction[] { - const directions: Direction[] = []; - if (point.x < 0) { - directions.push(Direction.Left); - } else if (point.x > 0) { - directions.push(Direction.Right); - } - if (point.y < 0) { - directions.push(Direction.Up); - } else if (point.y > 0) { - directions.push(Direction.Down); - } - return directions; -} +// kept for backwards compatibility after moving the definition +export const getDirectionOf = Vector.direction; export function getDirectionFrom(resize?: ResizeHandleLocation): Direction[] { if (resize === ResizeHandleLocation.TopLeft) { @@ -138,3 +116,6 @@ export function getDirectionFrom(resize?: ResizeHandleLocation): Direction[] { } return []; } + +// re-export for backwards compatibility after moving the definition +export { Direction } from '@eclipse-glsp/sprotty'; diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index b28c8d1..7996eb3 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -13,17 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, optional } from 'inversify'; import { Action, ChangeBoundsOperation, + Dimension, ElementAndBounds, ElementMove, + GModelElement, IActionDispatcher, IActionHandler, ICommand, MoveAction, - GModelElement, + Point, SetBoundsAction, TYPES, Writable, @@ -31,9 +32,10 @@ import { hasNumberProp, hasStringProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { SelectionService } from '../../base/selection-service'; -import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement, getElements } from '../../utils/gmodel-util'; +import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; @@ -227,12 +229,13 @@ export class ResizeElementsActionHandler extends LayoutElementsActionHandler { resizeWidthAndHeight(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void { const targetWidth = reduceFn(...elements.map(element => element.bounds.width)); const targetHeight = reduceFn(...elements.map(element => element.bounds.height)); + const targetDimension: Dimension = { width: targetWidth, height: targetHeight }; this.dispatchResizeActions(elements, (element, bounds) => { // resize around center and middle (horizontal & vertical) - const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width); - const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height); - bounds.newPosition = { x: element.bounds.x - halfDiffWidth, y: element.bounds.y - halfDiffHeight }; - bounds.newSize = { width: targetWidth, height: targetHeight }; + const difference = Dimension.subtract(targetDimension, element.bounds); + const center = Dimension.center(difference); + bounds.newPosition = Point.subtract(element.bounds, center); + bounds.newSize = targetDimension; }); } diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index 8c9d469..e291c77 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, Bounds, @@ -23,14 +22,16 @@ import { GChildElement, GModelElement, GModelRoot, + Point, TYPES, Viewport, getRouteBounds, hasArrayProp, isViewport } from '@eclipse-glsp/sprotty'; -import { calcElementAndRoute } from '../../utils/gmodel-util'; +import { inject, injectable } from 'inversify'; import { GEdge } from '../../model'; +import { calcElementAndRoute } from '../../utils/gmodel-util'; export interface RepositionAction extends Action { kind: typeof RepositionAction.KIND; @@ -83,25 +84,21 @@ export class RepositionCommand extends BoundsAwareViewportCommand { return this.action.elementIDs; } - getNewViewport(bounds: Bounds, model: GModelRoot): Viewport | undefined { + getNewViewport(combinedElementBounds: Bounds, model: GModelRoot): Viewport | undefined { if (!Dimension.isValid(model.canvasBounds)) { return undefined; } if (isViewport(model)) { - const zoom = model.zoom; - const c = Bounds.center(bounds); - - if (this.isFullyVisible(bounds, model)) { + if (this.isFullyVisible(combinedElementBounds, model)) { return undefined; } else { - return { - scroll: { - x: c.x - (0.5 * model.canvasBounds.width) / zoom, - y: c.y - (0.5 * model.canvasBounds.height) / zoom - }, - zoom: zoom - }; + const zoom = model.zoom; + const centerOfElements = Bounds.center(combinedElementBounds); + const canvasCenter = Dimension.center(model.canvasBounds); + const scrollCenter = Point.subtract(centerOfElements, canvasCenter); + const scroll = Point.map(scrollCenter, coordinate => coordinate / zoom); + return { scroll, zoom }; } } diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index bfb64db..47e6de7 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -14,88 +14,69 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Point } from '@eclipse-glsp/sprotty'; +import { Bounds } from '@eclipse-glsp/sprotty'; -export function left(bounds: Bounds): number { - return bounds.x; -} +/** @deprecated Use {@link Bounds.left} */ +export const left = Bounds.left; -export function center(bounds: Bounds): number { - return bounds.x + (bounds.width >= 0 ? bounds.width * 0.5 : 0); -} +/** @deprecated Use {@link Bounds.centerX} */ +export const center = Bounds.center; -export function right(bounds: Bounds): number { - return bounds.x + bounds.width; -} +/** @deprecated Use {@link Bounds.right} */ +export const right = Bounds.right; -export function top(bounds: Bounds): number { - return bounds.y; -} +/** @deprecated Use {@link Bounds.top} */ +export const top = Bounds.top; -export function middle(bounds: Bounds): number { - return bounds.y + (bounds.height >= 0 ? bounds.height * 0.5 : 0); -} +/** @deprecated Use {@link Bounds.middle} */ +export const middle = Bounds.middle; -export function bottom(bounds: Bounds): number { - return bounds.y + bounds.height; -} +/** @deprecated Use {@link Bounds.bottom} */ +export const bottom = Bounds.bottom; -export function topLeft(bounds: Bounds): Point { - return { x: left(bounds), y: top(bounds) }; -} +/** @deprecated Use {@link Bounds.topLeft} */ +export const topLeft = Bounds.topLeft; -export function topCenter(bounds: Bounds): Point { - return { x: center(bounds), y: top(bounds) }; -} +/** @deprecated Use {@link Bounds.topCenter} */ +export const topCenter = Bounds.topCenter; -export function topRight(bounds: Bounds): Point { - return { x: right(bounds), y: top(bounds) }; -} +/** @deprecated Use {@link Bounds.topRight} */ +export const topRight = Bounds.topRight; -export function middleLeft(bounds: Bounds): Point { - return { x: left(bounds), y: middle(bounds) }; -} +/** @deprecated Use {@link Bounds.middleLeft} */ +export const middleLeft = Bounds.middleLeft; -export function middleCenter(bounds: Bounds): Point { - return { x: center(bounds), y: middle(bounds) }; -} +/** @deprecated Use {@link Bounds.middleCenter} */ +export const middleCenter = Bounds.middleCenter; -export function middleRight(bounds: Bounds): Point { - return { x: right(bounds), y: middle(bounds) }; -} +/** @deprecated Use {@link Bounds.middleRight} */ +export const middleRight = Bounds.middleRight; -export function bottomLeft(bounds: Bounds): Point { - return { x: left(bounds), y: bottom(bounds) }; -} +/** @deprecated Use {@link Bounds.bottomLeft} */ +export const bottomLeft = Bounds.bottomLeft; -export function bottomCenter(bounds: Bounds): Point { - return { x: center(bounds), y: bottom(bounds) }; -} +/** @deprecated Use {@link Bounds.bottomCenter} */ +export const bottomCenter = Bounds.bottomCenter; -export function bottomRight(bounds: Bounds): Point { - return { x: right(bounds), y: bottom(bounds) }; -} +/** @deprecated Use {@link Bounds.bottomRight} */ +export const bottomRight = Bounds.bottomRight; -export function sortBy(rankFunc: (elem: Bounds) => number, ...points: Bounds[]): Bounds[] { - return points.sort(compareFunction(rankFunc)); -} +/** @deprecated Use {@link Bounds.left} */ +export const sortBy = Bounds.sortBy; -export function compareFunction(rankFunc: (elem: T) => number): (left: T, right: T) => number { - return (elemLeft, elemRight) => rankFunc(elemLeft) - rankFunc(elemRight); -} +export const compareFunction = + (rankFunc: (elem: T) => number): ((x: T, y: T) => number) => + (x, y) => + rankFunc(x) - rankFunc(y); -export function isAbove(leftBounds: Bounds, rightBounds: Bounds): boolean { - return top(leftBounds) <= top(rightBounds); -} +/** @deprecated Use {@link Bounds.isAbove} */ +export const isAbove = Bounds.isAbove; -export function isBelow(leftBounds: Bounds, rightBounds: Bounds): boolean { - return top(leftBounds) >= top(rightBounds); -} +/** @deprecated Use {@link Bounds.isBelow} */ +export const isBelow = Bounds.isBelow; -export function isBefore(leftBounds: Bounds, rightBounds: Bounds): boolean { - return left(leftBounds) < left(rightBounds); -} +/** @deprecated Use {@link Bounds.isBefore} */ +export const isBefore = Bounds.isBefore; -export function isAfter(leftBounds: Bounds, rightBounds: Bounds): boolean { - return left(leftBounds) >= left(rightBounds); -} +/** @deprecated Use {@link Bounds.isAfter} */ +export const isAfter = Bounds.isAfter; diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index a2b4c8c..f690f3f 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -347,7 +347,7 @@ export function findTopLevelElementByFeature( export function calculateDeltaBetweenPoints(target: Point, source: Point, element: GModelElement): Point { const delta = Point.subtract(target, source); const zoom = getZoom(element); - const adaptedDelta = { x: delta.x / zoom, y: delta.y / zoom }; + const adaptedDelta = Point.divideScalar(delta, zoom); return adaptedDelta; } diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 8ec28fe..acb7b7b 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -29,7 +29,6 @@ import { isViewport, translateBounds } from '@eclipse-glsp/sprotty'; -import { bottomRight, topLeft } from './geometry-util'; import { BoundsAwareModelElement } from './gmodel-util'; /** @@ -50,33 +49,23 @@ export function getAbsolutePosition(target: GModelElement, mouseEvent: MouseEven } export function getAbsolutePositionByPoint(target: GModelElement, point: Point): Point { - let xPos = point.x; - let yPos = point.y; - const canvasBounds = target.root.canvasBounds; - xPos -= canvasBounds.x; - yPos -= canvasBounds.y; + let position = Point.subtract(point, target.root.canvasBounds); const viewport: Viewport | undefined = findParentByFeature(target, isViewport); - const zoom = viewport ? viewport.zoom : 1; if (viewport) { - const scroll: Point = { x: viewport.scroll.x, y: viewport.scroll.y }; - xPos += scroll.x * zoom; - yPos += scroll.y * zoom; - - xPos /= zoom; - yPos /= zoom; + const zoom = viewport.zoom; + const zoomedScroll = Point.multiplyScalar(viewport.scroll, zoom); + position = Point.add(position, zoomedScroll); + position = Point.divideScalar(position, zoom); } - return { - x: xPos, - y: yPos - }; + return position; } export function getViewportBounds(target: GModelElement, bounds: Bounds): Bounds { - const start = getAbsolutePositionByPoint(target, topLeft(bounds)); - const end = getAbsolutePositionByPoint(target, bottomRight(bounds)); - return { ...start, width: end.x - start.x, height: end.y - start.y }; + const topLeft = getAbsolutePositionByPoint(target, Bounds.topLeft(bounds)); + const bottomRight = getAbsolutePositionByPoint(target, Bounds.bottomRight(bounds)); + return Bounds.from(topLeft, bottomRight); } /** @@ -140,7 +129,7 @@ export function absoluteToParent(element: BoundsAwareModelElement & GChildElemen */ export function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: Point): Point { const absoluteElementBounds = toAbsoluteBounds(element); - return { x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y }; + return Point.subtract(absolutePoint, absoluteElementBounds); } /** diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 39539de..bab6cb5 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -18,5 +18,7 @@ export * from './client-server-protocol'; export * from './model'; export * from './re-exports'; export * from './sprotty-actions'; +export * from './sprotty-geometry-bounds'; +export * from './sprotty-geometry-dimension'; export * from './sprotty-geometry-point'; export * from './utils'; diff --git a/packages/protocol/src/sprotty-geometry-bounds.spec.ts b/packages/protocol/src/sprotty-geometry-bounds.spec.ts new file mode 100644 index 0000000..e7b4f66 --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-bounds.spec.ts @@ -0,0 +1,287 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Point } from 'sprotty-protocol'; +import { Bounds } from './sprotty-geometry-bounds'; + +describe('Bounds', () => { + describe('encompasses', () => { + it('should return true if the outer bounds completely encompass the inner bounds', () => { + const outer: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const inner: Bounds = { x: 10, y: 10, width: 50, height: 50 }; + const result = Bounds.encompasses(outer, inner); + expect(result).to.be.true; + }); + + it('should return false if the outer bounds do not completely encompass the inner bounds', () => { + const outer: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const inner: Bounds = { x: 110, y: 110, width: 50, height: 50 }; + const result = Bounds.encompasses(outer, inner); + expect(result).to.be.false; + }); + }); + + describe('overlap', () => { + it('should return true if the two bounds overlap', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 50, y: 50, width: 100, height: 100 }; + const result = Bounds.overlap(bounds1, bounds2); + expect(result).to.be.true; + }); + + it('should return false if the two bounds touch at the right edge of the left bounds', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 100, y: 0, width: 100, height: 100 }; + const result = Bounds.overlap(bounds1, bounds2); + expect(result).to.be.false; + }); + + it('should return true if the two bounds touch at the right edge of the left bounds and the touch flag is set', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 100, y: 0, width: 100, height: 100 }; + const result = Bounds.overlap(bounds1, bounds2, true); + expect(result).to.be.true; + }); + }); + + describe('equals', () => { + it('should return true if the two bounds are equal', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.equals(bounds1, bounds2); + expect(result).to.be.true; + }); + + it('should return false if the two bounds are not equal', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 50, y: 50, width: 100, height: 100 }; + const result = Bounds.equals(bounds1, bounds2); + expect(result).to.be.false; + }); + }); + + describe('left', () => { + it('should return the x-coordinate of the left edge of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.left(bounds); + expect(result).to.equal(10); + }); + }); + + describe('centerX', () => { + it('should return the x-coordinate of the center of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.centerX(bounds); + expect(result).to.equal(60); + }); + }); + + describe('right', () => { + it('should return the x-coordinate of the right edge of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.right(bounds); + expect(result).to.equal(110); + }); + }); + + describe('top', () => { + it('should return the y-coordinate of the top edge of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.top(bounds); + expect(result).to.equal(20); + }); + }); + + describe('middle', () => { + it('should return the y-coordinate of the middle of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.middle(bounds); + expect(result).to.equal(120); + }); + }); + + describe('centerY', () => { + it('should return the y-coordinate of the center of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.centerY(bounds); + expect(result).to.equal(120); + }); + }); + + describe('bottom', () => { + it('should return the y-coordinate of the bottom edge of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.bottom(bounds); + expect(result).to.equal(220); + }); + }); + + describe('topLeft', () => { + it('should return the top left corner of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.topLeft(bounds); + expect(result).to.deep.equal({ x: 10, y: 20 }); + }); + }); + + describe('topCenter', () => { + it('should return the top center point of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.topCenter(bounds); + expect(result).to.deep.equal({ x: 60, y: 20 }); + }); + }); + + describe('topRight', () => { + it('should return the top right corner of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.topRight(bounds); + expect(result).to.deep.equal({ x: 110, y: 20 }); + }); + }); + + describe('middleLeft', () => { + it('should return the middle left point of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.middleLeft(bounds); + expect(result).to.deep.equal({ x: 10, y: 120 }); + }); + }); + + describe('middleCenter', () => { + it('should return the middle center point of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.middleCenter(bounds); + expect(result).to.deep.equal({ x: 60, y: 120 }); + }); + }); + + describe('middleRight', () => { + it('should return the middle right point of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.middleRight(bounds); + expect(result).to.deep.equal({ x: 110, y: 120 }); + }); + }); + + describe('bottomLeft', () => { + it('should return the bottom left corner of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.bottomLeft(bounds); + expect(result).to.deep.equal({ x: 10, y: 220 }); + }); + }); + + describe('bottomCenter', () => { + it('should return the bottom center point of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.bottomCenter(bounds); + expect(result).to.deep.equal({ x: 60, y: 220 }); + }); + }); + + describe('bottomRight', () => { + it('should return the bottom right corner of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const result = Bounds.bottomRight(bounds); + expect(result).to.deep.equal({ x: 110, y: 220 }); + }); + }); + + describe('isAbove', () => { + it('should return true if the left bounds are above the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 200, width: 100, height: 100 }; + const result = Bounds.isAbove(leftBounds, rightBounds); + expect(result).to.be.true; + }); + + it('should return false if the left bounds are not above the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 200, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.isAbove(leftBounds, rightBounds); + expect(result).to.be.false; + }); + }); + + describe('isBelow', () => { + it('should return true if the left bounds are below the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 200, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.isBelow(leftBounds, rightBounds); + expect(result).to.be.true; + }); + + it('should return false if the left bounds are not below the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 200, width: 100, height: 100 }; + const result = Bounds.isBelow(leftBounds, rightBounds); + expect(result).to.be.false; + }); + }); + + describe('isBefore', () => { + it('should return true if the left bounds are before the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 200, y: 0, width: 100, height: 100 }; + const result = Bounds.isBefore(leftBounds, rightBounds); + expect(result).to.be.true; + }); + + it('should return false if the left bounds are not before the right bounds', () => { + const leftBounds: Bounds = { x: 200, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.isBefore(leftBounds, rightBounds); + expect(result).to.be.false; + }); + }); + + describe('isAfter', () => { + it('should return true if the left bounds are after the right bounds', () => { + const leftBounds: Bounds = { x: 200, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.isAfter(leftBounds, rightBounds); + expect(result).to.be.true; + }); + + it('should return false if the left bounds are not after the right bounds', () => { + const leftBounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const rightBounds: Bounds = { x: 200, y: 0, width: 100, height: 100 }; + const result = Bounds.isAfter(leftBounds, rightBounds); + expect(result).to.be.false; + }); + }); + + describe('from', () => { + it('should create a bounds from the given top left and bottom right points', () => { + const topLeft: Point = { x: 10, y: 20 }; + const bottomRight: Point = { x: 110, y: 220 }; + const result = Bounds.from(topLeft, bottomRight); + expect(result).to.deep.equal({ x: 10, y: 20, width: 100, height: 200 }); + }); + }); + + describe('sortBy', () => { + it('should sort the bounds based on the rank function', () => { + const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const bounds2: Bounds = { x: 50, y: 50, width: 100, height: 100 }; + const bounds3: Bounds = { x: 200, y: 200, width: 100, height: 100 }; + const rankFunc = (bounds: Bounds): number => bounds.x; + const sortedBounds = Bounds.sortBy(rankFunc, bounds1, bounds2, bounds3); + expect(sortedBounds).to.deep.equal([bounds1, bounds2, bounds3]); + }); + }); +}); diff --git a/packages/protocol/src/sprotty-geometry-bounds.ts b/packages/protocol/src/sprotty-geometry-bounds.ts new file mode 100644 index 0000000..397da1b --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-bounds.ts @@ -0,0 +1,284 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ + +import { Bounds, Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; + +declare module 'sprotty-protocol/lib/utils/geometry' { + namespace Bounds { + /** + * Checks whether the inner bounds are compeletely encompassed by the outer bounds. + * + * @param outer outer bounds + * @param inner inner bounds + * @returns true if the outer bounds completely encompass the inner bounds + */ + function encompasses(outer: Bounds, inner: Bounds): boolean; + + /** + * Checks whether the two bounds overlap. + * + * @param left left bounds + * @param right right bounds + * @param touch if true the bounds are considered to overlap if they touch each other + * @returns true if the two bounds overlap + */ + function overlap(left: Bounds, right: Bounds, touch?: boolean): boolean; + + /** + * Checks whether the two bounds are equal. + * @param left left bounds + * @param right right bounds + * @returns true if the two bounds are equal + */ + function equals(left: Bounds, right: Bounds): boolean; + + /** + * Returns the x-coordinate of the left edge of the bounds. + * @param bounds the bounds + * @returns the x-coordinate of the left edge + */ + function left(bounds: Bounds): number; + + /** + * Returns the x-coordinate of the center of the bounds. + * @param bounds the bounds + * @returns the x-coordinate of the center + */ + function centerX(bounds: Bounds): number; + + /** + * Returns the x-coordinate of the right edge of the bounds. + * @param bounds the bounds + * @returns the x-coordinate of the right edge + */ + function right(bounds: Bounds): number; + + /** + * Returns the y-coordinate of the top edge of the bounds. + * @param bounds the bounds + * @returns the y-coordinate of the top edge + */ + function top(bounds: Bounds): number; + + /** + * Returns the y-coordinate of the middle of the bounds. + * @param bounds the bounds + * @returns the y-coordinate of the middle + */ + function middle(bounds: Bounds): number; + + /** + * Returns the y-coordinate of the center of the bounds. + * @param bounds the bounds + * @returns the y-coordinate of the center + */ + function centerY(bounds: Bounds): number; + + /** + * Returns the y-coordinate of the bottom edge of the bounds. + * @param bounds the bounds + * @returns the y-coordinate of the bottom edge + */ + function bottom(bounds: Bounds): number; + + /** + * Returns the top left corner of the bounds. + * @param bounds the bounds + * @returns the top left corner + */ + function topLeft(bounds: Bounds): Point; + + /** + * Returns the top center point of the bounds. + * @param bounds the bounds + * @returns the top center point + */ + function topCenter(bounds: Bounds): Point; + + /** + * Returns the top right corner of the bounds. + * @param bounds the bounds + * @returns the top right corner + */ + function topRight(bounds: Bounds): Point; + + /** + * Returns the middle left point of the bounds. + * @param bounds the bounds + * @returns the middle left point + */ + function middleLeft(bounds: Bounds): Point; + + /** + * Returns the middle center point of the bounds. + * @param bounds the bounds + * @returns the middle center point + */ + function middleCenter(bounds: Bounds): Point; + + /** + * Returns the middle right point of the bounds. + * @param bounds the bounds + * @returns the middle right point + */ + function middleRight(bounds: Bounds): Point; + + /** + * Returns the bottom left corner of the bounds. + * @param bounds the bounds + * @returns the bottom left corner + */ + function bottomLeft(bounds: Bounds): Point; + + /** + * Returns the bottom center point of the bounds. + * @param bounds the bounds + * @returns the bottom center point + */ + function bottomCenter(bounds: Bounds): Point; + + /** + * Returns the bottom right corner of the bounds. + * @param bounds the bounds + * @returns the bottom right corner + */ + function bottomRight(bounds: Bounds): Point; + + /** + * Checks if the left bounds are above the right bounds, i.e., the top edge of the left bounds is + * above the top edge of the right bounds. + * @param leftBounds the left bounds + * @param rightBounds the right bounds + * @returns true if the left bounds are above the right bounds + */ + function isAbove(leftBounds: Bounds, rightBounds: Bounds): boolean; + + /** + * Checks if the left bounds are below the right bounds, i.e., the top edge of the left bounds is + * below the top edge of the right bounds. + * @param leftBounds the left bounds + * @param rightBounds the right bounds + * @returns true if the left bounds are below the right bounds + */ + function isBelow(leftBounds: Bounds, rightBounds: Bounds): boolean; + + /** + * Checks if the left bounds are before the right bounds, i.e., the left edge of the left bounds is + * before the left edge of the right bounds. + * @param leftBounds the left bounds + * @param rightBounds the right bounds + * @returns true if the left bounds are before the right bounds + */ + function isBefore(leftBounds: Bounds, rightBounds: Bounds): boolean; + + /** + * Checks if the left bounds are after the right bounds, i.e., the left edge of the left bounds is + * after the left edge of the right bounds. + * @param leftBounds the left bounds + * @param rightBounds the right bounds + * @returns true if the left bounds are after the right bounds + */ + function isAfter(leftBounds: Bounds, rightBounds: Bounds): boolean; + + /** + * Creates a bounds from the given top left and bottom right points. + * @param topLeft top left point + * @param bottomRight bottom right point + * @returns the bounds + */ + function from(topLeft: Point, bottomRight: Point): Bounds; + + /** + * Sorts the given bounds by the given rank function. + * @param rankFunc the rank function + * @param bounds the bounds to sort + * @returns the sorted bounds + */ + function sortBy(rankFunc: (elem: T) => number, ...bounds: T[]): T[]; + } +} + +Bounds.encompasses = (outer: Bounds, inner: Bounds): boolean => + Bounds.includes(outer, Bounds.topLeft(inner)) && Bounds.includes(outer, Bounds.bottomRight(inner)); + +Bounds.overlap = (one: Bounds, other: Bounds, touch?: boolean): boolean => { + const oneTopLeft: Point = Bounds.topLeft(one); + const oneBottomRight = Bounds.bottomRight(one); + const otherTopLeft: Point = Bounds.topLeft(other); + const otherBottomRight = Bounds.bottomRight(other); + return touch + ? oneTopLeft.x <= otherBottomRight.x && + otherTopLeft.x <= oneBottomRight.x && + oneBottomRight.y >= otherTopLeft.y && + otherBottomRight.y >= oneTopLeft.y + : oneTopLeft.x < otherBottomRight.x && + otherTopLeft.x < oneBottomRight.x && + oneBottomRight.y > otherTopLeft.y && + otherBottomRight.y > oneTopLeft.y; +}; + +Bounds.equals = (left: Bounds, right: Bounds): boolean => Point.equals(left, right) && Dimension.equals(left, right); + +Bounds.left = (bounds: Bounds): number => bounds.x; + +Bounds.centerX = (bounds: Bounds): number => bounds.x + (bounds.width >= 0 ? bounds.width * 0.5 : 0); + +Bounds.right = (bounds: Bounds): number => bounds.x + bounds.width; + +Bounds.top = (bounds: Bounds): number => bounds.y; + +Bounds.middle = (bounds: Bounds): number => bounds.y + (bounds.height >= 0 ? bounds.height * 0.5 : 0); + +Bounds.centerY = Bounds.middle; + +Bounds.bottom = (bounds: Bounds): number => bounds.y + bounds.height; + +Bounds.topLeft = (bounds: Bounds): Point => ({ x: Bounds.left(bounds), y: Bounds.top(bounds) }); + +Bounds.topCenter = (bounds: Bounds): Point => ({ x: Bounds.centerX(bounds), y: Bounds.top(bounds) }); + +Bounds.topRight = (bounds: Bounds): Point => ({ x: Bounds.right(bounds), y: Bounds.top(bounds) }); + +Bounds.middleLeft = (bounds: Bounds): Point => ({ x: Bounds.left(bounds), y: Bounds.middle(bounds) }); + +Bounds.middleCenter = (bounds: Bounds): Point => ({ x: Bounds.centerX(bounds), y: Bounds.middle(bounds) }); + +Bounds.middleRight = (bounds: Bounds): Point => ({ x: Bounds.right(bounds), y: Bounds.middle(bounds) }); + +Bounds.bottomLeft = (bounds: Bounds): Point => ({ x: Bounds.left(bounds), y: Bounds.bottom(bounds) }); + +Bounds.bottomCenter = (bounds: Bounds): Point => ({ x: Bounds.centerX(bounds), y: Bounds.bottom(bounds) }); + +Bounds.bottomRight = (bounds: Bounds): Point => ({ x: Bounds.right(bounds), y: Bounds.bottom(bounds) }); + +Bounds.isAbove = (leftBounds: Bounds, rightBounds: Bounds): boolean => Bounds.top(leftBounds) <= Bounds.top(rightBounds); + +Bounds.isBelow = (leftBounds: Bounds, rightBounds: Bounds): boolean => Bounds.top(leftBounds) >= Bounds.top(rightBounds); + +Bounds.isBefore = (leftBounds: Bounds, rightBounds: Bounds): boolean => Bounds.left(leftBounds) < Bounds.left(rightBounds); + +Bounds.isAfter = (leftBounds: Bounds, rightBounds: Bounds): boolean => Bounds.left(leftBounds) >= Bounds.left(rightBounds); + +Bounds.sortBy = (rankFunc: (elem: T) => number, ...bounds: T[]): T[] => bounds.sort((left, right) => rankFunc(left) - rankFunc(right)); + +Bounds.from = (topLeft: Point, bottomRight: Point): Bounds => ({ + ...topLeft, + width: bottomRight.x - topLeft.x, + height: bottomRight.y - topLeft.y +}); + +export { Bounds }; diff --git a/packages/protocol/src/sprotty-geometry-dimension.spec.ts b/packages/protocol/src/sprotty-geometry-dimension.spec.ts new file mode 100644 index 0000000..02f0be6 --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-dimension.spec.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Dimension } from './sprotty-geometry-dimension'; + +describe('Dimension', () => { + describe('map', () => { + it('should apply the given function to width and height', () => { + const dimension: Dimension = { width: 10, height: 20 }; + const mappedDimension = Dimension.map(dimension, value => value * 2); + expect(mappedDimension).to.deep.equal({ width: 20, height: 40 }); + }); + }); + + describe('center', () => { + it('should return the center point of the dimension', () => { + const dimension: Dimension = { width: 100, height: 200 }; + const centerPoint = Dimension.center(dimension); + expect(centerPoint).to.deep.equal({ x: 50, y: 100 }); + }); + }); + + describe('add', () => { + it('should compute the sum of two dimensions', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 5, height: 10 }; + const sum = Dimension.add(dimension1, dimension2); + expect(sum).to.deep.equal({ width: 15, height: 30 }); + }); + }); + + describe('subtract', () => { + it('should compute the difference of two dimensions', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 5, height: 10 }; + const difference = Dimension.subtract(dimension1, dimension2); + expect(difference).to.deep.equal({ width: 5, height: 10 }); + }); + }); + + describe('multiplyMeasure', () => { + it('should compute the product of a dimension and a measure', () => { + const dimension: Dimension = { width: 10, height: 20 }; + const measure = 2; + const product = Dimension.multiplyMeasure(dimension, measure); + expect(product).to.deep.equal({ width: 20, height: 40 }); + }); + }); + + describe('divideMeasure', () => { + it('should compute the quotient of a dimension and a measure', () => { + const dimension: Dimension = { width: 10, height: 20 }; + const measure = 2; + const quotient = Dimension.divideMeasure(dimension, measure); + expect(quotient).to.deep.equal({ width: 5, height: 10 }); + }); + }); + + describe('equals', () => { + it('should return true if two dimensions are equal', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 10, height: 20 }; + const isEqual = Dimension.equals(dimension1, dimension2); + expect(isEqual).to.be.true; + }); + + it('should return false if two dimensions are not equal', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 5, height: 10 }; + const isEqual = Dimension.equals(dimension1, dimension2); + expect(isEqual).to.be.false; + }); + }); +}); diff --git a/packages/protocol/src/sprotty-geometry-dimension.ts b/packages/protocol/src/sprotty-geometry-dimension.ts new file mode 100644 index 0000000..e3a1ccd --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-dimension.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-shadow */ + +import { Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; + +declare module 'sprotty-protocol/lib/utils/geometry' { + namespace Dimension { + /** + * Applies the given function to the `width` and `height` of the given dimensional object to create a new dimensional object. + * + * @param dimension source dimension + * @param callbackfn function applied to `width` and `height` to create a new dimension + * @returns new dimension + */ + function map(dimension: T, callbackfn: (value: number, key: keyof Dimension) => number): T; + /** + * Returns the center point of the given dimension. + * + * @param dimension dimension + * @returns center point + */ + function center(dimension: Dimension): Point; + + /** + * Computes the sum of two dimensions. The result has the sum of the `width` and `height` of the two dimensions. + * @param dimension the first dimension + * @param add the second dimension + * @returns the sum of the two dimensions + */ + function add(dimension: Dimension, add: Dimension): Dimension; + + /** + * Computes the difference of two dimensions. The result has the difference of the `width` and `height` of the two dimensions. + * @param dimension the first dimension + * @param subtract the second dimension + * @returns the difference of the two dimensions + */ + function subtract(dimension: Dimension, subtract: Dimension): Dimension; + + /** + * Computes the product of a dimension and a measure. + * The result has the `width` and `height` of the dimension multiplied by the measure. + * @param dimension the dimension + * @param measure the measure + * @returns the product of the dimension and the measure + */ + function multiplyMeasure(dimension: Dimension, measure: number): Dimension; + + /** + * Computes the quotient of a dimension and a measure. + * @param dimension the dimension + * @param measure the measure + * @returns the quotient of the dimension and the measure + */ + function divideMeasure(dimension: Dimension, measure: number): Dimension; + + /** + * Checks if two dimensions are equal. Two dimensions are equal if their `width` and `height` are equal. + * @param left the left dimension + * @param right the right dimension + * @returns true if the dimensions are equal, false otherwise + */ + function equals(left: Dimension, right: Dimension): boolean; + } +} + +Dimension.center = (d: Dimension): Point => ({ x: d.width * 0.5, y: d.height * 0.5 }); +Dimension.add = (d: Dimension, a: Dimension): Dimension => ({ width: d.width + a.width, height: d.height + a.height }); +Dimension.subtract = (d: Dimension, a: Dimension): Dimension => ({ width: d.width - a.width, height: d.height - a.height }); +Dimension.multiplyMeasure = (d: Dimension, m: number): Dimension => ({ width: d.width * m, height: d.height * m }); +Dimension.divideMeasure = (d: Dimension, m: number): Dimension => ({ width: d.width / m, height: d.height / m }); + +Dimension.map = (dimension: T, callbackfn: (value: number, key: keyof Dimension) => number): T => ({ + ...dimension, + width: callbackfn(dimension.width, 'width'), + height: callbackfn(dimension.height, 'height') +}); +Dimension.equals = (left: Dimension, right: Dimension): boolean => left.width === right.width && left.height === right.height; + +export { Dimension }; diff --git a/packages/protocol/src/sprotty-geometry-point.spec.ts b/packages/protocol/src/sprotty-geometry-point.spec.ts new file mode 100644 index 0000000..fa47011 --- /dev/null +++ b/packages/protocol/src/sprotty-geometry-point.spec.ts @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { expect } from 'chai'; +import { Direction } from '../lib'; +import { Point } from './sprotty-geometry-point'; + +describe('Point', () => { + describe('is', () => { + it('returns true for a valid point', () => { + expect(Point.is({ x: 1, y: 2 })).to.be.true; + }); + it('returns false for an invalid point', () => { + expect(Point.is({ x: 1 })).to.be.false; + }); + }); + + describe('isOrigin', () => { + it('returns true for the origin', () => { + expect(Point.isOrigin({ x: 0, y: 0 })).to.be.true; + }); + it('returns false for a non-origin point', () => { + expect(Point.isOrigin({ x: 1, y: 2 })).to.be.false; + }); + }); + + describe('isValid', () => { + it('returns true for a valid point', () => { + expect(Point.isValid({ x: 1, y: 2 })).to.be.true; + }); + it('returns false for an invalid point', () => { + expect(Point.isValid({ x: 1, y: NaN })).to.be.false; + }); + }); + + describe('abs', () => { + it('returns the absolute point', () => { + expect(Point.abs({ x: -1, y: -2 })).to.deep.equal({ x: 1, y: 2 }); + }); + }); + + describe('divideScalar', () => { + it('divides both coordinates by the scalar', () => { + expect(Point.divideScalar({ x: 4, y: 6 }, 2)).to.deep.equal({ x: 2, y: 3 }); + }); + }); + + describe('multiplyScalar', () => { + it('multiplies both coordinates by the scalar', () => { + expect(Point.multiplyScalar({ x: 4, y: 6 }, 2)).to.deep.equal({ x: 8, y: 12 }); + }); + }); + + describe('map', () => { + it('applies the function to the coordinates', () => { + expect(Point.map({ x: 1, y: 2 }, c => c * 2)).to.deep.equal({ x: 2, y: 4 }); + }); + }); + + describe('snapToGrid', () => { + it('snaps the point to the grid', () => { + expect(Point.snapToGrid({ x: 3, y: 4 }, { x: 2, y: 2 })).to.deep.equal({ x: 4, y: 4 }); + }); + + it('snaps the point to the grid with a given origin', () => { + expect(Point.snapToGrid({ x: 3, y: 4 }, { x: 2, y: 2 }, { x: 1, y: 1 })).to.deep.equal({ x: 3, y: 5 }); + }); + }); + + describe('vector', () => { + it('returns the vector from the origin to the point', () => { + expect(Point.vector({ x: 3, y: 4 }, { x: 5, y: 8 })).to.deep.equal({ x: 2, y: 4 }); + }); + }); + + describe('move', () => { + it('computes the movement from the starting point to the end point', () => { + const from = { x: 1, y: 2 }; + const to = { x: 4, y: 6 }; + const expectedMovement = { + from: { x: 1, y: 2 }, + to: { x: 4, y: 6 }, + vector: { x: 3, y: 4 }, + direction: [Direction.Right, Direction.Down] + }; + expect(Point.move(from, to)).to.deep.equal(expectedMovement); + }); + }); + + describe('moveTowards', () => { + it('computes the movement from the starting point in the given vector direction', () => { + const from = { x: 1, y: 2 }; + const vector = { x: 3, y: 4 }; + const expectedMovement = { + from: { x: 1, y: 2 }, + to: { x: 4, y: 6 }, + vector: { x: 3, y: 4 }, + direction: [Direction.Right, Direction.Down] + }; + expect(Point.moveTowards(from, vector)).to.deep.equal(expectedMovement); + }); + }); +}); diff --git a/packages/protocol/src/sprotty-geometry-point.ts b/packages/protocol/src/sprotty-geometry-point.ts index e1090b7..5c30ac0 100644 --- a/packages/protocol/src/sprotty-geometry-point.ts +++ b/packages/protocol/src/sprotty-geometry-point.ts @@ -16,18 +16,123 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { Point } from 'sprotty-protocol/lib/utils/geometry'; +import { AnyObject, Movement, Vector, hasNumberProp } from './utils'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Point { + function is(point: any): point is Point; + /** + * The absolute variant of that point, i.e., each coordinate uses its absolute value. + * + * @param point the point + */ + function isOrigin(point: Point): boolean; /** * Type guard to check if a point is valid. For a point to be valid it needs to be defined and have valid x and y coordinates. * * @param point the point to be checked for validity */ function isValid(point?: Point): point is Point; + /** + * The absolute variant of that point, i.e., each coordinate uses its absolute value. + * + * @param point the point + */ + function abs(point: Point): Point; + /** + * Applys a uniform scaling on the given point with respect to the origin. + * Functionally, this divides both coordinates by the given scalar. + * + * @param point the point to be scaled + * @param scalar The factor by which the point schould be scaled + */ + function divideScalar(point: Point, scalar: number): Point; + + /** + * Applys a uniform scaling on the given point with respect to the origin. + * Functionally, this multiplies both coordinates by the given scalar. + * + * @param point the point to be scaled + * @param scalar The factor by which the point schould be scaled + */ + function multiplyScalar(point: Point, scalar: number): Point; + /** + * Applies the given function to the `x` and `y` coordinate of the given point to create a new point. + * + * @param point source point + * @param callbackfn function applied to the `x` and `y` coordinate of the given point to create a new point + * @returns new point + */ + function map(point: T, callbackfn: (value: number, key: keyof Point) => number): T; + /** + * Snaps the given point to the nearest point on the given grid. + * + * @param point point to be snapped + * @param grid grid + * @param gridOrigin grid origin + * @returns a point on the given grid that is closest tot he given point + */ + function snapToGrid(point: Point, grid: Point, gridOrigin?: Point): Point; + + /** + * Computes a vector from the given `from` point to the `to` point. + * @param from the starting point + * @param to the end point + * @returns the vector from `from` to `to` + */ + function vector(from: Point, to: Point): Vector; + + /** + * Computes the movement from the given `from` point to the `to` point. + * @param from the starting point + * @param to the end point + * @returns the movement from `from` to `to` + */ + function move(from: Point, to: Point): Movement; + + /** + * Computes the movement from the given `from` point in the given `vector` direction. + * @param from the starting point + * @param vector the vector direction + * @returns the movement from `from` in the `vector` direction + */ + function moveTowards(from: Point, vector: Vector): Movement; } } +Point.is = (point: any): point is Point => AnyObject.is(point) && hasNumberProp(point, 'x') && hasNumberProp(point, 'y'); +Point.isOrigin = (point: Point): boolean => Point.equals(point, Point.ORIGIN); Point.isValid = (point?: Point): point is Point => point !== undefined && !isNaN(point.x) && !isNaN(point.y); +Point.abs = (point: Point): Point => Point.map(point, Math.abs); +Point.divideScalar = (point: Point, scalar: number): Point => Point.map(point, coordinate => coordinate / scalar); +Point.multiplyScalar = (point: Point, scalar: number): Point => Point.map(point, coordinate => coordinate * scalar); +Point.map = (point: T, callbackfn: (value: number, key: keyof Point) => number): T => ({ + ...point, + x: callbackfn(point.x, 'x'), + y: callbackfn(point.y, 'y') +}); +Point.snapToGrid = (point: Point, grid: Point, gridOrigin?: Point): Point => { + if (gridOrigin) { + // move point relative to grid origin and then restore after snapping + const relative = Point.subtract(point, gridOrigin); + const snapped = Point.snapToGrid(relative, grid); + return Point.add(gridOrigin, snapped); + } else { + return { x: Math.round(point.x / grid.x) * grid.x, y: Math.round(point.y / grid.y) * grid.y }; + } +}; +Point.vector = (from: Point, to: Point): Vector => Point.subtract(to, from); + +Point.move = (from: Point, to: Point): Movement => { + const vector = Point.vector(from, to); + const direction = Vector.direction(vector); + return { from, to, vector, direction }; +}; + +Point.moveTowards = (from: Point, vector: Vector): Movement => { + const to = Point.add(from, vector); + const dir = Vector.direction(vector); + return { from, to, vector, direction: dir }; +}; export { Point }; diff --git a/packages/protocol/src/utils/geometry-movement.spec.ts b/packages/protocol/src/utils/geometry-movement.spec.ts new file mode 100644 index 0000000..641c834 --- /dev/null +++ b/packages/protocol/src/utils/geometry-movement.spec.ts @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Movement } from './geometry-movement'; +import { Direction } from './geometry-util'; + +describe('Movement', () => { + describe('is', () => { + it('should check if the given object is a movement', () => { + const movement: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const result = Movement.is(movement); + expect(result).to.be.true; + }); + it('should check if the given object is not a movement', () => { + const obj = { x: 1, y: 2 }; + const result = Movement.is(obj); + expect(result).to.be.false; + }); + }); + + describe('isStationary', () => { + it('should check if the movement is stationary', () => { + const movement: Movement = { + from: { x: 1, y: 2 }, + to: { x: 1, y: 2 }, + vector: { x: 0, y: 0 }, + direction: [] + }; + const result = Movement.isStationary(movement); + expect(result).to.be.true; + }); + it('should check if the movement is not stationary', () => { + const movement: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const result = Movement.isStationary(movement); + expect(result).to.be.false; + }); + }); + + describe('isZero', () => { + it('should check if the movement is zero', () => { + const movement: Movement = { + from: { x: 0, y: 0 }, + to: { x: 0, y: 0 }, + vector: { x: 0, y: 0 }, + direction: [] + }; + const result = Movement.isZero(movement); + expect(result).to.be.true; + }); + it('should check if the movement is not zero', () => { + const movement: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const result = Movement.isZero(movement); + expect(result).to.be.false; + }); + }); + + describe('equals', () => { + it('should check if two movements are equal', () => { + const movement1: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const movement2: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const result = Movement.equals(movement1, movement2); + expect(result).to.be.true; + }); + it('should check if two movements are not equal', () => { + const movement1: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 2 }, + direction: [Direction.Right, Direction.Up] + }; + const movement2: Movement = { + from: { x: 1, y: 2 }, + to: { x: 3, y: 4 }, + vector: { x: 2, y: 3 }, + direction: [Direction.Right, Direction.Up] + }; + const result = Movement.equals(movement1, movement2); + expect(result).to.be.false; + }); + }); +}); diff --git a/packages/protocol/src/utils/geometry-movement.ts b/packages/protocol/src/utils/geometry-movement.ts new file mode 100644 index 0000000..71c3f15 --- /dev/null +++ b/packages/protocol/src/utils/geometry-movement.ts @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Point } from 'sprotty-protocol/lib/utils/geometry'; +import { Direction } from './geometry-util'; +import { Vector } from './geometry-vector'; +import { AnyObject, hasObjectProp } from './type-util'; + +/** + * A movement in 2D space. A movement is defined by a starting point, an end point, a vector that describes the direction and magnitude. + */ +export interface Movement { + /** + * The starting point of the movement. + */ + from: Point; + /** + * The end point of the movement. + */ + to: Point; + /** + * The vector that describes the direction and magnitude of the movement. + */ + vector: Vector; + /** + * The directions that the movement is composed of, derived from the vector + */ + direction: Direction[]; +} + +/** + * A collection of utility functions for working with movements. + + */ +export namespace Movement { + /** + * The zero movement. It has from and to set to the origin and the vector set to zero. + */ + export const ZERO = Object.freeze({ + from: Point.ORIGIN, + to: Point.ORIGIN, + vector: Vector.ZERO, + direction: [] + }); + + /** + * A type guard that checks if the given object is a movement. + * @param obj the object to check + * @returns true if the object is a movement, false otherwise + */ + export function is(obj: any): obj is Movement { + return ( + AnyObject.is(obj) && + hasObjectProp(obj, 'from') && + Point.is((obj as any).from) && + hasObjectProp(obj, 'to') && + Point.is((obj as any).to) && + hasObjectProp(obj, 'vector') && + Vector.is((obj as any).to) && + hasObjectProp(obj, 'direction') + ); + } + + /** + * Checks if the given movement is stationary, i.e. the starting point and end point are equal and the vector is zero. + * @param movement the movement to check + * @returns true if the movement is stationary, false otherwise + */ + export function isStationary(movement: Movement): boolean { + return Vector.isZero(movement.vector); + } + + /** + * Checks if the given movement is zero, i.e., all values are zero. + * @param movement the movement to check + * @returns true if the movement is zero, false otherwise + */ + export function isZero(movement: Movement): boolean { + return Movement.equals(movement, ZERO); + } + + /** + * Checks if two movements are equal. Two movements are equal if their starting points, end points, and vectors are equal. + * @param left the left movement + * @param right the right movement + * @returns true if the movements are equal, false otherwise + */ + export function equals(left: Movement, right: Movement): boolean { + return Point.equals(left.from, right.from) && Point.equals(left.to, right.to) && Vector.equals(left.vector, right.vector); + } +} diff --git a/packages/protocol/src/utils/geometry-util.ts b/packages/protocol/src/utils/geometry-util.ts new file mode 100644 index 0000000..5e04c23 --- /dev/null +++ b/packages/protocol/src/utils/geometry-util.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * A direction in 2D space. + */ +export const Direction = { + Left: 'left', + Right: 'right', + Up: 'up', + Down: 'down' +} as const; +export type Direction = (typeof Direction)[keyof typeof Direction]; diff --git a/packages/protocol/src/utils/geometry-vector.spec.ts b/packages/protocol/src/utils/geometry-vector.spec.ts new file mode 100644 index 0000000..ebb83b3 --- /dev/null +++ b/packages/protocol/src/utils/geometry-vector.spec.ts @@ -0,0 +1,183 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Direction } from './geometry-util'; +import { Vector } from './geometry-vector'; + +describe('Vector', () => { + describe('abs', () => { + it('should compute the absolute value of the vector', () => { + const vector: Vector = { x: -5, y: -10 }; + const result = Vector.abs(vector); + expect(result).to.deep.equal({ x: 5, y: 10 }); + }); + }); + + describe('add', () => { + it('should compute the sum of two vectors', () => { + const vector1: Vector = { x: 1, y: 2 }; + const vector2: Vector = { x: 3, y: 4 }; + const result = Vector.add(vector1, vector2); + expect(result).to.deep.equal({ x: 4, y: 6 }); + }); + }); + + describe('equals', () => { + it('should check if two vectors are equal', () => { + const vector1: Vector = { x: 1, y: 2 }; + const vector2: Vector = { x: 1, y: 2 }; + const result = Vector.equals(vector1, vector2); + expect(result).to.be.true; + }); + }); + + describe('isValid', () => { + it('should check if a vector is valid', () => { + const vector: Vector = { x: 1, y: 2 }; + const result = Vector.isValid(vector); + expect(result).to.be.true; + }); + }); + + describe('magnitude', () => { + it('should compute the magnitude of a vector', () => { + const vector: Vector = { x: 3, y: 4 }; + const result = Vector.magnitude(vector); + expect(result).to.equal(5); + }); + }); + + describe('map', () => { + it('should map each component of the vector', () => { + const vector: Vector = { x: 1, y: 2 }; + const result = Vector.map(vector, (value, key) => value * 2); + expect(result).to.deep.equal({ x: 2, y: 4 }); + }); + }); + + describe('normalize', () => { + it('should compute the normalized vector', () => { + const vector: Vector = { x: 3, y: 4 }; + const result = Vector.normalize(vector); + expect(result).to.deep.equal({ x: 0.6, y: 0.8 }); + }); + it('should return the zero vector if the vector is the zero vector', () => { + const vector: Vector = { x: 0, y: 0 }; + const result = Vector.normalize(vector); + expect(result).to.deep.equal(Vector.ZERO); + }); + }); + + describe('isZero', () => { + it('should check if a vector is the zero vector', () => { + const vector: Vector = { x: 0, y: 0 }; + const result = Vector.isZero(vector); + expect(result).to.be.true; + }); + }); + + describe('is', () => { + it('should check if the given object is a vector', () => { + const vector: Vector = { x: 1, y: 2 }; + const result = Vector.is(vector); + expect(result).to.be.true; + }); + it('should check if the given object is not a vector', () => { + const vector = { x: 1, z: 2 }; + const result = Vector.is(vector); + expect(result).to.be.false; + }); + }); + + describe('divide', () => { + it('should divide each component of the vector by a scalar', () => { + const vector: Vector = { x: 4, y: 6 }; + const scalar = 2; + const result = Vector.divide(vector, scalar); + expect(result).to.deep.equal({ x: 2, y: 3 }); + }); + }); + + describe('multiply', () => { + it('should multiply each component of the vector by a scalar', () => { + const vector: Vector = { x: 2, y: 3 }; + const scalar = 2; + const result = Vector.multiply(vector, scalar); + expect(result).to.deep.equal({ x: 4, y: 6 }); + }); + }); + + describe('subtract', () => { + it('should subtract the subtrahend from the vector', () => { + const vector: Vector = { x: 4, y: 6 }; + const subtrahend: Vector = { x: 2, y: 3 }; + const result = Vector.subtract(vector, subtrahend); + expect(result).to.deep.equal({ x: 2, y: 3 }); + }); + }); + + describe('reverse', () => { + it('should reverse the direction of a vector', () => { + const vector: Vector = { x: 2, y: 3 }; + const result = Vector.reverse(vector); + expect(result).to.deep.equal({ x: -2, y: -3 }); + }); + }); + + describe('direction', () => { + it('should return right-up for a vector with positive x and negative y', () => { + const vector: Vector = { x: 2, y: -3 }; + const result = Vector.direction(vector); + expect(result).to.deep.equal([Direction.Right, Direction.Up]); + }); + it('should return left-down for a vector with negative x and positive y', () => { + const vector: Vector = { x: -2, y: 3 }; + const result = Vector.direction(vector); + expect(result).to.deep.equal([Direction.Left, Direction.Down]); + }); + }); + + describe('min', () => { + it('should compute the minimum vector', () => { + const vector1: Vector = { x: 1, y: 2 }; + const vector2: Vector = { x: 3, y: 4 }; + const result = Vector.min(vector1, vector2); + expect(result).to.deep.equal({ x: 1, y: 2 }); + }); + }); + + describe('max', () => { + it('should compute the maximum vector', () => { + const vector1: Vector = { x: 1, y: 2 }; + const vector2: Vector = { x: 3, y: 4 }; + const result = Vector.max(vector1, vector2); + expect(result).to.deep.equal({ x: 3, y: 4 }); + }); + }); + + describe('avg', () => { + it('should compute the average vector', () => { + const vector1: Vector = { x: 1, y: 2 }; + const vector2: Vector = { x: 3, y: 4 }; + const result = Vector.avg(vector1, vector2); + expect(result).to.deep.equal({ x: 2, y: 3 }); + }); + it('should return the zero vector if no vectors are given', () => { + const result = Vector.avg(); + expect(result).to.deep.equal(Vector.ZERO); + }); + }); +}); diff --git a/packages/protocol/src/utils/geometry-vector.ts b/packages/protocol/src/utils/geometry-vector.ts new file mode 100644 index 0000000..cc02e8e --- /dev/null +++ b/packages/protocol/src/utils/geometry-vector.ts @@ -0,0 +1,235 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Direction } from './geometry-util'; +import { AnyObject, hasNumberProp } from './type-util'; + +/** + * A vector in two-dimensional space with an x and y component. A vector describes a direction and magnitued + * epresented by coordinates that describe its endpoint relative to a starting point. + */ +export interface Vector { + x: number; + y: number; +} + +/** + * A collection of utility functions for working with vectors. + */ +export namespace Vector { + /** + * The zero vector. It has x and y set to 0. + */ + export const ZERO: Vector = Object.freeze({ + x: 0, + y: 0 + }); + + /** + * Compute the absolute value of the vector. + * @param vector the vector to compute the absolute value of + * @returns the absolute value of the vector + */ + export function abs(vector: Vector): Vector { + return { x: Math.abs(vector.x), y: Math.abs(vector.y) }; + } + + /** + * Computes the sum of two vectors. + * @param vector the vector to add to + * @param addend the vector to add + * @returns the sum of the two vectors + */ + export function add(vector: Vector, addend: Vector): Vector { + return { x: vector.x + addend.x, y: vector.y + addend.y }; + } + + /** + * Check if two vectors are equal. + * @param left the left vector + * @param right the right vector + * @returns true if the vectors are equal, false otherwise + */ + export function equals(left: Vector, right: Vector): boolean { + return left.x === right.x && left.y === right.y; + } + + /** + * Check if a vector is valid. A vector is valid if it is not undefined and both x and y are numbers. + * @param vector the vector to check + * @returns true if the vector is valid, false otherwise + */ + export function isValid(vector?: Vector): boolean { + return vector !== undefined && !isNaN(vector.x) && !isNaN(vector.y); + } + + /** + * Computes the magnitude of a vector defined as the square root of the sum of the squares of the x and y components. + * @param point the vector to compute the magnitude of + * @returns the magnitude of the vector + */ + export function magnitude(point: Vector): number { + return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); + } + + /** + * Maps each component of the vector to a new value given by the callback function. + * @param vector the vector to map + * @param callbackfn the function to map the components + * @returns the mapped vector + */ + export function map(vector: Vector, callbackfn: (value: number, key: keyof Vector) => number): Vector { + return { + ...vector, + x: callbackfn(vector.x, 'x'), + y: callbackfn(vector.y, 'y') + }; + } + + /** + * Computes the normalized vector of a given vector. + * The normalized vector has the same direction as the original vector but a magnitude of 1. + * + * @param vector the vector to normalize + * @returns the normalized vector + */ + export function normalize(vector: Vector): Vector { + const mag = magnitude(vector); + if (mag === 0 || mag === 1) { + return ZERO; + } + return { + x: vector.x / mag, + y: vector.y / mag + }; + } + + /** + * Check if a vector is the zero vector. + * @param vector the vector to check + * @returns true if the vector is the zero vector, false otherwise + */ + export function isZero(vector: Vector): boolean { + return Vector.equals(vector, ZERO); + } + + /** + * Type guard to check if a value is a vector. + * @param vector the value to check + * @returns true if the value is a vector, false otherwise + */ + export function is(vector: any): vector is Vector { + return AnyObject.is(vector) && hasNumberProp(vector, 'x') && hasNumberProp(vector, 'y'); + } + + /** + * Divides each component of the vector by a scalar. + * + * @param vector the vector to divide + * @param scalar the scalar to divide by + * @returns the divided vector + */ + export function divide(vector: Vector, scalar: number): Vector { + return Vector.map(vector, coordinate => coordinate / scalar); + } + + /** + * Multiplies each component of the vector by a scalar. + * + * @param vector the vector to multiply + * @param scalar the scalar to multiply by + * @returns the multiplied vector + */ + export function multiply(vector: Vector, scalar: number): Vector { + return Vector.map(vector, coordinate => coordinate * scalar); + } + + /** + * Subtracts the subtrahend from the vector. + * + * @param vector the vector to subtract from + * @param subtrahend the vector to subtract + * @returns the subtracted vector + */ + export function subtract(vector: Vector, subtrahend: Vector): Vector { + return { x: vector.x - subtrahend.x, y: vector.y - subtrahend.y }; + } + + /** + * Reverse the direction of a vector. + * @param vector the vector to reverse + * @returns the reversed vector + */ + export function reverse(vector: Vector): Vector { + return { x: -vector.x, y: -vector.y }; + } + + /** + * Computes the direction of a vector + * @param vector the vector to compute the direction of + * @returns the direction of the vector + */ + export function direction(vector: Vector): Direction[] { + const directions: Direction[] = []; + if (vector.x < 0) { + directions.push(Direction.Left); + } else if (vector.x > 0) { + directions.push(Direction.Right); + } + if (vector.y < 0) { + directions.push(Direction.Up); + } else if (vector.y > 0) { + directions.push(Direction.Down); + } + return directions; + } + + /** + * Computes a vector that is the minimum of all given vectors. + * @returns the minimum vector + */ + export function min(...vectors: Vector[]): Vector { + return { + x: Math.min(...vectors.map(vector => vector.x)), + y: Math.min(...vectors.map(vector => vector.y)) + }; + } + + /** + * Computes a vector that is the maximum of all given vectors. + * @returns the maximum vector + */ + export function max(...vectors: Vector[]): Vector { + return { + x: Math.max(...vectors.map(vector => vector.x)), + y: Math.max(...vectors.map(vector => vector.y)) + }; + } + + /** + * Computes a vector that is the average of all given vectors. + * @returns the average vector + */ + export function avg(...vectors: Vector[]): Vector { + if (vectors.length === 0) { + return Vector.ZERO; + } + return { + x: vectors.reduce((prev, cur) => prev + cur.x, 0) / vectors.length, + y: vectors.reduce((prev, cur) => prev + cur.y, 0) / vectors.length + }; + } +} diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index d606c75..e9ae9ef 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -16,4 +16,7 @@ export * from './array-util'; export * from './disposable'; export * from './event'; +export * from './geometry-movement'; +export * from './geometry-util'; +export * from './geometry-vector'; export * from './type-util'; From a5e5e0e7b14d25738da231a7be5911b3e08f8e05 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 29 Apr 2024 15:03:47 +0200 Subject: [PATCH 324/566] Revert "Fixed webworker startup bc elkjs hidden dependency (#78)" (#81) This reverts commit ac6eb792627fc2ca45badf5c933b96adfbcf67e5. --- examples/workflow-server-bundled/package.json | 2 +- packages/layout-elk/src/di.config.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index e291068..10a213e 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -41,7 +41,7 @@ "wf-glsp-server-node.js.map" ], "scripts": { - "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map wf-glsp-server-webworker.js wf-glsp-server-webworker.js.map", + "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map", "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 24fbcdb..41954b4 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -82,11 +82,7 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo const elkFactory: ElkFactory = () => new ElkConstructor({ algorithms: options.algorithms, - defaultLayoutOptions: options.defaultLayoutOptions, - // The node implementation relied on elkjs' `FakeWorker` to set the `workerFactory`. - // However, since the required file is dynamically loaded and not available in a web-worker context, - // it needs to be mocked manually. - workerFactory: () => ({ postMessage: () => {} }) as unknown as Worker + defaultLayoutOptions: options.defaultLayoutOptions }); bind(ElkFactory).toConstantValue(elkFactory); From 1b5508b04b6e04abf418bddf873c64a6d9d0b11d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 13 May 2024 14:08:47 +0200 Subject: [PATCH 325/566] Convert CLI package back to commonjs (#1335) * Convert CLI package back to commonjs Convert the CLI package back to commonjs as the esm module caused issues in downstream projects. Use async imports to still be able to use globby. * Fix copyright header violations * Fix linting warnings --- .../cli/{.eslintrc.cjs => .eslintrc.js} | 0 dev-packages/cli/bin/glsp | 2 +- dev-packages/cli/package.json | 1 - dev-packages/cli/src/app.ts | 12 +++---- dev-packages/cli/src/commands/check-header.ts | 10 +++--- .../cli/src/commands/coverage-report.ts | 8 ++--- .../cli/src/commands/generate-index.ts | 31 ++++++++++++++----- .../cli/src/commands/release/common.ts | 10 +++--- .../src/commands/release/release-client.ts | 8 ++--- .../release/release-eclipse-integration.ts | 6 ++-- .../commands/release/release-java-server.ts | 8 ++--- .../commands/release/release-server-node.ts | 4 +-- .../release/release-theia-integration.ts | 4 +-- .../release/release-vscode-integration.ts | 4 +-- .../cli/src/commands/release/release.ts | 20 ++++++------ dev-packages/cli/src/commands/update-next.ts | 8 ++--- dev-packages/cli/src/util/command-util.ts | 2 +- dev-packages/cli/src/util/git-util.ts | 4 +-- dev-packages/cli/src/util/logger.ts | 2 +- dev-packages/cli/src/util/validation-util.ts | 6 ++-- dev-packages/cli/tsconfig.json | 3 +- 21 files changed, 84 insertions(+), 69 deletions(-) rename dev-packages/cli/{.eslintrc.cjs => .eslintrc.js} (100%) diff --git a/dev-packages/cli/.eslintrc.cjs b/dev-packages/cli/.eslintrc.js similarity index 100% rename from dev-packages/cli/.eslintrc.cjs rename to dev-packages/cli/.eslintrc.js diff --git a/dev-packages/cli/bin/glsp b/dev-packages/cli/bin/glsp index 51a0e4e..cb8f5f2 100755 --- a/dev-packages/cli/bin/glsp +++ b/dev-packages/cli/bin/glsp @@ -1,2 +1,2 @@ #!/usr/bin/env node -import('../lib/app.js'); +require('../lib/app'); diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 2ca2138..ecadcfb 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -23,7 +23,6 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], - "type": "module", "bin": { "glsp": "bin/glsp" }, diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index 1c1f70e..fc3febb 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -14,12 +14,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CheckHeaderCommand } from './commands/check-header.js'; -import { CoverageReportCommand } from './commands/coverage-report.js'; -import { GenerateIndex } from './commands/generate-index.js'; -import { ReleaseCommand } from './commands/release/release.js'; -import { UpdateNextCommand } from './commands/update-next.js'; -import { baseCommand } from './util/command-util.js'; +import { CheckHeaderCommand } from './commands/check-header'; +import { CoverageReportCommand } from './commands/coverage-report'; +import { GenerateIndex } from './commands/generate-index'; +import { ReleaseCommand } from './commands/release/release'; +import { UpdateNextCommand } from './commands/update-next'; +import { baseCommand } from './util/command-util'; export const COMMAND_VERSION = '1.1.0-next'; diff --git a/dev-packages/cli/src/commands/check-header.ts b/dev-packages/cli/src/commands/check-header.ts index 4ee8afc..ef157df 100644 --- a/dev-packages/cli/src/commands/check-header.ts +++ b/dev-packages/cli/src/commands/check-header.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,12 +20,12 @@ import { glob } from 'glob'; import * as minimatch from 'minimatch'; import * as readline from 'readline-sync'; import sh from 'shelljs'; -import { baseCommand, configureShell, getShellConfig } from '../util/command-util.js'; -import { getChangesOfLastCommit, getLastModificationDate, getUncommittedChanges } from '../util/git-util.js'; +import { baseCommand, configureShell, getShellConfig } from '../util/command-util'; +import { getChangesOfLastCommit, getLastModificationDate, getUncommittedChanges } from '../util/git-util'; import * as path from 'path'; -import { LOGGER } from '../util/logger.js'; -import { validateGitDirectory } from '../util/validation-util.js'; +import { LOGGER } from '../util/logger'; +import { validateGitDirectory } from '../util/validation-util'; export interface HeaderCheckOptions { type: CheckType; diff --git a/dev-packages/cli/src/commands/coverage-report.ts b/dev-packages/cli/src/commands/coverage-report.ts index 7615a4d..b70ed7e 100644 --- a/dev-packages/cli/src/commands/coverage-report.ts +++ b/dev-packages/cli/src/commands/coverage-report.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,9 +17,9 @@ import * as fs from 'fs'; import * as path from 'path'; import sh from 'shelljs'; -import { baseCommand, fatalExec, getShellConfig } from '../util/command-util.js'; -import { LOGGER } from '../util/logger.js'; -import { validateDirectory } from '../util/validation-util.js'; +import { baseCommand, fatalExec, getShellConfig } from '../util/command-util'; +import { LOGGER } from '../util/logger'; +import { validateDirectory } from '../util/validation-util'; export interface CoverageCmdOptions { coverageScript: string; diff --git a/dev-packages/cli/src/commands/generate-index.ts b/dev-packages/cli/src/commands/generate-index.ts index aa71c0c..679857c 100644 --- a/dev-packages/cli/src/commands/generate-index.ts +++ b/dev-packages/cli/src/commands/generate-index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,13 +16,12 @@ import { createOption } from 'commander'; import * as fs from 'fs'; -import { Options as GlobbyOptions, globbySync } from 'globby'; import * as os from 'os'; import * as path from 'path'; import sh from 'shelljs'; -import { baseCommand } from '../util/command-util.js'; -import { LOGGER, configureLogger } from '../util/logger.js'; -import { validateDirectory } from '../util/validation-util.js'; +import { baseCommand } from '../util/command-util'; +import { LOGGER, configureLogger } from '../util/logger'; +import { validateDirectory } from '../util/validation-util'; export interface GenerateIndexCmdOptions { singleIndex: boolean; @@ -34,6 +33,15 @@ export interface GenerateIndexCmdOptions { verbose: boolean; } +// Partial type of the globby imports since we can not use the esm type directly +interface GlobbyOptions { + ignore: string[]; + cwd: string; + onlyFiles: boolean; + markDirectories: true; + ignoreFiles: string; +} + export const GenerateIndex = baseCommand() // .name('generateIndex') .description('Generate index files in a given source directory.') @@ -49,10 +57,17 @@ export const GenerateIndex = baseCommand() // export async function generateIndices(rootDirs: string[], options: GenerateIndexCmdOptions): Promise { const dirs = rootDirs.map(rootDir => validateDirectory(path.resolve(rootDir))); - dirs.forEach(dir => generateIndex(dir, options)); + const globby = await import('globby'); + const ignoreFilter: (pattern: string[], options: GlobbyOptions) => string[] = (pattern, globbyOptions) => + globby.globbySync(pattern, globbyOptions); + dirs.forEach(dir => generateIndex(dir, ignoreFilter, options)); } -export async function generateIndex(rootDir: string, options: GenerateIndexCmdOptions): Promise { +export async function generateIndex( + rootDir: string, + ignoreFilter: (pattern: string[], options: GlobbyOptions) => string[], + options: GenerateIndexCmdOptions +): Promise { configureLogger(options.verbose); LOGGER.debug('Run generateIndex for', rootDir, 'with the following options:', options); sh.cd(rootDir); @@ -69,7 +84,7 @@ export async function generateIndex(rootDir: string, options: GenerateIndexCmdOp ignoreFiles: '**/' + options.ignoreFile // users can add this file in their directories to ignore files for indexing }; LOGGER.debug('Search for children using the following globby options', globbyOptions); - const files = globbySync(pattern, globbyOptions); + const files = ignoreFilter(pattern, globbyOptions); LOGGER.debug('All children considered in the input directory', files); const relativeRootDirectory = ''; diff --git a/dev-packages/cli/src/commands/release/common.ts b/dev-packages/cli/src/commands/release/common.ts index a626023..e41e02c 100644 --- a/dev-packages/cli/src/commands/release/common.ts +++ b/dev-packages/cli/src/commands/release/common.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,10 +20,10 @@ import { resolve } from 'path'; import * as readline from 'readline-sync'; import * as semver from 'semver'; import sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util.js'; -import { getLatestGithubRelease, getLatestTag, hasGitChanges, isGitRepository } from '../../util/git-util.js'; -import { LOGGER } from '../../util/logger.js'; -import { validateVersion } from '../../util/validation-util.js'; +import { fatalExec, getShellConfig } from '../../util/command-util'; +import { getLatestGithubRelease, getLatestTag, hasGitChanges, isGitRepository } from '../../util/git-util'; +import { LOGGER } from '../../util/logger'; +import { validateVersion } from '../../util/validation-util'; export const VERDACCIO_REGISTRY = 'http://localhost:4873/'; diff --git a/dev-packages/cli/src/commands/release/release-client.ts b/dev-packages/cli/src/commands/release/release-client.ts index c6266bc..d8952f7 100644 --- a/dev-packages/cli/src/commands/release/release-client.ts +++ b/dev-packages/cli/src/commands/release/release-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import sh from 'shelljs'; -import { getShellConfig } from '../../util/command-util.js'; -import { LOGGER } from '../../util/logger.js'; -import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common.js'; +import { getShellConfig } from '../../util/command-util'; +import { LOGGER } from '../../util/logger'; +import { checkoutAndCd, commitAndTag, lernaSetVersion, publish, ReleaseOptions, updateLernaForDryRun, yarnInstall } from './common'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-eclipse-integration.ts b/dev-packages/cli/src/commands/release/release-eclipse-integration.ts index 77f7c6e..9326477 100644 --- a/dev-packages/cli/src/commands/release/release-eclipse-integration.ts +++ b/dev-packages/cli/src/commands/release/release-eclipse-integration.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,8 +15,8 @@ ********************************************************************************/ import sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util.js'; -import { LOGGER } from '../../util/logger.js'; +import { fatalExec, getShellConfig } from '../../util/command-util'; +import { LOGGER } from '../../util/logger'; import { ReleaseOptions, ReleaseType, diff --git a/dev-packages/cli/src/commands/release/release-java-server.ts b/dev-packages/cli/src/commands/release/release-java-server.ts index 1bc71a9..b2b5d01 100644 --- a/dev-packages/cli/src/commands/release/release-java-server.ts +++ b/dev-packages/cli/src/commands/release/release-java-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import sh from 'shelljs'; -import { fatalExec, getShellConfig } from '../../util/command-util.js'; -import { LOGGER } from '../../util/logger.js'; -import { ReleaseOptions, asMvnVersion, checkoutAndCd, commitAndTag, publish } from './common.js'; +import { fatalExec, getShellConfig } from '../../util/command-util'; +import { LOGGER } from '../../util/logger'; +import { ReleaseOptions, asMvnVersion, checkoutAndCd, commitAndTag, publish } from './common'; let REPO_ROOT: string; diff --git a/dev-packages/cli/src/commands/release/release-server-node.ts b/dev-packages/cli/src/commands/release/release-server-node.ts index 9937c76..e373dea 100644 --- a/dev-packages/cli/src/commands/release/release-server-node.ts +++ b/dev-packages/cli/src/commands/release/release-server-node.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import sh from 'shelljs'; -import { LOGGER } from '../../util/logger.js'; +import { LOGGER } from '../../util/logger'; import { checkoutAndCd, commitAndTag, diff --git a/dev-packages/cli/src/commands/release/release-theia-integration.ts b/dev-packages/cli/src/commands/release/release-theia-integration.ts index 26c366b..5302312 100644 --- a/dev-packages/cli/src/commands/release/release-theia-integration.ts +++ b/dev-packages/cli/src/commands/release/release-theia-integration.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import sh from 'shelljs'; -import { LOGGER } from '../../util/logger.js'; +import { LOGGER } from '../../util/logger'; import { checkoutAndCd, commitAndTag, diff --git a/dev-packages/cli/src/commands/release/release-vscode-integration.ts b/dev-packages/cli/src/commands/release/release-vscode-integration.ts index 2271da1..05af64f 100644 --- a/dev-packages/cli/src/commands/release/release-vscode-integration.ts +++ b/dev-packages/cli/src/commands/release/release-vscode-integration.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import sh from 'shelljs'; -import { LOGGER } from '../../util/logger.js'; +import { LOGGER } from '../../util/logger'; import { checkoutAndCd, commitAndTag, diff --git a/dev-packages/cli/src/commands/release/release.ts b/dev-packages/cli/src/commands/release/release.ts index 42ad454..ce12c22 100644 --- a/dev-packages/cli/src/commands/release/release.ts +++ b/dev-packages/cli/src/commands/release/release.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,9 +20,9 @@ import { createInterface } from 'readline'; import * as readline from 'readline-sync'; import * as semver from 'semver'; import sh from 'shelljs'; -import { baseCommand, configureShell, fatalExec, getShellConfig } from '../../util/command-util.js'; -import { LOGGER, configureLogger } from '../../util/logger.js'; -import { validateDirectory, validateVersion } from '../../util/validation-util.js'; +import { baseCommand, configureShell, fatalExec, getShellConfig } from '../../util/command-util'; +import { LOGGER, configureLogger } from '../../util/logger'; +import { validateDirectory, validateVersion } from '../../util/validation-util'; import { Component, ReleaseOptions, @@ -32,12 +32,12 @@ import { checkIfMavenVersionExists, checkIfNpmVersionIsNew } from './common.js'; -import { releaseClient } from './release-client.js'; -import { releaseEclipseIntegration } from './release-eclipse-integration.js'; -import { releaseJavaServer } from './release-java-server.js'; -import { releaseServerNode } from './release-server-node.js'; -import { releaseTheiaIntegration } from './release-theia-integration.js'; -import { releaseVscodeIntegration } from './release-vscode-integration.js'; +import { releaseClient } from './release-client'; +import { releaseEclipseIntegration } from './release-eclipse-integration'; +import { releaseJavaServer } from './release-java-server'; +import { releaseServerNode } from './release-server-node'; +import { releaseTheiaIntegration } from './release-theia-integration'; +import { releaseVscodeIntegration } from './release-vscode-integration'; interface ReleaseCmdOptions { checkoutDir: string; diff --git a/dev-packages/cli/src/commands/update-next.ts b/dev-packages/cli/src/commands/update-next.ts index 6b7471e..f3c5101 100644 --- a/dev-packages/cli/src/commands/update-next.ts +++ b/dev-packages/cli/src/commands/update-next.ts @@ -19,10 +19,10 @@ import { glob } from 'glob'; import * as jq from 'node-jq'; import * as path from 'path'; import sh from 'shelljs'; -import { baseCommand, configureShell } from '../util/command-util.js'; -import { getUncommittedChanges } from '../util/git-util.js'; -import { LOGGER, configureLogger } from '../util/logger.js'; -import { validateGitDirectory } from '../util/validation-util.js'; +import { baseCommand, configureShell } from '../util/command-util'; +import { getUncommittedChanges } from '../util/git-util'; +import { LOGGER, configureLogger } from '../util/logger'; +import { validateGitDirectory } from '../util/validation-util'; export const UpdateNextCommand = baseCommand() .name('updateNext') diff --git a/dev-packages/cli/src/util/command-util.ts b/dev-packages/cli/src/util/command-util.ts index 357ae3d..d6e35aa 100644 --- a/dev-packages/cli/src/util/command-util.ts +++ b/dev-packages/cli/src/util/command-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/dev-packages/cli/src/util/git-util.ts b/dev-packages/cli/src/util/git-util.ts index eb1f838..b8d32a5 100644 --- a/dev-packages/cli/src/util/git-util.ts +++ b/dev-packages/cli/src/util/git-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { resolve } from 'path'; import sh from 'shelljs'; -import { getShellConfig } from './command-util.js'; +import { getShellConfig } from './command-util'; export function isGitRepository(path?: string): boolean { cdIfPresent(path); diff --git a/dev-packages/cli/src/util/logger.ts b/dev-packages/cli/src/util/logger.ts index 3423f8d..b15a6c5 100644 --- a/dev-packages/cli/src/util/logger.ts +++ b/dev-packages/cli/src/util/logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/dev-packages/cli/src/util/validation-util.ts b/dev-packages/cli/src/util/validation-util.ts index 8229268..6950ee6 100644 --- a/dev-packages/cli/src/util/validation-util.ts +++ b/dev-packages/cli/src/util/validation-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,8 +17,8 @@ import { InvalidArgumentError } from 'commander'; import * as fs from 'fs'; import { resolve } from 'path'; import * as semver from 'semver'; -import { getGitRoot, isGitRepository } from './git-util.js'; -import { LOGGER } from './logger.js'; +import { getGitRoot, isGitRepository } from './git-util'; +import { LOGGER } from './logger'; export function validateDirectory(rootDir: string): string { const path = resolve(rootDir); diff --git a/dev-packages/cli/tsconfig.json b/dev-packages/cli/tsconfig.json index 1986af2..39ca0c8 100644 --- a/dev-packages/cli/tsconfig.json +++ b/dev-packages/cli/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "lib", "esModuleInterop": true, "resolveJsonModule": true, - "module": "ESNext" + "module": "Node16", + "moduleResolution": "Node16" }, "include": ["src"] } From 9d574977bfcae564b6085d208e95e9c422f75cdf Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 13 May 2024 16:04:51 +0000 Subject: [PATCH 326/566] Improve handling of feedback emitters and fix some minor bugs (#342) - Provide dedicated feedback emitter object as stable emitter - Allow cleanup functions to be calculated as a lazy function - Log if a potentially unstable feedback emitter is used - Split feedback in change bounds tool into dedicated files - Properly use feedback emitter to cleanup feedback correctly Fixes https://github.com/eclipse-glsp/glsp/issues/1323 Fixes https://github.com/eclipse-glsp/glsp/issues/1243 - Ensure correct snapping of ghost element position on grid Fixes https://github.com/eclipse-glsp/glsp/issues/1256 --- packages/client/css/change-bounds.css | 15 + packages/client/css/glsp-sprotty.css | 17 + .../src/base/drag-aware-mouse-listener.ts | 18 +- .../client/src/base/feedback/css-feedback.ts | 4 + .../src/base/feedback/feeback-emitter.ts | 112 +++++ .../feedback/feedback-action-dispatcher.ts | 47 +- packages/client/src/base/feedback/index.ts | 1 + .../client/src/base/selection-service.spec.ts | 5 + packages/client/src/base/selection-service.ts | 5 +- .../local-element-navigator.ts | 8 +- .../accessibility/move-zoom/move-handler.ts | 15 +- .../resize-key-tool/resize-key-handler.ts | 18 +- .../src/features/change-bounds/model.ts | 38 +- .../change-bounds/movement-restrictor.ts | 9 +- ...ouse-tracking-element-position-listener.ts | 88 ++-- .../helper-line-manager-default.ts | 33 +- .../features/hints/type-hint-provider.spec.ts | 5 +- .../src/features/hints/type-hint-provider.ts | 11 +- .../features/select/select-mouse-listener.ts | 7 +- .../client/src/features/tools/base-tools.ts | 23 +- .../change-bounds-tool-feedback.ts | 238 +--------- .../change-bounds-tool-module.ts | 1 + .../change-bounds-tool-move-feedback.ts | 254 ++++++++++ .../tools/change-bounds/change-bounds-tool.ts | 443 +++++++++--------- .../src/features/tools/change-bounds/index.ts | 1 + .../src/features/tools/change-bounds/view.tsx | 5 +- .../features/tools/deletion/delete-tool.ts | 12 +- .../edge-creation/dangling-edge-feedback.ts | 11 +- .../edge-creation-tool-feedback.ts | 18 +- .../tools/edge-creation/edge-creation-tool.ts | 56 ++- .../edge-edit/edge-edit-tool-feedback.ts | 16 +- .../tools/edge-edit/edge-edit-tool.ts | 91 ++-- .../marquee-selection/marquee-mouse-tool.ts | 13 +- .../tools/node-creation/node-creation-tool.ts | 94 +++- .../features/validation/marker-navigator.ts | 14 +- .../src/features/validation/validate.ts | 25 +- packages/client/src/utils/gmodel-util.ts | 45 +- packages/client/src/utils/layout-utils.ts | 29 +- packages/client/src/utils/viewpoint-util.ts | 7 +- packages/protocol/src/utils/array-util.ts | 9 + packages/protocol/src/utils/disposable.ts | 7 + packages/protocol/src/utils/type-util.ts | 6 + 42 files changed, 1109 insertions(+), 765 deletions(-) create mode 100644 packages/client/css/change-bounds.css create mode 100644 packages/client/src/base/feedback/feeback-emitter.ts create mode 100644 packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts diff --git a/packages/client/css/change-bounds.css b/packages/client/css/change-bounds.css new file mode 100644 index 0000000..6ba3fce --- /dev/null +++ b/packages/client/css/change-bounds.css @@ -0,0 +1,15 @@ +.sprotty-resize-handle[data-kind='top-left'] { + cursor: nw-resize; +} + +.sprotty-resize-handle[data-kind='top-right'] { + cursor: ne-resize; +} + +.sprotty-resize-handle[data-kind='bottom-left'] { + cursor: sw-resize; +} + +.sprotty-resize-handle[data-kind='bottom-right'] { + cursor: se-resize; +} diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index b678936..ca2a020 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -148,6 +148,22 @@ cursor: nwse-resize; } +.sprotty .resize-nw-mode { + cursor: nw-resize; +} + +.sprotty .resize-ne-mode { + cursor: ne-resize; +} + +.sprotty .resize-sw-mode { + cursor: sw-resize; +} + +.sprotty .resize-se-mode { + cursor: se-resize; +} + .sprotty .element-deletion-mode { cursor: pointer; } @@ -191,6 +207,7 @@ .sprotty g .movement-not-allowed > .sprotty-node { stroke: var(--glsp-error-foreground) !important; + stroke-width: 1.5px; } .sprotty-resize-handle.movement-not-allowed { diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index 452a995..feee90d 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MouseListener, GModelElement } from '@eclipse-glsp/sprotty'; +import { Action, Disposable, GModelElement, MouseListener } from '@eclipse-glsp/sprotty'; /** * A mouse listener that is aware of prior mouse dragging. @@ -23,7 +23,7 @@ import { Action, MouseListener, GModelElement } from '@eclipse-glsp/sprotty'; * `draggingMouseUp` and/or `nonDraggingMouseUp` to react to only these specific kinds * of mouse up events. */ -export class DragAwareMouseListener extends MouseListener { +export class DragAwareMouseListener extends MouseListener implements Disposable { protected _isMouseDown = false; protected _isMouseDrag = false; @@ -35,7 +35,16 @@ export class DragAwareMouseListener extends MouseListener { override mouseMove(target: GModelElement, event: MouseEvent): Action[] { if (this._isMouseDown) { this._isMouseDrag = true; + return this.draggingMouseMove(target, event); } + return this.nonDraggingMouseMove(target, event); + } + + protected draggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { + return []; + } + + protected nonDraggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { return []; } @@ -64,4 +73,9 @@ export class DragAwareMouseListener extends MouseListener { get isMouseDown(): boolean { return this._isMouseDown; } + + dispose(): void { + this._isMouseDrag = false; + this._isMouseDown = false; + } } diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index b13b976..a9e268d 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -84,6 +84,10 @@ export enum CursorCSS { ELEMENT_DELETION = 'element-deletion-mode', RESIZE_NESW = 'resize-nesw-mode', RESIZE_NWSE = 'resize-nwse-mode', + RESIZE_NW = 'resize-nw-mode', + RESIZE_NE = 'resize-ne-mode', + RESIZE_SW = 'resize-sw-mode', + RESIZE_SE = 'resize-se-mode', MOVE = 'move-mode', MARQUEE = 'marquee-mode' } diff --git a/packages/client/src/base/feedback/feeback-emitter.ts b/packages/client/src/base/feedback/feeback-emitter.ts new file mode 100644 index 0000000..53971ed --- /dev/null +++ b/packages/client/src/base/feedback/feeback-emitter.ts @@ -0,0 +1,112 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, Disposable, arrayOf } from '@eclipse-glsp/sprotty'; +import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; + +/** + * A helper object to collect, submit and undo feedback consisting of several actions. + */ +export class FeedbackEmitter implements IFeedbackEmitter, Disposable { + protected feedbackActions: (Action | undefined)[] = []; + protected cleanupActions: MaybeActions[] = []; + protected deregistration?: Disposable; + + constructor(protected feedbackDispatcher: IFeedbackActionDispatcher) {} + + /** + * Adds an action as part of this emitters feedback. Please note that the action is only applied as feedback + * once the {@link submit} method is called. + * + * @param action feedback action + * @param cleanupAction action that undoes the feedback action. This is only triggered when {@link revert} is called. + */ + add(action: Action, cleanupAction?: MaybeActions): this { + const idx = this.feedbackActions.length; + this.feedbackActions[idx] = action; + if (cleanupAction) { + this.cleanupActions[idx] = cleanupAction; + } + return this; + } + + /** + * Removes the action as part of this emitters feedback. If the action cannot be found, this is a no-op. + * Please note that this also removed the corresponding cleanup action. + * If the feedback has already been submitted as part of the {@link submit} method, the whole feedback must be de-registered + * or a new feedback without the given action needs to be registered. + * + * @param action feedback action + */ + remove(action: Action): this { + const idx = this.feedbackActions.indexOf(action); + if (idx) { + delete this.feedbackActions[idx]; + delete this.cleanupActions[idx]; + } + return this; + } + + /** + * Clears any, not yet registered feedback actions and their corresponding cleanup actions. + */ + clear(): this { + this.feedbackActions = []; + this.cleanupActions = []; + return this; + } + + /** + * Registers any pending actions as feedback. Any previously submitted feedback becomes invalid. + */ + submit(): this { + // with 'arrayOf' we skip undefined entries that are created for non-cleanup actions + const actions = arrayOf(...this.feedbackActions); + const cleanupActions = arrayOf(...this.cleanupActions); + this.deregistration = this.feedbackDispatcher.registerFeedback(this, actions, () => cleanupActions.flatMap(MaybeActions.asArray)); + this.clear(); + return this; + } + + /** + * Removes the registered feedback WITHOUT calling any potential cleanup actions + * Any pending actions can still be registerd with the {@link submit} method. + */ + discard(): this { + this.feedbackDispatcher.deregisterFeedback(this); + this.deregistration = undefined; + return this; + } + + /** + * Removes the registered feedback and calls the registered cleanup actions. + * Any pending actions can still be registerd with the {@link submit} method. + */ + revert(): this { + this.deregistration?.dispose(); + this.deregistration = undefined; + return this; + } + + /** + * Disposes any registered feedback and any pending, not yet registered feedback actions. + */ + dispose(): this { + this.revert(); + this.clear(); + return this; + } +} diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index b7f8491..cf6911b 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -20,17 +20,34 @@ import { CommandActionHandler, CommandExecutionContext, Disposable, + GModelElement, IActionDispatcher, ICommand, ILogger, + MaybeFunction, TYPES, + call, + asArray as toArray, toTypeGuard } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { getFeedbackRank } from './feedback-command'; +import { FeedbackEmitter } from './feeback-emitter'; export interface IFeedbackEmitter {} +export const feedbackFeature = Symbol('feedbackFeature'); + +export type MaybeActions = MaybeFunction; + +export namespace MaybeActions { + // eslint-disable-next-line @typescript-eslint/no-shadow + export function asArray(actions?: MaybeActions): Action[] { + const cleanup = actions ? call(actions) : []; + return cleanup ? toArray(cleanup) : []; + } +} + /** * Dispatcher for actions that are meant to show visual feedback on * the diagram that is not part of the diagram sent from the server @@ -52,7 +69,7 @@ export interface IFeedbackActionDispatcher { * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. */ - registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[]): Disposable; + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable; /** * Deregisters a `feedbackEmitter` from this dispatcher and thereafter @@ -64,7 +81,7 @@ export interface IFeedbackActionDispatcher { * to reset the normal state of the diagram without the feedback (e.g., reset a * CSS class that was set by a feedbackEmitter). */ - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[]): void; + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void; /** * Retrieve all `actions` sent out by currently registered `feedbackEmitter`. @@ -80,6 +97,12 @@ export interface IFeedbackActionDispatcher { * Applies all current feedback commands to the given command execution context. */ applyFeedbackCommands(context: CommandExecutionContext): Promise; + + /** + * Creates a new feedback emitter helper object. While anything can serve as a feedback emitter, + * this method ensures that the emitter is stable and does not change between model updates. + */ + createEmitter(): FeedbackEmitter; } @injectable() @@ -91,7 +114,14 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; - registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable { + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { + if (feedbackEmitter instanceof GModelElement) { + this.logger.log( + this, + // eslint-disable-next-line max-len + 'GModelElements as feedback emitters are discouraged, as they usually change between model updates and are considered unstable.' + ); + } if (feedbackActions.length > 0) { this.registeredFeedback.set(feedbackEmitter, feedbackActions); this.dispatchFeedback(feedbackActions, feedbackEmitter); @@ -99,10 +129,11 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); } - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[] | undefined): void { + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void { this.registeredFeedback.delete(feedbackEmitter); - if (cleanupActions && cleanupActions.length > 0) { - this.dispatchFeedback(cleanupActions, feedbackEmitter); + const actions = MaybeActions.asArray(cleanupActions); + if (actions.length > 0) { + this.dispatchFeedback(actions, feedbackEmitter); } } @@ -145,6 +176,10 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { ); } + createEmitter(): FeedbackEmitter { + return new FeedbackEmitter(this); + } + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { try { const actionDispatcher = await this.actionDispatcher(); diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 5f9f73b..73b366b 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './css-feedback'; +export * from './feeback-emitter'; export * from './feedback-action-dispatcher'; export * from './feedback-command'; export * from './set-model-command'; diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index f88d5cc..a153b4a 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -18,6 +18,7 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; import { defaultModule } from './default.module'; +import { FeedbackEmitter } from './feedback/feeback-emitter'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @@ -49,6 +50,10 @@ class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { return []; } + createEmitter(): FeedbackEmitter { + return new FeedbackEmitter(this); + } + async applyFeedbackCommands(context: CommandExecutionContext): Promise {} } diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index a1e55ce..1c5dd23 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -28,7 +28,6 @@ import { LazyInjector, SelectAction, SelectAllAction, - Selectable, SprottySelectAllCommand, SprottySelectCommand, TYPES, @@ -37,7 +36,7 @@ import { pluck } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; -import { getElements, getMatchingElements } from '../utils/gmodel-util'; +import { SelectableElement, getElements, getMatchingElements } from '../utils/gmodel-util'; import { IGModelRootListener } from './editor-context-service'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { IDiagramStartup } from './model'; @@ -164,7 +163,7 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr return this.root; } - getSelectedElements(): Readonly[] { + getSelectedElements(): Readonly[] { return !this.root ? [] : getElements(this.root.index, Array.from(this.selectedElementIDs), isSelectable); } diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index b5f6e7e..efb5abd 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BoundsAware, EdgeRouterRegistry, GConnectableElement, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; +import { EdgeRouterRegistry, GConnectableElement, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; -import { SelectableBoundsAware } from '../../../utils/gmodel-util'; -import { ElementNavigator } from './element-navigator'; import { GEdge } from '../../../model'; +import { BoundsAwareModelElement, SelectableBoundsAware } from '../../../utils/gmodel-util'; +import { ElementNavigator } from './element-navigator'; @injectable() export class LocalElementNavigator implements ElementNavigator { @@ -91,7 +91,7 @@ export class LocalElementNavigator implements ElementNavigator { protected getIterables( current: SelectableBoundsAware, - previousCurrent?: GModelElement & BoundsAware, + previousCurrent?: BoundsAwareModelElement, predicate: (element: GModelElement) => boolean = () => true ): GModelElement[] { const elements: GModelElement[] = []; diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index 077e6f0..0018f87 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -17,7 +17,6 @@ import { Action, ChangeBoundsOperation, - DisposableCollection, ElementAndBounds, ElementMove, GModelRoot, @@ -34,8 +33,9 @@ import { isBoundsAware, isViewport } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; +import { FeedbackEmitter } from '../../../base'; import { EditorContextService } from '../../../base/editor-context-service'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; @@ -164,7 +164,12 @@ export class MoveElementHandler implements IActionHandler { readonly movementRestrictor?: IMovementRestrictor; protected debouncedChangeBounds?: DebouncedFunc<() => void>; - protected disposableFeedback = new DisposableCollection(); + protected moveFeedback: FeedbackEmitter; + + @postConstruct() + protected init(): void { + this.moveFeedback = this.feedbackDispatcher.createEmitter(); + } handle(action: Action): void | Action | ICommand { if (MoveElementAction.is(action)) { @@ -199,7 +204,7 @@ export class MoveElementHandler implements IActionHandler { this.dispatcher.dispatchAll(viewportActions); const moveAction = MoveAction.create(elementMoves, { animate: false }); - this.disposableFeedback.push(this.feedbackDispatcher.registerFeedback(this, [moveAction])); + this.moveFeedback.add(moveAction).submit(); this.scheduleChangeBounds(this.toElementAndBounds(elementMoves)); } @@ -219,7 +224,7 @@ export class MoveElementHandler implements IActionHandler { protected scheduleChangeBounds(elementAndBounds: ElementAndBounds[]): void { this.debouncedChangeBounds?.cancel(); this.debouncedChangeBounds = debounce(() => { - this.disposableFeedback.dispose(); + this.moveFeedback.dispose(); this.dispatcher.dispatchAll([ChangeBoundsOperation.create(elementAndBounds)]); this.debouncedChangeBounds = undefined; }, 300); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index 6e3ce70..fa4aef1 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -18,7 +18,6 @@ import { Action, ChangeBoundsOperation, Dimension, - DisposableCollection, ElementAndBounds, GModelElement, GParentElement, @@ -30,13 +29,13 @@ import { SetBoundsAction, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; +import { FeedbackEmitter } from '../../../base'; import { EditorContextService } from '../../../base/editor-context-service'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; +import { Resizable, SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; -import { Resizable } from '../../change-bounds/model'; import { GridSnapper } from '../../change-bounds/snap'; export enum ResizeType { @@ -79,7 +78,7 @@ export class ResizeElementHandler implements IActionHandler { protected feedbackDispatcher: IFeedbackActionDispatcher; protected debouncedChangeBounds?: DebouncedFunc<() => void>; - protected disposableFeedback = new DisposableCollection(); + protected resizeFeedback: FeedbackEmitter; // Default x resize used if GridSnapper is not provided static readonly defaultResizeX = 20; @@ -96,6 +95,11 @@ export class ResizeElementHandler implements IActionHandler { } } + @postConstruct() + protected init(): void { + this.resizeFeedback = this.feedbackDispatcher.createEmitter(); + } + handle(action: Action): void | Action | ICommand { if (ResizeElementAction.is(action)) { this.handleResizeElement(action); @@ -106,11 +110,11 @@ export class ResizeElementHandler implements IActionHandler { const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); const elementAndBounds = this.computeElementAndBounds(elements, action); - this.disposableFeedback.push(this.feedbackDispatcher.registerFeedback(this, [SetBoundsAction.create(elementAndBounds)])); + this.resizeFeedback.add(SetBoundsAction.create(elementAndBounds)).submit(); this.debouncedChangeBounds?.cancel(); this.debouncedChangeBounds = debounce(() => { - this.disposableFeedback.dispose(); + this.resizeFeedback.dispose(); this.dispatcher.dispatchAll([ChangeBoundsOperation.create(elementAndBounds)]); this.debouncedChangeBounds = undefined; }, 300); diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 506af69..796eaeb 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -14,24 +14,20 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - BoundsAware, GChildElement, GModelElement, GParentElement, Hoverable, - Locateable, - Selectable, hoverFeedbackFeature, isBoundsAware, isMoveable, isSelectable } from '@eclipse-glsp/sprotty'; +import { BoundsAwareModelElement, MoveableElement, ResizableModelElement } from '../../utils'; export const resizeFeature = Symbol('resizeFeature'); -export interface Resizable extends BoundsAware, Selectable {} - -export function isResizable(element: GModelElement): element is GParentElement & Resizable { +export function isResizable(element: GModelElement): element is ResizableModelElement { return isBoundsAware(element) && isSelectable(element) && element instanceof GParentElement && element.hasFeature(resizeFeature); } @@ -43,15 +39,17 @@ export enum ResizeHandleLocation { BottomRight = 'bottom-right' } -export function isBoundsAwareMoveable(element: GModelElement): element is GModelElement & Locateable & BoundsAware { +export function isBoundsAwareMoveable(element: GModelElement): element is BoundsAwareModelElement & MoveableElement { return isMoveable(element) && isBoundsAware(element); } export class SResizeHandle extends GChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; + override readonly parent: ResizableModelElement; + constructor( - readonly location?: ResizeHandleLocation, + readonly location: ResizeHandleLocation, override readonly type: string = SResizeHandle.TYPE, readonly hoverFeedback: boolean = false ) { @@ -87,11 +85,25 @@ export class SResizeHandle extends GChildElement implements Hoverable { } } -export function addResizeHandles(element: GParentElement): void { - element.add(new SResizeHandle(ResizeHandleLocation.TopLeft)); - element.add(new SResizeHandle(ResizeHandleLocation.TopRight)); - element.add(new SResizeHandle(ResizeHandleLocation.BottomLeft)); - element.add(new SResizeHandle(ResizeHandleLocation.BottomRight)); +export function addResizeHandles( + element: ResizableModelElement, + locations: ResizeHandleLocation[] = [ + ResizeHandleLocation.TopLeft, + ResizeHandleLocation.TopRight, + ResizeHandleLocation.BottomLeft, + ResizeHandleLocation.BottomRight + ] +): void { + for (const location of Object.values(ResizeHandleLocation)) { + const existing = element.children.find(child => child instanceof SResizeHandle && child.location === location); + if (locations.includes(location) && !existing) { + // add missing handle + element.add(new SResizeHandle(location)); + } else if (!locations.includes(location) && existing) { + // remove existing handle + element.remove(existing); + } + } } export function removeResizeHandles(element: GParentElement): void { diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index b2c5d3b..da5e73b 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -13,9 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, BoundsAware, GModelElement, GNode, GParentElement, Point } from '@eclipse-glsp/sprotty'; +import { Bounds, GModelElement, GNode, GParentElement, Point } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; +import { BoundsAwareModelElement } from '../../utils'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { SResizeHandle, isBoundsAwareMoveable } from './model'; @@ -51,7 +52,7 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { return false; } // Create ghost element at the newLocation - const ghostElement = Object.create(element) as GModelElement & BoundsAware; + const ghostElement = Object.create(element) as BoundsAwareModelElement; ghostElement.bounds = { x: newLocation.x, y: newLocation.y, @@ -64,11 +65,11 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { element.root.index .all() .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof GNode) - .map(e => e as GModelElement & BoundsAware) + .map(e => e as BoundsAwareModelElement) ).some(e => this.areOverlapping(e, ghostElement)); } - protected areOverlapping(element1: GModelElement & BoundsAware, element2: GModelElement & BoundsAware): boolean { + protected areOverlapping(element1: BoundsAwareModelElement, element2: BoundsAwareModelElement): boolean { return Bounds.overlap(toAbsoluteBounds(element1), toAbsoluteBounds(element2)); } } diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index 49e68f5..cca0e2b 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -18,8 +18,8 @@ import { Action, Dimension, Disposable, + ElementMove, GModelElement, - Locateable, MoveAction, Point, isBoundsAware, @@ -28,8 +28,10 @@ import { import { injectable } from 'inversify'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../base/feedback/feeback-emitter'; import { IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { Tool } from '../../base/tool-manager/tool'; +import { MoveableElement } from '../../utils'; import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { IMovementRestrictor, @@ -39,20 +41,28 @@ import { import { PointPositionUpdater } from '../change-bounds/point-position-updater'; import { PositionSnapper } from '../change-bounds/position-snapper'; import { useSnap } from '../change-bounds/snap'; +import { MoveFinishedEventAction } from '../tools'; export interface PositioningTool extends Tool { readonly positionSnapper: PositionSnapper; readonly movementRestrictor?: IMovementRestrictor; + createFeedbackEmitter(): FeedbackEmitter; + /** + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback instead of using the tool. + */ registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable; + /** + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback and dispose it like that. + */ deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): void; } @injectable() export class MouseTrackingElementPositionListener extends DragAwareMouseListener { - currentPosition?: Point; - protected positionUpdater: PointPositionUpdater; + protected moveGhostFeedback: FeedbackEmitter; + protected currentPosition?: Point; constructor( protected elementId: string, @@ -61,6 +71,7 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener ) { super(); this.positionUpdater = new PointPositionUpdater(this.tool.positionSnapper); + this.moveGhostFeedback = this.tool.createFeedbackEmitter(); } override mouseMove(target: GModelElement, event: MouseEvent): Action[] { @@ -69,59 +80,48 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener if (!element || !isMoveable(element)) { return []; } - let targetPosition = this.getTargetPosition(target, event, element); if (this.positionUpdater.isLastDragPositionUndefined()) { - this.positionUpdater.updateLastDragPosition(targetPosition); + this.positionUpdater.updateLastDragPosition(element.position); } - const delta = this.positionUpdater.updatePosition(element, targetPosition, useSnap(event)); + const mousePosition = getAbsolutePosition(target, event); + const delta = this.positionUpdater.updatePosition(element, mousePosition, useSnap(event)); if (!delta) { return []; } - targetPosition = this.validateMove(this.currentPosition ?? targetPosition, targetPosition, element, false); - const moveGhostElement = MoveAction.create( - [ - { - elementId: element.id, - fromPosition: this.currentPosition, - toPosition: targetPosition - } - ], - { animate: false, finished: false } - ); - this.currentPosition = targetPosition; - this.tool.registerFeedback([moveGhostElement], this); - return element.cssClasses?.includes(CSS_HIDDEN) - ? [ModifyCSSFeedbackAction.create({ elements: [element.id], remove: [CSS_HIDDEN] })] - : []; - } - - protected getTargetPosition(target: GModelElement, event: MouseEvent, element: GModelElement & Locateable): Point { - let targetPosition = getAbsolutePosition(target, event); - if (this.cursorPosition === 'middle' && isBoundsAware(element)) { - targetPosition = Point.subtract(targetPosition, Dimension.center(element.bounds)); - } - return targetPosition; + const toPosition = this.getElementTargetPosition(mousePosition, element, event); + const elementMove = { elementId: element.id, toPosition: toPosition }; + this.addMoveFeeback(element, elementMove); + this.currentPosition = toPosition; + // since we are moving a ghost element that is feedback-only and will be removed anyway, + // we just send a MoveFinishedEventAction instead of reseting the position with a MoveAction and the finished flag set to true. + this.moveGhostFeedback.add(MoveAction.create([elementMove], { animate: false }), MoveFinishedEventAction.create()).submit(); + return []; } - protected snap(position: Point, element: GModelElement, isSnap = true): Point { - return this.tool.positionSnapper.snapPosition(position, element, isSnap); + protected getElementTargetPosition(mousePosition: Point, element: MoveableElement, event: MouseEvent): Point { + const unsnappedPosition = + this.cursorPosition === 'middle' && isBoundsAware(element) + ? Point.subtract(mousePosition, Dimension.center(element.bounds)) + : mousePosition; + return this.tool.positionSnapper.snapPosition(unsnappedPosition, element, useSnap(event)); } - protected validateMove(startPosition: Point, toPosition: Point, element: GModelElement, isFinished: boolean): Point { - let newPosition = toPosition; + protected addMoveFeeback(element: MoveableElement, elementMove: ElementMove): void { if (this.tool.movementRestrictor) { - const valid = this.tool.movementRestrictor.validate(element, toPosition); - let action; - if (!valid) { - action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); - if (isFinished) { - newPosition = startPosition; - } + if (!this.tool.movementRestrictor.validate(element, elementMove.toPosition)) { + this.moveGhostFeedback.add( + createMovementRestrictionFeedback(element, this.tool.movementRestrictor), + removeMovementRestrictionFeedback(element, this.tool.movementRestrictor) + ); } else { - action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); + this.moveGhostFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - this.tool.registerFeedback([action], this, [removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)]); } - return newPosition; + this.moveGhostFeedback.add(ModifyCSSFeedbackAction.create({ elements: [element.id], remove: [CSS_HIDDEN] })); + } + + override dispose(): void { + this.moveGhostFeedback.dispose(); + super.dispose(); } } diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 5dbcf22..d503d22 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -13,18 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - DisposableCollection, - GModelElement, - GModelRoot, - IActionHandler, - MoveAction, - Point, - SetBoundsAction, - TYPES -} from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, GModelRoot, IActionHandler, MoveAction, Point, SetBoundsAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; +import { FeedbackEmitter } from '../../base'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; @@ -95,10 +86,11 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH @optional() @inject(TYPES.IHelperLineOptions) protected userOptions?: IHelperLineOptions; protected options: Required; - protected feedback: DisposableCollection = new DisposableCollection(); + protected feedback: FeedbackEmitter; @postConstruct() protected init(): void { + this.feedback = this.feedbackDispatcher.createEmitter(); this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...this.userOptions }; this.selectionService.onSelectionChanged(change => this.selectionChanged(change.root, change.selectedElements, change.deselectedElements) @@ -118,9 +110,7 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH } protected handleMoveInitializedAction(_action: MoveInitializedEventAction): void { - this.feedback.dispose(); - const feedback = this.createHelperLineFeedback(this.selectionService.getSelectedElementIDs()); - this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + this.submitHelperLineFeedback(); } protected handleMoveFinishedAction(_action: MoveFinishedEventAction): void { @@ -129,22 +119,23 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH protected handleMoveAction(action: MoveAction): void { if (!action.finished) { - const elementIds = action.moves.map(move => move.elementId); - const feedback = this.createHelperLineFeedback(elementIds); - this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + this.submitHelperLineFeedback(action.moves.map(move => move.elementId)); } else { this.feedback.dispose(); } } + protected submitHelperLineFeedback(elementIds: string[] = this.selectionService.getSelectedElementIDs()): void { + const feedback = this.createHelperLineFeedback(elementIds); + this.feedback.add(feedback, [RemoveHelperLinesFeedbackAction.create()]).submit(); + } + protected createHelperLineFeedback(elementIds: string[]): DrawHelperLinesFeedbackAction { return DrawHelperLinesFeedbackAction.create({ elementIds, ...this.options }); } protected handleSetBoundsAction(action: SetBoundsAction | SetBoundsFeedbackAction): void { - const elementIds = action.bounds.map(bound => bound.elementId); - const feedback = this.createHelperLineFeedback(elementIds); - this.feedback.push(this.feedbackDispatcher.registerFeedback(this, [feedback], [RemoveHelperLinesFeedbackAction.create()])); + this.submitHelperLineFeedback(action.bounds.map(bound => bound.elementId)); } selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[] | undefined): void { diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index 5448aa3..458f512 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -35,6 +35,7 @@ import { import { expect } from 'chai'; import { Container } from 'inversify'; import * as sinon from 'sinon'; +import { FeedbackEmitter } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { GEdge } from '../../model'; @@ -45,7 +46,9 @@ import { ApplyTypeHintsAction, ApplyTypeHintsCommand, ITypeHintProvider, TypeHin describe('TypeHintProvider', () => { const container = new Container(); container.bind(GLSPActionDispatcher).toConstantValue(sinon.createStubInstance(GLSPActionDispatcher)); - container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(sinon.createStubInstance(FeedbackActionDispatcher)); + const stub = sinon.createStubInstance(FeedbackActionDispatcher); + stub.createEmitter.returns(new FeedbackEmitter(stub)); + container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(stub); const typeHintProvider = container.resolve(TypeHintProvider); describe('getShapeTypeHint', () => { diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 2e092db..51f46e8 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -35,8 +35,9 @@ import { isConnectable, moveFeature } from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, postConstruct } from 'inversify'; +import { FeedbackEmitter } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; @@ -204,15 +205,21 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDia @inject(GLSPActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + protected typeHintsFeedback: FeedbackEmitter; protected shapeHints: Map = new Map(); protected edgeHints: Map = new Map(); + @postConstruct() + protected init(): void { + this.typeHintsFeedback = this.feedbackActionDispatcher.createEmitter(); + } + handle(action: SetTypeHintsAction): void { this.shapeHints.clear(); this.edgeHints.clear(); action.shapeHints.forEach(hint => this.shapeHints.set(hint.elementTypeId, hint)); action.edgeHints.forEach(hint => this.edgeHints.set(hint.elementTypeId, hint)); - this.feedbackActionDispatcher.registerFeedback(this, [ApplyTypeHintsAction.create()]); + this.typeHintsFeedback.add(ApplyTypeHintsAction.create()).submit(); } getShapeTypeHint(input: GModelElement | GModelElementSchema | string): ShapeTypeHint | undefined { diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index ad8c50d..696681e 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener, Selectable } from '@eclipse-glsp/sprotty'; +import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener } from '@eclipse-glsp/sprotty'; import { Ranked } from '../../base/ranked'; +import { SelectableElement } from '../../utils'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. @@ -24,7 +25,7 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ protected override handleSelectTarget( - selectableTarget: GModelElement & Selectable, + selectableTarget: SelectableElement, deselectedElements: GModelElement[], event: MouseEvent ): (Action | Promise)[] { @@ -40,7 +41,7 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra return result; } - protected override handleDeselectTarget(selectableTarget: GModelElement & Selectable, event: MouseEvent): (Action | Promise)[] { + protected override handleDeselectTarget(selectableTarget: SelectableElement, event: MouseEvent): (Action | Promise)[] { const result: Action[] = []; result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: [selectableTarget.id] })); return result; diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 71f1890..b232f06 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feeback-emitter'; +import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from '../../base/feedback/feedback-action-dispatcher'; import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; import { GLSPKeyTool } from '../../base/view/key-tool'; import { GLSPMouseTool } from '../../base/view/mouse-tool'; @@ -47,8 +48,12 @@ export abstract class BaseEditTool implements Tool { return true; } - dispatchActions(actions: Action[]): void { - this.actionDispatcher.dispatchAll(actions); + dispatchActions(actions: Action[]): Promise { + return this.actionDispatcher.dispatchAll(actions); + } + + createFeedbackEmitter(): FeedbackEmitter { + return this.feedbackDispatcher.createEmitter(); } /** @@ -59,9 +64,10 @@ export abstract class BaseEditTool implements Tool { * @param feedbackEmitter the emitter sending out feedback actions (this tool by default). * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback instead of using the tool. */ - registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable { - return this.feedbackDispatcher.registerFeedback(feedbackEmitter ?? this, feedbackActions, cleanupActions); + registerFeedback(feedbackActions: Action[], feedbackEmitter: IFeedbackEmitter = this, cleanupActions?: MaybeActions): Disposable { + return this.feedbackDispatcher.registerFeedback(feedbackEmitter, feedbackActions, cleanupActions); } /** @@ -70,9 +76,10 @@ export abstract class BaseEditTool implements Tool { * * @param feedbackEmitter the emitter to be deregistered (this tool by default). * @param cleanupActions the actions to be dispatched right after the deregistration to clean up any pending feedback. + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback and dispose it like that. */ - deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): void { - this.feedbackDispatcher.deregisterFeedback(feedbackEmitter ?? this, cleanupActions); + deregisterFeedback(feedbackEmitter: IFeedbackEmitter = this, cleanupActions?: MaybeActions): void { + this.feedbackDispatcher.deregisterFeedback(feedbackEmitter, cleanupActions); } } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index 056ea2b..d79fd46 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -13,35 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - CommandExecutionContext, - CommandReturn, - Disposable, - ElementMove, - GChildElement, - GModelElement, - GModelRoot, - MoveAction, - Point, - TYPES, - findParentByFeature, - hasStringProp, - isMoveable, - isSelectable -} from '@eclipse-glsp/sprotty'; +import { Action, CommandExecutionContext, CommandReturn, TYPES, hasStringProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { DebouncedFunc, debounce } from 'lodash'; -import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; -import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { OptionalAction } from '../../../base/model/glsp-model-source'; import { forEachElement } from '../../../utils/gmodel-util'; -import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; -import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; -import { PointPositionUpdater } from '../../change-bounds/point-position-updater'; -import { ChangeBoundsTool } from './change-bounds-tool'; +import { addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; @@ -89,13 +67,11 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - forEachElement(index, isResizable, removeResizeHandles); + forEachElement(index, isResizable, element => element.id !== this.action.elementId && removeResizeHandles(element)); - if (this.action.elementId) { - const resizeElement = index.getById(this.action.elementId); - if (resizeElement && isResizable(resizeElement)) { - addResizeHandles(resizeElement); - } + const resizeElement = index.getById(this.action.elementId); + if (resizeElement && isResizable(resizeElement)) { + addResizeHandles(resizeElement); } return context.root; } @@ -145,205 +121,3 @@ export namespace MoveFinishedEventAction { return OptionalAction.mark({ kind: KIND }); } } - -/** - * This mouse listener provides visual feedback for moving by sending client-side - * `MoveAction`s while elements are selected and dragged. This will also update - * their bounds, which is important, as it is not only required for rendering - * the visual feedback but also the basis for sending the change to the server - * (see also `tools/MoveTool`). - */ -export class FeedbackMoveMouseListener extends DragAwareMouseListener implements Disposable { - protected rootElement?: GModelRoot; - protected positionUpdater; - protected elementId2startPos = new Map(); - protected pendingMoveInitialized?: DebouncedFunc<() => void>; - protected moveInitialized = false; - - constructor(protected tool: ChangeBoundsTool) { - super(); - this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); - } - - override mouseDown(target: GModelElement, event: MouseEvent): Action[] { - if (event.button === 0 && !(target instanceof SResizeHandle)) { - const moveable = findParentByFeature(target, isMoveable); - if (moveable !== undefined && !(target instanceof SResizeHandle)) { - this.positionUpdater.updateLastDragPosition(event); - this.scheduleMoveInitialized(); - } else { - this.positionUpdater.resetPosition(); - } - this._isMouseDrag = false; - } - return []; - } - - protected scheduleMoveInitialized(): void { - this.moveInitialized = false; - this.pendingMoveInitialized?.cancel(); - this.pendingMoveInitialized = debounce(() => { - this.tool.registerFeedback([MoveInitializedEventAction.create()], this); - this.pendingMoveInitialized = undefined; - this.moveInitialized = true; - }, 750); - this.pendingMoveInitialized(); - } - - override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - const result: Action[] = []; - if (event.buttons === 0) { - this.mouseUp(target, event); - } else if (!this.positionUpdater.isLastDragPositionUndefined()) { - if (this.elementId2startPos.size === 0) { - this.collectStartPositions(target.root); - } - this._isMouseDrag = true; - const moveAction = this.getElementMoves(target, event, false); - if (moveAction) { - this.pendingMoveInitialized?.cancel(); - result.push(moveAction); - result.push(cursorFeedbackAction(CursorCSS.MOVE)); - this.tool.registerFeedback(result, this); - } - } - return []; - } - - protected collectStartPositions(root: GModelRoot): void { - this.rootElement = root; - const selectedElements = root.index.all().filter(element => isSelectable(element) && element.selected); - const elementsSet = new Set(selectedElements); - selectedElements - .filter(element => !this.isChildOfSelected(elementsSet, element)) - .forEach(element => { - if (isMoveable(element)) { - this.elementId2startPos.set(element.id, element.position); - } - }); - } - - protected isChildOfSelected(selectedElements: Set, element: GModelElement): boolean { - while (element instanceof GChildElement) { - element = element.parent; - if (selectedElements.has(element)) { - return true; - } - } - return false; - } - - protected getElementMoves(target: GModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { - const delta = this.positionUpdater.updatePosition(target, event); - if (!delta) { - return undefined; - } - const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, finished); - if (elementMoves.length > 0) { - return MoveAction.create(elementMoves, { animate: false, finished }); - } else { - return undefined; - } - } - - protected getElementMovesForDelta(target: GModelElement, delta: Point, finished: boolean): ElementMove[] { - const elementMoves: ElementMove[] = []; - this.elementId2startPos.forEach((startPosition, elementId) => { - const element = target.root.index.getById(elementId); - if (element) { - if (isMoveable(element)) { - const targetPosition = Point.add(element.position, delta); - const toPosition = this.validateMove(startPosition, targetPosition, element, finished); - elementMoves.push({ elementId: element.id, fromPosition: element.position, toPosition }); - } - } - }); - return elementMoves; - } - - protected validateMove(startPosition: Point, toPosition: Point, element: GModelElement, isFinished: boolean): Point { - let newPosition = toPosition; - if (this.tool.movementRestrictor) { - const valid = this.tool.movementRestrictor.validate(element, toPosition); - let action; - if (!valid) { - action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); - if (isFinished) { - newPosition = startPosition; - } - } else { - action = removeMovementRestrictionFeedback(element, this.tool.movementRestrictor); - } - this.tool.registerFeedback([action], this); - } - return newPosition; - } - - override mouseEnter(target: GModelElement, event: MouseEvent): Action[] { - if (target instanceof GModelRoot && event.buttons === 0 && this.positionUpdater.isLastDragPositionUndefined()) { - this.mouseUp(target, event); - } - return []; - } - - override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { - this.reset(true); - return []; - } - - override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { - const result: Action[] = []; - if (this.positionUpdater.isLastDragPositionUndefined()) { - this.reset(true); - return result; - } else { - const moveAction = this.getElementMoves(target, event, true); - if (moveAction) { - result.push(moveAction); - } - const resetFeedback: Action[] = []; - if (this.tool.movementRestrictor) { - resetFeedback.push(removeMovementRestrictionFeedback(target, this.tool.movementRestrictor)); - } - resetFeedback.push(cursorFeedbackAction(CursorCSS.DEFAULT)); - this.tool.deregisterFeedback(this, resetFeedback); - } - this.reset(); - return result; - } - - protected resetMoveFeedback(): ElementMove[] { - const elementMoves: ElementMove[] = []; - this.elementId2startPos.forEach((startPosition, elementId) => { - const element = this.rootElement!.index.getById(elementId); - if (element) { - if (isMoveable(element)) { - elementMoves.push({ elementId: element.id, fromPosition: element.position, toPosition: startPosition }); - } - } - }); - return elementMoves; - } - - protected reset(resetFeedback = false): void { - this.pendingMoveInitialized?.cancel(); - if (this.rootElement && resetFeedback) { - const elementMoves: ElementMove[] = this.resetMoveFeedback(); - if (elementMoves.length > 0) { - const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true }); - this.tool.deregisterFeedback(this, [moveAction]); - } - } else if (resetFeedback && this.moveInitialized) { - this.tool.deregisterFeedback(this, [MoveFinishedEventAction.create()]); - } - this.positionUpdater.resetPosition(); - this._isMouseDrag = false; - this.moveInitialized = false; - this.rootElement = undefined; - this.elementId2startPos.clear(); - } - - dispose(): void { - this.reset(true); - } -} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index 8165cbf..c12fb22 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '@eclipse-glsp/sprotty'; +import '../../../../css/change-bounds.css'; import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts new file mode 100644 index 0000000..e7ddc0f --- /dev/null +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -0,0 +1,254 @@ +/******************************************************************************** + * Copyright (c) 2019-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, Disposable, ElementMove, GModelElement, GModelRoot, MoveAction, Point, findParentByFeature } from '@eclipse-glsp/sprotty'; + +import { DebouncedFunc, debounce } from 'lodash'; +import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; +import { MoveableElement, filter, getElements, isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils'; +import { PointPositionUpdater } from '../../change-bounds'; +import { SResizeHandle } from '../../change-bounds/model'; +import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; +import { ChangeBoundsTool } from './change-bounds-tool'; +import { MoveFinishedEventAction, MoveInitializedEventAction } from './change-bounds-tool-feedback'; + +export interface ValidatedElementMove extends ElementMove { + valid: boolean; +} + +export namespace ValidatedElementMove { + export function isValid(move: ElementMove): boolean { + return (move as ValidatedElementMove).valid ?? true; + } +} + +/** + * This mouse listener provides visual feedback for moving by sending client-side + * `MoveAction`s while elements are selected and dragged. This will also update + * their bounds, which is important, as it is not only required for rendering + * the visual feedback but also the basis for sending the change to the server + * (see also `tools/MoveTool`). + */ +export class FeedbackMoveMouseListener extends DragAwareMouseListener implements Disposable { + protected rootElement?: GModelRoot; + protected positionUpdater; + protected elementId2startPos = new Map(); + + protected pendingMoveInitialized?: DebouncedFunc<() => void>; + + protected moveInitializedFeedback: FeedbackEmitter; + protected moveFeedback: FeedbackEmitter; + + constructor(protected tool: ChangeBoundsTool) { + super(); + this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); + this.moveInitializedFeedback = tool.createFeedbackEmitter(); + this.moveFeedback = tool.createFeedbackEmitter(); + } + + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { + super.mouseDown(target, event); + if (event.button === 0) { + this.initializeMove(target, event); + return []; + } + this.positionUpdater.resetPosition(); + return []; + } + + protected initializeMove(target: GModelElement, event: MouseEvent): void { + if (target instanceof SResizeHandle) { + // avoid conflict with resize tool + return; + } + const moveable = findParentByFeature(target, this.isValidMoveable); + if (moveable !== undefined) { + this.positionUpdater.updateLastDragPosition(event); + this.scheduleMoveInitialized(); + } else { + this.positionUpdater.resetPosition(); + } + } + + protected scheduleMoveInitialized(): void { + this.pendingMoveInitialized?.cancel(); + this.pendingMoveInitialized = debounce(() => { + this.moveInitialized(); + this.pendingMoveInitialized = undefined; + }, 750); + this.pendingMoveInitialized(); + } + + protected moveInitializationTimeout(): number { + return 750; + } + + protected moveInitialized(): void { + if (this.isMouseDown) { + this.moveInitializedFeedback + .add(MoveInitializedEventAction.create(), MoveFinishedEventAction.create()) + .add(cursorFeedbackAction(CursorCSS.MOVE), cursorFeedbackAction(CursorCSS.DEFAULT)) + .submit(); + } + } + + protected isValidMoveable(element?: GModelElement): element is MoveableElement { + return !!element && isNonRoutableSelectedMovableBoundsAware(element) && !(element instanceof SResizeHandle); + } + + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { + // should reset everything that may have happend on mouse down + this.moveInitializedFeedback.dispose(); + this.positionUpdater.resetPosition(); + return []; + } + + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { + super.mouseMove(target, event); + if (event.buttons === 0) { + return this.mouseUp(target, event); + } else if (!this.positionUpdater.isLastDragPositionUndefined()) { + return this.moveElements(target, event); + } + return []; + } + + protected moveElements(target: GModelElement, event: MouseEvent): Action[] { + if (this.elementId2startPos.size === 0) { + this.initializeElementsToMove(target.root); + } + const moveAction = this.getElementMoves(target, event, false); + if (!moveAction) { + return []; + } + // cancel any pending move + this.pendingMoveInitialized?.cancel(); + this.moveFeedback.add(moveAction, () => this.resetElementPositions(target)); + this.addMovementFeedback(moveAction, target); + this.moveFeedback.submit(); + return []; + } + + protected addMovementFeedback(movement: MoveAction, ctx: GModelElement): void { + // cursor feedback + this.moveFeedback.add(cursorFeedbackAction(CursorCSS.MOVE), cursorFeedbackAction(CursorCSS.DEFAULT)); + + // restriction feedback + movement.moves.forEach(move => { + const element = ctx.root.index.getById(move.elementId); + if (element && this.tool.movementRestrictor) { + if (!ValidatedElementMove.isValid(move)) { + this.moveFeedback.add(createMovementRestrictionFeedback(element, this.tool.movementRestrictor), () => + removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!) + ); + } else { + this.moveFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); + } + } + }); + } + + protected initializeElementsToMove(root: GModelRoot): void { + const elementsToMove = this.collectElementsToMove(root); + elementsToMove.forEach(element => this.elementId2startPos.set(element.id, element.position)); + } + + protected collectElementsToMove(root: GModelRoot): MoveableElement[] { + const moveableElements = filter(root.index, this.isValidMoveable); + const topLevelElements = removeDescendants(moveableElements); + return Array.from(topLevelElements); + } + + protected getElementsToMove(context: GModelElement): MoveableElement[] { + return getElements(context.root.index, Array.from(this.elementId2startPos.keys()), this.isValidMoveable); + } + + protected getElementMoves(target: GModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { + const delta = this.positionUpdater.updatePosition(target, event); + if (!delta) { + return undefined; + } + const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, finished).filter(move => + this.filterElementMove(move) + ); + if (elementMoves.length > 0) { + // we never want to animate the move action as this interferes with the move feedback + return MoveAction.create(elementMoves, { animate: false, finished }); + } else { + return undefined; + } + } + + protected filterElementMove(elementMove: ValidatedElementMove): boolean { + return !!elementMove.fromPosition && !Point.equals(elementMove.fromPosition, elementMove.toPosition); + } + + protected getElementMovesForDelta(target: GModelElement, delta: Point, finished: boolean): ValidatedElementMove[] { + return this.getElementsToMove(target).flatMap(element => { + const startPosition = this.elementId2startPos.get(element.id); + if (!startPosition) { + return []; + } + const targetPosition = Point.add(element.position, delta); + const valid = this.tool.movementRestrictor?.validate(element, targetPosition) ?? true; + return [this.createElementMove(element, targetPosition, valid, finished)]; + }); + } + + protected createElementMove(element: MoveableElement, toPosition: Point, valid: boolean, finished: boolean): ValidatedElementMove { + // if we are finished and have an invalid move, we want to move the element back to its start position + return !valid && finished + ? { elementId: element.id, fromPosition: element.position, toPosition: element.position, valid } + : { elementId: element.id, fromPosition: element.position, toPosition, valid }; + } + + protected resetElementPositions(context: GModelElement): MoveAction | undefined { + const elementMoves: ElementMove[] = this.revertElementMoves(context); + return MoveAction.create(elementMoves, { animate: false, finished: true }); + } + + protected revertElementMoves(context?: GModelElement): ElementMove[] { + const elementMoves: ElementMove[] = []; + if (context?.root?.index) { + const movableElements = this.getElementsToMove(context); + movableElements.forEach(element => + elementMoves.push({ elementId: element.id, toPosition: this.elementId2startPos.get(element.id)! }) + ); + } + return elementMoves; + } + + override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { + if (this.positionUpdater.isLastDragPositionUndefined()) { + return []; + } + // only reset the move of invalid elements, the others will be handled by the change bounds tool itself + const moves = this.getElementMovesForDelta(target, Point.ORIGIN, false); + moves.filter(move => move.valid).forEach(move => this.elementId2startPos.delete(move.elementId)); + this.dispose(); + return []; + } + + override dispose(): void { + this.pendingMoveInitialized?.cancel(); + this.moveInitializedFeedback.dispose(); + this.moveFeedback.dispose(); + this.positionUpdater.resetPosition(); + this.elementId2startPos.clear(); + super.dispose(); + } +} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 6be5e57..d7b3645 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -17,7 +17,6 @@ import { inject, injectable, optional } from 'inversify'; import { Action, - Bounds, BoundsAware, ChangeBoundsOperation, ChangeRoutingPointsOperation, @@ -36,35 +35,39 @@ import { Operation, Point, TYPES, - findParentByFeature, - isSelected + findParentByFeature } from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; -import { PointPositionUpdater } from '../../../features/change-bounds/point-position-updater'; +import { isValidMove, isValidSize } from '../../../utils'; import { + BoundsAwareModelElement, + ResizableModelElement, calcElementAndRoutingPoints, forEachElement, + getMatchingElements, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; -import { isValidMove, isValidSize } from '../../../utils/layout-utils'; -import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-command'; -import { Resizable, ResizeHandleLocation, SResizeHandle, isBoundsAwareMoveable, isResizable } from '../../change-bounds/model'; +import { SetBoundsFeedbackAction } from '../../bounds'; +import { PointPositionUpdater } from '../../change-bounds'; +import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor, createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; import { PositionSnapper } from '../../change-bounds/position-snapper'; -import { getDirectionFrom } from '../../helper-lines/model'; +import { getDirectionFrom } from '../../helper-lines'; import { BaseEditTool } from '../base-tools'; import { - FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, + MoveFinishedEventAction, ShowChangeBoundsToolResizeFeedbackAction } from './change-bounds-tool-feedback'; +import { FeedbackMoveMouseListener } from './change-bounds-tool-move-feedback'; /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. @@ -108,8 +111,6 @@ export class ChangeBoundsTool extends BaseEditTool { this.toDisposeOnDisable.push( this.mouseTool.registerListener(feedbackMoveMouseListener), this.mouseTool.registerListener(changeBoundsListener), - Disposable.create(() => this.deregisterFeedback(feedbackMoveMouseListener)), - Disposable.create(() => this.deregisterFeedback(changeBoundsListener, [HideChangeBoundsToolResizeFeedbackAction.create()])), this.selectionService.onSelectionChanged(change => changeBoundsListener.selectionChanged(change.root, change.selectedElements)) ); } @@ -123,20 +124,34 @@ export class ChangeBoundsTool extends BaseEditTool { } } +export interface ValidatedElementAndBounds extends ElementAndBounds { + valid: boolean; +} + +export namespace ValidatedElementAndBounds { + export function isValid(move: ElementAndBounds): boolean { + return (move as ValidatedElementAndBounds).valid ?? true; + } +} + export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener, Disposable { static readonly CSS_CLASS_ACTIVE = 'active'; // members for calculating the correct position change - protected initialBounds: Bounds | undefined; - protected pointPositionUpdater: PointPositionUpdater; + protected initialBounds: ElementAndBounds | undefined; + protected positionUpdater: PointPositionUpdater; // members for resize mode - protected activeResizeElement?: GModelElement; + protected activeResizeElement?: ResizableModelElement; protected activeResizeHandle?: SResizeHandle; + protected handleFeedback: FeedbackEmitter; + protected resizeFeedback: FeedbackEmitter; constructor(protected tool: ChangeBoundsTool) { super(); - this.pointPositionUpdater = new PointPositionUpdater(tool.positionSnapper); + this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); + this.handleFeedback = tool.createFeedbackEmitter(); + this.resizeFeedback = tool.createFeedbackEmitter(); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { @@ -147,93 +162,157 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } // check if we have a resize handle (only single-selection) - if (this.activeResizeElement && target instanceof SResizeHandle) { - this.activeResizeHandle = target; - } else { - this.setActiveResizeElement(target); - } + this.updateResizeElement(target, event); + return []; + } + + protected updateResizeElement(target: GModelElement, event?: MouseEvent): boolean { + this.activeResizeHandle = target instanceof SResizeHandle ? target : undefined; + this.activeResizeElement = this.activeResizeHandle?.parent ?? this.findResizeElement(target); if (this.activeResizeElement) { - this.initPosition(event); + if (event) { + this.positionUpdater.updateLastDragPosition(event); + } + this.initialBounds = { + newSize: this.activeResizeElement.bounds, + newPosition: this.activeResizeElement.bounds, + elementId: this.activeResizeElement.id + }; + this.handleFeedback.add( + ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id), + HideChangeBoundsToolResizeFeedbackAction.create() + ); + this.handleFeedback.submit(); + return true; } else { - this.reset(); + this.positionUpdater.resetPosition(); + this.handleFeedback.dispose(); + return false; } - return []; } - override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - super.mouseMove(target, event); - if (this.isMouseDrag && this.activeResizeHandle) { - // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements - // consider resize handles ourselves - const actions: Action[] = [ - cursorFeedbackAction(this.activeResizeHandle.isNwSeResize() ? CursorCSS.RESIZE_NWSE : CursorCSS.RESIZE_NESW), - applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE) - ]; - const positionUpdate = this.pointPositionUpdater.updatePosition( - target, - event, - getDirectionFrom(this.activeResizeHandle.location) - ); + protected findResizeElement(target: GModelElement): ResizableModelElement | undefined { + // check if we have a selected, moveable element (multi-selection allowed) + // but only allow one element to have the element resize handles + return findParentByFeature(target, isResizable); + } + + protected override draggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { + // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements + // consider resize handles ourselves + if (this.activeResizeHandle && !this.positionUpdater.isLastDragPositionUndefined()) { + if (!this.initialBounds) { + const element = this.activeResizeHandle.parent; + this.initialBounds = { elementId: element.id, newSize: element.bounds, newPosition: element.bounds }; + } + const positionUpdate = this.positionUpdater.updatePosition(target, event, getDirectionFrom(this.activeResizeHandle.location)); if (positionUpdate) { - const resizeActions = this.handleResizeOnClient(positionUpdate); - actions.push(...resizeActions); + const resizeActions = this.handleResizeOnClient(positionUpdate).filter(action => action); + if (resizeActions.length > 0) { + this.resizeFeedback.add(SetBoundsFeedbackAction.create(resizeActions), () => this.resetBoundsAction()); + this.addResizeFeedback(resizeActions, this.activeResizeHandle, target, event); + this.resizeFeedback.submit(); + } } - this.tool.registerFeedback(actions, this); } - return []; + return super.draggingMouseMove(target, event); + } + + protected filterResizeOnClient(resize: ValidatedElementAndBounds, positionUpdate: Point): boolean { + return true; + } + + protected addResizeFeedback( + resizeActions: ValidatedElementAndBounds[], + handle: SResizeHandle, + target: GModelElement, + event: MouseEvent + ): void { + // handle feedback + this.resizeFeedback.add( + applyCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE), + deleteCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE) + ); + + // cursor feedback + const cursorClass = handle.isNeResize() + ? CursorCSS.RESIZE_NE + : handle.isNwResize() + ? CursorCSS.RESIZE_NW + : handle.isSeResize() + ? CursorCSS.RESIZE_SE + : CursorCSS.RESIZE_SW; + this.resizeFeedback.add(cursorFeedbackAction(cursorClass), cursorFeedbackAction(CursorCSS.DEFAULT)); + + // restriction feedback + resizeActions.forEach(elementResize => { + const element = handle.root.index.getById(elementResize.elementId); + if (element && this.tool.movementRestrictor) { + if (!elementResize.valid) { + this.resizeFeedback.add(createMovementRestrictionFeedback(element, this.tool.movementRestrictor), () => + removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!) + ); + } else { + this.resizeFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!)); + } + } + }); + } + + protected resetBoundsAction(): Action[] { + // reset the bounds to the initial bounds and ensure that we do not show helper line feedback anymore (MoveFinishedEventAction) + return this.initialBounds + ? [SetBoundsFeedbackAction.create([this.initialBounds]), MoveFinishedEventAction.create()] + : [MoveFinishedEventAction.create()]; } override draggingMouseUp(target: GModelElement, _event: MouseEvent): Action[] { - if (this.pointPositionUpdater.isLastDragPositionUndefined()) { - this.resetPosition(); + if (this.positionUpdater.isLastDragPositionUndefined()) { return []; } const actions: Action[] = []; - if (this.activeResizeHandle) { - // Resize - actions.push(...this.handleResize(this.activeResizeHandle)); + actions.push(...this.handleResizeOnServer(this.activeResizeHandle)); } else { - // Move actions.push(...this.handleMoveOnServer(target)); } - this.resetPosition(); - this.tool.deregisterFeedback(this, [cursorFeedbackAction(CursorCSS.DEFAULT)]); + this.disposeAllButHandles(); return actions; } + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { + this.disposeAllButHandles(); + return super.nonDraggingMouseUp(element, event); + } + protected handleMoveOnServer(target: GModelElement): Action[] { const operations: Operation[] = []; - operations.push(...this.handleMoveElementsOnServer(target)); operations.push(...this.handleMoveRoutingPointsOnServer(target)); - if (operations.length > 0) { - return [CompoundOperation.create(operations)]; - } - return operations; + return operations.length > 0 ? [CompoundOperation.create(operations)] : []; } protected handleMoveElementsOnServer(target: GModelElement): Operation[] { - const result: Operation[] = []; - const newBounds: ElementAndBounds[] = []; - const selectedElements: (GModelElement & BoundsAware)[] = []; - forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { - selectedElements.push(element); - }); - - const selectionSet: Set = new Set(selectedElements); - selectedElements - .filter(element => !this.isChildOfSelected(selectionSet, element)) - .map(element => this.createElementAndBounds(element)) - .forEach(bounds => newBounds.push(...bounds)); - - if (newBounds.length > 0) { - result.push(ChangeBoundsOperation.create(newBounds)); - } - return result; + const selectedElements = getMatchingElements(target.index, isNonRoutableSelectedMovableBoundsAware); + const selectionSet: Set = new Set(selectedElements); + const newBounds: ElementAndBounds[] = selectedElements + .filter(element => this.isValidElement(element, selectionSet)) + .map(toElementAndBounds); + return newBounds.length > 0 ? [ChangeBoundsOperation.create(newBounds)] : []; + } + + protected isValidElement(element: BoundsAwareModelElement, selectedElements: Set = new Set()): boolean { + return ( + this.isValidMove(element, element.bounds) && + this.isValidSize(element, element.bounds) && + !this.isChildOfSelected(selectedElements, element) + ); } protected isChildOfSelected(selectedElements: Set, element: GModelElement): boolean { + if (selectedElements.size === 0) { + return false; + } while (element instanceof GChildElement) { element = element.parent; if (selectedElements.has(element)) { @@ -262,217 +341,129 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return newRoutingPoints.length > 0 ? [ChangeRoutingPointsOperation.create(newRoutingPoints)] : []; } - protected handleResize(activeResizeHandle: SResizeHandle): Action[] { - const actions: Action[] = []; - actions.push(cursorFeedbackAction(CursorCSS.DEFAULT)); - actions.push(deleteCssClasses(activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE)); - const resizeElement = findParentByFeature(activeResizeHandle, isResizable); - if (this.isActiveResizeElement(resizeElement)) { - this.createChangeBoundsAction(resizeElement).forEach(action => actions.push(action)); + protected handleResizeOnServer(activeResizeHandle: SResizeHandle): Action[] { + if (this.isValidElement(activeResizeHandle.parent) && this.initialBounds) { + const elementAndBounds = toElementAndBounds(activeResizeHandle.parent); + if (!this.initialBounds.newPosition || !elementAndBounds.newPosition) { + return []; + } + if ( + !Point.equals(this.initialBounds.newPosition, elementAndBounds.newPosition) || + !Dimension.equals(this.initialBounds.newSize, elementAndBounds.newSize) + ) { + // UX: we do not want the element positions to be reset to their start as they will be moved to their start and + // only afterwards moved by the move action again, leading to a ping-pong movement. + // We therefore clear our element map so that they cannot be reset. + this.initialBounds = undefined; + return [ChangeBoundsOperation.create([elementAndBounds])]; + } } - return actions; + return []; } selectionChanged(root: GModelRoot, selectedElements: string[]): void { - if (this.activeResizeElement) { - if (selectedElements.includes(this.activeResizeElement.id)) { - // our active element is still selected, nothing to do - return; - } - - // try to find some other selected element and mark that active - for (const elementId of selectedElements.reverse()) { - const element = root.index.getById(elementId); - if (element && this.setActiveResizeElement(element)) { - return; - } - } - this.reset(); + if (this.activeResizeElement && selectedElements.includes(this.activeResizeElement.id)) { + // our active element is still selected, nothing to do + return; } - } - protected setActiveResizeElement(target: GModelElement): boolean { - // check if we have a selected, moveable element (multi-selection allowed) - const moveableElement = findParentByFeature(target, isBoundsAwareMoveable); - if (isSelected(moveableElement)) { - // only allow one element to have the element resize handles - this.activeResizeElement = moveableElement; - if (isResizable(this.activeResizeElement)) { - this.tool.registerFeedback( - [ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id)], - this.activeResizeElement - ); + // try to find some other selected element and mark that active + for (const elementId of selectedElements.reverse()) { + const element = root.index.getById(elementId); + if (element && this.updateResizeElement(element)) { + return; } - return true; } - return false; + this.updateResizeElement(root); + this.disposeAllButHandles(); } protected isActiveResizeElement(element?: GModelElement): element is GParentElement & BoundsAware { return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; } - protected initPosition(event: MouseEvent): void { - this.pointPositionUpdater.updateLastDragPosition(event); - if (this.activeResizeHandle) { - const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); - this.initialBounds = { - x: resizeElement!.bounds.x, - y: resizeElement!.bounds.y, - width: resizeElement!.bounds.width, - height: resizeElement!.bounds.height - }; - } - } - - dispose(): void { - this.reset(true); - } - - protected reset(resetBounds = false): void { - this.resetFeedback(resetBounds); - this.resetPosition(); - } - - protected resetFeedback(resetBounds = false): void { - const resetFeedback: Action[] = []; - if (this.activeResizeElement && isResizable(this.activeResizeElement)) { - if (this.initialBounds && this.activeResizeHandle && resetBounds) { - // we only reset the bounds if an active resize operation was cancelled due to the tool being disabled - resetFeedback.push( - SetBoundsFeedbackAction.create([ - { - elementId: this.activeResizeElement.id, - newPosition: this.initialBounds, - newSize: this.initialBounds - } - ]) - ); - } - this.tool.deregisterFeedback(this.activeResizeElement, [HideChangeBoundsToolResizeFeedbackAction.create()]); - } - resetFeedback.push(cursorFeedbackAction(CursorCSS.DEFAULT)); - this.tool.deregisterFeedback(this, resetFeedback); - } - - protected resetPosition(): void { - this.activeResizeHandle = undefined; - this.pointPositionUpdater.resetPosition(); - } - - protected handleResizeOnClient(positionUpdate: Point): Action[] { + protected handleResizeOnClient(positionUpdate: Point): ValidatedElementAndBounds[] { if (!this.activeResizeHandle) { return []; } - const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); - if (this.isActiveResizeElement(resizeElement)) { + if (this.isActiveResizeElement(this.activeResizeHandle.parent)) { switch (this.activeResizeHandle.location) { case ResizeHandleLocation.TopLeft: - return this.handleTopLeftResize(resizeElement, positionUpdate); + return this.handleTopLeftResize(this.activeResizeHandle.parent, positionUpdate); case ResizeHandleLocation.TopRight: - return this.handleTopRightResize(resizeElement, positionUpdate); + return this.handleTopRightResize(this.activeResizeHandle.parent, positionUpdate); case ResizeHandleLocation.BottomLeft: - return this.handleBottomLeftResize(resizeElement, positionUpdate); + return this.handleBottomLeftResize(this.activeResizeHandle.parent, positionUpdate); case ResizeHandleLocation.BottomRight: - return this.handleBottomRightResize(resizeElement, positionUpdate); + return this.handleBottomRightResize(this.activeResizeHandle.parent, positionUpdate); } } return []; } - protected handleTopLeftResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleTopLeftResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { return this.createSetBoundsAction( resizeElement, - resizeElement.bounds.x + positionUpdate.x, - resizeElement.bounds.y + positionUpdate.y, - resizeElement.bounds.width - positionUpdate.x, - resizeElement.bounds.height - positionUpdate.y + { x: resizeElement.bounds.x + positionUpdate.x, y: resizeElement.bounds.y + positionUpdate.y }, + { width: resizeElement.bounds.width - positionUpdate.x, height: resizeElement.bounds.height - positionUpdate.y } ); } - protected handleTopRightResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleTopRightResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { return this.createSetBoundsAction( resizeElement, - resizeElement.bounds.x, - resizeElement.bounds.y + positionUpdate.y, - resizeElement.bounds.width + positionUpdate.x, - resizeElement.bounds.height - positionUpdate.y + { x: resizeElement.bounds.x, y: resizeElement.bounds.y + positionUpdate.y }, + { width: resizeElement.bounds.width + positionUpdate.x, height: resizeElement.bounds.height - positionUpdate.y } ); } - protected handleBottomLeftResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleBottomLeftResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { return this.createSetBoundsAction( resizeElement, - resizeElement.bounds.x + positionUpdate.x, - resizeElement.bounds.y, - resizeElement.bounds.width - positionUpdate.x, - resizeElement.bounds.height + positionUpdate.y + { x: resizeElement.bounds.x + positionUpdate.x, y: resizeElement.bounds.y }, + { width: resizeElement.bounds.width - positionUpdate.x, height: resizeElement.bounds.height + positionUpdate.y } ); } - protected handleBottomRightResize(resizeElement: GParentElement & Resizable, positionUpdate: Point): Action[] { + protected handleBottomRightResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { return this.createSetBoundsAction( resizeElement, - resizeElement.bounds.x, - resizeElement.bounds.y, - resizeElement.bounds.width + positionUpdate.x, - resizeElement.bounds.height + positionUpdate.y + { x: resizeElement.bounds.x, y: resizeElement.bounds.y }, + { width: resizeElement.bounds.width + positionUpdate.x, height: resizeElement.bounds.height + positionUpdate.y } ); } - protected createChangeBoundsAction(element: GModelElement & BoundsAware): Action[] { - if (this.isValidBoundChange(element, element.bounds, element.bounds)) { - return [ChangeBoundsOperation.create([toElementAndBounds(element)])]; - } else if (this.initialBounds) { - const actions: Action[] = []; - if (this.tool.movementRestrictor) { - actions.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); - } - actions.push( - SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }]) - ); - return actions; - } - return []; - } - - protected createElementAndBounds(element: GModelElement & BoundsAware): ElementAndBounds[] { - if (this.isValidBoundChange(element, element.bounds, element.bounds)) { - return [toElementAndBounds(element)]; + protected createSetBoundsAction(element: BoundsAwareModelElement, newPosition: Point, newSize: Dimension): ValidatedElementAndBounds[] { + if (!isValidSize(element, newSize)) { + // we are not allowing any invalid sizes (breaking min size), not even during client feedback + return []; } - return []; + const valid = this.isValidMove(element, newPosition); + return [{ elementId: element.id, newPosition, newSize, valid }]; } - protected createSetBoundsAction(element: GModelElement & BoundsAware, x: number, y: number, width: number, height: number): Action[] { - const newPosition = { x, y }; - const newSize = { width, height }; - const result: Action[] = []; - - if (this.isValidBoundChange(element, newPosition, newSize)) { - if (this.tool.movementRestrictor) { - result.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); - } - result.push(SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition, newSize }])); - } else if (this.isValidSize(element, newSize)) { - if (this.tool.movementRestrictor) { - result.push(createMovementRestrictionFeedback(element, this.tool.movementRestrictor)); - } - result.push(SetBoundsFeedbackAction.create([{ elementId: element.id, newPosition, newSize }])); - } - - return result; + protected isValidSize(element: BoundsAwareModelElement, size: Dimension): boolean { + return isValidSize(element, size); } - protected isValidBoundChange(element: GModelElement & BoundsAware, newPosition: Point, newSize: Dimension): boolean { - return this.isValidSize(element, newSize) && this.isValidMove(element, newPosition); + protected isValidMove(element: BoundsAwareModelElement, newPosition: Point): boolean { + return isValidMove(element, newPosition, this.tool.movementRestrictor); } - protected isValidSize(element: GModelElement & BoundsAware, size: Dimension): boolean { - return isValidSize(element, size); + protected disposeAllButHandles(): void { + // We do not dispose the handle feedback as we want to keep showing the handles on selected elements + // this.handleFeedback.dispose(); + this.resizeFeedback.dispose(); + this.positionUpdater.resetPosition(); + this.activeResizeElement = undefined; + this.activeResizeHandle = undefined; + this.initialBounds = undefined; + super.dispose(); } - protected isValidMove(element: GModelElement & BoundsAware, newPosition: Point): boolean { - return isValidMove(element, newPosition, this.tool.movementRestrictor); + override dispose(): void { + this.handleFeedback.dispose(); + this.disposeAllButHandles(); } } diff --git a/packages/client/src/features/tools/change-bounds/index.ts b/packages/client/src/features/tools/change-bounds/index.ts index 434437f..d990255 100644 --- a/packages/client/src/features/tools/change-bounds/index.ts +++ b/packages/client/src/features/tools/change-bounds/index.ts @@ -16,4 +16,5 @@ export * from './change-bounds-tool'; export * from './change-bounds-tool-feedback'; export * from './change-bounds-tool-module'; +export * from './change-bounds-tool-move-feedback'; export * from './view'; diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index f0a4243..641b3a4 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -23,7 +23,10 @@ const JSX = { createElement: svg }; @injectable() export class SResizeHandleView implements IView { - render(handle: SResizeHandle, context: RenderingContext): VNode { + render(handle: SResizeHandle, context: RenderingContext): VNode | undefined { + if (context.targetKind === 'hidden') { + return undefined; + } const position = this.getPosition(handle); if (position !== undefined) { const node = ( diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 087b144..0a58676 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -13,20 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, DeleteElementOperation, + GModelElement, KeyListener, KeyTool, MouseListener, - GModelElement, findParentByFeature, isCtrlOrCmd, isDeletable, isSelectable, matchesKeystroke } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction, Tool } from '../../../base/tool-manager/tool'; import { BaseEditTool } from '../base-tools'; @@ -89,10 +89,10 @@ export class MouseDeleteTool extends BaseEditTool { } enable(): void { - this.toDisposeOnDisable.push( - this.mouseTool.registerListener(this.deleteToolMouseListener), - this.registerFeedback([cursorFeedbackAction(CursorCSS.ELEMENT_DELETION)], this, [cursorFeedbackAction()]) - ); + const cursorFeedback = this.createFeedbackEmitter() + .add(cursorFeedbackAction(CursorCSS.ELEMENT_DELETION), cursorFeedbackAction()) + .submit(); + this.toDisposeOnDisable.push(this.mouseTool.registerListener(this.deleteToolMouseListener), cursorFeedback); } } diff --git a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts index 6402beb..0a909bf 100644 --- a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts @@ -13,24 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, BindingContext, CommandExecutionContext, CommandReturn, GChildElement, + GDanglingAnchor, + GEdgeSchema, GModelRoot, GRoutableElement, TYPES, configureCommand, configureView, + deletableFeature, findParentByFeature, isBoundsAware, - isConnectable, - GDanglingAnchor, - GEdgeSchema + isConnectable } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { feedbackFeature } from '../../../base'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { isRoutable } from '../../../utils/gmodel-util'; import { toAbsolutePosition } from '../../../utils/viewpoint-util'; @@ -103,6 +105,7 @@ export class RemoveFeedbackEdgeCommand extends FeedbackCommand { } export class FeedbackEdgeEnd extends GDanglingAnchor { + static override readonly DEFAULT_FEATURES = [deletableFeature, feedbackFeature]; static readonly TYPE = 'feedback-edge-end'; constructor( readonly sourceId: string, diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts index ed8c6c7..0a57ed8 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts @@ -18,27 +18,31 @@ import { AnchorComputerRegistry, Bounds, Disposable, + GConnectableElement, + GModelElement, MouseListener, MoveAction, Point, PolylineEdgeRouter, - GConnectableElement, - GModelElement, findChildrenAtPosition, findParentByFeature, isBoundsAware, isConnectable } from '@eclipse-glsp/sprotty'; +import { FeedbackEmitter } from '../../../base'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { absoluteToParent, getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { FeedbackEdgeEnd, feedbackEdgeEndId } from './dangling-edge-feedback'; export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements Disposable { + protected feedback: FeedbackEmitter; + constructor( protected anchorRegistry: AnchorComputerRegistry, protected feedbackDispatcher: IFeedbackActionDispatcher ) { super(); + this.feedback = feedbackDispatcher.createEmitter(); } override mouseMove(target: GModelElement, event: MouseEvent): Action[] { @@ -57,14 +61,10 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements if (endAtMousePosition instanceof GConnectableElement && edge.source && isBoundsAware(edge.source)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(toAbsoluteBounds(edge.source))); if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { - this.feedbackDispatcher.registerFeedback(this, [ - MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false }) - ]); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })).submit(); } } else { - this.feedbackDispatcher.registerFeedback(this, [ - MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false }) - ]); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })).submit(); } return []; @@ -87,6 +87,6 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener implements } dispose(): void { - this.feedbackDispatcher.deregisterFeedback(this); + this.feedback.dispose(); } } diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 0c4420b..2c91414 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -13,28 +13,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, AnchorComputerRegistry, CreateEdgeOperation, - RequestCheckEdgeAction, GModelElement, + RequestCheckEdgeAction, TYPES, TriggerEdgeCreationAction, findParentByFeature, isConnectable, isCtrlOrCmd } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { FeedbackEmitter } from '../../../base'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { GEdge } from '../../../model'; import { ITypeHintProvider } from '../../hints/type-hint-provider'; import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from './edge-creation-tool-feedback'; -import { GEdge } from '../../../model'; /** * Tool to create connections in a Diagram, by selecting a source and target node. @@ -55,16 +56,16 @@ export class EdgeCreationTool extends BaseCreationTool 1) { - this.feedbackDispatcher.registerFeedback(this, [ - MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false }) - ]); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })).submit(); } } else { - this.feedbackDispatcher.registerFeedback(this, [ - MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false }) - ]); + this.feedback.add(MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })).submit(); } return []; @@ -250,7 +250,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme } dispose(): void { - this.feedbackDispatcher.deregisterFeedback(this); + this.feedback.dispose(); } } diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index 018122d..566227b 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -18,7 +18,6 @@ import { AnchorComputerRegistry, ChangeRoutingPointsOperation, Connectable, - Disposable, EdgeRouterRegistry, GModelElement, GModelRoot, @@ -31,6 +30,7 @@ import { isSelected } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; +import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; @@ -76,7 +76,7 @@ export class EdgeEditTool extends BaseEditTool { this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.positionSnapper, this.edgeRouterRegistry); this.toDisposeOnDisable.push( - Disposable.create(() => this.edgeEditListener.reset()), + this.edgeEditListener, this.mouseTool.registerListener(this.edgeEditListener), this.feedbackEdgeSourceMovingListener, this.feedbackEdgeTargetMovingListener, @@ -110,8 +110,11 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti // active reconnect handle data protected reconnectMode?: 'NEW_SOURCE' | 'NEW_TARGET'; + protected editFeedback: FeedbackEmitter; + constructor(protected tool: EdgeEditTool) { super(); + this.editFeedback = this.tool.createFeedbackEmitter(); } protected isValidEdge(edge?: GRoutableElement): edge is GRoutableElement { @@ -119,21 +122,21 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti } protected setEdgeSelected(edge: GRoutableElement): void { - if (this.edge && this.edge.id !== edge.id) { - // reset from a previously selected edge - this.reset(); - } - this.edge = edge; // note: order is important here as we want the reconnect handles to cover the routing handles - const feedbackActions = []; if (canEditRouting(edge)) { - feedbackActions.push(SwitchRoutingModeAction.create({ elementsToActivate: [this.edge.id] })); + this.editFeedback.add( + SwitchRoutingModeAction.create({ elementsToActivate: [this.edge.id] }), + SwitchRoutingModeAction.create({ elementsToDeactivate: [this.edge.id] }) + ); } if (isReconnectable(edge)) { - feedbackActions.push(ShowEdgeReconnectHandlesFeedbackAction.create(this.edge.id)); + this.editFeedback.add( + ShowEdgeReconnectHandlesFeedbackAction.create(this.edge.id), + HideEdgeReconnectHandlesFeedbackAction.create() + ); } - this.tool.registerFeedback(feedbackActions); + this.editFeedback.submit(); } protected isEdgeSelected(): boolean { @@ -142,19 +145,24 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti protected setReconnectHandleSelected(edge: GRoutableElement, reconnectHandle: GReconnectHandle): void { if (this.edge && this.edge.target && this.edge.source) { + this.editFeedback.dispose(); if (isSourceRoutingHandle(edge, reconnectHandle)) { - this.tool.registerFeedback([ - HideEdgeReconnectHandlesFeedbackAction.create(), - cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), - DrawFeedbackEdgeSourceAction.create({ elementTypeId: this.edge.type, targetId: this.edge.targetId }) - ]); + this.editFeedback + .add(cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), cursorFeedbackAction()) + .add( + DrawFeedbackEdgeSourceAction.create({ elementTypeId: this.edge.type, targetId: this.edge.targetId }), + RemoveFeedbackEdgeAction.create() + ) + .submit(); this.reconnectMode = 'NEW_SOURCE'; } else if (isTargetRoutingHandle(edge, reconnectHandle)) { - this.tool.registerFeedback([ - HideEdgeReconnectHandlesFeedbackAction.create(), - cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), - DrawFeedbackEdgeAction.create({ elementTypeId: this.edge.type, sourceId: this.edge.sourceId }) - ]); + this.editFeedback + .add(cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), cursorFeedbackAction()) + .add( + DrawFeedbackEdgeAction.create({ elementTypeId: this.edge.type, sourceId: this.edge.sourceId }), + RemoveFeedbackEdgeAction.create() + ) + .submit(); this.reconnectMode = 'NEW_TARGET'; } } @@ -204,22 +212,20 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti this.setRoutingHandleSelected(edge, routingHandle); } else if (this.isValidEdge(edge)) { // PHASE 1: Select edge + this.dispose(); this.tool.registerFeedbackListeners(); this.setEdgeSelected(edge); } } else if (event.button === 2) { - this.reset(); + this.dispose(); } return result; } - override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - const result = super.mouseMove(target, event); - if (this.isMouseDrag) { - // reset any selected connectables when we are dragging, maybe the user is just panning - this.setNewConnectable(undefined); - } - return result; + protected override draggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { + // reset any selected connectables when we are dragging, maybe the user is just panning + this.setNewConnectable(undefined); + return super.draggingMouseMove(target, event); } override mouseUp(target: GModelElement, event: MouseEvent): Action[] { @@ -234,7 +240,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti if (this.requiresReconnect(sourceElementId, targetElementId)) { result.push(ReconnectEdgeOperation.create({ edgeElementId: this.edge.id, sourceElementId, targetElementId })); } - this.reset(); + this.dispose(); } else if (this.edge && this.routingHandle) { // we need to re-retrieve the edge as it might have changed due to a server update since we do not reset the state between // reroute actions @@ -258,11 +264,11 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti (this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target')) ) { - this.tool.registerFeedback([cursorFeedbackAction(CursorCSS.EDGE_RECONNECT)]); + this.editFeedback.add(cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), cursorFeedbackAction()).submit(); return []; } } - this.tool.registerFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)]); + this.editFeedback.add(cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED), cursorFeedbackAction()).submit(); } } return []; @@ -292,30 +298,17 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti } } } - - this.reset(); + this.dispose(); } } - public reset(): void { - this.resetFeedback(); - this.resetData(); - } - - protected resetData(): void { + override dispose(): void { this.edge = undefined; this.reconnectMode = undefined; this.newConnectable = undefined; this.routingHandle = undefined; - } - - protected resetFeedback(): void { - const result: Action[] = []; - if (this.edge) { - result.push(SwitchRoutingModeAction.create({ elementsToDeactivate: [this.edge.id] })); - } - result.push(...[HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(), RemoveFeedbackEdgeAction.create()]); - this.tool.deregisterFeedback(undefined, result); + this.editFeedback.dispose(); this.tool.deregisterFeedbackListeners(); + super.dispose(); } } diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 68a60cf..2f7f0a6 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { Action, - BoundsAware, DOMHelper, GModelElement, GModelRoot, @@ -30,11 +29,12 @@ import { inject, injectable, optional } from 'inversify'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { GEdge } from '../../../model'; +import { BoundsAwareModelElement, SelectableBoundsAware, isSelectableAndBoundsAware } from '../../../utils'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { BaseEditTool } from '../base-tools'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; import { RemoveMarqueeAction } from './marquee-tool-feedback'; -import { GEdge } from '../../../model'; @injectable() export class MarqueeMouseTool extends BaseEditTool { @@ -53,7 +53,7 @@ export class MarqueeMouseTool extends BaseEditTool { this.toDisposeOnDisable.push( this.mouseTool.registerListener(new MarqueeMouseListener(this.domHelper, this.editorContext.modelRoot, this.marqueeBehavior)), this.keyTool.registerListener(this.shiftKeyListener), - this.registerFeedback([cursorFeedbackAction(CursorCSS.MARQUEE)], this, [cursorFeedbackAction()]) + this.createFeedbackEmitter().add(cursorFeedbackAction(CursorCSS.MARQUEE), cursorFeedbackAction()).submit() ); } } @@ -61,7 +61,7 @@ export class MarqueeMouseTool extends BaseEditTool { export class MarqueeMouseListener extends DragAwareMouseListener { protected domHelper: DOMHelper; protected marqueeUtil: MarqueeUtil; - protected nodes: (GModelElement & BoundsAware)[]; + protected nodes: BoundsAwareModelElement[]; protected edges: SVGGElement[]; protected previouslySelected: string[]; protected isActive = false; @@ -73,7 +73,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { this.nodes = Array.from( root.index .all() - .map(e => e as GModelElement & BoundsAware) + .map(e => e as BoundsAwareModelElement) .filter(e => isSelectable(e)) .filter(e => e instanceof GNode) ); @@ -94,7 +94,8 @@ export class MarqueeMouseListener extends DragAwareMouseListener { this.previouslySelected = Array.from( target.root.index .all() - .map(e => e as GModelElement & BoundsAware) + .filter(isSelectableAndBoundsAware) + .map(e => e as SelectableBoundsAware) .filter(e => isSelected(e)) .map(e => e.id) ); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index bd8a8ed..595166b 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -18,20 +18,25 @@ import { CreateNodeOperation, GModelElement, GNode, + GhostElement, Point, TYPES, TriggerNodeCreationAction, findParentByFeature, - isCtrlOrCmd + isCtrlOrCmd, + isMoveable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import '../../../../css/ghost-element.css'; +import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; +import { MoveableElement, isValidMove } from '../../../utils'; import { getAbsolutePosition } from '../../../utils/viewpoint-util'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { PositionSnapper } from '../../change-bounds/position-snapper'; +import { RemoveTemplateElementsAction } from '../../element-template'; import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; import { Containable, isContainable } from '../../hints/model'; @@ -53,34 +58,39 @@ export class NodeCreationTool extends BaseCreationTool, - current?: GModelElement & BoundsAware, + current?: BoundsAwareModelElement, predicate: (marker: GIssueMarker) => boolean = MarkerPredicates.ALL ): GIssueMarker | undefined { const markers = this.getMarkers(root, predicate); @@ -122,7 +120,7 @@ export class MarkerNavigator { previous( root: Readonly, - current?: GModelElement & BoundsAware, + current?: BoundsAwareModelElement, predicate: (marker: GIssueMarker) => boolean = MarkerPredicates.ALL ): GIssueMarker | undefined { const markers = this.getMarkers(root, predicate); @@ -137,7 +135,7 @@ export class MarkerNavigator { return markers.filter(predicate).sort(this.markerComparator.compare); } - protected getNextIndex(current: GModelElement & BoundsAware, markers: GIssueMarker[]): number { + protected getNextIndex(current: BoundsAwareModelElement, markers: GIssueMarker[]): number { for (let index = 0; index < markers.length; index++) { if (this.markerComparator.compare(markers[index], current) > 0) { return index; @@ -146,7 +144,7 @@ export class MarkerNavigator { return 0; } - protected getPreviousIndex(current: GModelElement & BoundsAware, markers: GIssueMarker[]): number { + protected getPreviousIndex(current: BoundsAwareModelElement, markers: GIssueMarker[]): number { for (let index = markers.length - 1; index >= 0; index--) { if (this.markerComparator.compare(markers[index], current) < 0) { return index; @@ -182,7 +180,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { } } - protected getSelectedElements(action: NavigateToMarkerAction): (GModelElement & Selectable)[] { + protected getSelectedElements(action: NavigateToMarkerAction): SelectableElement[] { if (action.selectedElementIds && action.selectedElementIds.length > 0) { return getElements(this.selectionService.getModelRoot().index, action.selectedElementIds, isSelectable); } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 612ef89..0f9815b 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -28,6 +28,7 @@ import { hasArrayProp } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; +import { FeedbackEmitter } from '../../base'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; @@ -42,7 +43,7 @@ import { GIssueMarker, createGIssue, getGIssueMarker, getOrCreateGIssueMarker, g export class ValidationFeedbackEmitter implements IFeedbackEmitter { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; - protected registeredFeedbackByReason: Map = new Map(); + protected registeredFeedbackByReason: Map = new Map(); /** * Register the action that should be emitted for visualizing validation feedback. @@ -51,25 +52,9 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { */ registerValidationFeedbackAction(action: ApplyMarkersAction, reason = ''): void { // De-register feedback and clear existing markers with the same reason - const previousFeedbackWithSameReason = this.registeredFeedbackByReason.get(reason); - let emitter: IFeedbackEmitter | undefined; - if (previousFeedbackWithSameReason) { - emitter = previousFeedbackWithSameReason.emitter; - const deleteMarkersAction = DeleteMarkersAction.create(previousFeedbackWithSameReason.action.markers); - this.feedbackActionDispatcher.deregisterFeedback(emitter, [deleteMarkersAction]); - } - - if (!emitter) { - emitter = this.createEmitter(reason); - } - - // Register new action responsible for applying markers and re-applying them when the model is updated - this.registeredFeedbackByReason.set(reason, { action, emitter }); - this.feedbackActionDispatcher.registerFeedback(emitter, [action]); - } - - protected createEmitter(reason: string): IFeedbackEmitter { - return { id: 'validationFeedbackEmitter_' + reason }; + const emitter = this.registeredFeedbackByReason.get(reason)?.dispose() ?? this.feedbackActionDispatcher.createEmitter(); + emitter.add(action, () => DeleteMarkersAction.create(action.markers)).submit(); + this.registeredFeedbackByReason.set(reason, emitter); } } diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index f690f3f..0555c5e 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -25,6 +25,7 @@ import { GParentElement, GRoutableElement, GRoutingHandle, + Locateable, ModelIndexImpl, Point, RoutedPoint, @@ -194,17 +195,30 @@ export function isRoutingHandle(element: GModelElement | undefined): element is export function isSelectableAndBoundsAware(element: GModelElement): element is SelectableBoundsAware { return isSelectable(element) && isBoundsAware(element); } +/** + * Union type to describe {@link GModelElement}s that implement the {@link Selectable} feature. + */ +export type SelectableElement = GModelElement & Selectable; /** * Union type to describe {@link GModelElement}s that implement the {@link Selectable} and {@link BoundsAware} feature. */ -export type SelectableBoundsAware = GModelElement & BoundsAware & Selectable; +export type SelectableBoundsAware = SelectableElement & BoundsAware; /** * Union type to describe {@link GModelElement}s that implement the {@link BoundsAware} feature. */ export type BoundsAwareModelElement = GModelElement & BoundsAware; +/** + * Union type to describe {@link GModelElement}s that implement the {@link Locateable} feature. + */ +export type MoveableElement = GModelElement & Locateable; + +export interface Resizable extends BoundsAware, Selectable {} + +export interface ResizableModelElement extends GParentElement, Resizable {} + /** * Helper function to translate a given {@link GModelElement} into its corresponding {@link ElementAndBounds} representation. * @param element The element to translate. @@ -373,3 +387,32 @@ export function getDescendantIds(element?: GModelElement, skip?: (t: GModelEleme } return ids; } + +/** + * Returns a filter function that checks if the given element is not a descendant of any of the given elements. + * + * @param elements The elements that the element should not be a descendant of. + * @returns the filter function + */ +export function isNotDescendantOfAnyElement(elements: FluentIterable): (element: T) => boolean { + const elementsSet = new Set(elements); + return (element: T): boolean => { + let parent: GModelElement = element; + while (parent instanceof GChildElement) { + parent = parent.parent; + if (elementsSet.has(parent)) { + return false; + } + } + return true; + }; +} + +/** + * Removes any descendants of the given elements from the given elements. + * @param elements The elements to filter. + * @returns the filtered elements + */ +export function removeDescendants(elements: FluentIterable): FluentIterable { + return elements.filter(isNotDescendantOfAnyElement(elements)); +} diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index b369d08..83670d3 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -13,19 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - BoundsAware, - Dimension, - ElementAndBounds, - ElementMove, - ModelLayoutOptions, - Point, - GModelElement, - Writable -} from '@eclipse-glsp/sprotty'; +import { Dimension, ElementAndBounds, ElementMove, GModelElement, ModelLayoutOptions, Point, Writable } from '@eclipse-glsp/sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; +import { BoundsAwareModelElement } from './gmodel-util'; -export function minWidth(element: GModelElement & BoundsAware): number { +export function minWidth(element: BoundsAwareModelElement): number { const layoutOptions = getLayoutOptions(element); if (layoutOptions !== undefined && typeof layoutOptions.minWidth === 'number') { return layoutOptions.minWidth; @@ -33,7 +25,7 @@ export function minWidth(element: GModelElement & BoundsAware): number { return 1; } -export function minHeight(element: GModelElement & BoundsAware): number { +export function minHeight(element: BoundsAwareModelElement): number { const layoutOptions = getLayoutOptions(element); if (layoutOptions !== undefined && typeof layoutOptions.minHeight === 'number') { return layoutOptions.minHeight; @@ -49,19 +41,16 @@ export function getLayoutOptions(element: GModelElement): ModelLayoutOptions | u return undefined; } -export function isValidSize(element: GModelElement & BoundsAware, size: Dimension): boolean { +export function isValidSize(element: BoundsAwareModelElement, size: Dimension): boolean { return size.width >= minWidth(element) && size.height >= minHeight(element); } -export function isValidMove(element: GModelElement & BoundsAware, newPosition?: Point, movementRestrictor?: IMovementRestrictor): boolean { - if (movementRestrictor) { - return movementRestrictor.validate(element, newPosition); - } - return true; +export function isValidMove(element: GModelElement, newPosition?: Point, movementRestrictor?: IMovementRestrictor): boolean { + return movementRestrictor?.validate(element, newPosition) ?? true; } export function toValidElementMove( - element: GModelElement & BoundsAware, + element: BoundsAwareModelElement, move: ElementMove, movementRestrictor?: IMovementRestrictor ): ElementMove | undefined { @@ -72,7 +61,7 @@ export function toValidElementMove( } export function toValidElementAndBounds( - element: GModelElement & BoundsAware, + element: BoundsAwareModelElement, bounds: Writable, movementRestrictor?: IMovementRestrictor ): ElementAndBounds | undefined { diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index acb7b7b..bcdb5b3 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -16,7 +16,6 @@ import { Bounds, - BoundsAware, Dimension, GChildElement, GModelElement, @@ -74,7 +73,7 @@ export function getViewportBounds(target: GModelElement, bounds: Bounds): Bounds * * @param target A bounds-aware element from the diagram */ -export function toAbsoluteBounds(element: GModelElement & BoundsAware): Bounds { +export function toAbsoluteBounds(element: BoundsAwareModelElement): Bounds { const location = isAlignable(element) ? element.alignment : Point.ORIGIN; const x = location.x; const y = location.y; @@ -89,7 +88,7 @@ export function toAbsoluteBounds(element: GModelElement & BoundsAware): Bounds { * * @param target A bounds-aware element from the diagram */ -export function toAbsolutePosition(target: GModelElement & BoundsAware): Point { +export function toAbsolutePosition(target: BoundsAwareModelElement): Point { return toAbsoluteBounds(target); } @@ -99,7 +98,7 @@ export function toAbsolutePosition(target: GModelElement & BoundsAware): Point { * * @param target A bounds-aware element from the diagram */ -export function toAbsoluteSize(target: GModelElement & BoundsAware): Dimension { +export function toAbsoluteSize(target: BoundsAwareModelElement): Dimension { return toAbsoluteBounds(target); } diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 0d6f826..7e8d23e 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -193,3 +193,12 @@ export function partition(source: T[], matchGuard: TypeGuard): { match: T[ }); return { match, rest }; } + +/** + * Helper function to create an array of values without any undefined values. + * @param values The values to create the array from. + * @returns The array of values without any undefined values. + */ +export function arrayOf(...values: (T | undefined)[]): T[] { + return values.filter(element => element !== undefined) as T[]; +} diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 4ee5286..4f2bafb 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -96,4 +96,11 @@ export class DisposableCollection implements Disposable { get isDisposed(): boolean { return this.disposed; } + + /** + * Removes all disposables in this collection WITHOUT triggering their disposal behavior. + */ + clear(): void { + this.disposables.length = 0; + } } diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index f3d8da5..e979ae7 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -75,6 +75,12 @@ export function toTypeGuard(constructor: Constructor): TypeGuard { */ export type SafeFunction = (...args: any[]) => T; +export type MaybeFunction = T | SafeFunction; + +export function call(maybeFun: MaybeFunction, ...args: any[]): T { + return typeof maybeFun === 'function' ? (maybeFun as SafeFunction)(...args) : maybeFun; +} + /** * Validates whether the given object has a property of type `string` with the given key. * @param object The object that should be validated From 09bb466fc6fb5eb65d9d9a58acdef81349abca3e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 14 May 2024 18:29:18 +0200 Subject: [PATCH 327/566] GLSP-1320: Update to latest glsp dev config (#334) (#80) - Updates to latest version of @eclipse-glsp/dev - Add generate:index utility script - Regenerate index files - Fix codeActionsOnSave in vscode settings - Refactor upgrade:next script - Reformat code base with prettier - Fix copyright headers (by running a full check with glsp checkHeaders . -t full) Part of https://github.com/eclipse-glsp/glsp/issues/1320 --- examples/workflow-server/package.json | 1 + .../workflow-server/src/browser/.indexignore | 4 ++ .../workflow-server/src/browser/example1.json | 66 +++++-------------- examples/workflow-server/src/browser/index.ts | 4 +- .../workflow-server/src/browser/reexport.ts | 16 +++++ .../handler/create-activity-node-handler.ts | 7 +- examples/workflow-server/src/common/index.ts | 4 +- .../workflow-server/src/node/.indexignore | 4 ++ examples/workflow-server/src/node/index.ts | 4 +- examples/workflow-server/src/node/reexport.ts | 16 +++++ packages/graph/package.json | 1 + packages/graph/src/default-types.ts | 2 +- packages/graph/src/galignable.ts | 2 +- packages/graph/src/gbounds-aware.ts | 2 +- packages/graph/src/ghtml-root.ts | 2 +- packages/graph/src/gmodel-element.ts | 2 +- packages/graph/src/index.ts | 2 +- packages/layout-elk/package.json | 1 + packages/layout-elk/src/di.config.ts | 2 +- packages/layout-elk/src/index.ts | 4 +- packages/layout-elk/src/reexport.ts | 16 +++++ packages/server/package.json | 1 + packages/server/src/browser/index.ts | 4 +- .../browser/launch/worker-server-launcher.ts | 9 ++- packages/server/src/browser/reexport.ts | 16 +++++ packages/server/src/common/.indexignore | 2 + .../common/command/recording-command.spec.ts | 7 +- .../src/common/command/recording-command.ts | 8 ++- .../src/common/di/client-session-module.ts | 5 +- packages/server/src/common/di/glsp-module.ts | 4 +- .../server/src/common/di/multi-binding.ts | 5 +- .../directediting/context-edit-validator.ts | 7 +- .../model/model-submission-handler.ts | 2 +- .../navigation-target-resolution.ts | 7 +- .../gmodel-create-node-operation-handler.ts | 15 +++-- packages/server/src/common/index.ts | 21 ++++-- .../common/{gmodel/index.ts => reexport.ts} | 16 ++--- packages/server/src/common/test/mock-util.ts | 5 +- .../server/src/common/utils/console-logger.ts | 9 ++- .../src/common/utils/glsp-server-error.ts | 7 +- packages/server/src/node/di/app-module.ts | 4 +- packages/server/src/node/di/winston-logger.ts | 7 +- packages/server/src/node/index.ts | 4 +- .../src/node/launch/socket-server-launcher.ts | 4 +- packages/server/src/node/reexport.ts | 16 +++++ 45 files changed, 218 insertions(+), 129 deletions(-) create mode 100644 examples/workflow-server/src/browser/.indexignore create mode 100644 examples/workflow-server/src/browser/reexport.ts create mode 100644 examples/workflow-server/src/node/.indexignore create mode 100644 examples/workflow-server/src/node/reexport.ts create mode 100644 packages/layout-elk/src/reexport.ts create mode 100644 packages/server/src/browser/reexport.ts create mode 100644 packages/server/src/common/.indexignore rename packages/server/src/common/{gmodel/index.ts => reexport.ts} (56%) create mode 100644 packages/server/src/node/reexport.ts diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index a169e47..44c2389 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -50,6 +50,7 @@ "bundle": "webpack", "bundle:browser": "webpack --env target=webworker ", "clean": "rimraf lib *.tsbuildinfo", + "generate:index": "glsp generateIndex src/browser src/common src/node -s -f", "lint": "eslint --ext .ts,.tsx ./src", "watch": "tsc -w", "watch:bundle": "webpack -w" diff --git a/examples/workflow-server/src/browser/.indexignore b/examples/workflow-server/src/browser/.indexignore new file mode 100644 index 0000000..c998fd9 --- /dev/null +++ b/examples/workflow-server/src/browser/.indexignore @@ -0,0 +1,4 @@ +# The app.ts is the entrypoint when the workflow server is used bundled in an external node process +# It launches the server when imported so it has to be excluded from the default export to avoid unintended +# autostarts when the server package is directly integrated into a node backed (e.g. Theia) +app.ts \ No newline at end of file diff --git a/examples/workflow-server/src/browser/example1.json b/examples/workflow-server/src/browser/example1.json index 1405953..fe53378 100644 --- a/examples/workflow-server/src/browser/example1.json +++ b/examples/workflow-server/src/browser/example1.json @@ -5,10 +5,7 @@ "name": "Push", "taskType": "manual", "id": "task0", - "cssClasses": [ - "task", - "manual" - ], + "cssClasses": ["task", "manual"], "children": [ { "id": "task0_icon", @@ -66,10 +63,7 @@ "name": "ChkWt", "taskType": "automated", "id": "task0_automated", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "task0_automated_icon", @@ -194,9 +188,7 @@ { "nodeType": "forkNode", "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "cssClasses": [ - "forkOrJoin" - ], + "cssClasses": ["forkOrJoin"], "type": "activityNode:fork", "position": { "x": 170.0, @@ -216,9 +208,7 @@ { "nodeType": "joinNode", "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "cssClasses": [ - "forkOrJoin" - ], + "cssClasses": ["forkOrJoin"], "type": "activityNode:join", "position": { "x": 560.0, @@ -245,10 +235,7 @@ "name": "WtOK", "taskType": "automated", "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04_icon", @@ -304,9 +291,7 @@ }, { "id": "2317f24a-034e-4352-8857-d361dc8f5ed7", - "cssClasses": [ - "medium" - ], + "cssClasses": ["medium"], "type": "edge:weighted", "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", "targetId": "e47d5eba-612d-4c43-9aba-2c5502ff4f04" @@ -333,10 +318,7 @@ "name": "RflWt", "taskType": "manual", "id": "a63cfd87-da7c-4846-912b-29040b776bfb", - "cssClasses": [ - "task", - "manual" - ], + "cssClasses": ["task", "manual"], "children": [ { "id": "54f08126-6041-44ce-9d8b-e7a549e48f91", @@ -392,9 +374,7 @@ }, { "id": "9bb443d7-44f3-4192-8dd8-1a2782593373", - "cssClasses": [ - "medium" - ], + "cssClasses": ["medium"], "type": "edge:weighted", "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", "targetId": "a63cfd87-da7c-4846-912b-29040b776bfb" @@ -421,10 +401,7 @@ "name": "Brew", "taskType": "automated", "id": "7afd430b-5031-4082-8190-7e755c57cde9", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "7afd430b-5031-4082-8190-7e755c57cde9_icon", @@ -491,10 +468,7 @@ "name": "ChkTp", "taskType": "automated", "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17_icon", @@ -570,10 +544,7 @@ "name": "KeepTp", "taskType": "automated", "id": "4b06ed95-c9be-47df-9941-98099259be4a", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "4b06ed95-c9be-47df-9941-98099259be4a_icon", @@ -631,10 +602,7 @@ "name": "PreHeat", "taskType": "automated", "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", - "cssClasses": [ - "task", - "automated" - ], + "cssClasses": ["task", "automated"], "children": [ { "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6_icon", @@ -690,18 +658,14 @@ }, { "id": "d04afaf8-0894-4284-b844-22b255893107", - "cssClasses": [ - "medium" - ], + "cssClasses": ["medium"], "type": "edge:weighted", "sourceId": "activityNode2", "targetId": "4b06ed95-c9be-47df-9941-98099259be4a" }, { "id": "b9ef468f-2aaf-41b9-b408-29e2d08e490e", - "cssClasses": [ - "medium" - ], + "cssClasses": ["medium"], "type": "edge:weighted", "sourceId": "activityNode2", "targetId": "80d1792f-9c7e-41c0-8eca-eeb0509397b6" @@ -727,4 +691,4 @@ ], "type": "graph", "revision": 10 -} \ No newline at end of file +} diff --git a/examples/workflow-server/src/browser/index.ts b/examples/workflow-server/src/browser/index.ts index 764900b..8cdce46 100644 --- a/examples/workflow-server/src/browser/index.ts +++ b/examples/workflow-server/src/browser/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from '../common/index'; export * from './mock-model-storage'; +export * from './reexport'; diff --git a/examples/workflow-server/src/browser/reexport.ts b/examples/workflow-server/src/browser/reexport.ts new file mode 100644 index 0000000..61a2c52 --- /dev/null +++ b/examples/workflow-server/src/browser/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; diff --git a/examples/workflow-server/src/common/handler/create-activity-node-handler.ts b/examples/workflow-server/src/common/handler/create-activity-node-handler.ts index fdbd8dd..6d620f1 100644 --- a/examples/workflow-server/src/common/handler/create-activity-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-activity-node-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,10 +26,7 @@ export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperat } protected builder(point: Point = Point.ORIGIN, elementTypeId = this.elementTypeIds[0]): ActivityNodeBuilder { - return ActivityNode.builder() - .position(point) - .type(elementTypeId) - .nodeType(ModelTypes.toNodeType(elementTypeId)); + return ActivityNode.builder().position(point).type(elementTypeId).nodeType(ModelTypes.toNodeType(elementTypeId)); } override createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { diff --git a/examples/workflow-server/src/common/index.ts b/examples/workflow-server/src/common/index.ts index 646634d..8953850 100644 --- a/examples/workflow-server/src/common/index.ts +++ b/examples/workflow-server/src/common/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 STMicroelectronics and others. + * Copyright (c) 2023-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,7 +29,6 @@ export * from './handler/create-task-handler'; export * from './handler/create-weighted-edge-handler'; export * from './handler/create-workflow-node-operation-handler'; export * from './handler/grid-snapper'; -export * from './index'; export * from './labeledit/workflow-label-edit-validator'; export * from './layout/workflow-layout-configurator'; export * from './marker/workflow-model-validator'; @@ -46,5 +45,6 @@ export * from './taskedit/task-edit-validator'; export * from './util/model-types'; export * from './workflow-diagram-configuration'; export * from './workflow-diagram-module'; +export * from './workflow-edge-creation-checker'; export * from './workflow-glsp-server'; export * from './workflow-popup-factory'; diff --git a/examples/workflow-server/src/node/.indexignore b/examples/workflow-server/src/node/.indexignore new file mode 100644 index 0000000..c998fd9 --- /dev/null +++ b/examples/workflow-server/src/node/.indexignore @@ -0,0 +1,4 @@ +# The app.ts is the entrypoint when the workflow server is used bundled in an external node process +# It launches the server when imported so it has to be excluded from the default export to avoid unintended +# autostarts when the server package is directly integrated into a node backed (e.g. Theia) +app.ts \ No newline at end of file diff --git a/examples/workflow-server/src/node/index.ts b/examples/workflow-server/src/node/index.ts index 051f52a..fca7741 100644 --- a/examples/workflow-server/src/node/index.ts +++ b/examples/workflow-server/src/node/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from '../common/index'; +export * from './reexport'; export * from './workflow-cli-parser'; diff --git a/examples/workflow-server/src/node/reexport.ts b/examples/workflow-server/src/node/reexport.ts new file mode 100644 index 0000000..61a2c52 --- /dev/null +++ b/examples/workflow-server/src/node/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; diff --git a/packages/graph/package.json b/packages/graph/package.json index cff58ca..b8a11db 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -41,6 +41,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", + "generate:index": "glsp generateIndex src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts index 39d09e4..0b6f628 100644 --- a/packages/graph/src/default-types.ts +++ b/packages/graph/src/default-types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/galignable.ts b/packages/graph/src/galignable.ts index ead156c..6dcf18e 100644 --- a/packages/graph/src/galignable.ts +++ b/packages/graph/src/galignable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gbounds-aware.ts b/packages/graph/src/gbounds-aware.ts index ba992bb..052e16a 100644 --- a/packages/graph/src/gbounds-aware.ts +++ b/packages/graph/src/gbounds-aware.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/ghtml-root.ts b/packages/graph/src/ghtml-root.ts index 36a5923..4cda1e1 100644 --- a/packages/graph/src/ghtml-root.ts +++ b/packages/graph/src/ghtml-root.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 StMicroelectronics. + * Copyright (c) 2022-2024 StMicroelectronics. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts index 37889fc..2997458 100644 --- a/packages/graph/src/gmodel-element.ts +++ b/packages/graph/src/gmodel-element.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index a8c2ac2..fd68dcd 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index cd73185..0f94953 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -41,6 +41,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", + "generate:index": "glsp generateIndex src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 41954b4..19d8705 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/src/index.ts b/packages/layout-elk/src/index.ts index 59d1710..2f1a686 100644 --- a/packages/layout-elk/src/index.ts +++ b/packages/layout-elk/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export { LayoutOptions } from 'elkjs'; export * from './di.config'; export * from './element-filter'; export * from './glsp-elk-layout-engine'; export * from './layout-configurator'; +export * from './reexport'; diff --git a/packages/layout-elk/src/reexport.ts b/packages/layout-elk/src/reexport.ts new file mode 100644 index 0000000..4094b3e --- /dev/null +++ b/packages/layout-elk/src/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export { LayoutOptions } from 'elkjs'; diff --git a/packages/server/package.json b/packages/server/package.json index d4b78eb..edb13db 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -50,6 +50,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", + "generate:index": "glsp generateIndex src/browser src/common src/node -s -f", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/server/src/browser/index.ts b/packages/server/src/browser/index.ts index 066dec9..2f5a370 100644 --- a/packages/server/src/browser/index.ts +++ b/packages/server/src/browser/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from '../common/index'; export * from './di/app-module'; export * from './launch/worker-server-launcher'; +export * from './reexport'; diff --git a/packages/server/src/browser/launch/worker-server-launcher.ts b/packages/server/src/browser/launch/worker-server-launcher.ts index ede3e6a..e9145ce 100644 --- a/packages/server/src/browser/launch/worker-server-launcher.ts +++ b/packages/server/src/browser/launch/worker-server-launcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,13 +16,12 @@ import { injectable } from 'inversify'; import * as jsonrpc from 'vscode-jsonrpc/browser'; -import { MaybePromise } from '../../common/index'; -import { JsonRpcGLSPServerLauncher, JsonRpcServerInstance } from '../../common/launch/jsonrpc-server-launcher'; +import { JsonRpcGLSPServerLauncher, JsonRpcServerInstance, MaybePromise } from '../../common'; export interface WorkerLaunchOptions { context?: Worker; } -export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed.'; +export const WORKER_START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed.'; @injectable() export class WorkerServerLauncher extends JsonRpcGLSPServerLauncher { @@ -35,7 +34,7 @@ export class WorkerServerLauncher extends JsonRpcGLSPServerLauncher { this.connection?.onClose(() => resolve(undefined)); this.connection?.onError(error => rejects(error)); diff --git a/packages/server/src/browser/reexport.ts b/packages/server/src/browser/reexport.ts new file mode 100644 index 0000000..61a2c52 --- /dev/null +++ b/packages/server/src/browser/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; diff --git a/packages/server/src/common/.indexignore b/packages/server/src/common/.indexignore new file mode 100644 index 0000000..55f50e2 --- /dev/null +++ b/packages/server/src/common/.indexignore @@ -0,0 +1,2 @@ +# Don't export test resources to avoid an implicit dependency on chai/sinon +test diff --git a/packages/server/src/common/command/recording-command.spec.ts b/packages/server/src/common/command/recording-command.spec.ts index 27b5a81..3befae8 100644 --- a/packages/server/src/common/command/recording-command.spec.ts +++ b/packages/server/src/common/command/recording-command.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,7 +28,10 @@ interface TestModel { let jsonObject: TestModel; class TestRecordingCommand extends AbstractRecordingCommand { - constructor(protected jsonObject: JsonObject, protected doExecute: () => MaybePromise) { + constructor( + protected jsonObject: JsonObject, + protected doExecute: () => MaybePromise + ) { super(); } diff --git a/packages/server/src/common/command/recording-command.ts b/packages/server/src/common/command/recording-command.ts index c8e4bb6..bfd69ba 100644 --- a/packages/server/src/common/command/recording-command.ts +++ b/packages/server/src/common/command/recording-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -93,7 +93,11 @@ export abstract class AbstractRecordingCommand imp * to the `GModelRoot` ({@link ModelState.root}) during the given `doExecute` function */ export class GModelRecordingCommand extends AbstractRecordingCommand { - constructor(protected modelState: ModelState, protected serializer: GModelSerializer, protected doExecute: () => MaybePromise) { + constructor( + protected modelState: ModelState, + protected serializer: GModelSerializer, + protected doExecute: () => MaybePromise + ) { super(); } diff --git a/packages/server/src/common/di/client-session-module.ts b/packages/server/src/common/di/client-session-module.ts index af267d6..77e9550 100644 --- a/packages/server/src/common/di/client-session-module.ts +++ b/packages/server/src/common/di/client-session-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GLSPClientProxy, bindOrRebind } from '@eclipse-glsp/protocol'; +import { GLSPClientProxy } from '@eclipse-glsp/protocol'; +import { bindOrRebind } from '@eclipse-glsp/protocol/lib/di'; import { ContainerModule } from 'inversify'; import { ClientActionKinds, ClientId } from './service-identifiers'; diff --git a/packages/server/src/common/di/glsp-module.ts b/packages/server/src/common/di/glsp-module.ts index a1dedd6..09d57af 100644 --- a/packages/server/src/common/di/glsp-module.ts +++ b/packages/server/src/common/di/glsp-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext } from '@eclipse-glsp/protocol'; +import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { ContainerModule, injectable, interfaces } from 'inversify'; import { AbstractMultiBinding } from './multi-binding'; diff --git a/packages/server/src/common/di/multi-binding.ts b/packages/server/src/common/di/multi-binding.ts index e822a2c..8bc7708 100644 --- a/packages/server/src/common/di/multi-binding.ts +++ b/packages/server/src/common/di/multi-binding.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext, distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; +import { distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; +import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { applyBindingTarget, BindingTarget } from './binding-target'; /** diff --git a/packages/server/src/common/features/directediting/context-edit-validator.ts b/packages/server/src/common/features/directediting/context-edit-validator.ts index adef275..d692f59 100644 --- a/packages/server/src/common/features/directediting/context-edit-validator.ts +++ b/packages/server/src/common/features/directediting/context-edit-validator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -37,7 +37,10 @@ export interface ContextEditValidator { export class ValidateLabelEditAdapter implements ContextEditValidator { labelEditValidator: LabelEditValidator; - constructor(readonly modelState: ModelState, labelEditValidator: LabelEditValidator) { + constructor( + readonly modelState: ModelState, + labelEditValidator: LabelEditValidator + ) { this.labelEditValidator = labelEditValidator; } diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index ccd78f9..f491db2 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/navigation/navigation-target-resolution.ts b/packages/server/src/common/features/navigation/navigation-target-resolution.ts index abf6d4b..76633b8 100644 --- a/packages/server/src/common/features/navigation/navigation-target-resolution.ts +++ b/packages/server/src/common/features/navigation/navigation-target-resolution.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,5 +18,8 @@ import { Args } from '@eclipse-glsp/protocol'; export class NavigationTargetResolution { static readonly EMPTY = new NavigationTargetResolution([]); - constructor(readonly elementIds: string[], readonly args?: Args) {} + constructor( + readonly elementIds: string[], + readonly args?: Args + ) {} } diff --git a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts index 7c4f69b..c2f3cea 100644 --- a/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts +++ b/packages/server/src/common/gmodel/gmodel-create-node-operation-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,12 @@ import { GModelElement, GNode } from '@eclipse-glsp/graph'; import { - Args, CreateNodeOperation, GhostElement, MaybePromise, Point, SelectAction, + Args, + CreateNodeOperation, + GhostElement, + MaybePromise, + Point, + SelectAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; @@ -62,8 +67,10 @@ export abstract class GModelCreateNodeOperationHandler extends GModelOperationHa } protected createTriggerNodeCreationAction(elementTypeId: string): TriggerNodeCreationAction { - return TriggerNodeCreationAction.create(elementTypeId, - { ghostElement: this.createTriggerGhostElement(elementTypeId), args: this.createTriggerArgs(elementTypeId) } ); + return TriggerNodeCreationAction.create(elementTypeId, { + ghostElement: this.createTriggerGhostElement(elementTypeId), + args: this.createTriggerArgs(elementTypeId) + }); } protected createTriggerGhostElement(elementTypeId: string): GhostElement | undefined { diff --git a/packages/server/src/common/index.ts b/packages/server/src/common/index.ts index a2e84b4..da86f5a 100644 --- a/packages/server/src/common/index.ts +++ b/packages/server/src/common/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from '@eclipse-glsp/graph'; -export * from '@eclipse-glsp/protocol'; export * from './actions/action-dispatcher'; export * from './actions/action-handler'; export * from './actions/action-handler-registry'; @@ -64,19 +62,34 @@ export * from './features/navigation/request-navigation-targets-action-handler'; export * from './features/navigation/resolve-navigation-targets-action-handler'; export * from './features/popup/popup-model-factory'; export * from './features/popup/request-popup-model-action-handler'; +export * from './features/progress/progress-service'; export * from './features/type-hints/edge-creation-checker'; +export * from './features/type-hints/request-check-edge-action-handler'; export * from './features/type-hints/request-type-hints-action-handler'; export * from './features/validation/model-validator'; export * from './features/validation/request-markers-handler'; -export * from './gmodel/index'; +export * from './gmodel/apply-label-edit-operation-handler'; +export * from './gmodel/change-bounds-operation-handler'; +export * from './gmodel/change-routing-points-operation-handler'; +export * from './gmodel/cut-operation-handler'; +export * from './gmodel/delete-operation-handler'; +export * from './gmodel/gmodel-create-edge-operation-handler'; +export * from './gmodel/gmodel-create-node-operation-handler'; +export * from './gmodel/gmodel-diagram-module'; +export * from './gmodel/gmodel-operation-handler'; +export * from './gmodel/paste-operation-handler'; +export * from './gmodel/reconnect-edge-operation-handler'; export * from './launch/glsp-server-launcher'; +export * from './launch/jsonrpc-server-launcher'; export * from './operations/compound-operation-handler'; export * from './operations/create-operation-handler'; export * from './operations/json-operation-handler'; export * from './operations/operation-action-handler'; export * from './operations/operation-handler'; export * from './operations/operation-handler-registry'; +export * from './protocol/client-action'; export * from './protocol/glsp-server'; +export * from './reexport'; export * from './session/client-session'; export * from './session/client-session-factory'; export * from './session/client-session-initializer'; diff --git a/packages/server/src/common/gmodel/index.ts b/packages/server/src/common/reexport.ts similarity index 56% rename from packages/server/src/common/gmodel/index.ts rename to packages/server/src/common/reexport.ts index 66056b3..662a16b 100644 --- a/packages/server/src/common/gmodel/index.ts +++ b/packages/server/src/common/reexport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,14 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './apply-label-edit-operation-handler'; -export * from './change-bounds-operation-handler'; -export * from './change-routing-points-operation-handler'; -export * from './cut-operation-handler'; -export * from './delete-operation-handler'; -export * from './gmodel-create-edge-operation-handler'; -export * from './gmodel-create-node-operation-handler'; -export * from './gmodel-diagram-module'; -export * from './gmodel-operation-handler'; -export * from './paste-operation-handler'; -export * from './reconnect-edge-operation-handler'; +export * from '@eclipse-glsp/graph'; +export * from '@eclipse-glsp/protocol'; +export * from '@eclipse-glsp/protocol/lib/di'; diff --git a/packages/server/src/common/test/mock-util.ts b/packages/server/src/common/test/mock-util.ts index 8ec0bc3..15f739d 100644 --- a/packages/server/src/common/test/mock-util.ts +++ b/packages/server/src/common/test/mock-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -43,7 +43,8 @@ import { Command } from '../command/command'; import { DiagramConfiguration, ServerLayoutKind } from '../diagram/diagram-configuration'; import { ContextEditValidator } from '../features/directediting/context-edit-validator'; import { LabelEditValidator } from '../features/directediting/label-edit-validator'; -import { GModelCreateEdgeOperationHandler, GModelCreateNodeOperationHandler } from '../gmodel/index'; +import { GModelCreateEdgeOperationHandler } from '../gmodel/gmodel-create-edge-operation-handler'; +import { GModelCreateNodeOperationHandler } from '../gmodel/gmodel-create-node-operation-handler'; import { ClientSession } from '../session/client-session'; import { ClientSessionFactory } from '../session/client-session-factory'; import { ClientSessionInitializer } from '../session/client-session-initializer'; diff --git a/packages/server/src/common/utils/console-logger.ts b/packages/server/src/common/utils/console-logger.ts index 9d84031..4a1c6e2 100644 --- a/packages/server/src/common/utils/console-logger.ts +++ b/packages/server/src/common/utils/console-logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext } from '@eclipse-glsp/protocol'; +import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { injectable } from 'inversify'; import { LogLevel, Logger, LoggerConfigOptions, LoggerFactory, NullLogger, getRequestParentName } from './logger'; @@ -24,7 +24,10 @@ import { LogLevel, Logger, LoggerConfigOptions, LoggerFactory, NullLogger, getRe */ @injectable() export class ConsoleLogger extends Logger { - constructor(public logLevel: LogLevel = LogLevel.none, public caller?: string) { + constructor( + public logLevel: LogLevel = LogLevel.none, + public caller?: string + ) { super(); } diff --git a/packages/server/src/common/utils/glsp-server-error.ts b/packages/server/src/common/utils/glsp-server-error.ts index 683d21a..41cf1ed 100644 --- a/packages/server/src/common/utils/glsp-server-error.ts +++ b/packages/server/src/common/utils/glsp-server-error.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export class GLSPServerError extends Error { - constructor(message: string, readonly cause?: unknown) { + constructor( + message: string, + readonly cause?: unknown + ) { super(message); } } diff --git a/packages/server/src/node/di/app-module.ts b/packages/server/src/node/di/app-module.ts index c566939..8404d62 100644 --- a/packages/server/src/node/di/app-module.ts +++ b/packages/server/src/node/di/app-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BindingContext } from '@eclipse-glsp/protocol'; +import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { ContainerModule } from 'inversify'; import * as winston from 'winston'; import { InjectionContainer, LogLevel, Logger, LoggerFactory, getRequestParentName } from '../../common'; diff --git a/packages/server/src/node/di/winston-logger.ts b/packages/server/src/node/di/winston-logger.ts index 1b3fd5f..d1180da 100644 --- a/packages/server/src/node/di/winston-logger.ts +++ b/packages/server/src/node/di/winston-logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,7 +17,10 @@ import * as winston from 'winston'; import { asLogLevel, Logger, LogLevel } from '../../common/utils/logger'; export class WinstonLogger extends Logger { - constructor(protected baseLogger: winston.Logger, public caller?: string) { + constructor( + protected baseLogger: winston.Logger, + public caller?: string + ) { super(); } diff --git a/packages/server/src/node/index.ts b/packages/server/src/node/index.ts index 705bc80..9de6264 100644 --- a/packages/server/src/node/index.ts +++ b/packages/server/src/node/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from '../common/index'; export * from './abstract-json-model-storage'; export * from './di/app-module'; export * from './di/winston-logger'; @@ -22,3 +21,4 @@ export * from './launch/cli-parser'; export * from './launch/socket-cli-parser'; export * from './launch/socket-server-launcher'; export * from './launch/websocket-server-launcher'; +export * from './reexport'; diff --git a/packages/server/src/node/launch/socket-server-launcher.ts b/packages/server/src/node/launch/socket-server-launcher.ts index d56cb6d..cebb25e 100644 --- a/packages/server/src/node/launch/socket-server-launcher.ts +++ b/packages/server/src/node/launch/socket-server-launcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,8 +20,6 @@ import * as jsonrpc from 'vscode-jsonrpc/node'; import { JsonRpcGLSPServerLauncher } from '../../common/launch/jsonrpc-server-launcher'; import { Logger } from '../../common/utils/logger'; -export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed. Accepting requests on port:'; - @injectable() export class SocketServerLauncher extends JsonRpcGLSPServerLauncher { @inject(Logger) protected override logger: Logger; diff --git a/packages/server/src/node/reexport.ts b/packages/server/src/node/reexport.ts new file mode 100644 index 0000000..61a2c52 --- /dev/null +++ b/packages/server/src/node/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; From 655e99404794a8a0c7399d05a74e2c7a3e94b2ca Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 15 May 2024 09:39:41 +0200 Subject: [PATCH 328/566] Align sprotty overrides (#345) - Move glsp-specific overrides of sprotty base classes into glsp-client directory. - Add rule from ui-extension.css to base css file. This also fixes an issue with the missing css file we currently encounter in downstream builds Sidenote: The sprotty-glsp package is intended as a small layer to enable a controlled reexport of sprotty provided modules. Apart from the overrides for our custom action definition (action-override), all other customization of sprotty classes should be handled in the glsp-client package. --- packages/client/css/glsp-sprotty.css | 5 ++ packages/client/src/base/default.module.ts | 2 +- packages/client/src/base/index.ts | 2 +- .../src/base/ui-extension/index.ts} | 7 +- .../ui-extension-registry.ts | 2 +- .../src/base/ui-extension/ui-extension.ts} | 11 +-- .../command-palette/command-palette.ts | 3 +- .../features/label-edit-ui/label-edit-ui.ts | 3 +- ...anhattan-edge-router.ts => edge-router.ts} | 74 +++++++++++++++- packages/client/src/features/routing/index.ts | 2 +- .../src/features/routing/routing-module.ts | 8 +- packages/glsp-sprotty/src/index.ts | 2 - packages/glsp-sprotty/src/re-exports.ts | 26 ++---- packages/glsp-sprotty/src/routing-override.ts | 88 ------------------- 14 files changed, 102 insertions(+), 133 deletions(-) rename packages/{glsp-sprotty/css/ui-extension.css => client/src/base/ui-extension/index.ts} (91%) rename packages/client/src/base/{ => ui-extension}/ui-extension-registry.ts (97%) rename packages/{glsp-sprotty/src/ui-extension-override.ts => client/src/base/ui-extension/ui-extension.ts} (93%) rename packages/client/src/features/routing/{glsp-manhattan-edge-router.ts => edge-router.ts} (52%) delete mode 100644 packages/glsp-sprotty/src/routing-override.ts diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index ca2a020..e2b2eb4 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -325,3 +325,8 @@ stroke: rgb(218, 89, 15) !important; stroke: #130bf7 !important; } + +.ui-extension.hidden { + display: none; + opacity: 0; +} diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index dc9ae0b..8025989 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -51,7 +51,7 @@ import { SelectionClearingMouseListener } from './selection-clearing-mouse-liste import { SelectionService } from './selection-service'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; -import { GLSPUIExtensionRegistry } from './ui-extension-registry'; +import { GLSPUIExtensionRegistry } from './ui-extension/ui-extension-registry'; import { GLSPKeyTool } from './view/key-tool'; import { GLSPMouseTool } from './view/mouse-tool'; import { GViewRegistry } from './view/view-registry'; diff --git a/packages/client/src/base/index.ts b/packages/client/src/base/index.ts index b38ba91..70cd9e3 100644 --- a/packages/client/src/base/index.ts +++ b/packages/client/src/base/index.ts @@ -28,5 +28,5 @@ export * from './ranked'; export * from './selection-clearing-mouse-listener'; export * from './selection-service'; export * from './tool-manager'; -export * from './ui-extension-registry'; +export * from './ui-extension'; export * from './view'; diff --git a/packages/glsp-sprotty/css/ui-extension.css b/packages/client/src/base/ui-extension/index.ts similarity index 91% rename from packages/glsp-sprotty/css/ui-extension.css rename to packages/client/src/base/ui-extension/index.ts index e5a90d8..da1662b 100644 --- a/packages/glsp-sprotty/css/ui-extension.css +++ b/packages/client/src/base/ui-extension/index.ts @@ -13,8 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -.ui-extension.hidden { - display: none; - opacity: 0; -} +export * from './ui-extension'; +export * from './ui-extension-registry'; diff --git a/packages/client/src/base/ui-extension-registry.ts b/packages/client/src/base/ui-extension/ui-extension-registry.ts similarity index 97% rename from packages/client/src/base/ui-extension-registry.ts rename to packages/client/src/base/ui-extension/ui-extension-registry.ts index e65deb1..803c63a 100644 --- a/packages/client/src/base/ui-extension-registry.ts +++ b/packages/client/src/base/ui-extension/ui-extension-registry.ts @@ -16,7 +16,7 @@ import { IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { IDiagramStartup } from './model'; +import { IDiagramStartup } from '../model'; @injectable() export class GLSPUIExtensionRegistry extends UIExtensionRegistry implements IDiagramStartup { diff --git a/packages/glsp-sprotty/src/ui-extension-override.ts b/packages/client/src/base/ui-extension/ui-extension.ts similarity index 93% rename from packages/glsp-sprotty/src/ui-extension-override.ts rename to packages/client/src/base/ui-extension/ui-extension.ts index 448a88d..8d19f4f 100644 --- a/packages/glsp-sprotty/src/ui-extension-override.ts +++ b/packages/client/src/base/ui-extension/ui-extension.ts @@ -13,18 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '../css/ui-extension.css'; +import { AbstractUIExtension } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { AbstractUIExtension as SprottyAbstractUIExtension } from 'sprotty'; export const CSS_UI_EXTENSION_CLASS = 'ui-extension'; export const CSS_HIDDEN_EXTENSION_CLASS = 'hidden'; -// An override to enables more fine-grained control of the container structure for the UI extension. - +/** + * Reusable abstract base implementation for UI extensions. + * Intended for UI extensions that directly interact with the DOM API to create and manage UI elements. + */ @injectable() -export abstract class AbstractUIExtension extends SprottyAbstractUIExtension { +export abstract class GLSPAbstractUIExtension extends AbstractUIExtension { protected get diagramContainerId(): string { return this.options.baseDiv; } diff --git a/packages/client/src/features/command-palette/command-palette.ts b/packages/client/src/features/command-palette/command-palette.ts index 1f02630..11d50a0 100644 --- a/packages/client/src/features/command-palette/command-palette.ts +++ b/packages/client/src/features/command-palette/command-palette.ts @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS, CommandPalette } from '@eclipse-glsp/sprotty'; +import { CommandPalette } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; +import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS } from '../../base/ui-extension/ui-extension'; @injectable() export class GlspCommandPalette extends CommandPalette { diff --git a/packages/client/src/features/label-edit-ui/label-edit-ui.ts b/packages/client/src/features/label-edit-ui/label-edit-ui.ts index e6de9eb..b6846d1 100644 --- a/packages/client/src/features/label-edit-ui/label-edit-ui.ts +++ b/packages/client/src/features/label-edit-ui/label-edit-ui.ts @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS, EditLabelUI } from '@eclipse-glsp/sprotty'; +import { EditLabelUI } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; +import { CSS_HIDDEN_EXTENSION_CLASS, CSS_UI_EXTENSION_CLASS } from '../../base/ui-extension/ui-extension'; @injectable() export class GlspEditLabelUI extends EditLabelUI { diff --git a/packages/client/src/features/routing/glsp-manhattan-edge-router.ts b/packages/client/src/features/routing/edge-router.ts similarity index 52% rename from packages/client/src/features/routing/glsp-manhattan-edge-router.ts rename to packages/client/src/features/routing/edge-router.ts index 7d9d379..cbfb71c 100644 --- a/packages/client/src/features/routing/glsp-manhattan-edge-router.ts +++ b/packages/client/src/features/routing/edge-router.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,64 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ManhattanEdgeRouter, ResolvedHandleMove, GRoutableElement, almostEquals } from '@eclipse-glsp/sprotty'; +import { + AbstractEdgeRouter, + BezierEdgeRouter, + GConnectableElement, + GParentElement, + GRoutableElement, + ManhattanEdgeRouter, + Point, + PolylineEdgeRouter, + ResolvedHandleMove, + almostEquals +} from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +@injectable() +export abstract class GLSPAbstractEdgeRouter extends AbstractEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} + +@injectable() +export class GLSPPolylineEdgeRouter extends PolylineEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} + +@injectable() export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } + protected override applyInnerHandleMoves(edge: GRoutableElement, moves: ResolvedHandleMove[]): void { const route = this.route(edge); const routingPoints = edge.routingPoints; @@ -57,3 +112,18 @@ export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { }); } } + +@injectable() +export class GLSPBezierEdgeRouter extends BezierEdgeRouter { + override getTranslatedAnchor( + connectable: GConnectableElement, + refPoint: Point, + refContainer: GParentElement, + edge: GRoutableElement, + anchorCorrection?: number | undefined + ): Point { + // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case + const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); + return Point.isValid(anchor) ? anchor : refPoint; + } +} diff --git a/packages/client/src/features/routing/index.ts b/packages/client/src/features/routing/index.ts index 452038f..87ced36 100644 --- a/packages/client/src/features/routing/index.ts +++ b/packages/client/src/features/routing/index.ts @@ -13,5 +13,5 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './glsp-manhattan-edge-router'; +export * from './edge-router'; export * from './routing-module'; diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 7fb859c..a31e4e7 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -17,7 +17,6 @@ import { AddRemoveBezierSegmentCommand, AnchorComputerRegistry, BezierDiamondAnchor, - BezierEdgeRouter, BezierEllipseAnchor, BezierRectangleAnchor, DiamondAnchor, @@ -27,13 +26,12 @@ import { ManhattanDiamondAnchor, ManhattanEllipticAnchor, ManhattanRectangularAnchor, - PolylineEdgeRouter, RectangleAnchor, TYPES, bindAsService, configureCommand } from '@eclipse-glsp/sprotty'; -import { GLSPManhattanEdgeRouter } from './glsp-manhattan-edge-router'; +import { GLSPBezierEdgeRouter, GLSPManhattanEdgeRouter, GLSPPolylineEdgeRouter } from './edge-router'; export const routingModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; @@ -45,12 +43,12 @@ export const routingModule = new FeatureModule((bind, unbind, isBound, rebind) = bindAsService(context, TYPES.IAnchorComputer, ManhattanRectangularAnchor); bindAsService(context, TYPES.IAnchorComputer, ManhattanDiamondAnchor); - bindAsService(context, TYPES.IEdgeRouter, PolylineEdgeRouter); + bindAsService(context, TYPES.IEdgeRouter, GLSPPolylineEdgeRouter); bindAsService(context, TYPES.IAnchorComputer, EllipseAnchor); bindAsService(context, TYPES.IAnchorComputer, RectangleAnchor); bindAsService(context, TYPES.IAnchorComputer, DiamondAnchor); - bindAsService(context, TYPES.IEdgeRouter, BezierEdgeRouter); + bindAsService(context, TYPES.IEdgeRouter, GLSPBezierEdgeRouter); bindAsService(context, TYPES.IAnchorComputer, BezierEllipseAnchor); bindAsService(context, TYPES.IAnchorComputer, BezierRectangleAnchor); bindAsService(context, TYPES.IAnchorComputer, BezierDiamondAnchor); diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index ff6e959..349405c 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -16,6 +16,4 @@ export * from './action-override'; export * from './feature-modules'; export * from './re-exports'; -export * from './routing-override'; export * from './types'; -export * from './ui-extension-override'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index e08cd14..e5d0a5c 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -74,7 +74,7 @@ export { } from 'sprotty/lib/base/model/smodel-factory'; export * from 'sprotty/lib/base/model/smodel-utils'; -export { IUIExtension, isUIExtension } from 'sprotty/lib/base/ui-extensions/ui-extension'; +export * from 'sprotty/lib/base/ui-extensions/ui-extension'; export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; export * from 'sprotty/lib/base/views/dom-helper'; @@ -131,7 +131,7 @@ export { } from 'sprotty/lib/features/command-palette/action-providers'; export * from 'sprotty/lib/features/command-palette/command-palette'; -// Exclude menu item. Aready provided by glsp-protocol +// Exclude menu item. Already provided by glsp-protocol export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; export { ContextMenuProviderRegistry, DeleteContextMenuItemProvider } from 'sprotty/lib/features/context-menu/menu-providers'; export * from 'sprotty/lib/features/context-menu/mouse-listener'; @@ -194,25 +194,12 @@ export * from 'sprotty/lib/features/open/open'; export * from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; -export { - DefaultAnchors, - LinearRouteOptions, - Side, - AbstractEdgeRouter as SprottyAbstractEdgeRouter -} from 'sprotty/lib/features/routing/abstract-edge-router'; +export * from 'sprotty/lib/features/routing/abstract-edge-router'; export * from 'sprotty/lib/features/routing/anchor'; export * from 'sprotty/lib/features/routing/bezier-anchors'; -export { - AddRemoveBezierSegmentAction, - AddRemoveBezierSegmentCommand, - BezierMouseListener, - BezierEdgeRouter as SprottyBezierEdgeRouter -} from 'sprotty/lib/features/routing/bezier-edge-router'; +export * from 'sprotty/lib/features/routing/bezier-edge-router'; export * from 'sprotty/lib/features/routing/manhattan-anchors'; -export { - ManhattanRouterOptions, - ManhattanEdgeRouter as SprottyManhattanEdgeRouter -} from 'sprotty/lib/features/routing/manhattan-edge-router'; +export * from 'sprotty/lib/features/routing/manhattan-edge-router'; // Alias SModel types export { @@ -230,7 +217,7 @@ export { isConnectable } from 'sprotty/lib/features/routing/model'; export * from 'sprotty/lib/features/routing/polyline-anchors'; -export { PolylineRouteOptions, PolylineEdgeRouter as SprottyPolylineEdgeRouter } from 'sprotty/lib/features/routing/polyline-edge-router'; +export * from 'sprotty/lib/features/routing/polyline-edge-router'; export * from 'sprotty/lib/features/routing/routing'; export * from 'sprotty/lib/features/routing/views'; @@ -326,4 +313,3 @@ export * from 'sprotty/lib/utils/iterable'; export * from 'sprotty/lib/utils/keyboard'; export * from 'sprotty/lib/utils/logging'; export * from 'sprotty/lib/utils/registry'; - diff --git a/packages/glsp-sprotty/src/routing-override.ts b/packages/glsp-sprotty/src/routing-override.ts deleted file mode 100644 index 8bde30c..0000000 --- a/packages/glsp-sprotty/src/routing-override.ts +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Point } from '@eclipse-glsp/protocol'; -import { injectable } from 'inversify'; -import { - SConnectableElementImpl, - SParentElementImpl, - SRoutableElementImpl, - AbstractEdgeRouter as SprottyAbstractEdgeRouter, - BezierEdgeRouter as SprottyBezierEdgeRouter, - ManhattanEdgeRouter as SprottyManhattanEdgeRouter, - PolylineEdgeRouter as SprottyPolylineEdgeRouter -} from 'sprotty'; -import { GConnectableElement, GParentElement, GRoutableElement } from './re-exports'; - -@injectable() -export abstract class AbstractEdgeRouter extends SprottyAbstractEdgeRouter { - override getTranslatedAnchor( - connectable: SConnectableElementImpl, - refPoint: Point, - refContainer: SParentElementImpl, - edge: SRoutableElementImpl, - anchorCorrection?: number | undefined - ): Point { - // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case - const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); - return Point.isValid(anchor) ? anchor : refPoint; - } -} - -@injectable() -export class PolylineEdgeRouter extends SprottyPolylineEdgeRouter { - override getTranslatedAnchor( - connectable: GConnectableElement, - refPoint: Point, - refContainer: GParentElement, - edge: GRoutableElement, - anchorCorrection?: number | undefined - ): Point { - // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case - const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); - return Point.isValid(anchor) ? anchor : refPoint; - } -} - -@injectable() -export class ManhattanEdgeRouter extends SprottyManhattanEdgeRouter { - override getTranslatedAnchor( - connectable: GConnectableElement, - refPoint: Point, - refContainer: GParentElement, - edge: GRoutableElement, - anchorCorrection?: number | undefined - ): Point { - // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case - const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); - return Point.isValid(anchor) ? anchor : refPoint; - } -} - -@injectable() -export class BezierEdgeRouter extends SprottyBezierEdgeRouter { - override getTranslatedAnchor( - connectable: GConnectableElement, - refPoint: Point, - refContainer: GParentElement, - edge: GRoutableElement, - anchorCorrection?: number | undefined - ): Point { - // users may define all kinds of anchors and anchor computers, we want to make sure we return a valid one in any case - const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); - return Point.isValid(anchor) ? anchor : refPoint; - } -} From 4b79112be4be2fa9ed1be7047dd30de629813625 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 15 May 2024 10:49:34 +0200 Subject: [PATCH 329/566] GLSP-1195: Handle parsing errors in winston logger (#82) - Ensure that the WinstonLogger does not throw an error if it encounters an unparseable object (e.g. circular structure) - Add fallback hooks for uncaught exceptions/promise rejections to example server to avoid nodes default behavior (process exit) Fixes https://github.com/eclipse-glsp/glsp/issues/1195 --- examples/workflow-server/src/node/app.ts | 12 +++++++++++- packages/server/src/node/di/winston-logger.ts | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/workflow-server/src/node/app.ts b/examples/workflow-server/src/node/app.ts index 6300ca3..a0b2fa2 100644 --- a/examples/workflow-server/src/node/app.ts +++ b/examples/workflow-server/src/node/app.ts @@ -16,7 +16,7 @@ import 'reflect-metadata'; import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk'; -import { createAppModule, GModelStorage, SocketServerLauncher, WebSocketServerLauncher } from '@eclipse-glsp/server/node'; +import { createAppModule, GModelStorage, Logger, SocketServerLauncher, WebSocketServerLauncher } from '@eclipse-glsp/server/node'; import { Container } from 'inversify'; import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator'; @@ -27,6 +27,16 @@ async function launch(argv?: string[]): Promise { const options = createWorkflowCliParser().parse(argv); const appContainer = new Container(); appContainer.load(createAppModule(options)); + const logger = appContainer.get(Logger); + + // Add fallback hooks to catch unhandled exceptions & promise rejections and prevent the node process from crashing + process.on('unhandledRejection', (reason, p) => { + logger.error('Unhandled Rejection:', p, reason); + }); + + process.on('uncaughtException', error => { + logger.error('Uncaught exception:', error); + }); const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); const serverModule = new WorkflowServerModule().configureDiagramModule(new WorkflowDiagramModule(() => GModelStorage), elkLayoutModule); diff --git a/packages/server/src/node/di/winston-logger.ts b/packages/server/src/node/di/winston-logger.ts index d1180da..b2c1126 100644 --- a/packages/server/src/node/di/winston-logger.ts +++ b/packages/server/src/node/di/winston-logger.ts @@ -74,6 +74,10 @@ export class WinstonLogger extends Logger { return `${param.message} ${param.stack || ''}`; } - return JSON.stringify(param, undefined, 4); + try { + return JSON.stringify(param, undefined, 4); + } catch (_) { + return ''; + } } } From 78d108805095b8801dc1bced408fed1b3fab56d3 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 15 May 2024 13:07:40 +0000 Subject: [PATCH 330/566] Introduce an optional grid module to deal with a grid layout (#343) * Introduce an optional grid module to deal with a grid layout - Optional grid module defines a grid based on x/y coordinates -- Adds the grid as a background that properly zooms/resizes -- By default uses the GridSnapper for positioning elements -- Use grid for helper lines and movement tools - Optional debug module that shows the bounds of elements -- Useful for debugging but not meant for production - Render optional debug and grid toggles in tool palette if present - Add both optional modules to the workflow example - Use half-grid snapping for routing handles for nicer UX - Bonus: Add OriginViewportAction to reset to 0,0 on zoom-level 1 Contributed on behalf of Axon Ivy AG Fixes https://github.com/eclipse-glsp/glsp/issues/1336 --- examples/workflow-glsp/src/index.ts | 2 + .../src/workflow-diagram-module.ts | 26 ++- .../workflow-glsp/src/workflow-snapper.ts | 29 +++ .../workflow-glsp/src/workflow-startup.ts | 30 +++ examples/workflow-standalone/css/diagram.css | 3 +- packages/client/css/debug.css | 25 +++ packages/client/css/grid.css | 20 ++ packages/client/css/keyboard-tool-palette.css | 6 - packages/client/css/tool-palette.css | 56 +++--- .../keyboard-tool-palette.ts | 31 +++- .../resize-key-tool/resize-key-handler.ts | 18 +- .../view-key-tools/movement-key-tool.ts | 24 +-- ...spec.ts => point-position-updater.spec.ts} | 13 +- .../client/src/features/change-bounds/snap.ts | 25 +-- .../features/debug/debug-bounds-decorator.tsx | 171 ++++++++++++++++++ .../src/features/debug/debug-manager.ts | 56 ++++++ .../client/src/features/debug/debug-model.ts | 63 +++++++ .../client/src/features/debug/debug-module.ts | 32 ++++ packages/client/src/features/debug/index.ts | 19 ++ .../client/src/features/grid/grid-manager.ts | 60 ++++++ .../client/src/features/grid/grid-model.ts | 62 +++++++ .../client/src/features/grid/grid-module.ts | 34 ++++ .../src/features/grid/grid-snapper.spec.ts | 30 +++ .../client/src/features/grid/grid-snapper.ts | 59 ++++++ packages/client/src/features/grid/grid.ts | 18 ++ packages/client/src/features/grid/index.ts | 20 ++ .../helper-line-manager-default.ts | 12 +- packages/client/src/features/index.ts | 2 + .../src/features/tool-palette/tool-palette.ts | 83 ++++++++- .../client/src/features/viewport/index.ts | 1 + .../src/features/viewport/origin-viewport.ts | 68 +++++++ .../src/features/viewport/viewport-modules.ts | 6 +- packages/client/src/views/base-view-module.ts | 6 +- packages/client/src/views/ggraph-view.tsx | 56 ++++++ .../client/src/views/glsp-projection-view.tsx | 41 ++++- packages/client/src/views/index.ts | 1 + packages/glsp-sprotty/src/re-exports.ts | 4 +- packages/glsp-sprotty/src/types.ts | 5 +- .../src/sprotty-geometry-dimension.spec.ts | 8 + .../src/sprotty-geometry-dimension.ts | 18 ++ packages/protocol/src/utils/type-util.ts | 10 + 41 files changed, 1127 insertions(+), 126 deletions(-) create mode 100644 examples/workflow-glsp/src/workflow-snapper.ts create mode 100644 examples/workflow-glsp/src/workflow-startup.ts create mode 100644 packages/client/css/debug.css create mode 100644 packages/client/css/grid.css rename packages/client/src/features/change-bounds/{snap.spec.ts => point-position-updater.spec.ts} (87%) create mode 100644 packages/client/src/features/debug/debug-bounds-decorator.tsx create mode 100644 packages/client/src/features/debug/debug-manager.ts create mode 100644 packages/client/src/features/debug/debug-model.ts create mode 100644 packages/client/src/features/debug/debug-module.ts create mode 100644 packages/client/src/features/debug/index.ts create mode 100644 packages/client/src/features/grid/grid-manager.ts create mode 100644 packages/client/src/features/grid/grid-model.ts create mode 100644 packages/client/src/features/grid/grid-module.ts create mode 100644 packages/client/src/features/grid/grid-snapper.spec.ts create mode 100644 packages/client/src/features/grid/grid-snapper.ts create mode 100644 packages/client/src/features/grid/grid.ts create mode 100644 packages/client/src/features/grid/index.ts create mode 100644 packages/client/src/features/viewport/origin-viewport.ts create mode 100644 packages/client/src/views/ggraph-view.tsx diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index d67a397..f5d9dc1 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -16,4 +16,6 @@ export * from './direct-task-editing'; export * from './model'; export * from './workflow-diagram-module'; +export * from './workflow-snapper'; +export * from './workflow-startup'; export * from './workflow-views'; diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 70bab38..81b670f 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -27,11 +27,8 @@ import { GLSPProjectionView, GLabel, GLabelView, - GridSnapper, IHelperLineOptions, - ISnapper, LogLevel, - Point, RectangularNodeView, RevealNamedElementActionProvider, RoundedCornerNodeView, @@ -41,7 +38,9 @@ import { bindOrRebind, configureDefaultModelElements, configureModelElement, + debugModule, editLabelFeature, + gridModule, helperLineModule, initializeDiagramContainer } from '@eclipse-glsp/client'; @@ -51,6 +50,8 @@ import 'sprotty/css/edit-label.css'; import '../css/diagram.css'; import { directTaskEditor } from './direct-task-editing/di.config'; import { ActivityNode, CategoryNode, Icon, TaskNode, WeightedEdge } from './model'; +import { WorkflowSnapper } from './workflow-snapper'; +import { WorkflowStartup } from './workflow-startup'; import { IconView, WorkflowEdgeView } from './workflow-views'; export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { @@ -58,7 +59,6 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, bindOrRebind(context, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(context, TYPES.LogLevel).toConstantValue(LogLevel.warn); - bind(TYPES.ISnapper).to(GridSnapper); bindAsService(context, TYPES.ICommandPaletteActionProvider, RevealNamedElementActionProvider); bindAsService(context, TYPES.IContextMenuItemProvider, DeleteElementContextMenuItemProvider); @@ -82,16 +82,14 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, bind(TYPES.IHelperLineOptions).toDynamicValue(ctx => { const options: IHelperLineOptions = {}; - // the user needs to use twice the force (double the distance) to break through a helper line compared to moving on the grid - const snapper = ctx.container.get(TYPES.ISnapper); - if (snapper instanceof GridSnapper) { - options.minimumMoveDelta = Point.multiplyScalar(snapper.grid, 2); - } // skip icons for alignment as well as compartments which are only used for structure options.alignmentElementFilter = element => DEFAULT_ALIGNABLE_ELEMENT_FILTER(element) && !(element instanceof Icon) && !(element instanceof GCompartment); return options; }); + + bindAsService(context, TYPES.IDiagramStartup, WorkflowStartup); + bindOrRebind(context, TYPES.ISnapper).to(WorkflowSnapper); }); export function createWorkflowDiagramContainer(...containerConfiguration: ContainerConfiguration): Container { @@ -99,5 +97,13 @@ export function createWorkflowDiagramContainer(...containerConfiguration: Contai } export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { - return initializeDiagramContainer(container, workflowDiagramModule, directTaskEditor, helperLineModule, ...containerConfiguration); + return initializeDiagramContainer( + container, + directTaskEditor, + helperLineModule, + gridModule, + debugModule, + workflowDiagramModule, + ...containerConfiguration + ); } diff --git a/examples/workflow-glsp/src/workflow-snapper.ts b/examples/workflow-glsp/src/workflow-snapper.ts new file mode 100644 index 0000000..4c71fd2 --- /dev/null +++ b/examples/workflow-glsp/src/workflow-snapper.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement, GRoutingHandle, GridSnapper, Point } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class WorkflowSnapper extends GridSnapper { + override snap(position: Point, element: GModelElement): Point { + // we snap our edges to the center of the elements and our elements to the grid, + // so to allow for nicer angles and more fine-grained control, we allow routing points to be snapped half-grid + return element instanceof GRoutingHandle + ? Point.snapToGrid(position, Point.divideScalar(this.grid, 2)) + : super.snap(position, element); + } +} diff --git a/examples/workflow-glsp/src/workflow-startup.ts b/examples/workflow-glsp/src/workflow-startup.ts new file mode 100644 index 0000000..7004323 --- /dev/null +++ b/examples/workflow-glsp/src/workflow-startup.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GridManager, IDiagramStartup } from '@eclipse-glsp/client'; +import { MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; + +@injectable() +export class WorkflowStartup implements IDiagramStartup { + rank = -1; + + @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; + + preRequestModel(): MaybePromise { + this.gridManager?.setGridVisible(true); + } +} diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index bc379b9..782f1d5 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -17,7 +17,8 @@ --glsp-info-foreground: blue; } -.sprotty-graph { +.sprotty-graph, +.grid-background { background: rgb(179, 196, 202); } diff --git a/packages/client/css/debug.css b/packages/client/css/debug.css new file mode 100644 index 0000000..8c1ace4 --- /dev/null +++ b/packages/client/css/debug.css @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.debug-bounds:has(> .debug-bounds-decoration) { + fill-opacity: 0.5; +} + +.debug-bounds-decoration { + fill: none; + stroke: black; + stroke-width: 1px; +} diff --git a/packages/client/css/grid.css b/packages/client/css/grid.css new file mode 100644 index 0000000..8cad8ef --- /dev/null +++ b/packages/client/css/grid.css @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** Control visibility of background image through CSS class on parent */ +.grid-background .sprotty-graph { + background-image: var(--grid-background-image); +} diff --git a/packages/client/css/keyboard-tool-palette.css b/packages/client/css/keyboard-tool-palette.css index 2ce9fa8..5e31b6d 100644 --- a/packages/client/css/keyboard-tool-palette.css +++ b/packages/client/css/keyboard-tool-palette.css @@ -14,13 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -.accessibility-tool-palette.tool-palette { - top: 48px; - width: 240px; -} - .accessibility-tool-palette .header-tools i { - margin-right: 0.5em; position: relative; } diff --git a/packages/client/css/tool-palette.css b/packages/client/css/tool-palette.css index d92f468..b1ab6e5 100644 --- a/packages/client/css/tool-palette.css +++ b/packages/client/css/tool-palette.css @@ -17,10 +17,10 @@ /* Css for main container */ .tool-palette { position: absolute; - right: 40px; - top: 25px; + right: 45px; + top: 35px; text-align: center; - width: 225px; + width: fit-content; display: block; z-index: 1000; border-style: solid; @@ -38,6 +38,11 @@ /* Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ + + box-shadow: + 4px 0 4px 0 rgba(0, 0, 0, 0.2), + 0 4px 4px 0 rgba(0, 0, 0, 0.19); + font-size: 14px; } /* Css for header compartment */ @@ -45,33 +50,29 @@ .palette-header { padding: 0.4em; text-align: left; - background: #cccccc; + background: rgb(151, 160, 165); border: 1px solid rgba(60, 60, 60, 0.6); - box-shadow: - 0 4px 8px 0 rgba(0, 0, 0, 0.2), - 0 6px 20px 0 rgba(0, 0, 0, 0.19); + border-bottom: 0; display: flex; + flex-direction: column; align-items: center; justify-content: space-between; - flex-wrap: wrap; + gap: 6px; } .header-icon { - display: flex; + display: none; align-items: center; } -.header-icon i { - margin-right: 0.2em; -} - .header-tools { display: flex; align-items: center; + gap: 3px; } .header-tools i { - border: 1px solid #cccccc; + border: 1px solid transparent; padding: 0.15em; margin-right: 0.15em; } @@ -90,9 +91,6 @@ .palette-body { background: rgba(100, 100, 100, 0.2); border: 1px solid rgba(60, 60, 60, 0.6); - box-shadow: - 0 4px 8px 0 rgba(0, 0, 0, 0.2), - 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .tool-group { @@ -101,13 +99,13 @@ } .group-header { - background: #cccccc; + background: rgb(187, 193, 196); display: flex; align-items: center; } .group-header:hover { - background: #aaaaaa; + background: rgb(187, 193, 196); } .group-header i { @@ -115,7 +113,7 @@ } .tool-button { - background: #ededee; + background: rgb(252, 253, 253); padding: 0.4em; display: flex; align-items: center; @@ -126,11 +124,11 @@ } .tool-button:hover { - background: #dfdfdf; + background: rgb(220, 223, 224); } .tool-button.clicked { - background: #bddaef; + background: rgb(163, 201, 219); } .tool-button.collapsed { @@ -147,13 +145,19 @@ right: 20px; top: 35px; z-index: 1000; + color: rgb(58, 63, 65); +} + +.minimize-palette-button .codicon::before { + font-size: 20px; } .search-input { - background: #dfdfdf; + box-sizing: border-box; + background: rgb(252, 253, 253); color: black; - border: #bddaef; - padding-left: 3px; + border: 1px solid rgb(187, 193, 196); + padding: 4px; width: 100%; - margin: 3px; + margin: 3px 0; } diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index a20e0d2..e8984cc 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -44,7 +44,7 @@ import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; const SEARCH_ICON_ID = 'search'; -const PALETTE_ICON_ID = 'symbol-color'; +const PALETTE_ICON_ID = 'tools'; const CHEVRON_DOWN_ICON_ID = 'chevron-right'; const PALETTE_HEIGHT = '500px'; const SELECTION_TOOL_KEY: KeyCode[] = ['Digit1', 'Numpad1']; @@ -212,29 +212,46 @@ export class KeyboardToolPalette extends ToolPalette { protected override createHeaderTools(): HTMLElement { this.headerToolsButtonMapping.clear(); + let mappingIndex = 0; const headerTools = document.createElement('div'); headerTools.classList.add('header-tools'); this.defaultToolsButton = this.createDefaultToolButton(); - this.headerToolsButtonMapping.set(0, this.defaultToolsButton); + this.headerToolsButtonMapping.set(mappingIndex++, this.defaultToolsButton); headerTools.appendChild(this.defaultToolsButton); this.deleteToolButton = this.createMouseDeleteToolButton(); - this.headerToolsButtonMapping.set(1, this.deleteToolButton); + this.headerToolsButtonMapping.set(mappingIndex++, this.deleteToolButton); headerTools.appendChild(this.deleteToolButton); this.marqueeToolButton = this.createMarqueeToolButton(); - this.headerToolsButtonMapping.set(2, this.marqueeToolButton); + this.headerToolsButtonMapping.set(mappingIndex++, this.marqueeToolButton); headerTools.appendChild(this.marqueeToolButton); this.validateToolButton = this.createValidateButton(); - this.headerToolsButtonMapping.set(3, this.validateToolButton); + this.headerToolsButtonMapping.set(mappingIndex++, this.validateToolButton); headerTools.appendChild(this.validateToolButton); + const resetViewportButton = this.createResetViewportButton(); + this.headerToolsButtonMapping.set(mappingIndex++, resetViewportButton); + headerTools.appendChild(resetViewportButton); + + if (this.gridManager) { + const toggleGridButton = this.createToggleGridButton(); + this.headerToolsButtonMapping.set(mappingIndex++, toggleGridButton); + headerTools.appendChild(toggleGridButton); + } + + if (this.debugManager) { + const toggleDebugButton = this.createToggleDebugButton(); + this.headerToolsButtonMapping.set(mappingIndex++, toggleDebugButton); + headerTools.appendChild(toggleDebugButton); + } + // Create button for Search this.searchToolButton = this.createSearchButton(); - this.headerToolsButtonMapping.set(4, this.searchToolButton); + this.headerToolsButtonMapping.set(mappingIndex++, this.searchToolButton); headerTools.appendChild(this.searchToolButton); return headerTools; @@ -324,7 +341,7 @@ export class KeyboardToolPalette extends ToolPalette { searchField.tabIndex = 21; searchField.id = this.containerElement.id + '_search_field'; searchField.type = 'text'; - searchField.placeholder = ' Search...'; + searchField.placeholder = 'Search...'; searchField.style.display = 'none'; searchField.onkeyup = ev => { this.requestFilterUpdate(this.searchField.value); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index fa4aef1..d9e2251 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -36,7 +36,7 @@ import { EditorContextService } from '../../../base/editor-context-service'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { Resizable, SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; -import { GridSnapper } from '../../change-bounds/snap'; +import { Grid } from '../../grid'; export enum ResizeType { Increase, @@ -80,20 +80,20 @@ export class ResizeElementHandler implements IActionHandler { protected debouncedChangeBounds?: DebouncedFunc<() => void>; protected resizeFeedback: FeedbackEmitter; - // Default x resize used if GridSnapper is not provided + // Default x resize used if grid is not provided static readonly defaultResizeX = 20; - // Default y resize used if GridSnapper is not provided + // Default y resize used if grid is not provided static readonly defaultResizeY = 20; - protected grid = { x: ResizeElementHandler.defaultResizeX, y: ResizeElementHandler.defaultResizeY }; + + @inject(TYPES.Grid) @optional() protected grid: Grid = { + x: ResizeElementHandler.defaultResizeX, + y: ResizeElementHandler.defaultResizeY + }; protected isEditMode = false; - constructor(@inject(TYPES.ISnapper) @optional() protected readonly snapper?: ISnapper) { - if (snapper instanceof GridSnapper) { - this.grid = snapper.grid; - } - } + constructor(@inject(TYPES.ISnapper) @optional() protected readonly snapper?: ISnapper) {} @postConstruct() protected init(): void { diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index ddee997..1649791 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -20,7 +20,8 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; -import { GridSnapper, unsnapModifier, useSnap } from '../../change-bounds/snap'; +import { unsnapModifier, useSnap } from '../../change-bounds/snap'; +import { Grid } from '../../grid'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; @@ -33,18 +34,22 @@ export class MovementKeyTool implements Tool { isEditTool = true; - protected readonly movementKeyListener = new MoveKeyListener(this); + protected movementKeyListener: MoveKeyListener; @inject(KeyTool) protected readonly keytool: KeyTool; @inject(SelectionService) selectionService: SelectionService; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @optional() @inject(TYPES.Grid) protected grid: Grid; get id(): string { return MovementKeyTool.ID; } enable(): void { + if (!this.movementKeyListener) { + this.movementKeyListener = new MoveKeyListener(this, this.grid); + } this.keytool.register(this.movementKeyListener); this.movementKeyListener.registerShortcutKey(); } @@ -55,22 +60,19 @@ export class MovementKeyTool implements Tool { } export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { - // Default x distance used if GridSnapper is not provided + // Default x distance used if grid is not provided static readonly defaultMoveX = 20; - // Default y distance used if GridSnapper is not provided + // Default y distance used if grid is not provided static readonly defaultMoveY = 20; protected readonly token = MoveKeyListener.name; - protected grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY }; - - constructor(protected readonly tool: MovementKeyTool) { + constructor( + protected readonly tool: MovementKeyTool, + protected grid: Grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY } + ) { super(); - - if (this.tool.snapper instanceof GridSnapper) { - this.grid = this.tool.snapper.grid; - } } registerShortcutKey(): void { diff --git a/packages/client/src/features/change-bounds/snap.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts similarity index 87% rename from packages/client/src/features/change-bounds/snap.spec.ts rename to packages/client/src/features/change-bounds/point-position-updater.spec.ts index ca6a82e..0d29442 100644 --- a/packages/client/src/features/change-bounds/snap.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -16,19 +16,8 @@ import { GModelElement } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; +import { GridSnapper } from '../grid'; import { PointPositionUpdater } from './point-position-updater'; -import { GridSnapper } from './snap'; - -describe('GridSnapper', () => { - it('snap', () => { - const element = new GModelElement(); - const snapper = new GridSnapper(); - expect(snapper.snap({ x: 0, y: 0 }, element)).to.be.deep.equals({ x: 0, y: 0 }); - expect(snapper.snap({ x: 4, y: 5 }, element)).to.be.deep.equals({ x: 0, y: 10 }); - expect(snapper.snap({ x: 8, y: 11 }, element)).to.be.deep.equals({ x: 10, y: 10 }); - expect(snapper.snap({ x: -7, y: -4 }, element)).to.be.deep.equals({ x: -10, y: -0 }); - }); -}); describe('PointPositionUpdater', () => { it('updatePosition with no last drag position', () => { diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 36f218c..246c07c 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -14,30 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable @typescript-eslint/no-shadow */ -import { GModelElement, ISnapper, KeyboardModifier, Point } from '@eclipse-glsp/sprotty'; -import { injectable } from 'inversify'; - -/** - * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. - * The default grid size is 10x10 pixel. - * To configure a custom grid size bind the `TYPES.ISnapper` service identifier - * to constant value, e.g: - * - * ```ts - * bind(TYPES.ISnapper).toConstantValue(new GridSnapper({x:25 ,y:25 })); - * ``` - */ -@injectable() -export class GridSnapper implements ISnapper { - constructor(public grid: { x: number; y: number } = { x: 10, y: 10 }) {} - - snap(position: Point, _element: GModelElement): Point { - return { - x: Math.round(position.x / this.grid.x) * this.grid.x, - y: Math.round(position.y / this.grid.y) * this.grid.y - }; - } -} +import { KeyboardModifier } from '@eclipse-glsp/sprotty'; export function useSnap(event: MouseEvent | KeyboardEvent): boolean { return !event.shiftKey; diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx new file mode 100644 index 0000000..e594151 --- /dev/null +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -0,0 +1,171 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ + +import { Bounds, GModelElement, IVNodePostprocessor, Point, TYPES, isBoundsAware, setClass, svg } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { VNode } from 'snabbdom'; +import { GGraph } from '../../model'; +import { BoundsAwareModelElement } from '../../utils'; +import { DebugManager } from './debug-manager'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: svg }; + +@injectable() +export class DebugBoundsDecorator implements IVNodePostprocessor { + @inject(TYPES.IDebugManager) @optional() protected debugManager?: DebugManager; + + decorate(vnode: VNode, element: GModelElement): VNode { + if (!this.debugManager?.isDebugEnabled) { + return vnode; + } + if (isBoundsAware(element)) { + this.decorateBoundsAware(vnode, element); + } + if (element instanceof GGraph) { + this.decorateGraph(vnode, element); + } + return vnode; + } + + postUpdate(): void {} + + protected get decorationSize(): number { + return 5; + } + + protected decorateGraph(vnode: VNode, graph: GGraph): void { + setClass(vnode, 'debug-bounds', true); + const svgChild = vnode.children?.find(child => typeof child !== 'string' && child.sel === 'svg') as VNode | undefined; + const group = svgChild?.children?.find(child => typeof child !== 'string' && child.sel === 'g') as VNode | undefined; + group?.children?.push(this.renderOrigin(graph)); + } + + protected renderOrigin(graph: GGraph): VNode { + return ( + + Origin = x: 0, y: 0 + + ); + } + + protected decorateBoundsAware(vnode: VNode, element: BoundsAwareModelElement): void { + setClass(vnode, 'debug-bounds', true); + vnode.children?.push(this.renderTopLeftCorner(element)); + vnode.children?.push(this.renderTopRightCorner(element)); + vnode.children?.push(this.renderBottomLeftCorner(element)); + vnode.children?.push(this.renderBottomRightCorner(element)); + vnode.children?.push(this.renderCenter(element)); + } + + protected renderTopLeftCorner(element: BoundsAwareModelElement): VNode { + const position = Bounds.topLeft(element.bounds); + const topLeft = Bounds.topLeft(element.bounds); + const corner = Point.subtract(topLeft, position); + return ( + + + Top Left = x: {topLeft.x}, y: {topLeft.y} + + + ); + } + + protected renderTopRightCorner(element: BoundsAwareModelElement): VNode { + const position = Bounds.topLeft(element.bounds); + const topRight = Bounds.topRight(element.bounds); + const corner = Point.subtract(topRight, position); + return ( + + + Top Right = x: {topRight.x}, y: {topRight.y} + + + ); + } + + protected renderBottomLeftCorner(element: BoundsAwareModelElement): VNode { + const position = Bounds.topLeft(element.bounds); + const bottomLeft = Bounds.bottomLeft(element.bounds); + const corner = Point.subtract(bottomLeft, position); + return ( + + + Bottom Left = x: {bottomLeft.x}, y: {bottomLeft.y} + + + ); + } + + protected renderBottomRightCorner(element: BoundsAwareModelElement): VNode { + const position = Bounds.topLeft(element.bounds); + const bottomRight = Bounds.bottomRight(element.bounds); + const corner = Point.subtract(bottomRight, position); + return ( + + + Bottom Right = x: {bottomRight.x}, y: {bottomRight.y} + + + ); + } + + protected renderCenter(element: BoundsAwareModelElement): VNode { + const bounds = element.bounds; + const position = Bounds.topLeft(bounds); + const center = Bounds.center(bounds); + const corner = Point.subtract(center, position); + return ( + + + Center = x: {center.x}, y: {center.y} + Bounds = x: {bounds.x}, y: {bounds.y}, width: {bounds.width}, height: {bounds.height} + + + + + ); + } +} diff --git a/packages/client/src/features/debug/debug-manager.ts b/packages/client/src/features/debug/debug-manager.ts new file mode 100644 index 0000000..ccd4775 --- /dev/null +++ b/packages/client/src/features/debug/debug-manager.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, postConstruct } from 'inversify'; +import { FeedbackEmitter, IFeedbackActionDispatcher } from '../../base'; +import { EnableDebugModeAction } from './debug-model'; + +@injectable() +export class DebugManager implements IActionHandler { + protected _debugEnabled: boolean = false; + protected debugFeedback: FeedbackEmitter; + + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackDispatcher: IFeedbackActionDispatcher; + + get isDebugEnabled(): boolean { + return this._debugEnabled; + } + + handle(action: EnableDebugModeAction): void { + this._debugEnabled = action.enable; + } + + @postConstruct() + protected init(): void { + this.debugFeedback = this.feedbackDispatcher.createEmitter(); + } + + setDebugEnabled(visible: boolean): void { + if (!visible) { + this.debugFeedback.dispose(); + } else { + this.debugFeedback + .add(EnableDebugModeAction.create({ enable: true }), EnableDebugModeAction.create({ enable: false })) + .submit(); + } + } + + toggleDebugEnabled(): void { + this.setDebugEnabled(!this._debugEnabled); + } +} diff --git a/packages/client/src/features/debug/debug-model.ts b/packages/client/src/features/debug/debug-model.ts new file mode 100644 index 0000000..cea8313 --- /dev/null +++ b/packages/client/src/features/debug/debug-model.ts @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, CommandExecutionContext, CommandReturn, GModelRoot, TYPES, hasBooleanProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { FeedbackCommand } from '../../base'; +import { addCssClasses, removeCssClasses } from '../../utils'; + +export interface EnableDebugModeAction extends Action { + kind: typeof EnableDebugModeAction.KIND; + enable: boolean; +} + +export namespace EnableDebugModeAction { + export const KIND = 'enableDebugMode'; + export const CSS_ROOT_CLASS = 'debug-mode'; + + export function is(object: any): object is EnableDebugModeAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'enable'); + } + + export function create(options: { enable: boolean }): EnableDebugModeAction { + return { + kind: EnableDebugModeAction.KIND, + ...options + }; + } +} + +@injectable() +export class EnableDebugModeCommand extends FeedbackCommand { + static readonly KIND = EnableDebugModeAction.KIND; + + constructor(@inject(TYPES.Action) protected readonly action: EnableDebugModeAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + return this.setDebugMode(context.root, this.action.enable); + } + + protected setDebugMode(root: GModelRoot, show: boolean): CommandReturn { + if (show) { + addCssClasses(root, [EnableDebugModeAction.CSS_ROOT_CLASS]); + } else { + removeCssClasses(root, [EnableDebugModeAction.CSS_ROOT_CLASS]); + } + return root; + } +} diff --git a/packages/client/src/features/debug/debug-module.ts b/packages/client/src/features/debug/debug-module.ts new file mode 100644 index 0000000..203558a --- /dev/null +++ b/packages/client/src/features/debug/debug-module.ts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { FeatureModule, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; +import '../../../css/debug.css'; +import { DebugBoundsDecorator } from './debug-bounds-decorator'; +import { DebugManager } from './debug-manager'; +import { EnableDebugModeAction, EnableDebugModeCommand } from './debug-model'; + +export const debugModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + + configureCommand(context, EnableDebugModeCommand); + + bindAsService(bind, TYPES.IDebugManager, DebugManager); + configureActionHandler(context, EnableDebugModeAction.KIND, DebugManager); + + bindAsService(context, TYPES.IVNodePostprocessor, DebugBoundsDecorator); +}); diff --git a/packages/client/src/features/debug/index.ts b/packages/client/src/features/debug/index.ts new file mode 100644 index 0000000..f63e8a6 --- /dev/null +++ b/packages/client/src/features/debug/index.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './debug-bounds-decorator'; +export * from './debug-manager'; +export * from './debug-model'; +export * from './debug-module'; diff --git a/packages/client/src/features/grid/grid-manager.ts b/packages/client/src/features/grid/grid-manager.ts new file mode 100644 index 0000000..29f2e9e --- /dev/null +++ b/packages/client/src/features/grid/grid-manager.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { IActionHandler, PropertiesOfType, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, postConstruct } from 'inversify'; +import { FeedbackEmitter, IFeedbackActionDispatcher } from '../../base'; +import { Grid } from './grid'; +import { ShowGridAction } from './grid-model'; + +export type GridStyle = Record & Partial>; + +@injectable() +export class GridManager implements IActionHandler { + protected _gridVisible: boolean = false; + protected gridFeedback: FeedbackEmitter; + + @inject(TYPES.IFeedbackActionDispatcher) + protected feedbackDispatcher: IFeedbackActionDispatcher; + + @inject(TYPES.Grid) + public readonly grid: Grid; + + get isGridVisible(): boolean { + return this._gridVisible; + } + + @postConstruct() + protected init(): void { + this.gridFeedback = this.feedbackDispatcher.createEmitter(); + } + + handle(action: ShowGridAction): void { + this._gridVisible = action.show; + } + + setGridVisible(visible: boolean): void { + if (!visible) { + this.gridFeedback.dispose(); + } else { + this.gridFeedback.add(ShowGridAction.create({ show: true }), ShowGridAction.create({ show: false })).submit(); + } + } + + toggleGridVisible(): void { + this.setGridVisible(!this._gridVisible); + } +} diff --git a/packages/client/src/features/grid/grid-model.ts b/packages/client/src/features/grid/grid-model.ts new file mode 100644 index 0000000..8be2ff0 --- /dev/null +++ b/packages/client/src/features/grid/grid-model.ts @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, CommandExecutionContext, CommandReturn, GModelRoot, TYPES, hasBooleanProp } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { FeedbackCommand } from '../../base'; +import { addCssClasses, removeCssClasses } from '../../utils'; + +export interface ShowGridAction extends Action { + kind: typeof ShowGridCommand.KIND; + show: boolean; +} + +export namespace ShowGridAction { + export const KIND = 'showGrid'; + export const CSS_ROOT_CLASS = 'grid-background'; + + export function is(object: any): object is ShowGridAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'show'); + } + + export function create(options: { show: boolean }): ShowGridAction { + return { + kind: ShowGridCommand.KIND, + ...options + }; + } +} + +@injectable() +export class ShowGridCommand extends FeedbackCommand { + static readonly KIND = ShowGridAction.KIND; + + constructor(@inject(TYPES.Action) protected readonly action: ShowGridAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + return this.setGrid(context.root, this.action.show); + } + + protected setGrid(root: GModelRoot, show: boolean): CommandReturn { + if (show) { + addCssClasses(root, [ShowGridAction.CSS_ROOT_CLASS]); + } else { + removeCssClasses(root, [ShowGridAction.CSS_ROOT_CLASS]); + } + return root; + } +} diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts new file mode 100644 index 0000000..44966bb --- /dev/null +++ b/packages/client/src/features/grid/grid-module.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2023 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { FeatureModule, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; +import '../../../css/grid.css'; +import { GridManager } from './grid-manager'; +import { ShowGridAction, ShowGridCommand } from './grid-model'; +import { GridSnapper } from './grid-snapper'; + +export const gridModule = new FeatureModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + + bind(TYPES.Grid).toConstantValue({ x: 10, y: 10 }); + + configureCommand(context, ShowGridCommand); + + bindAsService(bind, TYPES.IGridManager, GridManager); + configureActionHandler(context, ShowGridAction.KIND, GridManager); + + bind(TYPES.ISnapper).to(GridSnapper); +}); diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts new file mode 100644 index 0000000..6e99eae --- /dev/null +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2022-2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement } from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { GridSnapper } from './grid-snapper'; + +describe('GridSnapper', () => { + it('snap', () => { + const element = new GModelElement(); + const snapper = new GridSnapper(); + expect(snapper.snap({ x: 0, y: 0 }, element)).to.be.deep.equals({ x: 0, y: 0 }); + expect(snapper.snap({ x: 4, y: 5 }, element)).to.be.deep.equals({ x: 0, y: 10 }); + expect(snapper.snap({ x: 8, y: 11 }, element)).to.be.deep.equals({ x: 10, y: 10 }); + expect(snapper.snap({ x: -7, y: -4 }, element)).to.be.deep.equals({ x: -10, y: -0 }); + }); +}); diff --git a/packages/client/src/features/grid/grid-snapper.ts b/packages/client/src/features/grid/grid-snapper.ts new file mode 100644 index 0000000..210edf1 --- /dev/null +++ b/packages/client/src/features/grid/grid-snapper.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { CenterGridSnapper, GModelElement, ISnapper, Point, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { Grid } from './grid'; + +/** + * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. + * The default grid size is 10x10 pixel. + * To configure a custom grid size bind the `TYPES.ISnapper` service identifier + * to constant value, e.g: + * + * ```ts + * bind(TYPES.ISnapper).toConstantValue(new GridSnapper({ x: 25, y: 25 })); + * ``` + * + * or use the `Grid` to define the grid size more generically: + * ```ts + * bind(TYPES.Grid).toConstantValue({ x: 25, y: 25 }); + * bind(TYPES.ISnapper).to(GridSnapper); + * ``` + */ +@injectable() +export class GridSnapper implements ISnapper { + constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = { x: 10, y: 10 }) {} + + snap(position: Point, element: GModelElement): Point { + return Point.snapToGrid(position, this.grid); + } +} + +@injectable() +export class GLSPCenterGridSnapper extends CenterGridSnapper { + constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = { x: 10, y: 10 }) { + super(); + } + + override get gridX(): number { + return this.grid.x; + } + + override get gridY(): number { + return this.grid.y; + } +} diff --git a/packages/client/src/features/grid/grid.ts b/packages/client/src/features/grid/grid.ts new file mode 100644 index 0000000..2ef94be --- /dev/null +++ b/packages/client/src/features/grid/grid.ts @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Point } from '@eclipse-glsp/sprotty'; + +export type Grid = Point; diff --git a/packages/client/src/features/grid/index.ts b/packages/client/src/features/grid/index.ts new file mode 100644 index 0000000..16699e0 --- /dev/null +++ b/packages/client/src/features/grid/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './grid'; +export * from './grid-manager'; +export * from './grid-model'; +export * from './grid-module'; +export * from './grid-snapper'; diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index d503d22..a358c7c 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -19,6 +19,7 @@ import { FeedbackEmitter } from '../../base'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; +import { Grid } from '../grid'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { AlignmentElementFilter, @@ -47,7 +48,7 @@ export interface IHelperLineOptions { viewportLines?: ViewportLineType[]; /** * The minimum difference between two coordinates - * Defaults to 1. + * Defaults to 1 or zero (perfect match) if the optional grid module is loaded. */ alignmentEpsilon?: number; /** @@ -58,6 +59,7 @@ export interface IHelperLineOptions { /** * The minimum move delta that is necessary for an element to break through a helper line. * Defaults to { x: 1, y: 1 } whereas the x represents the horizontal distance and y represents the vertical distance. + * If the optional grid module is loaded, defaults to twice the grid size, i.e., two grid moves to break through a helper line. */ minimumMoveDelta?: Point; @@ -84,6 +86,7 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(SelectionService) protected selectionService: SelectionService; @optional() @inject(TYPES.IHelperLineOptions) protected userOptions?: IHelperLineOptions; + @optional() @inject(TYPES.Grid) protected grid?: Grid; protected options: Required; protected feedback: FeedbackEmitter; @@ -91,7 +94,12 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH @postConstruct() protected init(): void { this.feedback = this.feedbackDispatcher.createEmitter(); - this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...this.userOptions }; + const dynamicOptions: IHelperLineOptions = {}; + if (this.grid) { + dynamicOptions.alignmentEpsilon = 0; + dynamicOptions.minimumMoveDelta = Point.multiplyScalar(this.grid, 2); + } + this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...dynamicOptions, ...this.userOptions }; this.selectionService.onSelectionChanged(change => this.selectionChanged(change.root, change.selectedElements, change.deselectedElements) ); diff --git a/packages/client/src/features/index.ts b/packages/client/src/features/index.ts index 2afb595..8f8fad3 100644 --- a/packages/client/src/features/index.ts +++ b/packages/client/src/features/index.ts @@ -19,9 +19,11 @@ export * from './change-bounds'; export * from './command-palette'; export * from './context-menu'; export * from './copy-paste'; +export * from './debug'; export * from './decoration'; export * from './element-template'; export * from './export'; +export * from './grid'; export * from './helper-lines'; export * from './hints'; export * from './hover'; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index e2ccb21..968d53c 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -26,23 +26,27 @@ import { SetContextActions, SetModelAction, SetUIExtensionVisibilityAction, + TYPES, TriggerNodeCreationAction, UpdateModelAction, codiconCSSClasses, matchesKeystroke } from '@eclipse-glsp/sprotty'; -import { inject, injectable, postConstruct } from 'inversify'; +import { inject, injectable, optional, postConstruct } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; +import { DebugManager } from '../debug'; +import { GridManager } from '../grid'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; +import { OriginViewportAction } from '../viewport'; const CLICKED_CSS_CLASS = 'clicked'; const SEARCH_ICON_ID = 'search'; -const PALETTE_ICON_ID = 'symbol-color'; +const PALETTE_ICON_ID = 'tools'; const CHEVRON_DOWN_ICON_ID = 'chevron-right'; const PALETTE_HEIGHT = '500px'; @@ -74,6 +78,14 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, @inject(FocusTracker) protected focusTracker: FocusTracker; + @inject(TYPES.IGridManager) + @optional() + protected gridManager?: GridManager; + + @inject(TYPES.IDebugManager) + @optional() + protected debugManager?: DebugManager; + protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; protected dynamic = false; @@ -202,6 +214,19 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, const validateActionButton = this.createValidateButton(); headerTools.appendChild(validateActionButton); + const resetViewportButton = this.createResetViewportButton(); + headerTools.appendChild(resetViewportButton); + + if (this.gridManager) { + const toggleGridButton = this.createToggleGridButton(); + headerTools.appendChild(toggleGridButton); + } + + if (this.debugManager) { + const toggleDebugButton = this.createToggleDebugButton(); + headerTools.appendChild(toggleDebugButton); + } + // Create button for Search const searchIcon = this.createSearchButton(); headerTools.appendChild(searchIcon); @@ -250,6 +275,58 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, return validateActionButton; } + protected createResetViewportButton(): HTMLElement { + const resetViewportButton = createIcon('screen-normal'); + resetViewportButton.title = 'Reset Viewport'; + resetViewportButton.onclick = _event => { + this.actionDispatcher.dispatch(OriginViewportAction.create()); + resetViewportButton.focus(); + }; + resetViewportButton.ariaLabel = resetViewportButton.title; + resetViewportButton.tabIndex = 1; + return resetViewportButton; + } + + protected createToggleGridButton(): HTMLElement { + const toggleGridButton = createIcon('symbol-numeric'); + toggleGridButton.title = 'Toggle Grid'; + toggleGridButton.onclick = () => { + if (this.gridManager?.isGridVisible) { + toggleGridButton.classList.remove(CLICKED_CSS_CLASS); + this.gridManager?.setGridVisible(false); + } else { + toggleGridButton.classList.add(CLICKED_CSS_CLASS); + this.gridManager?.setGridVisible(true); + } + }; + if (this.gridManager?.isGridVisible) { + toggleGridButton.classList.add(CLICKED_CSS_CLASS); + } + toggleGridButton.ariaLabel = toggleGridButton.title; + toggleGridButton.tabIndex = 1; + return toggleGridButton; + } + + protected createToggleDebugButton(): HTMLElement { + const toggleDebugButton = createIcon('debug'); + toggleDebugButton.title = 'Debug Mode'; + toggleDebugButton.onclick = () => { + if (this.debugManager?.isDebugEnabled) { + toggleDebugButton.classList.remove(CLICKED_CSS_CLASS); + this.debugManager?.setDebugEnabled(false); + } else { + toggleDebugButton.classList.add(CLICKED_CSS_CLASS); + this.debugManager?.setDebugEnabled(true); + } + }; + if (this.debugManager?.isDebugEnabled) { + toggleDebugButton.classList.add(CLICKED_CSS_CLASS); + } + toggleDebugButton.ariaLabel = toggleDebugButton.title; + toggleDebugButton.tabIndex = 1; + return toggleDebugButton; + } + protected createSearchButton(): HTMLElement { const searchIcon = createIcon(SEARCH_ICON_ID); searchIcon.onclick = _ev => { @@ -275,7 +352,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, searchField.classList.add('search-input'); searchField.id = this.containerElement.id + '_search_field'; searchField.type = 'text'; - searchField.placeholder = ' Search...'; + searchField.placeholder = 'Search...'; searchField.style.display = 'none'; searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); searchField.onkeydown = ev => this.clearOnEscape(ev); diff --git a/packages/client/src/features/viewport/index.ts b/packages/client/src/features/viewport/index.ts index 852ad19..0ccb5ef 100644 --- a/packages/client/src/features/viewport/index.ts +++ b/packages/client/src/features/viewport/index.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './glsp-scroll-mouse-listener'; +export * from './origin-viewport'; export * from './reposition'; export * from './viewport-handler'; export * from './viewport-modules'; diff --git a/packages/client/src/features/viewport/origin-viewport.ts b/packages/client/src/features/viewport/origin-viewport.ts new file mode 100644 index 0000000..7c621a4 --- /dev/null +++ b/packages/client/src/features/viewport/origin-viewport.ts @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action, Bounds, BoundsAwareViewportCommand, GModelRoot, TYPES, Viewport, isViewport, limitViewport } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; + +export interface OriginViewportAction extends Action { + kind: typeof OriginViewportAction.KIND; + animate: boolean; +} + +export namespace OriginViewportAction { + export const KIND = 'originViewport'; + + export function is(object: any): object is OriginViewportAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { animate?: boolean } = {}): OriginViewportAction { + return { + kind: KIND, + animate: true, + ...options + }; + } +} + +@injectable() +export class OriginViewportCommand extends BoundsAwareViewportCommand { + static readonly KIND = OriginViewportAction.KIND; + + constructor(@inject(TYPES.Action) protected action: OriginViewportAction) { + super(action.animate); + } + + getElementIds(): string[] { + return []; + } + + protected override initialize(model: GModelRoot): void { + if (!isViewport(model)) { + return; + } + this.oldViewport = { scroll: model.scroll, zoom: model.zoom }; + const newViewport = this.getNewViewport(Bounds.EMPTY, model); + if (newViewport) { + const { zoomLimits, horizontalScrollLimits, verticalScrollLimits } = this.viewerOptions; + this.newViewport = limitViewport(newViewport, model.canvasBounds, horizontalScrollLimits, verticalScrollLimits, zoomLimits); + } + } + + getNewViewport(_bounds: Bounds, _model: GModelRoot): Viewport | undefined { + return { zoom: 1, scroll: { x: 0, y: 0 } }; + } +} diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 6947078..e2ea673 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -27,10 +27,11 @@ import { ZoomMouseListener } from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; +import { FocusDomAction } from '../accessibility/actions'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; -import { RestoreViewportHandler } from './viewport-handler'; +import { OriginViewportCommand } from './origin-viewport'; import { RepositionCommand } from './reposition'; -import { FocusDomAction } from '../accessibility/actions'; +import { RestoreViewportHandler } from './viewport-handler'; export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { const context = { bind, isBound }; @@ -39,6 +40,7 @@ export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { configureCommand(context, GetViewportCommand); configureCommand(context, SetViewportCommand); configureCommand(context, RepositionCommand); + configureCommand(context, OriginViewportCommand); bindAsService(context, TYPES.MouseListener, ZoomMouseListener); bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index db89de6..3e198cf 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -27,9 +27,9 @@ import { GCompartment, GCompartmentView, GForeignObjectElement, - GGraphView, GHtmlRoot, GLabel, + GLabelView, GNode, GPort, GPreRenderedElement, @@ -41,17 +41,17 @@ import { PreRenderedView, RectangularNode, RectangularNodeView, - GLabelView, SvgViewportView, configureModelElement, moveFeature, selectFeature } from '@eclipse-glsp/sprotty'; import { GIssueMarker } from '../features/validation/issue-marker'; +import { GEdge, GGraph } from '../model'; import { GEdgeView } from './gedge-view'; +import { GGraphView } from './ggraph-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; -import { GEdge, GGraph } from '../model'; export const baseViewModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; diff --git a/packages/client/src/views/ggraph-view.tsx b/packages/client/src/views/ggraph-view.tsx new file mode 100644 index 0000000..7c2c9f1 --- /dev/null +++ b/packages/client/src/views/ggraph-view.tsx @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Bounds, Dimension, Point, RenderingContext, SGraphImpl, SGraphView, TYPES, Writable } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { VNode } from 'snabbdom'; +import { GridManager, GridStyle } from '../features'; + +@injectable() +export class GGraphView extends SGraphView { + @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; + + override render(model: Readonly, context: RenderingContext): VNode { + const graph = super.render(model, context); + if (graph.data) { + graph.data.style = { ...graph.data.style, ...this.getGridStyle(model, context) }; + } + return graph; + } + + protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { + if (!this.gridManager?.isGridVisible) { + return {}; + } + const bounds = this.getBackgroundBounds(model, context, this.gridManager); + return { + backgroundPosition: `${bounds.x}px ${bounds.y}px`, + backgroundSize: `${bounds.width}px ${bounds.height}px`, + // we do not set the background image directly in the style object, because we want to toggle it on and off via CSS + '--grid-background-image': this.getBackgroundImage(model, context, this.gridManager) + }; + } + + protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: GridManager): Writable { + const position = Point.multiplyScalar(Point.subtract(gridManager.grid, viewport.scroll), viewport.zoom); + const size = Dimension.fromPoint(Point.multiplyScalar(gridManager.grid, viewport.zoom)); + return { ...position, ...size }; + } + + protected getBackgroundImage(model: Readonly, context: RenderingContext, gridManager: GridManager): string { + // eslint-disable-next-line max-len + return `url('data:image/svg+xml;utf8, ')`; + } +} diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 2837cb5..69ee9ce 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -16,19 +16,25 @@ import { Bounds, + Dimension, EdgeRouterRegistry, GViewportRootElement, IViewArgs, + Point, ProjectedViewportView, ProjectionParams, RenderingContext, + SGraphImpl, + TYPES, ViewProjection, + Writable, html, setAttr, setClass } from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, optional } from 'inversify'; import { VNode, VNodeStyle, h } from 'snabbdom'; +import { GridManager, GridStyle } from '../features'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: html }; @@ -39,15 +45,12 @@ const JSX = { createElement: html }; @injectable() export class GLSPProjectionView extends ProjectedViewportView { @inject(EdgeRouterRegistry) edgeRouterRegistry: EdgeRouterRegistry; + @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { - const svgElem = this.renderSvg(model, context, args); - if (svgElem.data) { - svgElem.data!.class = { 'sprotty-graph': true }; - } const rootNode: VNode = (
- {svgElem} + {this.renderSvg(model, context, args)} {this.renderProjections(model, context, args)}
); @@ -63,12 +66,36 @@ export class GLSPProjectionView extends ProjectedViewportView { const ns = 'http://www.w3.org/2000/svg'; const svg = h( 'svg', - { ns, style: { height: '100%' } }, + { ns, style: { height: '100%', ...this.getGridStyle(model, context) }, class: { 'sprotty-graph': true } }, h('g', { ns, attrs: { transform } }, context.renderChildren(model, { edgeRouting })) ); return svg; } + protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { + if (!this.gridManager?.isGridVisible) { + return {}; + } + const bounds = this.getBackgroundBounds(model, context, this.gridManager); + return { + backgroundPosition: `${bounds.x}px ${bounds.y}px`, + backgroundSize: `${bounds.width}px ${bounds.height}px`, + // we do not set the background image directly in the style object, because we want to toggle it on and off via CSS + '--grid-background-image': this.getBackgroundImage(model, context, this.gridManager) + }; + } + + protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: GridManager): Writable { + const position = Point.multiplyScalar(Point.subtract(gridManager.grid, viewport.scroll), viewport.zoom); + const size = Dimension.fromPoint(Point.multiplyScalar(gridManager.grid, viewport.zoom)); + return { ...position, ...size }; + } + + protected getBackgroundImage(model: Readonly, context: RenderingContext, gridManager: GridManager): string { + // eslint-disable-next-line max-len + return `url('data:image/svg+xml;utf8, ')`; + } + protected override renderProjectionBar( projections: ViewProjection[], model: Readonly, diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts index 97690b3..29e3d75 100644 --- a/packages/client/src/views/index.ts +++ b/packages/client/src/views/index.ts @@ -16,6 +16,7 @@ export * from './base-view-module'; export * from './compartments'; export * from './gedge-view'; +export * from './ggraph-view'; export * from './glsp-projection-view'; export * from './issue-marker-view'; export * from './rounded-corner'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index e5d0a5c..5978e58 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -266,12 +266,12 @@ export { SBezierControlHandleView as GBezierControlHandleView, SBezierCreateHandleView as GBezierCreateHandleView, SCompartmentView as GCompartmentView, - SGraphView as GGraphView, SLabelView as GLabelView, SRoutingHandleView as GRoutingHandleView, JumpingPolylineEdgeView, PolylineEdgeView, - PolylineEdgeViewWithGapsOnIntersections + PolylineEdgeViewWithGapsOnIntersections, + SGraphView } from 'sprotty/lib/graph/views'; // ------------------ Library ------------------ diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index bb796d5..1b018bd 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -47,5 +47,8 @@ export const TYPES = { ILocalElementNavigator: Symbol('ILocalElementNavigator'), IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), - IToolManager: Symbol('IToolManager') + IToolManager: Symbol('IToolManager'), + IDebugManager: Symbol('IDebugManager'), + Grid: Symbol('Grid'), + IGridManager: Symbol('IGridManager') }; diff --git a/packages/protocol/src/sprotty-geometry-dimension.spec.ts b/packages/protocol/src/sprotty-geometry-dimension.spec.ts index 02f0be6..1ed63d6 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.spec.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.spec.ts @@ -84,4 +84,12 @@ describe('Dimension', () => { expect(isEqual).to.be.false; }); }); + + describe('fromPoint', () => { + it('should create a new dimension from the given point', () => { + const point = { x: 10, y: 20 }; + const dimension = Dimension.fromPoint(point); + expect(dimension).to.deep.equal({ width: 10, height: 20 }); + }); + }); }); diff --git a/packages/protocol/src/sprotty-geometry-dimension.ts b/packages/protocol/src/sprotty-geometry-dimension.ts index e3a1ccd..c9582c2 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.ts @@ -19,6 +19,11 @@ import { Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Dimension { + /** + * The smallest valid dimension with width, and height set to 0. + */ + const ZERO: Dimension; + /** * Applies the given function to the `width` and `height` of the given dimensional object to create a new dimensional object. * @@ -75,9 +80,21 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * @returns true if the dimensions are equal, false otherwise */ function equals(left: Dimension, right: Dimension): boolean; + + /** + * Creates a new dimension from the given point. The `width` and `height` of the new dimension are the `x` and `y` of the point. + * @param point the point + * @returns new dimension + */ + function fromPoint(point: Point): Dimension; } } +(Dimension as any).ZERO = Object.freeze({ + width: 0, + height: 0 +}); + Dimension.center = (d: Dimension): Point => ({ x: d.width * 0.5, y: d.height * 0.5 }); Dimension.add = (d: Dimension, a: Dimension): Dimension => ({ width: d.width + a.width, height: d.height + a.height }); Dimension.subtract = (d: Dimension, a: Dimension): Dimension => ({ width: d.width - a.width, height: d.height - a.height }); @@ -90,5 +107,6 @@ Dimension.map = (dimension: T, callbackfn: (value: number, height: callbackfn(dimension.height, 'height') }); Dimension.equals = (left: Dimension, right: Dimension): boolean => left.width === right.width && left.height === right.height; +Dimension.fromPoint = (point: Point): Dimension => ({ width: point.x, height: point.y }); export { Dimension }; diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index e979ae7..9d5d740 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -49,6 +49,16 @@ export interface Constructor { // eslint-disable-next-line @typescript-eslint/ban-types export type Writable = { -readonly [P in keyof T]: Writable }; +/** + * Utility type to extract all key of type `V` from a given type `T`. + */ +export type KeysOfType = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]; + +/** + * Utility type to extract all properties of type `V` from a given type `T`. + */ +export type PropertiesOfType = Pick>; + /** * Utility type to describe a value as might be provided as a promise. */ From 029f164876edea9dd224974b994e57117061e60f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 16 May 2024 10:18:08 +0200 Subject: [PATCH 331/566] GLSP-1309: Improve error handling of startup hooks (#346) - Refactor `DiagramLoader` to catch and log errors that occur when invoking startup hooks - This ensures both: failure of one hook does not affect execution of following hooks and if a hook fails for any reason we provide the reason in the log output - Refactor `RestoreViewportHandler` to use the actual dom id of the root element for querying/focusing the graph. This ensures that the focusing also works if the root model element is not of type `graph`. Fixes https://github.com/eclipse-glsp/glsp/issues/1309 --- .../client/src/base/model/diagram-loader.ts | 6 +++++- .../src/features/viewport/viewport-handler.ts | 20 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index e9255d0..473fc67 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -192,7 +192,11 @@ export class DiagramLoader { protected async invokeStartupHook(hook: keyof Omit): Promise { for (const startup of this.diagramStartups) { - await startup[hook]?.(); + try { + await startup[hook]?.(); + } catch (err) { + console.error(`Error invoking diagram startup hook '${hook}':`, '\n', err); + } } } diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 44bc751..986dd55 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, IActionHandler, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; +import { Action, DOMHelper, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../../base'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; @@ -25,20 +26,26 @@ import { FocusDomAction } from '../accessibility/actions'; */ @injectable() export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { - protected readonly graphSelector = '[data-svg-metadata-type="graph"]'; - - @inject(TYPES.ViewerOptions) - protected options: ViewerOptions; + @inject(TYPES.DOMHelper) + protected domHelper: DOMHelper; @inject(FocusTracker) protected focusTracker: FocusTracker; + @inject(EditorContextService) + protected editorContext: EditorContextService; + handle(action: Action): void | Action { if (EnableDefaultToolsAction.is(action) || (FocusDomAction.is(action) && action.id === 'graph')) { this.focusGraph(); } } + get graphSelector(): string { + const rootId = this.domHelper.createUniqueDOMElementId(this.editorContext.modelRoot); + return `#${rootId}`; + } + async postRequestModel(): Promise { await this.waitForElement(this.graphSelector); this.focusGraph(); @@ -51,7 +58,6 @@ export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { } } - // https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists protected waitForElement(selector: string): Promise { return new Promise(resolve => { if (document.querySelector(selector)) { From c08d97f7f787eacc9486bfbb93c29af93c6e0c26 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 16 May 2024 12:31:58 +0000 Subject: [PATCH 332/566] Improve handling of change bounds in several areas (#344) * Improve handling of change bounds - Introduce change bounds manager to centralize bounds-related services -- Bounds changes through position snapping and movement restriction -- Validation for size and position of an element -- Customizable methods for when to use move and resize options - Introduce change bounds tracker for moves and resizes -- Tracker calculates move on diagram and calculates move and resizes -- Tracker supports options on which parts of the process are applied - Provide moveable wrappers for resize and routing handles Fixes https://github.com/eclipse-glsp/glsp/issues/1337 - Extend current resize capabilities -- Introduce mode for symmetric resize -- Introduce one-dimensional resize on top, right, bottom and left side Fixes https://github.com/eclipse-glsp/glsp/issues/1338 Fixes https://github.com/eclipse-glsp/glsp/issues/1339 - Fix elements moving during resizing when hitting minimum bounds -- Store calculated minimum size from layouter in element -- Adapt resize so we do not produce invalid sized bounds Fixes https://github.com/eclipse-glsp/glsp/issues/1340 Minor: - Ensure we get proper cursor feedback when hovering over resize handle - Add additional convenience functions - Add origin viewport command for convenience Contributed on behalf of Axon Ivy AG --- packages/client/css/change-bounds.css | 38 +- packages/client/css/glsp-sprotty.css | 20 +- packages/client/css/helper-lines.css | 6 +- packages/client/src/base/default.module.ts | 5 +- .../client/src/base/feedback/css-feedback.ts | 12 +- .../src/base/feedback/feeback-emitter.ts | 9 +- packages/client/src/base/index.ts | 1 + .../client/src/base/mouse-position-tracker.ts | 25 + .../view-key-tools/movement-key-tool.ts | 19 +- .../src/features/bounds/bounds-module.ts | 2 + .../src/features/bounds/freeform-layout.ts | 22 +- .../bounds/glsp-hidden-bounds-updater.ts | 109 +++-- .../client/src/features/bounds/hbox-layout.ts | 27 +- packages/client/src/features/bounds/index.ts | 1 + .../client/src/features/bounds/layout-data.ts | 48 ++ .../src/features/bounds/local-bounds.ts | 24 +- .../client/src/features/bounds/vbox-layout.ts | 28 +- .../src/features/change-bounds/index.ts | 1 + .../src/features/change-bounds/model.ts | 108 ++++- .../change-bounds/movement-restrictor.ts | 10 + .../point-position-updater.spec.ts | 2 + .../change-bounds/point-position-updater.ts | 6 + .../change-bounds/position-snapper.ts | 4 + .../client/src/features/change-bounds/snap.ts | 6 + .../src/features/change-bounds/tracker.ts | 63 +++ .../features/debug/debug-bounds-decorator.tsx | 24 +- ...ouse-tracking-element-position-listener.ts | 120 ++--- .../helper-lines/helper-line-feedback.ts | 3 +- .../helper-line-manager-default.ts | 39 +- .../helper-lines/helper-line-manager.ts | 13 +- .../src/features/routing/edge-router.ts | 42 ++ .../features/select/select-mouse-listener.ts | 16 +- .../client/src/features/tools/base-tools.ts | 50 +- .../change-bounds/change-bounds-manager.ts | 145 ++++++ .../change-bounds-tool-feedback.ts | 23 +- .../change-bounds-tool-module.ts | 2 + .../change-bounds-tool-move-feedback.ts | 112 ++--- .../tools/change-bounds/change-bounds-tool.ts | 237 ++++----- .../change-bounds/change-bounds-tracker.ts | 455 ++++++++++++++++++ .../src/features/tools/change-bounds/index.ts | 2 + .../src/features/tools/change-bounds/view.tsx | 16 +- .../edge-edit/edge-edit-tool-feedback.ts | 86 ++-- .../tools/edge-edit/edge-edit-tool.ts | 7 +- .../marquee-selection/marquee-mouse-tool.ts | 4 + .../tools/node-creation/node-creation-tool.ts | 14 +- packages/client/src/utils/gmodel-util.ts | 46 +- packages/client/src/utils/layout-utils.ts | 4 + packages/glsp-sprotty/src/types.ts | 3 +- .../src/action-protocol/model-layout.ts | 8 +- .../protocol/src/action-protocol/types.ts | 24 + .../src/sprotty-geometry-bounds.spec.ts | 34 +- .../protocol/src/sprotty-geometry-bounds.ts | 42 +- .../src/sprotty-geometry-dimension.spec.ts | 29 ++ .../src/sprotty-geometry-dimension.ts | 15 +- .../src/sprotty-geometry-point.spec.ts | 14 + .../protocol/src/sprotty-geometry-point.ts | 16 + packages/protocol/src/utils/index.ts | 1 + packages/protocol/src/utils/math-util.ts | 19 + packages/protocol/src/utils/type-util.ts | 5 + 59 files changed, 1741 insertions(+), 525 deletions(-) create mode 100644 packages/client/src/base/mouse-position-tracker.ts create mode 100644 packages/client/src/features/bounds/layout-data.ts create mode 100644 packages/client/src/features/change-bounds/tracker.ts create mode 100644 packages/client/src/features/tools/change-bounds/change-bounds-manager.ts create mode 100644 packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts create mode 100644 packages/protocol/src/utils/math-util.ts diff --git a/packages/client/css/change-bounds.css b/packages/client/css/change-bounds.css index 6ba3fce..099647d 100644 --- a/packages/client/css/change-bounds.css +++ b/packages/client/css/change-bounds.css @@ -2,14 +2,48 @@ cursor: nw-resize; } +.sprotty-resize-handle[data-kind='top'] { + cursor: n-resize; +} + .sprotty-resize-handle[data-kind='top-right'] { cursor: ne-resize; } -.sprotty-resize-handle[data-kind='bottom-left'] { - cursor: sw-resize; +.sprotty-resize-handle[data-kind='right'] { + cursor: e-resize; } .sprotty-resize-handle[data-kind='bottom-right'] { cursor: se-resize; } + +.sprotty-resize-handle[data-kind='bottom'] { + cursor: s-resize; +} + +.sprotty-resize-handle[data-kind='bottom-left'] { + cursor: sw-resize; +} + +.sprotty-resize-handle[data-kind='left'] { + cursor: w-resize; +} + +.sprotty-resize-handle.resize-not-allowed { + fill: var(--glsp-error-foreground); +} + +.sprotty g .resize-not-allowed > .sprotty-node { + stroke: var(--glsp-error-foreground); + stroke-width: 1.5px; +} + +.move-mode .sprotty-projection-bar, +.resize-mode .sprotty-projection-bar { + /** + * We are using mouse events (offsetX, offsetY) in the GLSPMousePositionTracker to calculate the diagram position relative to the parent. + * Other elements result in relative coordinates different from the graph and will therefore interfere with the correct position calculation. + */ + pointer-events: none; +} diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index e2b2eb4..7f678a4 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -152,18 +152,34 @@ cursor: nw-resize; } +.sprotty .resize-w-mode { + cursor: n-resize; +} + .sprotty .resize-ne-mode { cursor: ne-resize; } -.sprotty .resize-sw-mode { - cursor: sw-resize; +.sprotty .resize-e-mode { + cursor: e-resize; } .sprotty .resize-se-mode { cursor: se-resize; } +.sprotty .resize-s-mode { + cursor: s-resize; +} + +.sprotty .resize-sw-mode { + cursor: sw-resize; +} + +.sprotty .resize-w-mode { + cursor: w-resize; +} + .sprotty .element-deletion-mode { cursor: pointer; } diff --git a/packages/client/css/helper-lines.css b/packages/client/css/helper-lines.css index 07a7de0..d83ccb3 100644 --- a/packages/client/css/helper-lines.css +++ b/packages/client/css/helper-lines.css @@ -16,8 +16,8 @@ .helper-line { pointer-events: none; - stroke: red; - stroke-width: 1px; + stroke: #1d80d1; + stroke-width: 1; opacity: 1; } @@ -28,6 +28,6 @@ stroke-linejoin: miter; stroke-linecap: round; stroke: darkblue; - stroke-width: 1px; + stroke-width: 0.5; stroke-dasharray: 2; } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 8025989..6c4d36c 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -18,6 +18,7 @@ import { FeatureModule, KeyTool, LocationPostprocessor, + MousePositionTracker, MouseTool, MoveCommand, SetDirtyStateAction, @@ -47,6 +48,7 @@ import { DiagramLoader } from './model/diagram-loader'; import { GLSPModelSource } from './model/glsp-model-source'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model/model-initialization-constraint'; import { GModelRegistry } from './model/model-registry'; +import { GLSPMousePositionTracker } from './mouse-position-tracker'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; @@ -85,7 +87,8 @@ export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, . bind(GLSPMouseTool).toSelf().inSingletonScope(); bindOrRebind(context, MouseTool).toService(GLSPMouseTool); bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); - + bind(GLSPMousePositionTracker).toSelf().inSingletonScope(); + bindOrRebind(context, MousePositionTracker).toService(GLSPMousePositionTracker); bind(GLSPKeyTool).toSelf().inSingletonScope(); bindOrRebind(context, KeyTool).toService(GLSPKeyTool); bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index a9e268d..713b44f 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -85,14 +85,18 @@ export enum CursorCSS { RESIZE_NESW = 'resize-nesw-mode', RESIZE_NWSE = 'resize-nwse-mode', RESIZE_NW = 'resize-nw-mode', + RESIZE_N = 'resize-n-mode', RESIZE_NE = 'resize-ne-mode', - RESIZE_SW = 'resize-sw-mode', + RESIZE_E = 'resize-e-mode', RESIZE_SE = 'resize-se-mode', + RESIZE_S = 'resize-s-mode', + RESIZE_SW = 'resize-sw-mode', + RESIZE_W = 'resize-w-mode', MOVE = 'move-mode', MARQUEE = 'marquee-mode' } -export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAction { +export function cursorFeedbackAction(cursorCss?: string): ModifyCSSFeedbackAction { const add = []; if (cursorCss) { add.push(cursorCss); @@ -107,3 +111,7 @@ export function applyCssClasses(element: GModelElement, ...add: string[]): Modif export function deleteCssClasses(element: GModelElement, ...remove: string[]): ModifyCSSFeedbackAction { return ModifyCSSFeedbackAction.create({ elements: [element], remove }); } + +export function toggleCssClasses(element: GModelElement, add: boolean, ...cssClasses: string[]): ModifyCSSFeedbackAction { + return add ? applyCssClasses(element, ...cssClasses) : deleteCssClasses(element, ...cssClasses); +} diff --git a/packages/client/src/base/feedback/feeback-emitter.ts b/packages/client/src/base/feedback/feeback-emitter.ts index 53971ed..0833f3f 100644 --- a/packages/client/src/base/feedback/feeback-emitter.ts +++ b/packages/client/src/base/feedback/feeback-emitter.ts @@ -32,9 +32,12 @@ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { * once the {@link submit} method is called. * * @param action feedback action - * @param cleanupAction action that undoes the feedback action. This is only triggered when {@link revert} is called. + * @param cleanupAction action that undoes the feedback action. This is only triggered when {@link revert} or {@link dispose} is called. */ - add(action: Action, cleanupAction?: MaybeActions): this { + add(action?: Action, cleanupAction?: MaybeActions): this { + if (!action && !cleanupAction) { + return this; + } const idx = this.feedbackActions.length; this.feedbackActions[idx] = action; if (cleanupAction) { @@ -73,7 +76,7 @@ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { * Registers any pending actions as feedback. Any previously submitted feedback becomes invalid. */ submit(): this { - // with 'arrayOf' we skip undefined entries that are created for non-cleanup actions + // with 'arrayOf' we skip undefined entries that are created for non-cleanup actions or cleanup-only actions const actions = arrayOf(...this.feedbackActions); const cleanupActions = arrayOf(...this.cleanupActions); this.deregistration = this.feedbackDispatcher.registerFeedback(this, actions, () => cleanupActions.flatMap(MaybeActions.asArray)); diff --git a/packages/client/src/base/index.ts b/packages/client/src/base/index.ts index 70cd9e3..cc79046 100644 --- a/packages/client/src/base/index.ts +++ b/packages/client/src/base/index.ts @@ -24,6 +24,7 @@ export * from './editor-context-service'; export * from './feedback'; export * from './focus'; export * from './model'; +export * from './mouse-position-tracker'; export * from './ranked'; export * from './selection-clearing-mouse-listener'; export * from './selection-service'; diff --git a/packages/client/src/base/mouse-position-tracker.ts b/packages/client/src/base/mouse-position-tracker.ts new file mode 100644 index 0000000..f66fcf6 --- /dev/null +++ b/packages/client/src/base/mouse-position-tracker.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + rank: number; + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MousePositionTracker } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { Ranked } from './ranked'; + +@injectable() +export class GLSPMousePositionTracker extends MousePositionTracker implements Ranked { + /* we want to be executed before all default mouse listeners since we are just tracking the position and others may need it */ + rank = Ranked.DEFAULT_RANK - 200; +} diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 1649791..f48f32f 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -20,8 +20,8 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; -import { unsnapModifier, useSnap } from '../../change-bounds/snap'; import { Grid } from '../../grid'; +import { ChangeBoundsManager } from '../../tools'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; @@ -40,7 +40,8 @@ export class MovementKeyTool implements Tool { @inject(SelectionService) selectionService: SelectionService; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; - @optional() @inject(TYPES.Grid) protected grid: Grid; + @inject(TYPES.Grid) @optional() protected grid: Grid; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; get id(): string { return MovementKeyTool.ID; @@ -86,7 +87,7 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); - const snap = useSnap(event); + const snap = this.tool.changeBoundsManager.usePositionSnap(event); const offsetX = snap ? this.grid.x : 1; const offsetY = snap ? this.grid.y : 1; @@ -115,18 +116,22 @@ export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcu } protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowUp') || matchesKeystroke(event, 'ArrowUp', unsnapModifier()); + const unsnap = this.tool.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowUp') || (!!unsnap && matchesKeystroke(event, 'ArrowUp', unsnap)); } protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowDown') || matchesKeystroke(event, 'ArrowDown', unsnapModifier()); + const unsnap = this.tool.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowDown') || (!!unsnap && matchesKeystroke(event, 'ArrowDown', unsnap)); } protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowRight') || matchesKeystroke(event, 'ArrowRight', unsnapModifier()); + const unsnap = this.tool.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowRight') || (!!unsnap && matchesKeystroke(event, 'ArrowRight', unsnap)); } protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'ArrowLeft') || matchesKeystroke(event, 'ArrowLeft', unsnapModifier()); + const unsnap = this.tool.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowLeft') || (!!unsnap && matchesKeystroke(event, 'ArrowLeft', unsnap)); } } diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index cf86680..0cd9891 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -52,5 +52,7 @@ export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); + // backwards compatibility + // eslint-disable-next-line deprecation/deprecation bind(PositionSnapper).toSelf(); }); diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 8f6b684..0cdfe78 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { AbstractLayout, AbstractLayoutOptions, @@ -21,11 +20,13 @@ import { BoundsData, Dimension, GChildElement, + GParentElement, LayoutContainer, Point, - GParentElement, StatefulLayouter } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { LayoutAware } from './layout-data'; /** * Layouts children of a container with explicit X/Y positions @@ -44,9 +45,11 @@ export class FreeFormLayouter extends AbstractLayout { const maxWidth = childrenSize.width > 0 ? childrenSize.width + options.paddingLeft + options.paddingRight : 0; const maxHeight = childrenSize.height > 0 ? childrenSize.height + options.paddingTop + options.paddingBottom : 0; - if (maxWidth > 0 && maxHeight > 0) { + if (childrenSize.width > 0 && childrenSize.height > 0) { const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight); - boundsData.bounds = this.getFinalContainerBounds(container, offset, options, maxWidth, maxHeight); + const computed = this.getComputedContainerDimensions(options, childrenSize.width, childrenSize.height); + LayoutAware.setComputedDimensions(boundsData, computed); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, computed.width, computed.height); boundsData.boundsChanged = true; } else { boundsData.bounds = { x: boundsData.bounds!.x, y: boundsData.bounds!.y, width: 0, height: 0 }; @@ -96,6 +99,13 @@ export class FreeFormLayouter extends AbstractLayout { return currentOffset; } + protected getComputedContainerDimensions(options: AbstractLayoutOptions, maxWidth: number, maxHeight: number): Dimension { + return { + width: maxWidth + options.paddingLeft + options.paddingRight, + height: maxHeight + options.paddingTop + options.paddingBottom + }; + } + protected override getFinalContainerBounds( container: GParentElement & LayoutContainer, lastOffset: Point, @@ -106,8 +116,8 @@ export class FreeFormLayouter extends AbstractLayout { const result = { x: container.bounds.x, y: container.bounds.y, - width: Math.max(options.minWidth, maxWidth + options.paddingLeft + options.paddingRight), - height: Math.max(options.minHeight, maxHeight + options.paddingTop + options.paddingBottom) + width: Math.max(options.minWidth, maxWidth), + height: Math.max(options.minHeight, maxHeight) }; return result; diff --git a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts index 36453de..44fad42 100644 --- a/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts +++ b/packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts @@ -18,21 +18,29 @@ import { Action, BoundsData, ComputedBoundsAction, - Deferred, EdgeRouterRegistry, + ElementAndAlignment, + ElementAndBounds, + ElementAndLayoutData, ElementAndRoutingPoints, + GChildElement, GModelElement, HiddenBoundsUpdater, - IActionDispatcher, + LayoutData, ModelIndexImpl, - RequestAction, - ResponseAction + RequestBoundsAction, + isLayoutContainer } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { BoundsAwareModelElement, calcElementAndRoute, getDescendantIds, isRoutable } from '../../utils/gmodel-util'; +import { LayoutAware } from './layout-data'; import { LocalComputedBoundsAction, LocalRequestBoundsAction } from './local-bounds'; +export class BoundsDataExt extends BoundsData { + layoutData?: LayoutData; +} + /** * Grabs the bounds from hidden SVG DOM elements, applies layouts, collects routes and fires {@link ComputedBoundsAction}s. * @@ -44,7 +52,7 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { protected element2route: ElementAndRoutingPoints[] = []; - protected getElement2BoundsData(): Map { + protected getElement2BoundsData(): Map { return this['element2boundsData']; } @@ -60,10 +68,56 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { if (LocalRequestBoundsAction.is(cause) && cause.elementIDs) { this.focusOnElements(cause.elementIDs); } - const actions = this.captureActions(() => super.postUpdate(cause)); - actions - .filter(action => ComputedBoundsAction.is(action)) - .forEach(action => this.actionDispatcher.dispatch(this.enhanceAction(action as ComputedBoundsAction, cause))); + + // collect bounds and layout data in element2BoundsData + this.getBoundsFromDOM(); + this.layouter.layout(this.getElement2BoundsData()); + + // prepare data for action + const resizes: ElementAndBounds[] = []; + const alignments: ElementAndAlignment[] = []; + const layoutData: ElementAndLayoutData[] = []; + this.getElement2BoundsData().forEach((boundsData, element) => { + if (boundsData.boundsChanged && boundsData.bounds !== undefined) { + const resize: ElementAndBounds = { + elementId: element.id, + newSize: { + width: boundsData.bounds.width, + height: boundsData.bounds.height + } + }; + // don't copy position if the element is layouted by the server + if (element instanceof GChildElement && isLayoutContainer(element.parent)) { + resize.newPosition = { + x: boundsData.bounds.x, + y: boundsData.bounds.y + }; + } + resizes.push(resize); + } + if (boundsData.alignmentChanged && boundsData.alignment !== undefined) { + alignments.push({ + elementId: element.id, + newAlignment: boundsData.alignment + }); + } + if (LayoutAware.is(boundsData)) { + layoutData.push({ elementId: element.id, layoutData: boundsData.layoutData }); + } + }); + const routes = this.element2route.length === 0 ? undefined : this.element2route; + + // prepare and dispatch action + const responseId = (cause as RequestBoundsAction).requestId; + const revision = this.root !== undefined ? this.root.revision : undefined; + const computedBoundsAction = ComputedBoundsAction.create(resizes, { revision, alignments, layoutData, routes, responseId }); + if (LocalRequestBoundsAction.is(cause)) { + LocalComputedBoundsAction.mark(computedBoundsAction); + } + this.actionDispatcher.dispatch(computedBoundsAction); + + // cleanup + this.getElement2BoundsData().clear(); this.element2route = []; } @@ -82,41 +136,4 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater { protected expandElementId(id: string, index: ModelIndexImpl, elementIDs: string[]): string[] { return getDescendantIds(index.getById(id)); } - - protected captureActions(call: () => void): Action[] { - const capturingActionDispatcher = new CapturingActionDispatcher(); - const actualActionDispatcher = this.actionDispatcher; - this.actionDispatcher = capturingActionDispatcher; - try { - call(); - return capturingActionDispatcher.actions; - } finally { - this.actionDispatcher = actualActionDispatcher; - } - } - - protected enhanceAction(action: ComputedBoundsAction, cause?: Action): ComputedBoundsAction { - if (LocalRequestBoundsAction.is(cause)) { - LocalComputedBoundsAction.mark(action); - } - action.routes = this.element2route.length === 0 ? undefined : this.element2route; - return action; - } -} - -class CapturingActionDispatcher implements IActionDispatcher { - readonly actions: Action[] = []; - - async dispatch(action: Action): Promise { - this.actions.push(action); - } - - async dispatchAll(actions: Action[]): Promise { - this.actions.push(...actions); - } - - async request(action: RequestAction): Promise { - // ignore, not needed for our purposes - return new Deferred().promise; - } } diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index 4028037..ac0b080 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -13,22 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { Bounds, BoundsData, Dimension, GChildElement, + GModelElement, + GParentElement, HBoxLayoutOptions, HBoxLayouter, LayoutContainer, Point, - GModelElement, - GParentElement, StatefulLayouter, isBoundsAware, isLayoutableChild } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { LayoutAware } from './layout-data'; export interface HBoxLayoutOptionsExt extends HBoxLayoutOptions { hGrab: boolean; @@ -79,7 +80,9 @@ export class HBoxLayouterExt extends HBoxLayouter { if (width > 0 && height > 0) { const offset = this.layoutChildren(container, layouter, options, width, height, grabWidth, grabbingChildren); - boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); + const computed = this.getComputedContainerDimensions(options, childrenSize.width, childrenSize.height); + LayoutAware.setComputedDimensions(boundsData, computed); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, computed.width, computed.height); boundsData.boundsChanged = true; } } @@ -223,22 +226,28 @@ export class HBoxLayouterExt extends HBoxLayouter { return (element as any).layoutOptions; } + protected getComputedContainerDimensions(options: HBoxLayoutOptionsExt, maxWidth: number, maxHeight: number): Dimension { + return { + width: maxWidth + options.paddingLeft + options.paddingRight, + height: maxHeight + options.paddingTop + options.paddingBottom + }; + } + protected override getFinalContainerBounds( container: GParentElement & LayoutContainer, lastOffset: Point, options: HBoxLayoutOptionsExt, - maxWidth: number, - maxHeight: number + computedWidth: number, + computedHeight: number ): Bounds { const elementOptions = this.getElementLayoutOptions(container); const width = elementOptions?.prefWidth ?? options.minWidth; const height = elementOptions?.prefHeight ?? options.minHeight; - const result = { x: container.bounds.x, y: container.bounds.y, - width: Math.max(width, maxWidth + options.paddingLeft + options.paddingRight), - height: Math.max(height, maxHeight + options.paddingTop + options.paddingBottom) + width: Math.max(width, computedWidth), + height: Math.max(height, computedHeight) }; return result; diff --git a/packages/client/src/features/bounds/index.ts b/packages/client/src/features/bounds/index.ts index b35779b..4bd0493 100644 --- a/packages/client/src/features/bounds/index.ts +++ b/packages/client/src/features/bounds/index.ts @@ -17,6 +17,7 @@ export * from './bounds-module'; export * from './freeform-layout'; export * from './glsp-hidden-bounds-updater'; export * from './hbox-layout'; +export * from './layout-data'; export * from './layouter'; export * from './local-bounds'; export * from './set-bounds-feedback-command'; diff --git a/packages/client/src/features/bounds/layout-data.ts b/packages/client/src/features/bounds/layout-data.ts new file mode 100644 index 0000000..34dad4b --- /dev/null +++ b/packages/client/src/features/bounds/layout-data.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Dimension, LayoutData } from '@eclipse-glsp/sprotty'; + +export interface LayoutAware { + layoutData: LayoutData; +} + +export namespace LayoutAware { + export function is(element: T): element is T & LayoutAware { + return 'layoutData' in element; + } + + export function getLayoutData(element: T): LayoutData | undefined { + return is(element) ? element.layoutData : undefined; + } + + export function setLayoutData(element: T, data: LayoutData): void { + (element as LayoutAware).layoutData = data; + } + + export function setComputedDimensions(element: T, computedDimensions: Dimension): void { + ensureLayoutAware(element).layoutData.computedDimensions = computedDimensions; + } + + export function getComputedDimensions(element: T): Dimension | undefined { + return getLayoutData(element)?.computedDimensions; + } + + function ensureLayoutAware(element: T): T & LayoutAware { + (element as LayoutAware).layoutData = (element as LayoutAware).layoutData ?? {}; + return element as T & LayoutAware; + } +} diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts index 101968d..df21698 100644 --- a/packages/client/src/features/bounds/local-bounds.ts +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -31,6 +31,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { ServerAction } from '../../base/model/glsp-model-source'; +import { LayoutAware } from './layout-data'; export interface LocalRequestBoundsAction extends RequestBoundsAction { elementIDs?: string[]; @@ -41,9 +42,11 @@ export namespace LocalRequestBoundsAction { return RequestBoundsAction.is(object) && !ServerAction.is(object) && hasArrayProp(object, 'elementIDs', true); } - export function create(newRoot: GModelRootSchema, elementIDs?: string[]): LocalRequestBoundsAction { + export function create(newRoot: GModelRoot, elementIDs?: string[]): LocalRequestBoundsAction; + export function create(newRoot: GModelRootSchema, elementIDs?: string[]): LocalRequestBoundsAction; + export function create(newRoot: GModelRoot | GModelRootSchema, elementIDs?: string[]): LocalRequestBoundsAction { return { - ...RequestBoundsAction.create(newRoot), + ...RequestBoundsAction.create(newRoot as unknown as GModelRootSchema), elementIDs }; } @@ -55,7 +58,7 @@ export namespace LocalRequestBoundsAction { elementIDs?: string[] ): CommandResult { // do not modify the main model (modelChanged = false) but request local bounds calculation on hidden model - actionDispatcher.dispatch(LocalRequestBoundsAction.create(root as unknown as GModelRootSchema, elementIDs)); + actionDispatcher.dispatch(LocalRequestBoundsAction.create(root, elementIDs)); return { model: root, modelChanged: false, @@ -65,7 +68,7 @@ export namespace LocalRequestBoundsAction { } export namespace LocalComputedBoundsAction { - export function is(object: unknown): object is RequestBoundsAction { + export function is(object: unknown): object is ComputedBoundsAction & ServerAction { return ComputedBoundsAction.is(object) && ServerAction.is(object); } @@ -94,9 +97,22 @@ export class LocalComputedBoundsCommand extends Command { } // apply computed bounds from the hidden model and return updated model to render new main model this.computedBoundsApplicator.apply(context.root as unknown as GModelRootSchema, this.action); + this.action.layoutData?.forEach(({ elementId, layoutData }) => { + const element = context.root.index.getById(elementId); + if (element !== undefined) { + LayoutAware.setLayoutData(element, layoutData); + } + }); return context.root; } + this.action.layoutData?.forEach(({ elementId, layoutData }) => { + const element = context.root.index.getById(elementId); + if (element !== undefined) { + LayoutAware.setLayoutData(element, layoutData); + } + }); + // computed bounds action from server -> we do not care and do not trigger any update of the main model return { model: context.root, diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 95f59a6..9f6641c 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -13,22 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { Bounds, BoundsData, Dimension, GChildElement, - LayoutContainer, - Point, GModelElement, GParentElement, + LayoutContainer, + Point, StatefulLayouter, VBoxLayoutOptions, VBoxLayouter, isBoundsAware, isLayoutableChild } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { LayoutAware } from './layout-data'; export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { hGrab: boolean; @@ -79,7 +80,9 @@ export class VBoxLayouterExt extends VBoxLayouter { if (maxWidth > 0 && maxHeight > 0) { const offset = this.layoutChildren(container, layouter, options, width, height, grabHeight, grabbingChildren); - boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); + const computed = this.getComputedContainerDimensions(options, childrenSize.width, childrenSize.height); + LayoutAware.setComputedDimensions(boundsData, computed); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, computed.width, computed.height); boundsData.boundsChanged = true; } } @@ -223,24 +226,29 @@ export class VBoxLayouterExt extends VBoxLayouter { return (element as any).layoutOptions; } + protected getComputedContainerDimensions(options: VBoxLayoutOptionsExt, maxWidth: number, maxHeight: number): Dimension { + return { + width: maxWidth + options.paddingLeft + options.paddingRight, + height: maxHeight + options.paddingTop + options.paddingBottom + }; + } + protected override getFinalContainerBounds( container: GParentElement & LayoutContainer, lastOffset: Point, options: VBoxLayoutOptionsExt, - maxWidth: number, - maxHeight: number + computedWidth: number, + computedHeight: number ): Bounds { const elementOptions = this.getElementLayoutOptions(container); const width = elementOptions?.prefWidth ?? options.minWidth; const height = elementOptions?.prefHeight ?? options.minHeight; - const result = { x: container.bounds.x, y: container.bounds.y, - width: Math.max(width, maxWidth + options.paddingLeft + options.paddingRight), - height: Math.max(height, maxHeight + options.paddingTop + options.paddingBottom) + width: Math.max(width, computedWidth), + height: Math.max(height, computedHeight) }; - return result; } diff --git a/packages/client/src/features/change-bounds/index.ts b/packages/client/src/features/change-bounds/index.ts index 85d8b38..dc4fc97 100644 --- a/packages/client/src/features/change-bounds/index.ts +++ b/packages/client/src/features/change-bounds/index.ts @@ -18,3 +18,4 @@ export * from './movement-restrictor'; export * from './point-position-updater'; export * from './position-snapper'; export * from './snap'; +export * from './tracker'; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 796eaeb..c6d283b 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -14,15 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + Bounds, GChildElement, GModelElement, GParentElement, Hoverable, + Point, hoverFeedbackFeature, isBoundsAware, isMoveable, isSelectable } from '@eclipse-glsp/sprotty'; +import { CursorCSS } from '../../base'; import { BoundsAwareModelElement, MoveableElement, ResizableModelElement } from '../../utils'; export const resizeFeature = Symbol('resizeFeature'); @@ -34,9 +37,36 @@ export function isResizable(element: GModelElement): element is ResizableModelEl // eslint-disable-next-line no-shadow export enum ResizeHandleLocation { TopLeft = 'top-left', + Top = 'top', TopRight = 'top-right', + Right = 'right', + BottomRight = 'bottom-right', + Bottom = 'bottom', BottomLeft = 'bottom-left', - BottomRight = 'bottom-right' + Left = 'left' +} + +export namespace ResizeHandleLocation { + export function opposite(location: ResizeHandleLocation): ResizeHandleLocation { + switch (location) { + case ResizeHandleLocation.TopLeft: + return ResizeHandleLocation.BottomRight; + case ResizeHandleLocation.Top: + return ResizeHandleLocation.Bottom; + case ResizeHandleLocation.TopRight: + return ResizeHandleLocation.BottomLeft; + case ResizeHandleLocation.Right: + return ResizeHandleLocation.Left; + case ResizeHandleLocation.BottomRight: + return ResizeHandleLocation.TopLeft; + case ResizeHandleLocation.Bottom: + return ResizeHandleLocation.Top; + case ResizeHandleLocation.BottomLeft: + return ResizeHandleLocation.TopRight; + case ResizeHandleLocation.Left: + return ResizeHandleLocation.Right; + } + } } export function isBoundsAwareMoveable(element: GModelElement): element is BoundsAwareModelElement & MoveableElement { @@ -64,18 +94,34 @@ export class SResizeHandle extends GChildElement implements Hoverable { return this.location === ResizeHandleLocation.TopLeft; } - isSeResize(): boolean { - return this.location === ResizeHandleLocation.BottomRight; + isNResize(): boolean { + return this.location === ResizeHandleLocation.Top; } isNeResize(): boolean { return this.location === ResizeHandleLocation.TopRight; } + isEResize(): boolean { + return this.location === ResizeHandleLocation.Right; + } + + isSeResize(): boolean { + return this.location === ResizeHandleLocation.BottomRight; + } + + isSResize(): boolean { + return this.location === ResizeHandleLocation.Bottom; + } + isSwResize(): boolean { return this.location === ResizeHandleLocation.BottomLeft; } + isWResize(): boolean { + return this.location === ResizeHandleLocation.Left; + } + isNwSeResize(): boolean { return this.isNwResize() || this.isSeResize(); } @@ -83,18 +129,72 @@ export class SResizeHandle extends GChildElement implements Hoverable { isNeSwResize(): boolean { return this.isNeResize() || this.isSwResize(); } + + static getHandlePosition(handle: SResizeHandle): Point; + static getHandlePosition(parent: ResizableModelElement, location: ResizeHandleLocation): Point; + static getHandlePosition(bounds: Bounds, location: ResizeHandleLocation): Point; + static getHandlePosition(first: ResizableModelElement | SResizeHandle | Bounds, second?: ResizeHandleLocation): Point { + const bounds = SResizeHandle.is(first) ? first.parent.bounds : first instanceof GModelElement ? first.bounds : first; + const location = SResizeHandle.is(first) ? first.location : second!; + switch (location) { + case ResizeHandleLocation.TopLeft: + return Bounds.topLeft(bounds); + case ResizeHandleLocation.Top: + return Bounds.topCenter(bounds); + case ResizeHandleLocation.TopRight: + return Bounds.topRight(bounds); + case ResizeHandleLocation.Right: + return Bounds.middleRight(bounds); + case ResizeHandleLocation.BottomRight: + return Bounds.bottomRight(bounds); + case ResizeHandleLocation.Bottom: + return Bounds.bottomCenter(bounds); + case ResizeHandleLocation.BottomLeft: + return Bounds.bottomLeft(bounds); + case ResizeHandleLocation.Left: + return Bounds.middleLeft(bounds); + } + } + + static getCursorCss(handle: SResizeHandle): string { + switch (handle.location) { + case ResizeHandleLocation.TopLeft: + return CursorCSS.RESIZE_NW; + case ResizeHandleLocation.Top: + return CursorCSS.RESIZE_N; + case ResizeHandleLocation.TopRight: + return CursorCSS.RESIZE_NE; + case ResizeHandleLocation.Right: + return CursorCSS.RESIZE_E; + case ResizeHandleLocation.BottomRight: + return CursorCSS.RESIZE_SE; + case ResizeHandleLocation.Bottom: + return CursorCSS.RESIZE_S; + case ResizeHandleLocation.BottomLeft: + return CursorCSS.RESIZE_SW; + case ResizeHandleLocation.Left: + return CursorCSS.RESIZE_W; + } + } + + static is(handle: unknown): handle is SResizeHandle { + return typeof handle === 'object' && !!handle && 'type' in handle && handle.type === SResizeHandle.TYPE; + } } export function addResizeHandles( element: ResizableModelElement, locations: ResizeHandleLocation[] = [ ResizeHandleLocation.TopLeft, - ResizeHandleLocation.TopRight, + ResizeHandleLocation.Top, ResizeHandleLocation.BottomLeft, ResizeHandleLocation.BottomRight ] ): void { for (const location of Object.values(ResizeHandleLocation)) { + if (typeof location === 'function') { + continue; + } const existing = element.children.find(child => child instanceof SResizeHandle && child.location === location); if (locations.includes(location) && !existing) { // add missing handle diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index da5e73b..8158d2f 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -108,3 +108,13 @@ export function removeMovementRestrictionFeedback( return ModifyCSSFeedbackAction.create({ elements, remove: movementRestrictor.cssClasses }); } + +export function movementRestrictionFeedback( + element: GModelElement, + movementRestrictor: IMovementRestrictor, + valid: boolean +): ModifyCSSFeedbackAction { + return valid + ? removeMovementRestrictionFeedback(element, movementRestrictor) + : createMovementRestrictionFeedback(element, movementRestrictor); +} diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index 0d29442..743e4da 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -13,6 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable import/no-deprecated */ +/* eslint-disable deprecation/deprecation */ import { GModelElement } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index 244de68..d09991a 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -14,6 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable import/no-deprecated */ +/* eslint-disable deprecation/deprecation */ + import { GModelElement, ISnapper, Point, Writable } from '@eclipse-glsp/sprotty'; import { calculateDeltaBetweenPoints } from '../../utils/gmodel-util'; import { isMouseEvent } from '../../utils/html-utils'; @@ -29,6 +32,9 @@ import { useSnap } from './snap'; * * You can initialize a this class with a optional {@link ISnapper}. If a * snapper is present, the positions will be snapped to the defined grid. + * + * @deprecated The use of this class is discouraged. Use the {@link ChangeBoundsManager.createTracker} + * instead which centralized a few aspects of the tracking. */ export class PointPositionUpdater { protected positionSnapper: PositionSnapper; diff --git a/packages/client/src/features/change-bounds/position-snapper.ts b/packages/client/src/features/change-bounds/position-snapper.ts index 573c49c..eeacae0 100644 --- a/packages/client/src/features/change-bounds/position-snapper.ts +++ b/packages/client/src/features/change-bounds/position-snapper.ts @@ -18,6 +18,10 @@ import { inject, injectable, optional } from 'inversify'; import { IHelperLineManager } from '../helper-lines/helper-line-manager'; import { Direction, HelperLine, isHelperLine } from '../helper-lines/model'; +/** + * @deprecated The use of this class is discouraged. Use the {@link ChangeBoundsManager.createTracker} + * instead which centralized a few aspects of the tracking. + */ @injectable() export class PositionSnapper { constructor( diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 246c07c..5716c18 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -16,10 +16,16 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { KeyboardModifier } from '@eclipse-glsp/sprotty'; +/** + * @deprecated Use {@link ChangeBoundsManager.useSnap} instead which may be customized. + */ export function useSnap(event: MouseEvent | KeyboardEvent): boolean { return !event.shiftKey; } +/** + * @deprecated Use {@link ChangeBoundsManager.unsnapModifier} instead which may be customized. + */ export function unsnapModifier(): KeyboardModifier { return 'shift'; } diff --git a/packages/client/src/features/change-bounds/tracker.ts b/packages/client/src/features/change-bounds/tracker.ts new file mode 100644 index 0000000..70049d7 --- /dev/null +++ b/packages/client/src/features/change-bounds/tracker.ts @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Disposable, MousePositionTracker, Movement, Point, Vector } from '@eclipse-glsp/sprotty'; + +export class MovementCalculator implements Disposable { + protected position?: Point; + + setPosition(position: Point): void { + this.position = { ...position }; + } + + updatePosition(param: Vector | Movement): void { + const vector = Vector.is(param) ? param : param.vector; + this.setPosition(Point.add(this.position ?? Point.ORIGIN, vector)); + } + + get hasPosition(): boolean { + return this.position !== undefined; + } + + calculateMoveTo(targetPosition: Point): Movement { + return !this.position ? Movement.ZERO : Point.move(this.position, targetPosition); + } + + dispose(): void { + this.position = undefined; + } +} + +export class DiagramMovementCalculator extends MovementCalculator { + constructor(readonly positionTracker: MousePositionTracker) { + super(); + } + + init(): void { + const position = this.positionTracker.lastPositionOnDiagram; + if (position) { + this.setPosition(position); + } + } + + calculateMoveToCurrent(): Movement { + const targetPosition = this.positionTracker.lastPositionOnDiagram; + return targetPosition ? this.calculateMoveTo(targetPosition) : Movement.ZERO; + } + + reset(): void { + this.dispose(); + } +} diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx index e594151..07380f3 100644 --- a/packages/client/src/features/debug/debug-bounds-decorator.tsx +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ -import { Bounds, GModelElement, IVNodePostprocessor, Point, TYPES, isBoundsAware, setClass, svg } from '@eclipse-glsp/sprotty'; +import { Bounds, GModelElement, IVNodePostprocessor, Point, TYPES, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GGraph } from '../../model'; @@ -25,6 +25,8 @@ import { DebugManager } from './debug-manager'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; +export const CSS_DEBUG_BOUNDS = 'debug-bounds'; + @injectable() export class DebugBoundsDecorator implements IVNodePostprocessor { @inject(TYPES.IDebugManager) @optional() protected debugManager?: DebugManager; @@ -33,10 +35,10 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { if (!this.debugManager?.isDebugEnabled) { return vnode; } - if (isBoundsAware(element)) { - this.decorateBoundsAware(vnode, element); + if (isSizeable(element) && this.shouldDecorateSizeable(element)) { + this.decorateSizeable(vnode, element); } - if (element instanceof GGraph) { + if (element instanceof GGraph && this.shouldDecorateGraph(element)) { this.decorateGraph(vnode, element); } return vnode; @@ -48,8 +50,12 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { return 5; } + protected shouldDecorateGraph(graph: GGraph): boolean { + return true; + } + protected decorateGraph(vnode: VNode, graph: GGraph): void { - setClass(vnode, 'debug-bounds', true); + setClass(vnode, CSS_DEBUG_BOUNDS, true); const svgChild = vnode.children?.find(child => typeof child !== 'string' && child.sel === 'svg') as VNode | undefined; const group = svgChild?.children?.find(child => typeof child !== 'string' && child.sel === 'g') as VNode | undefined; group?.children?.push(this.renderOrigin(graph)); @@ -67,8 +73,12 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { ); } - protected decorateBoundsAware(vnode: VNode, element: BoundsAwareModelElement): void { - setClass(vnode, 'debug-bounds', true); + protected shouldDecorateSizeable(element: BoundsAwareModelElement): boolean { + return !isDecoration(element); + } + + protected decorateSizeable(vnode: VNode, element: BoundsAwareModelElement): void { + setClass(vnode, CSS_DEBUG_BOUNDS, true); vnode.children?.push(this.renderTopLeftCorner(element)); vnode.children?.push(this.renderTopRightCorner(element)); vnode.children?.push(this.renderBottomLeftCorner(element)); diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index cca0e2b..af5c9bd 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -14,55 +14,27 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - Dimension, - Disposable, - ElementMove, - GModelElement, - MoveAction, - Point, - isBoundsAware, - isMoveable -} from '@eclipse-glsp/sprotty'; -import { injectable } from 'inversify'; +import { Action, Dimension, GModelElement, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../base/feedback/feeback-emitter'; -import { IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; -import { Tool } from '../../base/tool-manager/tool'; -import { MoveableElement } from '../../utils'; -import { getAbsolutePosition } from '../../utils/viewpoint-util'; +import { MoveableElement, getAbsolutePosition } from '../../utils'; import { - IMovementRestrictor, - createMovementRestrictionFeedback, - removeMovementRestrictionFeedback -} from '../change-bounds/movement-restrictor'; -import { PointPositionUpdater } from '../change-bounds/point-position-updater'; -import { PositionSnapper } from '../change-bounds/position-snapper'; -import { useSnap } from '../change-bounds/snap'; -import { MoveFinishedEventAction } from '../tools'; - -export interface PositioningTool extends Tool { - readonly positionSnapper: PositionSnapper; - readonly movementRestrictor?: IMovementRestrictor; + CSS_RESIZE_MODE, + ChangeBoundsManager, + ChangeBoundsTracker, + FeedbackAwareTool, + MoveFinishedEventAction, + TrackedElementMove +} from '../tools'; - createFeedbackEmitter(): FeedbackEmitter; - /** - * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback instead of using the tool. - */ - registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): Disposable; - /** - * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback and dispose it like that. - */ - deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: Action[]): void; +export interface PositioningTool extends FeedbackAwareTool { + readonly changeBoundsManager: ChangeBoundsManager; } -@injectable() export class MouseTrackingElementPositionListener extends DragAwareMouseListener { - protected positionUpdater: PointPositionUpdater; protected moveGhostFeedback: FeedbackEmitter; - protected currentPosition?: Point; + protected tracker: ChangeBoundsTracker; constructor( protected elementId: string, @@ -70,54 +42,60 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener protected cursorPosition: 'top-left' | 'middle' = 'top-left' ) { super(); - this.positionUpdater = new PointPositionUpdater(this.tool.positionSnapper); + this.tracker = this.tool.changeBoundsManager.createTracker(); this.moveGhostFeedback = this.tool.createFeedbackEmitter(); } + protected getTrackedElement(target: GModelElement, event: MouseEvent): MoveableElement | undefined { + const element = target.root.index.getById(this.elementId); + return !element || !isMoveable(element) ? undefined : element; + } + override mouseMove(target: GModelElement, event: MouseEvent): Action[] { super.mouseMove(target, event); - const element = target.root.index.getById(this.elementId); - if (!element || !isMoveable(element)) { + const element = this.getTrackedElement(target, event); + if (!element) { return []; } - if (this.positionUpdater.isLastDragPositionUndefined()) { - this.positionUpdater.updateLastDragPosition(element.position); + if (!this.tracker.isTracking()) { + this.initialize(element, target, event); } - const mousePosition = getAbsolutePosition(target, event); - const delta = this.positionUpdater.updatePosition(element, mousePosition, useSnap(event)); - if (!delta) { + const move = this.tracker.moveElements([element], { snap: event, restrict: event, validate: true }); + const elementMove = move.elementMoves[0]; + if (!elementMove) { return []; } - const toPosition = this.getElementTargetPosition(mousePosition, element, event); - const elementMove = { elementId: element.id, toPosition: toPosition }; - this.addMoveFeeback(element, elementMove); - this.currentPosition = toPosition; // since we are moving a ghost element that is feedback-only and will be removed anyway, // we just send a MoveFinishedEventAction instead of reseting the position with a MoveAction and the finished flag set to true. - this.moveGhostFeedback.add(MoveAction.create([elementMove], { animate: false }), MoveFinishedEventAction.create()).submit(); + this.moveGhostFeedback.add( + MoveAction.create([{ elementId: this.elementId, toPosition: elementMove.toPosition }], { animate: false }), + MoveFinishedEventAction.create() + ); + this.addMoveFeeback(elementMove); + this.moveGhostFeedback.submit(); + this.tracker.updateTrackingPosition(elementMove.moveVector); return []; } - protected getElementTargetPosition(mousePosition: Point, element: MoveableElement, event: MouseEvent): Point { - const unsnappedPosition = - this.cursorPosition === 'middle' && isBoundsAware(element) - ? Point.subtract(mousePosition, Dimension.center(element.bounds)) - : mousePosition; - return this.tool.positionSnapper.snapPosition(unsnappedPosition, element, useSnap(event)); + protected initialize(element: MoveableElement, target: GModelElement, event: MouseEvent): void { + this.tracker.startTracking(); + element.position = this.initializeElementPosition(element, target, event); } - protected addMoveFeeback(element: MoveableElement, elementMove: ElementMove): void { - if (this.tool.movementRestrictor) { - if (!this.tool.movementRestrictor.validate(element, elementMove.toPosition)) { - this.moveGhostFeedback.add( - createMovementRestrictionFeedback(element, this.tool.movementRestrictor), - removeMovementRestrictionFeedback(element, this.tool.movementRestrictor) - ); - } else { - this.moveGhostFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); - } - } - this.moveGhostFeedback.add(ModifyCSSFeedbackAction.create({ elements: [element.id], remove: [CSS_HIDDEN] })); + protected initializeElementPosition(element: MoveableElement, target: GModelElement, event: MouseEvent): Point { + const mousePosition = getAbsolutePosition(target, event); + return this.cursorPosition === 'middle' && isBoundsAware(element) + ? Point.subtract(mousePosition, Dimension.center(element.bounds)) + : mousePosition; + } + + protected addMoveFeeback(move: TrackedElementMove): void { + this.tool.changeBoundsManager.addRestrictionFeedback(this.moveGhostFeedback, move); + this.moveGhostFeedback.add(ModifyCSSFeedbackAction.create({ elements: [move.element.id], remove: [CSS_HIDDEN] })); + this.moveGhostFeedback.add( + ModifyCSSFeedbackAction.create({ add: [CSS_RESIZE_MODE] }), + ModifyCSSFeedbackAction.create({ remove: [CSS_RESIZE_MODE] }) + ); } override dispose(): void { diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index d4b1b9d..4ff9437 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -27,6 +27,7 @@ import { Point, TYPES, Viewport, + equalUpTo, findParentByFeature, isBoundsAware, isDecoration, @@ -293,7 +294,7 @@ export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { } protected isMatch(leftCoordinate: number, rightCoordinate: number, epsilon: number): boolean { - return Math.abs(leftCoordinate - rightCoordinate) <= epsilon; + return equalUpTo(leftCoordinate, rightCoordinate, epsilon); } protected log(message: string, ...params: any[]): void { diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index a358c7c..55fd201 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -13,7 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelElement, GModelRoot, IActionHandler, MoveAction, Point, SetBoundsAction, TYPES } from '@eclipse-glsp/sprotty'; +import { + Action, + GModelElement, + GModelRoot, + IActionHandler, + MoveAction, + Point, + SetBoundsAction, + TYPES, + Vector, + Writable +} from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { FeedbackEmitter } from '../../base'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; @@ -33,7 +44,7 @@ import { ViewportLineType } from './helper-line-feedback'; import { IHelperLineManager } from './helper-line-manager'; -import { Direction, HelperLineType } from './model'; +import { Direction, HelperLine, HelperLineType, isHelperLine } from './model'; export interface IHelperLineOptions { /** @@ -158,4 +169,28 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH ? this.options.minimumMoveDelta.x : this.options.minimumMoveDelta.y; } + + getMinimumMoveVector(element: GModelElement, isSnap: boolean, directions: Direction[]): Vector | undefined { + if (!isSnap) { + return undefined; + } + + const helperLines = element.root.children.filter(child => isHelperLine(child)) as HelperLine[]; + if (helperLines.length === 0) { + return undefined; + } + + const minimum: Writable = { ...Vector.ZERO }; + if (directions.includes(Direction.Left) && helperLines.some(line => line.isLeft || line.isCenter)) { + minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Left); + } else if (directions.includes(Direction.Right) && helperLines.some(line => line.isRight || line.isCenter)) { + minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Right); + } + if (directions.includes(Direction.Up) && helperLines.some(line => line.isTop || line.isMiddle)) { + minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Up); + } else if (directions.includes(Direction.Down) && helperLines.some(line => line.isBottom || line.isMiddle)) { + minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Down); + } + return Vector.isZero(minimum) ? undefined : minimum; + } } diff --git a/packages/client/src/features/helper-lines/helper-line-manager.ts b/packages/client/src/features/helper-lines/helper-line-manager.ts index 18f7a57..5c00c88 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager.ts @@ -13,16 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement } from '@eclipse-glsp/sprotty'; +import { GModelElement, Vector } from '@eclipse-glsp/sprotty'; import { Direction } from './model'; export interface IHelperLineManager { /** - * Calculates the minimum move delta that is necessary to break through a helper line. + * Calculates the minimum move delta on one axis that is necessary to break through a helper line. * * @param element element that is being moved * @param isSnap whether snapping is active or not * @param direction direction in which the target element is moving */ getMinimumMoveDelta(element: GModelElement, isSnap: boolean, direction: Direction): number; + + /** + * Calculates the minimum move vector that is necessary to break through a helper line. + * + * @param element element that is being moved + * @param isSnap whether snapping is active or not + * @param directions directions in which the target element is moving + */ + getMinimumMoveVector(element: GModelElement, isSnap: boolean, directions: Direction[]): Vector | undefined; } diff --git a/packages/client/src/features/routing/edge-router.ts b/packages/client/src/features/routing/edge-router.ts index cbfb71c..c2ebdc5 100644 --- a/packages/client/src/features/routing/edge-router.ts +++ b/packages/client/src/features/routing/edge-router.ts @@ -41,6 +41,13 @@ export abstract class GLSPAbstractEdgeRouter extends AbstractEdgeRouter { const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); return Point.isValid(anchor) ? anchor : refPoint; } + + override cleanupRoutingPoints(edge: GRoutableElement, routingPoints: Point[], updateHandles: boolean, addRoutingPoints: boolean): void { + // sometimes it might happen that the source or target has the bounds not properly set when using the feedback edge + if (ensureBounds(edge.source) && ensureBounds(edge.target)) { + super.cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints); + } + } } @injectable() @@ -56,6 +63,13 @@ export class GLSPPolylineEdgeRouter extends PolylineEdgeRouter { const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); return Point.isValid(anchor) ? anchor : refPoint; } + + override cleanupRoutingPoints(edge: GRoutableElement, routingPoints: Point[], updateHandles: boolean, addRoutingPoints: boolean): void { + // sometimes it might happen that the source or target has the bounds not properly set when using the feedback edge + if (ensureBounds(edge.source) && ensureBounds(edge.target)) { + super.cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints); + } + } } @injectable() @@ -111,6 +125,13 @@ export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { } }); } + + override cleanupRoutingPoints(edge: GRoutableElement, routingPoints: Point[], updateHandles: boolean, addRoutingPoints: boolean): void { + // sometimes it might happen that the source or target has the bounds not properly set when using the feedback edge + if (ensureBounds(edge.source) && ensureBounds(edge.target)) { + super.cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints); + } + } } @injectable() @@ -126,4 +147,25 @@ export class GLSPBezierEdgeRouter extends BezierEdgeRouter { const anchor = super.getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection); return Point.isValid(anchor) ? anchor : refPoint; } + + override cleanupRoutingPoints(edge: GRoutableElement, routingPoints: Point[], updateHandles: boolean, addRoutingPoints: boolean): void { + // sometimes it might happen that the source or target has the bounds not properly set when using the feedback edge + if (ensureBounds(edge.source) && ensureBounds(edge.target)) { + super.cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints); + } + } +} + +function ensureBounds(element?: GConnectableElement): boolean { + if (!element) { + return false; + } + if (element.bounds) { + return true; + } + if (element.position && element.size) { + element.bounds = { ...element.position, ...element.size }; + return true; + } + return false; } diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 696681e..6717fc8 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,17 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener } from '@eclipse-glsp/sprotty'; +import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; import { Ranked } from '../../base/ranked'; import { SelectableElement } from '../../utils'; +import { SResizeHandle } from '../change-bounds'; +import { ChangeBoundsManager } from '../tools'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. * This ensures that default mouse listeners are working on a model that has selection changes already applied. */ +@injectable() export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ + @inject(TYPES.IChangeBoundsManager) @optional() readonly changeBoundsManager?: ChangeBoundsManager; + protected override handleSelectTarget( selectableTarget: SelectableElement, deselectedElements: GModelElement[], @@ -52,4 +58,12 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra result.push(SelectAction.create({ selectedElementsIDs: [], deselectedElementsIDs: deselectedElements.map(e => e.id) })); return result; } + + protected override handleButton(target: GModelElement, event: MouseEvent): (Action | Promise)[] | undefined { + if (target instanceof SResizeHandle && this.changeBoundsManager?.useSymmetricResize(event)) { + // avoid de-selecting elements when resizing with a modifier key + return []; + } + return super.handleButton(target, event); + } } diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index b232f06..25917bf 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -23,11 +23,41 @@ import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; import { GLSPKeyTool } from '../../base/view/key-tool'; import { GLSPMouseTool } from '../../base/view/mouse-tool'; +export interface FeedbackAwareTool extends Tool { + /** + * Creates a new feedback emitter helper object. While anything can serve as a feedback emitter, + * this method ensures that the emitter is stable and does not change between model updates. + */ + createFeedbackEmitter(): FeedbackEmitter; + + /** + * Registers `actions` to be sent out as feedback, i.e., changes that are re-established whenever the `GModelRoot` + * has been set or updated. + * + * @param feedbackActions the actions to be sent out. + * @param feedbackEmitter the emitter sending out feedback actions (this tool by default). + * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. + * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback instead of using the tool. + */ + registerFeedback(feedbackActions: Action[], feedbackEmitter?: IFeedbackEmitter, cleanupActions?: MaybeActions): Disposable; + + /** + * De-registers all feedback from the given `feedbackEmitter` (this tool by default) and cleans up any pending feedback with the + * given `cleanupActions`. + * + * @param feedbackEmitter the emitter to be deregistered (this tool by default). + * @param cleanupActions the actions to be dispatched right after the deregistration to clean up any pending feedback. + * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback and dispose it like that. + */ + deregisterFeedback(feedbackEmitter?: IFeedbackEmitter, cleanupActions?: MaybeActions): void; +} + /** * A reusable base implementation for edit {@link Tool}s. */ @injectable() -export abstract class BaseEditTool implements Tool { +export abstract class BaseEditTool implements FeedbackAwareTool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; @@ -56,28 +86,10 @@ export abstract class BaseEditTool implements Tool { return this.feedbackDispatcher.createEmitter(); } - /** - * Registers `actions` to be sent out as feedback, i.e., changes that are re-established whenever the `GModelRoot` - * has been set or updated. - * - * @param feedbackActions the actions to be sent out. - * @param feedbackEmitter the emitter sending out feedback actions (this tool by default). - * @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable. - * @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`. - * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback instead of using the tool. - */ registerFeedback(feedbackActions: Action[], feedbackEmitter: IFeedbackEmitter = this, cleanupActions?: MaybeActions): Disposable { return this.feedbackDispatcher.registerFeedback(feedbackEmitter, feedbackActions, cleanupActions); } - /** - * De-registers all feedback from the given `feedbackEmitter` (this tool by default) and cleans up any pending feedback with the - * given `cleanupActions`. - * - * @param feedbackEmitter the emitter to be deregistered (this tool by default). - * @param cleanupActions the actions to be dispatched right after the deregistration to clean up any pending feedback. - * @deprecated It is recommended to create a {@link createFeedbackEmitter dedicated emitter} per feedback and dispose it like that. - */ deregisterFeedback(feedbackEmitter: IFeedbackEmitter = this, cleanupActions?: MaybeActions): void { this.feedbackDispatcher.deregisterFeedback(feedbackEmitter, cleanupActions); } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts new file mode 100644 index 0000000..38a5a8c --- /dev/null +++ b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts @@ -0,0 +1,145 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Dimension, + GModelElement, + ISnapper, + KeyboardModifier, + MousePositionTracker, + Movement, + Point, + TYPES, + Vector, + isBoundsAware, + isLocateable +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { FeedbackEmitter } from '../../../base'; +import { isValidMove, minDimensions } from '../../../utils'; +import { LayoutAware } from '../../bounds/layout-data'; +import { + IMovementRestrictor, + ResizeHandleLocation, + movementRestrictionFeedback, + removeMovementRestrictionFeedback +} from '../../change-bounds'; +import { IHelperLineManager } from '../../helper-lines'; +import { ChangeBoundsTracker, TrackedElementMove } from './change-bounds-tracker'; + +export const CSS_RESIZE_MODE = 'resize-mode'; + +@injectable() +export class ChangeBoundsManager { + constructor( + @inject(MousePositionTracker) readonly positionTracker: MousePositionTracker, + @optional() @inject(TYPES.IMovementRestrictor) readonly movementRestrictor?: IMovementRestrictor, + @optional() @inject(TYPES.ISnapper) readonly snapper?: ISnapper, + @optional() @inject(TYPES.IHelperLineManager) readonly helperLineManager?: IHelperLineManager + ) {} + + unsnapModifier(): KeyboardModifier | undefined { + return 'shift'; + } + + usePositionSnap(arg: MouseEvent | KeyboardEvent | any): boolean { + return typeof arg === 'boolean' ? arg : !(arg as MouseEvent | KeyboardEvent).shiftKey; + } + + snapPosition(element: GModelElement, position: Point): Point { + return this.snapper?.snap(position, element) ?? position; + } + + isValid(element: GModelElement): boolean { + return this.hasValidPosition(element) && this.hasValidSize(element); + } + + hasValidPosition(element: GModelElement, position?: Point): boolean { + return !isLocateable(element) || isValidMove(element, position ?? element.position, this.movementRestrictor); + } + + hasValidSize(element: GModelElement, size?: Dimension): boolean { + if (!isBoundsAware(element)) { + return true; + } + const dimension: Dimension = size ?? element.bounds; + const minimum = this.getMinimumSize(element); + if (dimension.width < minimum.width || dimension.height < minimum.height) { + return false; + } + return true; + } + + getMinimumSize(element: GModelElement): Dimension { + if (!isBoundsAware(element)) { + return Dimension.EMPTY; + } + const definedMinimum = minDimensions(element); + const computedMinimum = LayoutAware.getComputedDimensions(element); + return computedMinimum + ? { + width: Math.max(definedMinimum.width, computedMinimum.width), + height: Math.max(definedMinimum.height, computedMinimum.height) + } + : definedMinimum; + } + + useMovementRestriction(arg: MouseEvent | KeyboardEvent | any): boolean { + return this.usePositionSnap(arg); + } + + restrictMovement(element: GModelElement, movement: Movement): Movement { + const minimumMovement = this.helperLineManager?.getMinimumMoveVector(element, true, movement.direction); + if (!minimumMovement) { + return movement; + } + // minimum is given in absolute coordinates + // if minimum is not reached, reset to original position for that coordinate + const absVector = Vector.abs(movement.vector); + const targetPosition: Point = { + x: absVector.x < minimumMovement.x ? movement.from.x : movement.to.x, + y: absVector.y < minimumMovement.y ? movement.from.y : movement.to.y + }; + return Point.move(movement.from, targetPosition); + } + + addRestrictionFeedback(feedback: FeedbackEmitter, move: TrackedElementMove): FeedbackEmitter { + if (this.movementRestrictor) { + feedback.add( + movementRestrictionFeedback(move.element, this.movementRestrictor!, move.valid), + removeMovementRestrictionFeedback(move.element, this.movementRestrictor!) + ); + } + return feedback; + } + + defaultResizeLocations(): ResizeHandleLocation[] { + return [ + ResizeHandleLocation.TopLeft, + ResizeHandleLocation.TopRight, + ResizeHandleLocation.BottomRight, + ResizeHandleLocation.BottomLeft + ]; + } + + useSymmetricResize(arg: MouseEvent | KeyboardEvent | any): boolean { + return typeof arg === 'boolean' ? arg : (arg as MouseEvent | KeyboardEvent).ctrlKey; + } + + createTracker(): ChangeBoundsTracker { + return new ChangeBoundsTracker(this); + } +} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index d79fd46..9cce984 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -19,12 +19,14 @@ import { inject, injectable } from 'inversify'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { OptionalAction } from '../../../base/model/glsp-model-source'; import { forEachElement } from '../../../utils/gmodel-util'; -import { addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; +import { ResizeHandleLocation, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; +import { ChangeBoundsManager } from './change-bounds-manager'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; elementId: string; + resizeLocations?: ResizeHandleLocation[]; } export namespace ShowChangeBoundsToolResizeFeedbackAction { @@ -34,10 +36,16 @@ export namespace ShowChangeBoundsToolResizeFeedbackAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'elementId'); } - export function create(elementId: string): ShowChangeBoundsToolResizeFeedbackAction { + /** @deprecated Use the create method with the options object parameter instead and set the 'elementId' parameter. */ + export function create(elementId: string): ShowChangeBoundsToolResizeFeedbackAction; + export function create(options: Omit): ShowChangeBoundsToolResizeFeedbackAction; + export function create( + options: Omit | string + ): ShowChangeBoundsToolResizeFeedbackAction { + const opts = typeof options === 'string' ? { elementId: options } : options; return { kind: KIND, - elementId + ...opts }; } } @@ -63,15 +71,18 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { static readonly KIND = ShowChangeBoundsToolResizeFeedbackAction.KIND; @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; + @inject(TYPES.IChangeBoundsManager) protected changeBoundsManager: ChangeBoundsManager; execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; forEachElement(index, isResizable, element => element.id !== this.action.elementId && removeResizeHandles(element)); - const resizeElement = index.getById(this.action.elementId); - if (resizeElement && isResizable(resizeElement)) { - addResizeHandles(resizeElement); + if (this.action.elementId) { + const resizeElement = index.getById(this.action.elementId); + if (resizeElement && isResizable(resizeElement)) { + addResizeHandles(resizeElement, this.action.resizeLocations ?? this.changeBoundsManager.defaultResizeLocations()); + } } return context.root; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index c12fb22..7bfb651 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -16,12 +16,14 @@ import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '@eclipse-glsp/sprotty'; import '../../../../css/change-bounds.css'; import { SResizeHandle } from '../../change-bounds/model'; +import { ChangeBoundsManager } from './change-bounds-manager'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { SResizeHandleView } from './view'; export const changeBoundsToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IChangeBoundsManager, ChangeBoundsManager); bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index e7ddc0f..49c02dd 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -13,29 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, ElementMove, GModelElement, GModelRoot, MoveAction, Point, findParentByFeature } from '@eclipse-glsp/sprotty'; +import { Action, ElementMove, GModelElement, GModelRoot, MoveAction, Point, findParentByFeature } from '@eclipse-glsp/sprotty'; import { DebouncedFunc, debounce } from 'lodash'; +import { ChangeBoundsTracker, TrackedMove } from '.'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; import { MoveableElement, filter, getElements, isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils'; -import { PointPositionUpdater } from '../../change-bounds'; import { SResizeHandle } from '../../change-bounds/model'; -import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; import { ChangeBoundsTool } from './change-bounds-tool'; import { MoveFinishedEventAction, MoveInitializedEventAction } from './change-bounds-tool-feedback'; -export interface ValidatedElementMove extends ElementMove { - valid: boolean; -} - -export namespace ValidatedElementMove { - export function isValid(move: ElementMove): boolean { - return (move as ValidatedElementMove).valid ?? true; - } -} - /** * This mouse listener provides visual feedback for moving by sending client-side * `MoveAction`s while elements are selected and dragged. This will also update @@ -43,9 +32,9 @@ export namespace ValidatedElementMove { * the visual feedback but also the basis for sending the change to the server * (see also `tools/MoveTool`). */ -export class FeedbackMoveMouseListener extends DragAwareMouseListener implements Disposable { +export class FeedbackMoveMouseListener extends DragAwareMouseListener { protected rootElement?: GModelRoot; - protected positionUpdater; + protected tracker: ChangeBoundsTracker; protected elementId2startPos = new Map(); protected pendingMoveInitialized?: DebouncedFunc<() => void>; @@ -55,7 +44,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements constructor(protected tool: ChangeBoundsTool) { super(); - this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); + this.tracker = tool.createChangeBoundsTracker(); this.moveInitializedFeedback = tool.createFeedbackEmitter(); this.moveFeedback = tool.createFeedbackEmitter(); } @@ -66,7 +55,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements this.initializeMove(target, event); return []; } - this.positionUpdater.resetPosition(); + this.tracker.stopTracking(); return []; } @@ -77,10 +66,10 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements } const moveable = findParentByFeature(target, this.isValidMoveable); if (moveable !== undefined) { - this.positionUpdater.updateLastDragPosition(event); + this.tracker.startTracking(); this.scheduleMoveInitialized(); } else { - this.positionUpdater.resetPosition(); + this.tracker.stopTracking(); } } @@ -113,7 +102,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { // should reset everything that may have happend on mouse down this.moveInitializedFeedback.dispose(); - this.positionUpdater.resetPosition(); + this.tracker.stopTracking(); return []; } @@ -121,7 +110,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements super.mouseMove(target, event); if (event.buttons === 0) { return this.mouseUp(target, event); - } else if (!this.positionUpdater.isLastDragPositionUndefined()) { + } else if (this.tracker.isTracking()) { return this.moveElements(target, event); } return []; @@ -131,35 +120,34 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements if (this.elementId2startPos.size === 0) { this.initializeElementsToMove(target.root); } - const moveAction = this.getElementMoves(target, event, false); - if (!moveAction) { + const elementsToMove = this.getElementsToMove(target); + const move = this.tracker.moveElements(elementsToMove, { snap: event, restrict: event }); + if (move.elementMoves.length === 0) { return []; } // cancel any pending move this.pendingMoveInitialized?.cancel(); - this.moveFeedback.add(moveAction, () => this.resetElementPositions(target)); - this.addMovementFeedback(moveAction, target); + this.moveFeedback.add(this.createMoveAction(move), () => this.resetElementPositions(target)); + this.addMovementFeedback(move, target, event); + this.tracker.updateTrackingPosition(move); this.moveFeedback.submit(); return []; } - protected addMovementFeedback(movement: MoveAction, ctx: GModelElement): void { + protected createMoveAction(trackedMove: TrackedMove): Action { + // we never want to animate the move action as this interferes with the move feedback + return MoveAction.create( + trackedMove.elementMoves.map(move => ({ elementId: move.element.id, toPosition: move.toPosition })), + { animate: false } + ); + } + + protected addMovementFeedback(trackedMove: TrackedMove, ctx: GModelElement, event: MouseEvent): void { // cursor feedback this.moveFeedback.add(cursorFeedbackAction(CursorCSS.MOVE), cursorFeedbackAction(CursorCSS.DEFAULT)); // restriction feedback - movement.moves.forEach(move => { - const element = ctx.root.index.getById(move.elementId); - if (element && this.tool.movementRestrictor) { - if (!ValidatedElementMove.isValid(move)) { - this.moveFeedback.add(createMovementRestrictionFeedback(element, this.tool.movementRestrictor), () => - removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!) - ); - } else { - this.moveFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); - } - } - }); + trackedMove.elementMoves.forEach(move => this.tool.changeBoundsManager.addRestrictionFeedback(this.moveFeedback, move)); } protected initializeElementsToMove(root: GModelRoot): void { @@ -177,45 +165,6 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements return getElements(context.root.index, Array.from(this.elementId2startPos.keys()), this.isValidMoveable); } - protected getElementMoves(target: GModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { - const delta = this.positionUpdater.updatePosition(target, event); - if (!delta) { - return undefined; - } - const elementMoves: ElementMove[] = this.getElementMovesForDelta(target, delta, finished).filter(move => - this.filterElementMove(move) - ); - if (elementMoves.length > 0) { - // we never want to animate the move action as this interferes with the move feedback - return MoveAction.create(elementMoves, { animate: false, finished }); - } else { - return undefined; - } - } - - protected filterElementMove(elementMove: ValidatedElementMove): boolean { - return !!elementMove.fromPosition && !Point.equals(elementMove.fromPosition, elementMove.toPosition); - } - - protected getElementMovesForDelta(target: GModelElement, delta: Point, finished: boolean): ValidatedElementMove[] { - return this.getElementsToMove(target).flatMap(element => { - const startPosition = this.elementId2startPos.get(element.id); - if (!startPosition) { - return []; - } - const targetPosition = Point.add(element.position, delta); - const valid = this.tool.movementRestrictor?.validate(element, targetPosition) ?? true; - return [this.createElementMove(element, targetPosition, valid, finished)]; - }); - } - - protected createElementMove(element: MoveableElement, toPosition: Point, valid: boolean, finished: boolean): ValidatedElementMove { - // if we are finished and have an invalid move, we want to move the element back to its start position - return !valid && finished - ? { elementId: element.id, fromPosition: element.position, toPosition: element.position, valid } - : { elementId: element.id, fromPosition: element.position, toPosition, valid }; - } - protected resetElementPositions(context: GModelElement): MoveAction | undefined { const elementMoves: ElementMove[] = this.revertElementMoves(context); return MoveAction.create(elementMoves, { animate: false, finished: true }); @@ -233,12 +182,13 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements } override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { - if (this.positionUpdater.isLastDragPositionUndefined()) { + if (!this.tracker.isTracking()) { return []; } // only reset the move of invalid elements, the others will be handled by the change bounds tool itself - const moves = this.getElementMovesForDelta(target, Point.ORIGIN, false); - moves.filter(move => move.valid).forEach(move => this.elementId2startPos.delete(move.elementId)); + this.getElementsToMove(target) + .filter(element => this.tool.changeBoundsManager.isValid(element)) + .forEach(element => this.elementId2startPos.delete(element.id)); this.dispose(); return []; } @@ -247,7 +197,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements this.pendingMoveInitialized?.cancel(); this.moveInitializedFeedback.dispose(); this.moveFeedback.dispose(); - this.positionUpdater.resetPosition(); + this.tracker.dispose(); this.elementId2startPos.clear(); super.dispose(); } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index d7b3645..fa2306b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -37,11 +37,18 @@ import { TYPES, findParentByFeature } from '@eclipse-glsp/sprotty'; +import { CSS_RESIZE_MODE, ChangeBoundsManager, ChangeBoundsTracker, TrackedElementResize, TrackedResize } from '..'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; -import { CursorCSS, applyCssClasses, cursorFeedbackAction, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { + CursorCSS, + ModifyCSSFeedbackAction, + applyCssClasses, + cursorFeedbackAction, + deleteCssClasses, + toggleCssClasses +} from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; -import { isValidMove, isValidSize } from '../../../utils'; import { BoundsAwareModelElement, ResizableModelElement, @@ -51,16 +58,13 @@ import { isNonRoutableSelectedMovableBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; -import { SetBoundsFeedbackAction } from '../../bounds'; -import { PointPositionUpdater } from '../../change-bounds'; -import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; +import { LocalRequestBoundsAction, SetBoundsFeedbackAction } from '../../bounds'; +import { SResizeHandle, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor, - createMovementRestrictionFeedback, + movementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; -import { PositionSnapper } from '../../change-bounds/position-snapper'; -import { getDirectionFrom } from '../../helper-lines'; import { BaseEditTool } from '../base-tools'; import { HideChangeBoundsToolResizeFeedbackAction, @@ -89,7 +93,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; - @inject(PositionSnapper) readonly positionSnapper: PositionSnapper; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; get id(): string { return ChangeBoundsTool.ID; @@ -115,6 +119,10 @@ export class ChangeBoundsTool extends BaseEditTool { ); } + createChangeBoundsTracker(): ChangeBoundsTracker { + return this.changeBoundsManager.createTracker(); + } + protected createMoveMouseListener(): MouseListener { return new FeedbackMoveMouseListener(this); } @@ -124,22 +132,12 @@ export class ChangeBoundsTool extends BaseEditTool { } } -export interface ValidatedElementAndBounds extends ElementAndBounds { - valid: boolean; -} - -export namespace ValidatedElementAndBounds { - export function isValid(move: ElementAndBounds): boolean { - return (move as ValidatedElementAndBounds).valid ?? true; - } -} - -export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener, Disposable { +export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener { static readonly CSS_CLASS_ACTIVE = 'active'; // members for calculating the correct position change protected initialBounds: ElementAndBounds | undefined; - protected positionUpdater: PointPositionUpdater; + protected tracker: ChangeBoundsTracker; // members for resize mode protected activeResizeElement?: ResizableModelElement; @@ -149,7 +147,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel constructor(protected tool: ChangeBoundsTool) { super(); - this.positionUpdater = new PointPositionUpdater(tool.positionSnapper); + this.tracker = tool.createChangeBoundsTracker(); this.handleFeedback = tool.createFeedbackEmitter(); this.resizeFeedback = tool.createFeedbackEmitter(); } @@ -171,22 +169,28 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel this.activeResizeElement = this.activeResizeHandle?.parent ?? this.findResizeElement(target); if (this.activeResizeElement) { if (event) { - this.positionUpdater.updateLastDragPosition(event); + this.tracker.startTracking(); } this.initialBounds = { newSize: this.activeResizeElement.bounds, newPosition: this.activeResizeElement.bounds, elementId: this.activeResizeElement.id }; + // we trigger the local bounds calculation once to get the correct layout information for reszing + // for any sub-sequent calls the layout information will be updated automatically + this.tool + .createFeedbackEmitter() + .add(LocalRequestBoundsAction.create(target.root, [this.activeResizeElement.id])) + .submit() + .dispose(); this.handleFeedback.add( - ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id), + ShowChangeBoundsToolResizeFeedbackAction.create({ elementId: this.activeResizeElement.id }), HideChangeBoundsToolResizeFeedbackAction.create() ); this.handleFeedback.submit(); return true; } else { - this.positionUpdater.resetPosition(); - this.handleFeedback.dispose(); + this.dispose(); return false; } } @@ -200,74 +204,81 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel protected override draggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements // consider resize handles ourselves - if (this.activeResizeHandle && !this.positionUpdater.isLastDragPositionUndefined()) { - if (!this.initialBounds) { - const element = this.activeResizeHandle.parent; - this.initialBounds = { elementId: element.id, newSize: element.bounds, newPosition: element.bounds }; - } - const positionUpdate = this.positionUpdater.updatePosition(target, event, getDirectionFrom(this.activeResizeHandle.location)); - if (positionUpdate) { - const resizeActions = this.handleResizeOnClient(positionUpdate).filter(action => action); - if (resizeActions.length > 0) { - this.resizeFeedback.add(SetBoundsFeedbackAction.create(resizeActions), () => this.resetBoundsAction()); - this.addResizeFeedback(resizeActions, this.activeResizeHandle, target, event); - this.resizeFeedback.submit(); - } + if (this.activeResizeHandle && this.tracker.isTracking()) { + const resize = this.tracker.resizeElements(this.activeResizeHandle, { snap: event, symmetric: event, restrict: event }); + this.addResizeFeedback(resize, target, event); + const resizeAction = this.resizeBoundsAction(resize); + if (resizeAction.bounds.length > 0) { + this.resizeFeedback.add(resizeAction, () => this.resetBounds()); + this.tracker.updateTrackingPosition(resize.handleMove.moveVector); + } else { + this.resizeFeedback.add(undefined, () => this.resetBounds()); } + this.resizeFeedback.submit(); } return super.draggingMouseMove(target, event); } - protected filterResizeOnClient(resize: ValidatedElementAndBounds, positionUpdate: Point): boolean { - return true; + protected resizeBoundsAction(resize: TrackedResize): SetBoundsFeedbackAction { + // we do not want to resize elements beyond their valid size, not even for feedback, as the next layout cycle usually corrects this + const elementResizes = resize.elementResizes.filter(elementResize => elementResize.valid.size); + return SetBoundsFeedbackAction.create(elementResizes.map(elementResize => this.toElementAndBounds(elementResize))); } - protected addResizeFeedback( - resizeActions: ValidatedElementAndBounds[], - handle: SResizeHandle, - target: GModelElement, - event: MouseEvent - ): void { + protected toElementAndBounds(elementResize: TrackedElementResize): ElementAndBounds { + return { + elementId: elementResize.element.id, + newSize: elementResize.toBounds, + newPosition: elementResize.toBounds + }; + } + + protected addResizeFeedback(resize: TrackedResize, target: GModelElement, event: MouseEvent): void { + const handle = resize.handleMove.element; // handle feedback this.resizeFeedback.add( applyCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE), deleteCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE) ); + // graph feedback + this.resizeFeedback.add( + ModifyCSSFeedbackAction.create({ add: [CSS_RESIZE_MODE] }), + ModifyCSSFeedbackAction.create({ remove: [CSS_RESIZE_MODE] }) + ); + // cursor feedback - const cursorClass = handle.isNeResize() - ? CursorCSS.RESIZE_NE - : handle.isNwResize() - ? CursorCSS.RESIZE_NW - : handle.isSeResize() - ? CursorCSS.RESIZE_SE - : CursorCSS.RESIZE_SW; + const cursorClass = SResizeHandle.getCursorCss(resize.handleMove.element); this.resizeFeedback.add(cursorFeedbackAction(cursorClass), cursorFeedbackAction(CursorCSS.DEFAULT)); - // restriction feedback - resizeActions.forEach(elementResize => { - const element = handle.root.index.getById(elementResize.elementId); - if (element && this.tool.movementRestrictor) { - if (!elementResize.valid) { - this.resizeFeedback.add(createMovementRestrictionFeedback(element, this.tool.movementRestrictor), () => - removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!) - ); - } else { - this.resizeFeedback.add(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor!)); - } + const movementRestrictor = this.tool.changeBoundsManager.movementRestrictor; + resize.elementResizes.forEach(elementResize => { + if (movementRestrictor) { + this.resizeFeedback.add( + movementRestrictionFeedback(elementResize.element, movementRestrictor, elementResize.valid.move), + removeMovementRestrictionFeedback(elementResize.element, movementRestrictor) + ); } + this.resizeFeedback.add( + toggleCssClasses(elementResize.element, !elementResize.valid.size, 'resize-not-allowed'), + deleteCssClasses(elementResize.element, 'resize-not-allowed') + ); }); + this.resizeFeedback.add( + toggleCssClasses(resize.handleMove.element, !resize.valid.size, 'resize-not-allowed'), + deleteCssClasses(resize.handleMove.element, 'resize-not-allowed') + ); } - protected resetBoundsAction(): Action[] { + protected resetBounds(): Action[] { // reset the bounds to the initial bounds and ensure that we do not show helper line feedback anymore (MoveFinishedEventAction) return this.initialBounds ? [SetBoundsFeedbackAction.create([this.initialBounds]), MoveFinishedEventAction.create()] : [MoveFinishedEventAction.create()]; } - override draggingMouseUp(target: GModelElement, _event: MouseEvent): Action[] { - if (this.positionUpdater.isLastDragPositionUndefined()) { + override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { + if (!this.tracker.isTracking()) { return []; } const actions: Action[] = []; @@ -296,17 +307,13 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel const selectedElements = getMatchingElements(target.index, isNonRoutableSelectedMovableBoundsAware); const selectionSet: Set = new Set(selectedElements); const newBounds: ElementAndBounds[] = selectedElements - .filter(element => this.isValidElement(element, selectionSet)) + .filter(element => this.isValidMove(element, selectionSet)) .map(toElementAndBounds); return newBounds.length > 0 ? [ChangeBoundsOperation.create(newBounds)] : []; } - protected isValidElement(element: BoundsAwareModelElement, selectedElements: Set = new Set()): boolean { - return ( - this.isValidMove(element, element.bounds) && - this.isValidSize(element, element.bounds) && - !this.isChildOfSelected(selectedElements, element) - ); + protected isValidMove(element: BoundsAwareModelElement, selectedElements: Set = new Set()): boolean { + return this.tool.changeBoundsManager.hasValidPosition(element) && !this.isChildOfSelected(selectedElements, element); } protected isChildOfSelected(selectedElements: Set, element: GModelElement): boolean { @@ -342,7 +349,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } protected handleResizeOnServer(activeResizeHandle: SResizeHandle): Action[] { - if (this.isValidElement(activeResizeHandle.parent) && this.initialBounds) { + if (this.initialBounds && this.isValidResize(activeResizeHandle.parent)) { const elementAndBounds = toElementAndBounds(activeResizeHandle.parent); if (!this.initialBounds.newPosition || !elementAndBounds.newPosition) { return []; @@ -361,6 +368,10 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return []; } + protected isValidResize(element: BoundsAwareModelElement): boolean { + return this.tool.changeBoundsManager.isValid(element); + } + selectionChanged(root: GModelRoot, selectedElements: string[]): void { if (this.activeResizeElement && selectedElements.includes(this.activeResizeElement.id)) { // our active element is still selected, nothing to do @@ -374,88 +385,18 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return; } } - this.updateResizeElement(root); - this.disposeAllButHandles(); + this.dispose(); } protected isActiveResizeElement(element?: GModelElement): element is GParentElement & BoundsAware { return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; } - protected handleResizeOnClient(positionUpdate: Point): ValidatedElementAndBounds[] { - if (!this.activeResizeHandle) { - return []; - } - - if (this.isActiveResizeElement(this.activeResizeHandle.parent)) { - switch (this.activeResizeHandle.location) { - case ResizeHandleLocation.TopLeft: - return this.handleTopLeftResize(this.activeResizeHandle.parent, positionUpdate); - case ResizeHandleLocation.TopRight: - return this.handleTopRightResize(this.activeResizeHandle.parent, positionUpdate); - case ResizeHandleLocation.BottomLeft: - return this.handleBottomLeftResize(this.activeResizeHandle.parent, positionUpdate); - case ResizeHandleLocation.BottomRight: - return this.handleBottomRightResize(this.activeResizeHandle.parent, positionUpdate); - } - } - return []; - } - - protected handleTopLeftResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { - return this.createSetBoundsAction( - resizeElement, - { x: resizeElement.bounds.x + positionUpdate.x, y: resizeElement.bounds.y + positionUpdate.y }, - { width: resizeElement.bounds.width - positionUpdate.x, height: resizeElement.bounds.height - positionUpdate.y } - ); - } - - protected handleTopRightResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { - return this.createSetBoundsAction( - resizeElement, - { x: resizeElement.bounds.x, y: resizeElement.bounds.y + positionUpdate.y }, - { width: resizeElement.bounds.width + positionUpdate.x, height: resizeElement.bounds.height - positionUpdate.y } - ); - } - - protected handleBottomLeftResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { - return this.createSetBoundsAction( - resizeElement, - { x: resizeElement.bounds.x + positionUpdate.x, y: resizeElement.bounds.y }, - { width: resizeElement.bounds.width - positionUpdate.x, height: resizeElement.bounds.height + positionUpdate.y } - ); - } - - protected handleBottomRightResize(resizeElement: ResizableModelElement, positionUpdate: Point): ValidatedElementAndBounds[] { - return this.createSetBoundsAction( - resizeElement, - { x: resizeElement.bounds.x, y: resizeElement.bounds.y }, - { width: resizeElement.bounds.width + positionUpdate.x, height: resizeElement.bounds.height + positionUpdate.y } - ); - } - - protected createSetBoundsAction(element: BoundsAwareModelElement, newPosition: Point, newSize: Dimension): ValidatedElementAndBounds[] { - if (!isValidSize(element, newSize)) { - // we are not allowing any invalid sizes (breaking min size), not even during client feedback - return []; - } - const valid = this.isValidMove(element, newPosition); - return [{ elementId: element.id, newPosition, newSize, valid }]; - } - - protected isValidSize(element: BoundsAwareModelElement, size: Dimension): boolean { - return isValidSize(element, size); - } - - protected isValidMove(element: BoundsAwareModelElement, newPosition: Point): boolean { - return isValidMove(element, newPosition, this.tool.movementRestrictor); - } - protected disposeAllButHandles(): void { // We do not dispose the handle feedback as we want to keep showing the handles on selected elements // this.handleFeedback.dispose(); this.resizeFeedback.dispose(); - this.positionUpdater.resetPosition(); + this.tracker.dispose(); this.activeResizeElement = undefined; this.activeResizeHandle = undefined; this.initialBounds = undefined; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts new file mode 100644 index 0000000..63efd0c --- /dev/null +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts @@ -0,0 +1,455 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Bounds, + Dimension, + GModelElement, + GRoutingHandle, + Locateable, + Movement, + Point, + ResolvedElementMove, + TypeGuard, + Vector, + Writable, + hasBooleanProp, + isMoveable +} from '@eclipse-glsp/sprotty'; +import { ChangeBoundsManager } from '..'; +import { BoundsAwareModelElement, MoveableElement, ResizableModelElement, getElements } from '../../../utils'; +import { DiagramMovementCalculator, ResizeHandleLocation, SResizeHandle } from '../../change-bounds'; + +export interface ElementTrackingOptions { + /** Snap position. Default: true. */ + snap: boolean | MouseEvent | KeyboardEvent | any; + /** Restrict position. Default: true */ + restrict: boolean | MouseEvent | KeyboardEvent | any; + /** Validate operation. Default: true */ + validate: boolean; + + /** Skip operations that do not trigger change. Default: true */ + skipStatic: boolean; +} + +export interface MoveOptions extends ElementTrackingOptions { + /** Skip operations are invalid. Default: false */ + skipInvalid: boolean; +} + +export const DEFAULT_MOVE_OPTIONS: MoveOptions = { + snap: true, + restrict: true, + validate: true, + + skipStatic: true, + skipInvalid: false +}; + +export type MoveableElements = + | MoveableElement[] + | { + ctx: GModelElement; + elementIDs: string[]; + guard?: TypeGuard; + }; + +export interface TrackedElementMove extends ResolvedElementMove { + moveVector: Vector; + sourceVector: Vector; + valid: boolean; +} + +export type TypedElementMove = TrackedElementMove & { element: T }; + +export interface TrackedMove extends Movement { + elementMoves: TrackedElementMove[]; + valid: boolean; + options: MoveOptions; +} + +export namespace TrackedMove { + export function is(obj: any): obj is TrackedMove { + return Movement.is(obj) && hasBooleanProp(obj, 'valid'); + } +} + +export interface ResizeOptions extends ElementTrackingOptions { + /** Skip resizes that do not actually change the dimension of the element. Default: true. */ + skipStatic: boolean; + + /** Perform symmetric resize on the opposite side. Default: false. */ + symmetric: boolean | MouseEvent | KeyboardEvent | any; + + /** + * Avoids resizes smaller than the minimum size which will result in invalid sizes. + * Please note that the snapping will be applied before the constraining so an element may still be resized to an unsnapped size. + * + * Default: true. + */ + constrainResize: boolean; + + /** Skip resizes that produce an invalid size. Default: false. */ + skipInvalidSize: boolean; + + /** Skip resizes that produce an invalid move. Default: false. */ + skipInvalidMove: boolean; +} + +export const DEFAULT_RESIZE_OPTIONS: ResizeOptions = { + snap: true, + restrict: true, + validate: true, + symmetric: true, + + constrainResize: true, + + skipStatic: true, + skipInvalidSize: false, + skipInvalidMove: false +}; + +export interface TrackedHandleMove extends TypedElementMove {} + +export interface TrackedElementResize { + element: BoundsAwareModelElement; + fromBounds: Bounds; + toBounds: Bounds; + valid: { + size: boolean; + move: boolean; + }; +} + +export interface TrackedResize extends Movement { + handleMove: TrackedHandleMove; + elementResizes: TrackedElementResize[]; + valid: { + size: boolean; + move: boolean; + }; + options: ResizeOptions; +} + +export class ChangeBoundsTracker { + protected diagramMovement: DiagramMovementCalculator; + + constructor(readonly manager: ChangeBoundsManager) { + this.diagramMovement = new DiagramMovementCalculator(manager.positionTracker); + } + + startTracking(): this { + this.diagramMovement.init(); + return this; + } + + updateTrackingPosition(param: Vector | Movement | TrackedMove): void { + const update = TrackedMove.is(param) ? Vector.max(...param.elementMoves.map(move => move.moveVector)) : param; + this.diagramMovement.updatePosition(update); + } + + isTracking(): boolean { + return this.diagramMovement.hasPosition; + } + + stopTracking(): this { + this.diagramMovement.dispose(); + return this; + } + + // + // MOVE + // + + moveElements(elements: MoveableElements, opts?: Partial): TrackedMove { + const options = this.resolveMoveOptions(opts); + const update = this.calculateDiagramMovement(); + const move: TrackedMove = { ...update, elementMoves: [], valid: true, options }; + + if (Vector.isZero(update.vector)) { + // no movement detected so elements won't be moved, exit early + return move; + } + + // calculate move for each element + const elementsToMove = this.getMoveableElements(elements, options); + for (const element of elementsToMove) { + const elementMove = this.calculateElementMove(element, update.vector, options); + if (!this.skipElementMove(elementMove, options)) { + move.elementMoves.push(elementMove); + move.valid &&= elementMove.valid; + } + } + return move; + } + + protected resolveMoveOptions(opts?: Partial): MoveOptions { + return { + ...DEFAULT_MOVE_OPTIONS, + ...opts, + snap: this.manager.usePositionSnap(opts?.snap ?? DEFAULT_MOVE_OPTIONS.snap), + restrict: this.manager.useMovementRestriction(opts?.restrict ?? DEFAULT_MOVE_OPTIONS.restrict) + }; + } + + protected calculateDiagramMovement(): Movement { + return this.diagramMovement.calculateMoveToCurrent(); + } + + protected getMoveableElements(elements: MoveableElements, options: MoveOptions): MoveableElement[] { + return Array.isArray(elements) ? elements : getElements(elements.ctx.index, elements.elementIDs, elements.guard ?? isMoveable); + } + + protected skipElementMove(elementMove: TrackedElementMove, options: MoveOptions): boolean { + return (options.skipInvalid && !elementMove.valid) || (options.skipStatic && Vector.isZero(elementMove.moveVector)); + } + + protected calculateElementMove(element: T, vector: Vector, options: MoveOptions): TypedElementMove { + const fromPosition = element.position; + const toPosition = Point.add(fromPosition, vector); + const move: TypedElementMove = { element, fromPosition, toPosition, valid: true, moveVector: vector, sourceVector: vector }; + + if (options.snap) { + move.toPosition = this.snapPosition(move, options); + } + + if (options.restrict) { + move.toPosition = this.restrictMovement(move, options); + } + + if (options.validate) { + move.valid = this.validateElementMove(move, options); + } + + move.moveVector = Point.vector(move.fromPosition, move.toPosition); + return move; + } + + protected snapPosition(elementMove: TrackedElementMove, opts: MoveOptions): Point { + return this.manager.snapPosition(elementMove.element, elementMove.toPosition); + } + + protected restrictMovement(elementMove: TrackedElementMove, opts: MoveOptions): Point { + const movement = Point.move(elementMove.fromPosition, elementMove.toPosition); + return this.manager.restrictMovement(elementMove.element, movement).to; + } + + protected validateElementMove(elementMove: TrackedElementMove, opts: MoveOptions): boolean { + return this.manager.hasValidPosition(elementMove.element, elementMove.toPosition); + } + + // + // RESIZE + // + + resizeElements(handle: SResizeHandle, opts?: Partial): TrackedResize { + const options = this.resolveResizeOptions(opts); + const update = this.calculateDiagramMovement(); + const handleMove = this.calculateHandleMove(new MoveableResizeHandle(handle), update.vector, options); + const resize: TrackedResize = { ...update, valid: { move: true, size: true }, options, handleMove, elementResizes: [] }; + if (Vector.isZero(handleMove.moveVector)) { + // no movement detected so elements won't be moved, exit early + return resize; + } + + // calculate resize for each element (typically only one element is resized at a time but customizations are possible) + const elementsToResize = this.getResizeableElements(handle, options); + for (const element of elementsToResize) { + const elementResize = this.calculateElementResize(element, handleMove, options); + if (!this.skipElementResize(elementResize, options)) { + resize.elementResizes.push(elementResize); + resize.valid.move = resize.valid.move && elementResize.valid.move; + resize.valid.size = resize.valid.size && elementResize.valid.size; + } + } + return resize; + } + + protected resolveResizeOptions(opts?: Partial): ResizeOptions { + return { + ...DEFAULT_RESIZE_OPTIONS, + ...opts, + snap: this.manager.usePositionSnap(opts?.snap ?? DEFAULT_RESIZE_OPTIONS.snap), + restrict: this.manager.useMovementRestriction(opts?.restrict ?? DEFAULT_RESIZE_OPTIONS.restrict), + symmetric: this.manager.useSymmetricResize(opts?.symmetric ?? DEFAULT_RESIZE_OPTIONS.symmetric) + }; + } + + protected calculateHandleMove(handle: MoveableResizeHandle, diagramMovement: Vector, opts?: Partial): TrackedHandleMove { + const moveOptions = this.resolveMoveOptions({ ...opts, validate: false }); + return this.calculateElementMove(handle, diagramMovement, moveOptions); + } + + protected getResizeableElements(handle: SResizeHandle, options: ResizeOptions): ResizableModelElement[] { + return [handle.parent]; + } + + protected skipElementResize(elementResize: TrackedElementResize, options: ResizeOptions): boolean { + return ( + (options.skipInvalidMove && !elementResize.valid.move) || + (options.skipInvalidSize && !elementResize.valid.size) || + (options.skipStatic && Dimension.equals(elementResize.fromBounds, elementResize.toBounds)) + ); + } + + protected calculateElementResize( + element: ResizableModelElement, + handleMove: TrackedHandleMove, + options: ResizeOptions + ): TrackedElementResize { + const fromBounds = element.bounds; + const toBounds = this.calculateElementBounds(element, handleMove, options); + const resize: TrackedElementResize = { element, fromBounds, toBounds, valid: { size: true, move: true } }; + + if (options.validate) { + resize.valid.size = this.manager.hasValidSize(resize.element, resize.toBounds); + resize.valid.move = handleMove.valid && this.manager.hasValidPosition(resize.element, resize.toBounds); + } + + return resize; + } + + protected calculateElementBounds(element: ResizableModelElement, handleMove: TrackedHandleMove, options: ResizeOptions): Bounds { + let toBounds = this.calculateBounds(element.bounds, handleMove); + if (options.symmetric) { + const symmetricHandleMove = this.calculateSymmetricHandleMove(handleMove, options); + toBounds = this.calculateBounds(toBounds, symmetricHandleMove); + } + if (!options.constrainResize || this.manager.hasValidSize(element, toBounds)) { + return toBounds; + } + + // we need to adjust to the minimum size but it is not enough to simply set the size + // we need to make sure that the element is still at the expected position + // we therefore constrain the movement vector to actually avoid going below the minimum size + const minimum = this.manager.getMinimumSize(element); + handleMove.moveVector = this.constrainResizeVector(element.bounds, handleMove, minimum); + if (options.symmetric) { + // if we have symmetric resize we want to distribute the constrained movement vector to both sides + // but only for the dimension that was actually resized beyond the minimum + handleMove.moveVector.x = element.bounds.width > minimum.width ? handleMove.moveVector.x / 2 : handleMove.moveVector.x; + handleMove.moveVector.y = element.bounds.height > minimum.height ? handleMove.moveVector.y / 2 : handleMove.moveVector.y; + } + toBounds = this.calculateBounds(element.bounds, handleMove); + if (options.symmetric) { + // since we already distributed the available movement vector, we do not want to snap the symmetric handle move + const symmetricHandleMove = this.calculateSymmetricHandleMove(handleMove, { ...options, snap: false }); + toBounds = this.calculateBounds(toBounds, symmetricHandleMove); + } + return toBounds; + } + + protected calculateSymmetricHandleMove(handleMove: TrackedHandleMove, options: ResizeOptions): TrackedHandleMove { + const moveOptions = this.resolveMoveOptions({ ...options, validate: false, restrict: false }); + return this.calculateElementMove(handleMove.element.opposite(), Vector.reverse(handleMove.moveVector), moveOptions); + } + + protected calculateBounds(src: Readonly, handleMove?: TrackedHandleMove): Bounds { + if (!handleMove || Vector.isZero(handleMove.moveVector)) { + return src; + } + return this.doCalculateBounds(src, handleMove.moveVector, handleMove.element.location); + } + + protected doCalculateBounds(src: Readonly, vector: Vector, location: ResizeHandleLocation): Bounds { + switch (location) { + case ResizeHandleLocation.TopLeft: + return { x: src.x + vector.x, y: src.y + vector.y, width: src.width - vector.x, height: src.height - vector.y }; + case ResizeHandleLocation.Top: + return { ...src, y: src.y + vector.y, height: src.height - vector.y }; + case ResizeHandleLocation.TopRight: + return { ...src, y: src.y + vector.y, width: src.width + vector.x, height: src.height - vector.y }; + case ResizeHandleLocation.Right: + return { ...src, width: src.width + vector.x }; + case ResizeHandleLocation.BottomRight: + return { ...src, width: src.width + vector.x, height: src.height + vector.y }; + case ResizeHandleLocation.Bottom: + return { ...src, height: src.height + vector.y }; + case ResizeHandleLocation.BottomLeft: + return { ...src, x: src.x + vector.x, width: src.width - vector.x, height: src.height + vector.y }; + case ResizeHandleLocation.Left: + return { ...src, x: src.x + vector.x, width: src.width - vector.x }; + } + } + + protected constrainResizeVector(src: Readonly, handleMove: TrackedHandleMove, minimum: Dimension): Vector { + const vector = handleMove.moveVector as Writable; + switch (handleMove.element.location) { + case ResizeHandleLocation.TopLeft: + vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x; + vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y; + break; + case ResizeHandleLocation.Top: + vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y; + break; + case ResizeHandleLocation.TopRight: + vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x; + vector.y = src.height - vector.y < minimum.height ? src.height - minimum.height : vector.y; + break; + case ResizeHandleLocation.Right: + vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x; + break; + case ResizeHandleLocation.BottomRight: + vector.x = src.width + vector.x < minimum.width ? minimum.width - src.width : vector.x; + vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y; + break; + case ResizeHandleLocation.Bottom: + vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y; + break; + case ResizeHandleLocation.BottomLeft: + vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x; + vector.y = src.height + vector.y < minimum.height ? minimum.height - src.height : vector.y; + break; + case ResizeHandleLocation.Left: + vector.x = src.width - vector.x < minimum.width ? src.width - minimum.width : vector.x; + break; + } + return vector; + } + + dispose(): void { + this.stopTracking(); + } +} + +export class MoveableResizeHandle extends SResizeHandle implements Locateable { + constructor( + protected handle: SResizeHandle, + override location: ResizeHandleLocation = handle.location, + readonly position = SResizeHandle.getHandlePosition(handle.parent, location) + ) { + super(location, handle.type, handle.hoverFeedback); + this.id = handle.id; + // this only acts as a wrapper so we do not actually add this to the parent but still want the parent reference + (this as any).parent = handle.parent; + } + + opposite(): MoveableResizeHandle { + return new MoveableResizeHandle(this.handle, ResizeHandleLocation.opposite(this.location)); + } +} + +export class MoveableRoutingHandle extends GRoutingHandle implements Locateable { + constructor( + protected handle: GRoutingHandle, + readonly position: Point + ) { + super(); + this.id = handle.id; + // this only acts as a wrapper so we do not actually add this to the parent but still want the parent reference + (this as any).parent = handle.parent; + } +} diff --git a/packages/client/src/features/tools/change-bounds/index.ts b/packages/client/src/features/tools/change-bounds/index.ts index d990255..be79d32 100644 --- a/packages/client/src/features/tools/change-bounds/index.ts +++ b/packages/client/src/features/tools/change-bounds/index.ts @@ -13,8 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './change-bounds-manager'; export * from './change-bounds-tool'; export * from './change-bounds-tool-feedback'; export * from './change-bounds-tool-module'; export * from './change-bounds-tool-move-feedback'; +export * from './change-bounds-tracker'; export * from './view'; diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index 641b3a4..2571b81 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -16,7 +16,7 @@ import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { ResizeHandleLocation, SResizeHandle, isResizable } from '../../change-bounds/model'; +import { SResizeHandle } from '../../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; @@ -46,19 +46,7 @@ export class SResizeHandleView implements IView { } protected getPosition(handle: SResizeHandle): Point | undefined { - const parent = handle.parent; - if (isResizable(parent)) { - if (handle.location === ResizeHandleLocation.TopLeft) { - return { x: 0, y: 0 }; - } else if (handle.location === ResizeHandleLocation.TopRight) { - return { x: parent.bounds.width, y: 0 }; - } else if (handle.location === ResizeHandleLocation.BottomLeft) { - return { x: 0, y: parent.bounds.height }; - } else if (handle.location === ResizeHandleLocation.BottomRight) { - return { x: parent.bounds.width, y: parent.bounds.height }; - } - } - return undefined; + return Point.subtract(SResizeHandle.getHandlePosition(handle), handle.parent.bounds); } getRadius(): number { diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index 505af94..ea979e8 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -21,7 +21,6 @@ import { CommandReturn, Disposable, EdgeRouterRegistry, - ElementMove, GConnectableElement, GModelElement, GRoutingHandle, @@ -37,17 +36,17 @@ import { hasStringProp, isBoundsAware, isConnectable, - isSelected + isSelected, + toTypeGuard, + typeGuard } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { ChangeBoundsManager, ChangeBoundsTracker, MoveableRoutingHandle } from '..'; import { FeedbackEmitter } from '../../../base'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; -import { forEachElement, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; +import { forEachElement, getMatchingElements, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; -import { PointPositionUpdater } from '../../change-bounds/point-position-updater'; -import { PositionSnapper } from '../../change-bounds/position-snapper'; -import { useSnap } from '../../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; import { FeedbackEdgeEnd, feedbackEdgeEndId, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from '../edge-creation/edge-creation-tool-feedback'; @@ -255,14 +254,14 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener impleme } export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { - protected pointPositionUpdater: PointPositionUpdater; + protected tracker: ChangeBoundsTracker; constructor( - protected positionSnapper: PositionSnapper, + protected changeBoundsManager: ChangeBoundsManager, protected edgeRouterRegistry?: EdgeRouterRegistry ) { super(); - this.pointPositionUpdater = new PointPositionUpdater(positionSnapper); + this.tracker = this.changeBoundsManager.createTracker(); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { @@ -271,64 +270,47 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { const routingHandle = findParentByFeature(target, isRoutingHandle); if (routingHandle !== undefined) { result.push(SwitchRoutingModeAction.create({ elementsToActivate: [target.id] })); - this.pointPositionUpdater.updateLastDragPosition(event); + this.tracker.startTracking(); } else { - this.pointPositionUpdater.resetPosition(); + this.tracker.dispose(); } } return result; } override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - const result: Action[] = []; + super.mouseMove(target, event); if (event.buttons === 0) { return this.mouseUp(target, event); - } - const positionUpdate = this.pointPositionUpdater.updatePosition(target, event); - if (positionUpdate) { - const moveActions = this.handleMoveOnClient(target, positionUpdate, useSnap(event)); - result.push(...moveActions); - } - return result; - } - - protected handleMoveOnClient(target: GModelElement, positionUpdate: Point, isSnap: boolean): Action[] { - const handleMoves: ElementMove[] = []; - target.root.index - .all() - .filter(element => isSelected(element)) - .forEach(element => { - if (isRoutingHandle(element)) { - const elementMove = this.toElementMove(element, positionUpdate, isSnap); - if (elementMove) { - handleMoves.push(elementMove); - } - } - }); - if (handleMoves.length > 0) { - return [MoveAction.create(handleMoves, { animate: false })]; + } else if (this.tracker.isTracking()) { + return this.moveRoutingHandles(target, event); } return []; } - protected toElementMove(element: GRoutingHandle, positionDelta: Point, isSnap: boolean): ElementMove | undefined { - const point = this.getHandlePosition(element); - if (point !== undefined) { - const snappedPoint = this.getSnappedHandlePosition(element, point, isSnap); - return { - elementId: element.id, - fromPosition: point, - toPosition: { - x: snappedPoint.x + positionDelta.x, - y: snappedPoint.y + positionDelta.y - } - }; + protected moveRoutingHandles(target: GModelElement, event: MouseEvent): Action[] { + const routingHandlesToMove = this.getRoutingHandlesToMove(target); + const move = this.tracker.moveElements(routingHandlesToMove, { snap: event, restrict: event }); + if (move.elementMoves.length === 0) { + return []; } - return undefined; + this.tracker.updateTrackingPosition(move); + return [ + MoveAction.create( + move.elementMoves.map(elementMove => ({ elementId: elementMove.element.id, toPosition: elementMove.toPosition })), + { animate: false } + ) + ]; } - protected getSnappedHandlePosition(element: GRoutingHandle, point: Point, isSnap: boolean): Point { - return this.positionSnapper?.snapPosition(point, element, isSnap); + protected getRoutingHandlesToMove(context: GModelElement): MoveableRoutingHandle[] { + const selectedRoutingHandles = getMatchingElements(context.root.index, typeGuard(isRoutingHandle, isSelected)); + return selectedRoutingHandles + .map(handle => { + const position = this.getHandlePosition(handle); + return position ? new MoveableRoutingHandle(handle, position) : undefined; + }) + .filter(toTypeGuard(MoveableRoutingHandle)); } protected getHandlePosition(handle: GRoutingHandle): Point | undefined { @@ -345,7 +327,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } override mouseUp(_target: GModelElement, _event: MouseEvent): Action[] { - this.pointPositionUpdater.resetPosition(); + this.tracker.dispose(); return []; } } diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index 566227b..806b23f 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -24,18 +24,19 @@ import { GRoutableElement, GRoutingHandle, ReconnectEdgeOperation, + TYPES, canEditRouting, findParentByFeature, isConnectable, isSelected } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; +import { ChangeBoundsManager } from '..'; import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; -import { PositionSnapper } from '../../change-bounds/position-snapper'; import { GReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; import { BaseEditTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; @@ -56,7 +57,7 @@ export class EdgeEditTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; - @inject(PositionSnapper) readonly positionSnapper: PositionSnapper; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; @@ -73,7 +74,7 @@ export class EdgeEditTool extends BaseEditTool { // install feedback move mouse listener for client-side move updates this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); - this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.positionSnapper, this.edgeRouterRegistry); + this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.changeBoundsManager, this.edgeRouterRegistry); this.toDisposeOnDisable.push( this.edgeEditListener, diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 2f7f0a6..45edec3 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -56,6 +56,10 @@ export class MarqueeMouseTool extends BaseEditTool { this.createFeedbackEmitter().add(cursorFeedbackAction(CursorCSS.MARQUEE), cursorFeedbackAction()).submit() ); } + + override get isEditTool(): boolean { + return false; + } } export class MarqueeMouseListener extends DragAwareMouseListener { diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 595166b..f34fe5b 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -26,7 +26,7 @@ import { isCtrlOrCmd, isMoveable } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import '../../../../css/ghost-element.css'; import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; @@ -34,13 +34,12 @@ import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from ' import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { MoveableElement, isValidMove } from '../../../utils'; import { getAbsolutePosition } from '../../../utils/viewpoint-util'; -import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; -import { PositionSnapper } from '../../change-bounds/position-snapper'; import { RemoveTemplateElementsAction } from '../../element-template'; import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; import { Containable, isContainable } from '../../hints/model'; import { BaseCreationTool } from '../base-tools'; +import { ChangeBoundsManager } from '../change-bounds'; @injectable() export class NodeCreationTool extends BaseCreationTool implements PositioningTool { @@ -48,8 +47,7 @@ export class NodeCreationTool extends BaseCreationTool(element: T | undefined): element is T { * @param element The element to which the css classes should be added. * @param cssClasses The set of css classes as string array. */ -export function addCssClasses(element: GModelElement, cssClasses: string[]): void { +export function addCssClasses(element: GModelElement, cssClasses: string[]): void; +export function addCssClasses(element: GModelElement, ...cssClasses: string[]): void; +export function addCssClasses(element: GModelElement, ...cssClasses: string[] | [string[]]): void { + const classes = Array.isArray(cssClasses[0]) ? cssClasses[0] : cssClasses; const elementCssClasses: string[] = element.cssClasses ?? []; - distinctAdd(elementCssClasses, ...cssClasses); + distinctAdd(elementCssClasses, ...classes); element.cssClasses = elementCssClasses; } @@ -148,11 +151,44 @@ export function addCssClasses(element: GModelElement, cssClasses: string[]): voi * @param element The element from which the css classes should be removed. * @param cssClasses The set of css classes as string array. */ -export function removeCssClasses(root: GModelElement, cssClasses: string[]): void { - if (!root.cssClasses || root.cssClasses.length === 0) { +export function removeCssClasses(element: GModelElement, cssClasses: string[]): void; +export function removeCssClasses(element: GModelElement, ...cssClasses: string[]): void; +export function removeCssClasses(element: GModelElement, ...cssClasses: string[] | [string[]]): void { + if (!element.cssClasses || element.cssClasses.length === 0) { return; } - remove(root.cssClasses, ...cssClasses); + const classes = Array.isArray(cssClasses[0]) ? cssClasses[0] : cssClasses; + remove(element.cssClasses, ...classes); +} + +/** + * Adds a css classs to a set of {@link GModelElement}s. + * + * @param elements The elements to which the css class should be added. + * @param cssClass The css class to add. + */ +export function addCssClassToElements(elements: GModelElement[], ...cssClasses: string[]): void { + for (const element of elements) { + addCssClasses(element, cssClasses); + } +} + +/** + * Removes a css class from a set of {@link GModelElement}s. + * @param elements The elements from which the css class should be removed. + * @param cssClass The css class to remove. + */ +export function removeCssClassOfElements(elements: GModelElement[], ...cssClasses: string[]): void { + for (const element of elements) { + removeCssClasses(element, cssClasses); + } +} + +/** + * Toggles a css class on a {@link GModelElement} based on the given toggle flag. + */ +export function toggleCssClass(element: GModelElement, cssClass: string, toggle: boolean): void { + return toggle ? addCssClasses(element, cssClass) : removeCssClasses(element, cssClass); } export function isNonRoutableSelectedMovableBoundsAware(element: GModelElement): element is SelectableBoundsAware { diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 83670d3..52c5d4d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -33,6 +33,10 @@ export function minHeight(element: BoundsAwareModelElement): number { return 1; } +export function minDimensions(element: BoundsAwareModelElement): Dimension { + return { width: minWidth(element), height: minHeight(element) }; +} + export function getLayoutOptions(element: GModelElement): ModelLayoutOptions | undefined { const layoutOptions = (element as any).layoutOptions; if (layoutOptions !== undefined) { diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 1b018bd..704e354 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -50,5 +50,6 @@ export const TYPES = { IToolManager: Symbol('IToolManager'), IDebugManager: Symbol('IDebugManager'), Grid: Symbol('Grid'), - IGridManager: Symbol('IGridManager') + IGridManager: Symbol('IGridManager'), + IChangeBoundsManager: Symbol('IChangeBoundsManager') }; diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index dd190e9..ce8c281 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -17,7 +17,7 @@ import * as sprotty from 'sprotty-protocol/lib/actions'; import { GModelRootSchema } from '..'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; -import { Args, ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints } from './types'; +import { Args, ElementAndAlignment, ElementAndBounds, ElementAndLayoutData, ElementAndRoutingPoints } from './types'; /** * Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can @@ -79,6 +79,11 @@ export interface ComputedBoundsAction extends ResponseAction, sprotty.ComputedBo * The route of the model elements. */ routes?: ElementAndRoutingPoints[]; + + /** + * The layout data of hte model elements. + */ + layoutData?: ElementAndLayoutData[]; } export namespace ComputedBoundsAction { @@ -95,6 +100,7 @@ export namespace ComputedBoundsAction { responseId?: string; alignments?: ElementAndAlignment[]; routes?: ElementAndRoutingPoints[]; + layoutData?: ElementAndLayoutData[]; } = {} ): ComputedBoundsAction { return { diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index a614bc3..28d6650 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -81,6 +81,30 @@ export interface ElementAndRoutingPoints { newRoutingPoints?: Point[]; } +/** + * Data provided by the layouter. + */ +export interface LayoutData { + /** + * The computed minimum size of the element. + */ + computedDimensions?: Dimension; +} + +/** + * The `ElementAndLayoutData` type is used to associate new layout data with a model element, which is referenced via its id. + */ +export interface ElementAndLayoutData { + /** + * The identifier of the element. + */ + elementId: string; + /** + * The data provided by the layouter. + */ + layoutData: LayoutData; +} + /** * The `EditorContext` may be used to represent the current state of the editor for particular actions. * It encompasses the last recorded mouse position, the list of selected elements, and may contain diff --git a/packages/protocol/src/sprotty-geometry-bounds.spec.ts b/packages/protocol/src/sprotty-geometry-bounds.spec.ts index e7b4f66..ac3348d 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.spec.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.spec.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { expect } from 'chai'; -import { Point } from 'sprotty-protocol'; +import { Dimension, Point } from 'sprotty-protocol'; import { Bounds } from './sprotty-geometry-bounds'; describe('Bounds', () => { @@ -284,4 +284,36 @@ describe('Bounds', () => { expect(sortedBounds).to.deep.equal([bounds1, bounds2, bounds3]); }); }); + + describe('move', () => { + it('should move the bounds by the given delta', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const delta: Point = { x: 10, y: 20 }; + const result = Bounds.move(bounds, delta); + expect(result).to.deep.equal({ x: 20, y: 40, width: 100, height: 200 }); + }); + + it('should move the bounds by the given delta with negative values', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const delta: Point = { x: -10, y: -20 }; + const result = Bounds.move(bounds, delta); + expect(result).to.deep.equal({ x: 0, y: 0, width: 100, height: 200 }); + }); + }); + + describe('resize', () => { + it('should resize the bounds by the given delta', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const delta: Dimension = { width: 10, height: 20 }; + const result = Bounds.resize(bounds, delta); + expect(result).to.deep.equal({ x: 10, y: 20, width: 110, height: 220 }); + }); + + it('should resize the bounds by the given delta with negative values', () => { + const bounds: Bounds = { x: 10, y: 20, width: 100, height: 200 }; + const delta: Dimension = { width: -10, height: -20 }; + const result = Bounds.resize(bounds, delta); + expect(result).to.deep.equal({ x: 10, y: 20, width: 90, height: 180 }); + }); + }); }); diff --git a/packages/protocol/src/sprotty-geometry-bounds.ts b/packages/protocol/src/sprotty-geometry-bounds.ts index 397da1b..8e8a6b6 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.ts @@ -19,6 +19,11 @@ import { Bounds, Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Bounds { + /** + * The empty bounds with valid dimensions. It has x, y, width, and height set to 0. + */ + const ZERO: Bounds; + /** * Checks whether the inner bounds are compeletely encompassed by the outer bounds. * @@ -42,9 +47,10 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * Checks whether the two bounds are equal. * @param left left bounds * @param right right bounds + * @param eps the epsilon for the comparison * @returns true if the two bounds are equal */ - function equals(left: Bounds, right: Bounds): boolean; + function equals(left: Bounds, right: Bounds, eps?: number): boolean; /** * Returns the x-coordinate of the left edge of the bounds. @@ -209,9 +215,32 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * @returns the sorted bounds */ function sortBy(rankFunc: (elem: T) => number, ...bounds: T[]): T[]; + + /** + * Moves the bounds by the given delta. + * @param bounds the bounds to move + * @param delta the delta to move the bounds by + * @returns the moved bounds + */ + function move(bounds: Bounds, delta: Point): Bounds; + + /** + * Resizes the bounds by the given delta. + * @param bounds the bounds to resize + * @param delta the delta to resize the bounds by + * @returns the resized bounds + */ + function resize(bounds: Bounds, delta: Dimension): Bounds; } } +(Bounds as any).ZERO = Object.freeze({ + x: 0, + y: 0, + width: 0, + height: 0 +}); + Bounds.encompasses = (outer: Bounds, inner: Bounds): boolean => Bounds.includes(outer, Bounds.topLeft(inner)) && Bounds.includes(outer, Bounds.bottomRight(inner)); @@ -231,7 +260,8 @@ Bounds.overlap = (one: Bounds, other: Bounds, touch?: boolean): boolean => { otherBottomRight.y > oneTopLeft.y; }; -Bounds.equals = (left: Bounds, right: Bounds): boolean => Point.equals(left, right) && Dimension.equals(left, right); +Bounds.equals = (left: Bounds, right: Bounds, eps?: number): boolean => + Point.equals(left, right, eps) && Dimension.equals(left, right, eps); Bounds.left = (bounds: Bounds): number => bounds.x; @@ -281,4 +311,12 @@ Bounds.from = (topLeft: Point, bottomRight: Point): Bounds => ({ height: bottomRight.y - topLeft.y }); +Bounds.move = Bounds.translate; + +Bounds.resize = (bounds: Bounds, delta: Dimension): Bounds => ({ + ...bounds, + width: bounds.width + delta.width, + height: bounds.height + delta.height +}); + export { Bounds }; diff --git a/packages/protocol/src/sprotty-geometry-dimension.spec.ts b/packages/protocol/src/sprotty-geometry-dimension.spec.ts index 1ed63d6..a9256d1 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.spec.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.spec.ts @@ -83,6 +83,27 @@ describe('Dimension', () => { const isEqual = Dimension.equals(dimension1, dimension2); expect(isEqual).to.be.false; }); + + it('should return false if the dimensions have different width', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 5, height: 20 }; + const isEqual = Dimension.equals(dimension1, dimension2); + expect(isEqual).to.be.false; + }); + + it('should return false if the dimensions have different height', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 10, height: 10 }; + const isEqual = Dimension.equals(dimension1, dimension2); + expect(isEqual).to.be.false; + }); + + it('should consider epsilon', () => { + const dimension1: Dimension = { width: 10, height: 20 }; + const dimension2: Dimension = { width: 10.0001, height: 20.0001 }; + const isEqual = Dimension.equals(dimension1, dimension2, 0.001); + expect(isEqual).to.be.true; + }); }); describe('fromPoint', () => { @@ -92,4 +113,12 @@ describe('Dimension', () => { expect(dimension).to.deep.equal({ width: 10, height: 20 }); }); }); + + describe('area', () => { + it('should compute the area of the dimension', () => { + const dimension: Dimension = { width: 10, height: 20 }; + const area = Dimension.area(dimension); + expect(area).to.equal(200); + }); + }); }); diff --git a/packages/protocol/src/sprotty-geometry-dimension.ts b/packages/protocol/src/sprotty-geometry-dimension.ts index c9582c2..2c9e0e8 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.ts @@ -16,6 +16,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; +import { equalUpTo } from './utils/math-util'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Dimension { @@ -77,9 +78,10 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * Checks if two dimensions are equal. Two dimensions are equal if their `width` and `height` are equal. * @param left the left dimension * @param right the right dimension + * @param eps @param eps the epsilon for the comparison * @returns true if the dimensions are equal, false otherwise */ - function equals(left: Dimension, right: Dimension): boolean; + function equals(left: Dimension, right: Dimension, eps?: number): boolean; /** * Creates a new dimension from the given point. The `width` and `height` of the new dimension are the `x` and `y` of the point. @@ -87,6 +89,13 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * @returns new dimension */ function fromPoint(point: Point): Dimension; + + /** + * Computes the area of the given dimension. + * @param dimension the dimension + * @returns the area of the dimension + */ + function area(dimension: Dimension): number; } } @@ -106,7 +115,9 @@ Dimension.map = (dimension: T, callbackfn: (value: number, width: callbackfn(dimension.width, 'width'), height: callbackfn(dimension.height, 'height') }); -Dimension.equals = (left: Dimension, right: Dimension): boolean => left.width === right.width && left.height === right.height; +Dimension.equals = (left: Dimension, right: Dimension, eps?: number): boolean => + equalUpTo(left.width, right.width, eps) && equalUpTo(left.height, right.height, eps); Dimension.fromPoint = (point: Point): Dimension => ({ width: point.x, height: point.y }); +Dimension.area = (dimension: Dimension): number => dimension.width * dimension.height; export { Dimension }; diff --git a/packages/protocol/src/sprotty-geometry-point.spec.ts b/packages/protocol/src/sprotty-geometry-point.spec.ts index fa47011..2611482 100644 --- a/packages/protocol/src/sprotty-geometry-point.spec.ts +++ b/packages/protocol/src/sprotty-geometry-point.spec.ts @@ -113,4 +113,18 @@ describe('Point', () => { expect(Point.moveTowards(from, vector)).to.deep.equal(expectedMovement); }); }); + + describe('equals', () => { + it('returns true for equal points', () => { + expect(Point.equals({ x: 1, y: 2 }, { x: 1, y: 2 })).to.be.true; + }); + + it('returns false for different points', () => { + expect(Point.equals({ x: 1, y: 2 }, { x: 1, y: 3 })).to.be.false; + }); + + it('returns true up to an epsilon', () => { + expect(Point.equals({ x: 1, y: 2 }, { x: 1.0001, y: 2.0001 }, 0.001)).to.be.true; + }); + }); }); diff --git a/packages/protocol/src/sprotty-geometry-point.ts b/packages/protocol/src/sprotty-geometry-point.ts index 5c30ac0..fcb5206 100644 --- a/packages/protocol/src/sprotty-geometry-point.ts +++ b/packages/protocol/src/sprotty-geometry-point.ts @@ -17,9 +17,14 @@ import { Point } from 'sprotty-protocol/lib/utils/geometry'; import { AnyObject, Movement, Vector, hasNumberProp } from './utils'; +import { equalUpTo } from './utils/math-util'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Point { + /** + * Type guard to check if the given object is a point. + * @param point the object to be checked + */ function is(point: any): point is Point; /** * The absolute variant of that point, i.e., each coordinate uses its absolute value. @@ -33,6 +38,15 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * @param point the point to be checked for validity */ function isValid(point?: Point): point is Point; + + /** + * Checks whether the given points are equal up to a certain epsilon. + * @param one the first point + * @param other the second point + * @param eps @param eps the epsilon for the comparison + */ + function equals(one: Point, other: Point, eps?: number): boolean; + /** * The absolute variant of that point, i.e., each coordinate uses its absolute value. * @@ -135,4 +149,6 @@ Point.moveTowards = (from: Point, vector: Vector): Movement => { return { from, to, vector, direction: dir }; }; +Point.equals = (one: Point, other: Point, eps?: number): boolean => equalUpTo(one.x, other.x, eps) && equalUpTo(one.y, other.y, eps); + export { Point }; diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts index e9ae9ef..d5d3ba3 100644 --- a/packages/protocol/src/utils/index.ts +++ b/packages/protocol/src/utils/index.ts @@ -19,4 +19,5 @@ export * from './event'; export * from './geometry-movement'; export * from './geometry-util'; export * from './geometry-vector'; +export * from './math-util'; export * from './type-util'; diff --git a/packages/protocol/src/utils/math-util.ts b/packages/protocol/src/utils/math-util.ts new file mode 100644 index 0000000..7b54b0c --- /dev/null +++ b/packages/protocol/src/utils/math-util.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export function equalUpTo(one: number, other: number, epsilon: number = Number.EPSILON): boolean { + return Math.abs(one - other) <= epsilon; +} diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 9d5d740..c6b70d5 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -69,6 +69,11 @@ export type MaybePromise = T | PromiseLike; */ export type TypeGuard = (element: any) => element is T; +/** Utility function to combine two type guards */ +export function typeGuard(one: TypeGuard, other: TypeGuard): TypeGuard { + return (element: any): element is T & G => one(element) && other(element); +} + /** * Utility function that create a typeguard function for a given class constructor. * Essentially this wraps an instance of check as typeguard function. From c6511dc5f61023656b53d5d34577b437b78fe7bf Mon Sep 17 00:00:00 2001 From: Andreas Hell <44035624+Sakrafux@users.noreply.github.com> Date: Fri, 17 May 2024 11:02:01 +0200 Subject: [PATCH 333/566] Mocked workerFactory only for webworker context (#83) --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/src/browser/app.ts | 6 +++++- packages/layout-elk/src/di.config.ts | 11 ++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 10a213e..e291068 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -41,7 +41,7 @@ "wf-glsp-server-node.js.map" ], "scripts": { - "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map", + "clean": "rimraf wf-glsp-server-node.js wf-glsp-server-node.js.map wf-glsp-server-webworker.js wf-glsp-server-webworker.js.map", "start": "node --enable-source-maps ./wf-glsp-server-node.js --port 5007", "start:websocket": "node --enable-source-maps ./wf-glsp-server-node.js -w --port 8081", "watch": "tsc -w" diff --git a/examples/workflow-server/src/browser/app.ts b/examples/workflow-server/src/browser/app.ts index a4a6e63..2c80990 100644 --- a/examples/workflow-server/src/browser/app.ts +++ b/examples/workflow-server/src/browser/app.ts @@ -27,7 +27,11 @@ export async function launch(argv?: string[]): Promise { appContainer.load(createAppModule({ logLevel: LogLevel.info })); const launcher = appContainer.resolve(WorkerServerLauncher); - const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); + const elkLayoutModule = configureELKLayoutModule({ + algorithms: ['layered'], + layoutConfigurator: WorkflowLayoutConfigurator, + isWebWorker: true + }); const serverModule = new WorkflowServerModule().configureDiagramModule( new WorkflowDiagramModule(() => WorkflowMockModelStorage), diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 19d8705..853fab6 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -45,6 +45,11 @@ export interface ElkModuleOptions { * is bound. */ elementFilter?: Constructor; + /** + * A flag to indicate whether a WebWorker context is provided. If this option is set, a feature is mocked that would + * only be available in a node environment. + */ + isWebWorker?: boolean; } /** @@ -82,7 +87,11 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo const elkFactory: ElkFactory = () => new ElkConstructor({ algorithms: options.algorithms, - defaultLayoutOptions: options.defaultLayoutOptions + defaultLayoutOptions: options.defaultLayoutOptions, + // The node implementation relied on elkjs' `FakeWorker` to set the `workerFactory`. + // However, since the required file is dynamically loaded and not available in a web-worker context, + // it needs to be mocked manually. + workerFactory: options.isWebWorker ? () => ({ postMessage: () => {} }) as unknown as Worker : undefined }); bind(ElkFactory).toConstantValue(elkFactory); From 97f27106d27d9469003d1475b44a0e838c1f80b1 Mon Sep 17 00:00:00 2001 From: Andreas Hell <44035624+Sakrafux@users.noreply.github.com> Date: Fri, 17 May 2024 11:05:59 +0200 Subject: [PATCH 334/566] Implemented webworker connection provider (#332) --- .../client-server-protocol/jsonrpc/index.ts | 1 + .../jsonrpc/worker-connection-provider.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/index.ts b/packages/protocol/src/client-server-protocol/jsonrpc/index.ts index b466993..38dc79f 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/index.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/index.ts @@ -17,4 +17,5 @@ export * from './base-jsonrpc-glsp-client'; export * from './glsp-jsonrpc-client'; export * from './glsp-jsonrpc-server'; export * from './websocket-connection'; +export * from './worker-connection-provider'; export * from './ws-connection-provider'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts new file mode 100644 index 0000000..f292443 --- /dev/null +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2023 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { BrowserMessageReader, BrowserMessageWriter, MessageConnection, createMessageConnection } from 'vscode-jsonrpc/browser'; +import { GLSPConnectionHandler } from './ws-connection-provider'; + +export class GLSPWebWorkerProvider { + protected worker: Worker; + + constructor(protected url: string) {} + + protected createWebWorker(url: string): Worker { + return new Worker(url); + } + + listen(handler: GLSPConnectionHandler): MessageConnection { + this.worker = this.createWebWorker(this.url); + const wwConnection = createMessageConnection(new BrowserMessageReader(this.worker), new BrowserMessageWriter(this.worker)); + + handler.logger?.warn('GLSPWebWorkerProvider Initializing!'); + handler.onConnection?.(wwConnection); + + return wwConnection; + } +} From 0eec6501f691216984e25eedd208b9b0c23a5f56 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 24 May 2024 15:04:24 +0200 Subject: [PATCH 335/566] Remove eager default prevention from standalone copy paste handlder (#347) Default event prevention should be handled by the `ICopyPasteHandler` implementation. e.g. The default `ServerCopyPasteHandler` has guard clauses to check wether a copy or paste operation should be executed (diagram must be active and selection >0). It only default prevents if this condition is met. Currently we bypass this special check and always default prevent which causes issues in scenarios where the diagram is nested into a surrounding application frame or is currently not focused (e.g.) hidden --- .../client/src/features/copy-paste/copy-paste-standalone.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index 065e809..f3afae8 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -35,15 +35,12 @@ export class CopyPasteStartup implements IDiagramStartup, Disposable { } const copyListener = (e: ClipboardEvent): void => { this.copyPasteHandler?.handleCopy(e); - e.preventDefault(); }; const cutListener = (e: ClipboardEvent): void => { this.copyPasteHandler?.handleCut(e); - e.preventDefault(); }; const pasteListener = (e: ClipboardEvent): void => { this.copyPasteHandler?.handlePaste(e); - e.preventDefault(); }; window.addEventListener('copy', copyListener); window.addEventListener('cut', cutListener); From b3487887868c70636eabca1a463e920418726e25 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Sun, 26 May 2024 15:23:17 +0000 Subject: [PATCH 336/566] Improve bounds and insert management and customizability (#348) - Allow resizable elements to specify resize handle location - Use correct feedback (move) on ghost element move (not resize) - Centralize most move/resize feedback in change bounds manager - Do not draw grid on hidden SVG as we do not have transformation data - Ensure we also apply move feedback if we do not have ghost element - Ensure that the container feedback considers the insert position - Extract container insert management into manager class - Avoid unnecessary TYPES.* for classes - Fix typos - Remove circular dependencies by fixing index imports Contributed on behalf of Axon Ivy AG --- examples/workflow-glsp/src/model.ts | 32 +- .../src/workflow-diagram-module.ts | 10 +- .../workflow-glsp/src/workflow-startup.ts | 4 +- examples/workflow-standalone/app/example1.wf | 1090 +++++++++-------- packages/client/css/ghost-element.css | 7 + packages/client/src/base/default.module.ts | 3 +- .../src/base/drag-aware-mouse-listener.ts | 18 +- .../feedback-action-dispatcher-default.ts | 119 ++ .../feedback/feedback-action-dispatcher.ts | 108 +- .../base/feedback/feedback-emitter.spec.ts | 176 +++ ...feeback-emitter.ts => feedback-emitter.ts} | 29 +- packages/client/src/base/feedback/index.ts | 3 +- .../client/src/base/selection-service.spec.ts | 2 +- .../view-key-tools/movement-key-tool.ts | 2 +- .../src/features/change-bounds/model.ts | 51 +- .../change-bounds/movement-restrictor.ts | 22 +- .../features/debug/debug-bounds-decorator.tsx | 14 +- .../client/src/features/debug/debug-module.ts | 2 +- ...ouse-tracking-element-position-listener.ts | 36 +- .../client/src/features/grid/grid-module.ts | 4 +- .../helper-line-manager-default.ts | 76 +- packages/client/src/features/hints/model.ts | 7 +- .../features/hints/type-hint-provider.spec.ts | 2 +- .../features/select/select-mouse-listener.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 5 +- .../client/src/features/tools/base-tools.ts | 2 +- .../change-bounds/change-bounds-manager.ts | 84 +- .../change-bounds-tool-feedback.ts | 7 +- .../change-bounds-tool-module.ts | 2 +- .../change-bounds-tool-move-feedback.ts | 12 +- .../tools/change-bounds/change-bounds-tool.ts | 64 +- .../change-bounds/change-bounds-tracker.ts | 31 +- .../tools/edge-creation/edge-creation-tool.ts | 19 +- .../edge-edit/edge-edit-tool-feedback.ts | 2 +- .../tools/edge-edit/edge-edit-tool.ts | 5 +- .../tools/node-creation/container-manager.ts | 76 ++ .../src/features/tools/node-creation/index.ts | 3 + .../tools/node-creation/insert-indicator.ts | 41 + .../node-creation/node-creation-module.ts | 14 +- .../tools/node-creation/node-creation-tool.ts | 188 +-- .../node-creation/node-creation-views.tsx | 49 + packages/client/src/utils/gmodel-util.ts | 5 +- packages/client/src/views/ggraph-view.tsx | 6 +- .../client/src/views/glsp-projection-view.tsx | 5 +- packages/glsp-sprotty/src/index.ts | 1 + packages/glsp-sprotty/src/re-exports.ts | 2 +- .../glsp-sprotty/src/svg-views-override.tsx | 88 ++ packages/glsp-sprotty/src/types.ts | 5 +- .../src/action-protocol/model-layout.ts | 2 +- .../src/sprotty-geometry-bounds.spec.ts | 30 + .../protocol/src/sprotty-geometry-bounds.ts | 37 +- .../src/sprotty-geometry-dimension.spec.ts | 18 + .../src/sprotty-geometry-dimension.ts | 10 + .../protocol/src/sprotty-geometry-point.ts | 4 +- .../protocol/src/utils/geometry-movement.ts | 2 +- 55 files changed, 1714 insertions(+), 926 deletions(-) create mode 100644 packages/client/src/base/feedback/feedback-action-dispatcher-default.ts create mode 100644 packages/client/src/base/feedback/feedback-emitter.spec.ts rename packages/client/src/base/feedback/{feeback-emitter.ts => feedback-emitter.ts} (83%) create mode 100644 packages/client/src/features/tools/node-creation/container-manager.ts create mode 100644 packages/client/src/features/tools/node-creation/insert-indicator.ts create mode 100644 packages/client/src/features/tools/node-creation/node-creation-views.tsx create mode 100644 packages/glsp-sprotty/src/svg-views-override.tsx diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index 94953ed..9e6c7b3 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -14,28 +14,30 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + DiamondNode, + EditableLabel, + GChildElement, + GEdge, + GModelElement, + GShapeElement, + LayoutContainer, + Nameable, + RectangularNode, + ResizableModelElement, + ResizeHandleLocation, + WithEditableLabel, boundsFeature, connectableFeature, deletableFeature, - DiamondNode, - EditableLabel, fadeFeature, - GEdge, hoverFeedbackFeature, isEditableLabel, - layoutableChildFeature, - LayoutContainer, layoutContainerFeature, + layoutableChildFeature, moveFeature, - Nameable, nameFeature, popupFeature, - RectangularNode, - GChildElement, selectFeature, - GModelElement, - GShapeElement, - WithEditableLabel, withEditLabelFeature } from '@eclipse-glsp/client'; @@ -79,7 +81,7 @@ export class WeightedEdge extends GEdge { probability?: string; } -export class ActivityNode extends DiamondNode { +export class ControlNode extends DiamondNode implements ResizableModelElement { nodeType: string = ActivityNode.Type.UNDEFINED; override size = { width: 32, @@ -88,6 +90,12 @@ export class ActivityNode extends DiamondNode { override strokeWidth = 1; } +export class BranchingNode extends ControlNode { + resizeLocations = ResizeHandleLocation.CROSS; +} + +export class SynchronizationNode extends ControlNode {} + export namespace ActivityNode { export namespace Type { export const INITIAL = 'initialNode'; diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 81b670f..3c0942e 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -49,7 +49,7 @@ import { Container, ContainerModule } from 'inversify'; import 'sprotty/css/edit-label.css'; import '../css/diagram.css'; import { directTaskEditor } from './direct-task-editing/di.config'; -import { ActivityNode, CategoryNode, Icon, TaskNode, WeightedEdge } from './model'; +import { BranchingNode, CategoryNode, Icon, SynchronizationNode, TaskNode, WeightedEdge } from './model'; import { WorkflowSnapper } from './workflow-snapper'; import { WorkflowStartup } from './workflow-startup'; import { IconView, WorkflowEdgeView } from './workflow-views'; @@ -72,10 +72,10 @@ export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, configureModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); configureModelElement(context, 'icon', Icon, IconView); - configureModelElement(context, 'activityNode:merge', ActivityNode, DiamondNodeView); - configureModelElement(context, 'activityNode:decision', ActivityNode, DiamondNodeView); - configureModelElement(context, 'activityNode:fork', ActivityNode, RectangularNodeView); - configureModelElement(context, 'activityNode:join', ActivityNode, RectangularNodeView); + configureModelElement(context, 'activityNode:merge', BranchingNode, DiamondNodeView); + configureModelElement(context, 'activityNode:decision', BranchingNode, DiamondNodeView); + configureModelElement(context, 'activityNode:fork', SynchronizationNode, RectangularNodeView); + configureModelElement(context, 'activityNode:join', SynchronizationNode, RectangularNodeView); configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); diff --git a/examples/workflow-glsp/src/workflow-startup.ts b/examples/workflow-glsp/src/workflow-startup.ts index 7004323..c0cd9d3 100644 --- a/examples/workflow-glsp/src/workflow-startup.ts +++ b/examples/workflow-glsp/src/workflow-startup.ts @@ -15,14 +15,14 @@ ********************************************************************************/ import { GridManager, IDiagramStartup } from '@eclipse-glsp/client'; -import { MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; +import { MaybePromise } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; @injectable() export class WorkflowStartup implements IDiagramStartup { rank = -1; - @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; + @inject(GridManager) @optional() protected gridManager?: GridManager; preRequestModel(): MaybePromise { this.gridManager?.setGridVisible(true); diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 1405953..3757ab5 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -1,730 +1,790 @@ { + "cssClasses": [], + "type": "graph", + "position": { + "x": 0, + "y": 0 + }, "id": "sprotty", + "revision": 14, "children": [ { - "name": "Push", - "taskType": "manual", - "id": "task0", "cssClasses": [ "task", - "manual" + "automated" ], + "type": "task:automated", + "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 220, + "y": 150 + }, + "name": "AutomatedTask8", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 82.4482421875, + "prefHeight": 30 + }, + "size": { + "width": 82.4482421875, + "height": 30 + }, "children": [ { - "id": "task0_icon", + "cssClasses": [], "type": "icon", + "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "task0_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 20.6171875, + "y": 13 + }, + "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd_classname", + "text": "ChkTp", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 31.921875, - "height": 16.0 + "width": 41.4482421875, + "height": 16 }, - "text": "Push", - "alignment": { - "x": 15.9609375, - "y": 13.0 - } + "children": [] } + ] + }, + { + "cssClasses": [ + "task", + "manual" ], "type": "task:manual", - "position": { - "x": 70.0, - "y": 140.0 + "id": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 }, - "size": { - "width": 72.921875, - "height": 30.0 + "position": { + "x": 70, + "y": 100 }, - "layout": "hbox", + "name": "ManualTask9", + "taskType": "manual", "layoutOptions": { - "paddingRight": 10.0, + "paddingRight": 10, "prefWidth": 72.921875, - "prefHeight": 30.0 + "prefHeight": 30 + }, + "size": { + "width": 72.921875, + "height": 30 }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ChkWt", - "taskType": "automated", - "id": "task0_automated", - "cssClasses": [ - "task", - "automated" - ], "children": [ { - "id": "task0_automated_icon", + "cssClasses": [], "type": "icon", + "id": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "task0_automated_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 15.9609375, + "y": 13 + }, + "id": "task0", + "text": "Push", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 42.1103515625, - "height": 16.0 + "width": 31.921875, + "height": 16 }, - "text": "ChkWt", - "alignment": { - "x": 21.0, - "y": 13.0 - } + "children": [] } - ], - "type": "task:automated", - "position": { - "x": 220.0, - "y": 90.0 - }, - "size": { - "width": 83.1103515625, - "height": 30.0 - }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 75.6103515625, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "nodeType": "mergeNode", - "id": "activityNode1", - "type": "activityNode:merge", - "position": { - "x": 500.0, - "y": 90.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } + ] }, { - "nodeType": "decisionNode", - "id": "activityNode2", - "type": "activityNode:decision", - "position": { - "x": 330.0, - "y": 190.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "nodeType": "mergeNode", - "id": "activityNode4", - "type": "activityNode:merge", - "position": { - "x": 500.0, - "y": 190.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } - }, - { - "nodeType": "decisionNode", - "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "type": "activityNode:decision", - "position": { - "x": 330.0, - "y": 90.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "nodeType": "forkNode", - "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", "cssClasses": [ "forkOrJoin" ], "type": "activityNode:fork", + "id": "16c7860a-c38b-4761-bb48-baa92113478b", "position": { - "x": 170.0, - "y": 130.0 + "x": 170, + "y": 90 }, + "nodeType": "forkNode", "size": { - "width": 10.0, - "height": 50.0 - } - }, - { - "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", - "type": "edge", - "sourceId": "task0", - "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" - }, - { - "nodeType": "joinNode", - "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "cssClasses": [ - "forkOrJoin" - ], - "type": "activityNode:join", - "position": { - "x": 560.0, - "y": 130.0 + "width": 10, + "height": 50 }, - "size": { - "width": 10.0, - "height": 50.0 - } + "children": [] }, { - "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", + "cssClasses": [], "type": "edge", - "sourceId": "activityNode1", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + "routingPoints": [], + "id": "a8ffb72a-a632-4133-a55f-95e4df297875", + "sourceId": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d", + "targetId": "16c7860a-c38b-4761-bb48-baa92113478b", + "children": [] }, { - "id": "0471cae4-c754-4e89-8337-96ed1546dd02", + "cssClasses": [], "type": "edge", - "sourceId": "activityNode4", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" + "routingPoints": [], + "id": "d6fa4391-4139-4789-bd00-4c7509e5d4e1", + "sourceId": "16c7860a-c38b-4761-bb48-baa92113478b", + "targetId": "b300bdb8-52c3-4903-ba09-36dff9b281fd", + "children": [] }, { - "name": "WtOK", - "taskType": "automated", - "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", "cssClasses": [ "task", "automated" ], + "type": "task:automated", + "id": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 220, + "y": 50 + }, + "name": "AutomatedTask10", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 83.1103515625, + "prefHeight": 30 + }, + "size": { + "width": 83.1103515625, + "height": 30 + }, "children": [ { - "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04_icon", + "cssClasses": [], "type": "icon", + "id": "5496d85e-104c-47a8-bfe6-75211dbf5ae1_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 21, + "y": 13 + }, + "id": "task0_automated", + "text": "ChkWt", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 37.9931640625, - "height": 16.0 + "width": 42.1103515625, + "height": 16 }, - "text": "WtOK", - "alignment": { - "x": 18.671875, - "y": 13.0 - } + "children": [] } + ] + }, + { + "cssClasses": [ + "decision" ], - "type": "task:automated", + "type": "activityNode:decision", + "id": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", "position": { - "x": 390.0, - "y": 120.0 + "x": 330, + "y": 50 }, + "nodeType": "decisionNode", "size": { - "width": 78.9931640625, - "height": 30.0 + "width": 32, + "height": 32 }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 71.4931640625, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + "children": [] }, { - "id": "2317f24a-034e-4352-8857-d361dc8f5ed7", "cssClasses": [ - "medium" + "decision" ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "e47d5eba-612d-4c43-9aba-2c5502ff4f04" + "type": "activityNode:decision", + "id": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "position": { + "x": 330, + "y": 150 + }, + "nodeType": "decisionNode", + "size": { + "width": 32, + "height": 32 + }, + "children": [] }, { - "id": "0a57ab51-c0b6-4a51-b42e-0192bf3767dc", + "cssClasses": [], "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task0_automated", - "args": { - "edgePadding": 10.0 - } + "routingPoints": [], + "id": "ea94aeb9-c86f-42b2-94c6-4cc91d34a883", + "sourceId": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", + "targetId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", + "children": [] }, { - "id": "7a5d6bc3-c61e-4baa-8556-6015f33269c7", + "cssClasses": [], "type": "edge", - "sourceId": "task0_automated", - "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "args": { - "edgePadding": 10.0 - } + "routingPoints": [], + "id": "d7b69577-d35f-4e3b-b306-86e3f0b7008e", + "sourceId": "16c7860a-c38b-4761-bb48-baa92113478b", + "targetId": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", + "children": [] + }, + { + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "5cb74a75-9676-4db4-a088-13d467234c19", + "sourceId": "b300bdb8-52c3-4903-ba09-36dff9b281fd", + "targetId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "children": [] }, { - "name": "RflWt", - "taskType": "manual", - "id": "a63cfd87-da7c-4846-912b-29040b776bfb", "cssClasses": [ "task", - "manual" + "automated" ], + "type": "task:automated", + "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 390, + "y": 180 + }, + "name": "AutomatedTask11", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 92.46875, + "prefHeight": 30 + }, + "size": { + "width": 92.46875, + "height": 30 + }, "children": [ { - "id": "54f08126-6041-44ce-9d8b-e7a549e48f91", + "cssClasses": [], "type": "icon", + "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "6e007d62-d441-4b08-8815-ad59de3b2ca8", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 25.6796875, + "y": 13 + }, + "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437_classname", + "text": "PreHeat", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 34.32421875, - "height": 16.0 + "width": 51.46875, + "height": 16 }, - "text": "RflWt", - "alignment": { - "x": 17.109375, - "y": 13.0 - } + "children": [] } - ], - "type": "task:manual", - "position": { - "x": 390.0, - "y": 70.0 - }, - "size": { - "width": 75.32421875, - "height": 30.0 - }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 67.82421875, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + ] }, { - "id": "9bb443d7-44f3-4192-8dd8-1a2782593373", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "a63cfd87-da7c-4846-912b-29040b776bfb" - }, - { - "id": "a36985a7-3e61-499c-9bdb-5be2b00cb75c", - "type": "edge", - "sourceId": "a63cfd87-da7c-4846-912b-29040b776bfb", - "targetId": "activityNode1", - "args": { - "edgePadding": 10.0 - } - }, - { - "id": "8880cd08-8d90-4408-acd4-6eae218b1650", - "type": "edge", - "sourceId": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", - "targetId": "activityNode1", - "args": { - "edgePadding": 10.0 - } - }, - { - "name": "Brew", - "taskType": "automated", - "id": "7afd430b-5031-4082-8190-7e755c57cde9", "cssClasses": [ "task", "automated" ], + "type": "task:automated", + "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 390, + "y": 130 + }, + "name": "AutomatedTask12", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 90.248046875, + "prefHeight": 30 + }, + "size": { + "width": 90.248046875, + "height": 30 + }, "children": [ { - "id": "7afd430b-5031-4082-8190-7e755c57cde9_icon", + "cssClasses": [], "type": "icon", + "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "7afd430b-5031-4082-8190-7e755c57cde9_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 24.5234375, + "y": 13 + }, + "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd_classname", + "text": "KeepTp", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 31.90625, - "height": 16.0 + "width": 49.248046875, + "height": 16 }, - "text": "Brew", - "alignment": { - "x": 15.953125, - "y": 13.0 - } + "children": [] } - ], - "type": "task:automated", - "position": { - "x": 600.0, - "y": 140.0 - }, - "size": { - "width": 72.90625, - "height": 30.0 - }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 72.90625, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + ] }, { - "id": "81efd1ef-ae19-4ea6-a20b-20857b1c6552", - "type": "edge", - "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "targetId": "7afd430b-5031-4082-8190-7e755c57cde9", - "args": { - "edgePadding": 10.0 - } - }, - { - "name": "ChkTp", - "taskType": "automated", - "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", "cssClasses": [ "task", - "automated" + "manual" ], + "type": "task:manual", + "id": "ef349859-2a89-46b0-b394-c339a79c10e3", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 390, + "y": 30 + }, + "name": "ManualTask13", + "taskType": "manual", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 75.32421875, + "prefHeight": 30 + }, + "size": { + "width": 75.32421875, + "height": 30 + }, "children": [ { - "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17_icon", + "cssClasses": [], "type": "icon", + "id": "ef349859-2a89-46b0-b394-c339a79c10e3_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 17.109375, + "y": 13 + }, + "id": "ef349859-2a89-46b0-b394-c339a79c10e3_classname", + "text": "RflWt", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 41.4482421875, - "height": 16.0 + "width": 34.32421875, + "height": 16 }, - "text": "ChkTp", - "alignment": { - "x": 20.6171875, - "y": 13.0 - } + "children": [] } - ], - "type": "task:automated", - "position": { - "x": 220.0, - "y": 190.0 - }, - "size": { - "width": 82.4482421875, - "height": 30.0 - }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 74.9482421875, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + ] }, { - "id": "189aecf6-6043-4915-9f3e-7b2da8fd0d9c", - "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", - "args": { - "edgePadding": 10.0 - } - }, - { - "id": "69e45ef0-9c74-41da-a3ab-0a5669f83f96", - "type": "edge", - "sourceId": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", - "targetId": "activityNode2", - "args": { - "edgePadding": 10.0 - } - }, - { - "name": "KeepTp", - "taskType": "automated", - "id": "4b06ed95-c9be-47df-9941-98099259be4a", "cssClasses": [ "task", "automated" ], + "type": "task:automated", + "id": "da626312-7545-4b15-a502-766ab2da1e65", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 390, + "y": 80 + }, + "name": "AutomatedTask14", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 78.9931640625, + "prefHeight": 30 + }, + "size": { + "width": 78.9931640625, + "height": 30 + }, "children": [ { - "id": "4b06ed95-c9be-47df-9941-98099259be4a_icon", + "cssClasses": [], "type": "icon", + "id": "da626312-7545-4b15-a502-766ab2da1e65_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "4b06ed95-c9be-47df-9941-98099259be4a_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 18.671875, + "y": 13 + }, + "id": "da626312-7545-4b15-a502-766ab2da1e65_classname", + "text": "WtOK", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 49.248046875, - "height": 16.0 + "width": 37.9931640625, + "height": 16 }, - "text": "KeepTp", - "alignment": { - "x": 24.5234375, - "y": 13.0 - } + "children": [] } + ] + }, + { + "cssClasses": [ + "medium" ], - "type": "task:automated", + "type": "edge:weighted", + "routingPoints": [], + "id": "b42e5b46-38c8-4a8b-a75c-34e7b503b123", + "sourceId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", + "targetId": "ef349859-2a89-46b0-b394-c339a79c10e3", + "probability": "medium", + "children": [] + }, + { + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "routingPoints": [], + "id": "254aa972-f692-4e71-ba47-69dd564146e6", + "sourceId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", + "targetId": "da626312-7545-4b15-a502-766ab2da1e65", + "probability": "medium", + "children": [] + }, + { + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "routingPoints": [], + "id": "b65bb287-ba47-4c2c-9714-0fa61345e891", + "sourceId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "targetId": "8360c168-8f25-438a-a213-d5a84e9ae4cd", + "probability": "medium", + "children": [] + }, + { + "cssClasses": [ + "medium" + ], + "type": "edge:weighted", + "routingPoints": [], + "id": "c3fd1ee7-d8df-4ba2-9741-95cca3d6f268", + "sourceId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "targetId": "ab6e230a-33e6-4a0c-a08b-c0930767a437", + "probability": "medium", + "children": [] + }, + { + "cssClasses": [ + "forkOrJoin" + ], + "type": "activityNode:join", + "id": "8668b502-0a10-4f32-b751-97dbaa4f5372", "position": { - "x": 390.0, - "y": 170.0 + "x": 560, + "y": 90 }, + "nodeType": "joinNode", "size": { - "width": 90.248046875, - "height": 30.0 + "width": 10, + "height": 50 }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 82.748046875, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + "children": [] }, { - "name": "PreHeat", - "taskType": "automated", - "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", "cssClasses": [ "task", "automated" ], + "type": "task:automated", + "id": "532ff124-9e72-4335-9e2b-423a544f1318", + "layout": "hbox", + "args": { + "radiusTopLeft": 5, + "radiusBottomLeft": 5, + "radiusBottomRight": 5, + "radiusTopRight": 5 + }, + "position": { + "x": 600, + "y": 100 + }, + "name": "AutomatedTask15", + "taskType": "automated", + "layoutOptions": { + "paddingRight": 10, + "prefWidth": 73.20530319213867, + "prefHeight": 30 + }, + "size": { + "width": 73.20530319213867, + "height": 30 + }, "children": [ { - "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6_icon", + "cssClasses": [], "type": "icon", + "id": "532ff124-9e72-4335-9e2b-423a544f1318_icon", "position": { - "x": 5.0, - "y": 5.0 + "x": 5, + "y": 5 }, "size": { - "width": 25.0, - "height": 20.0 - } + "width": 25, + "height": 20 + }, + "children": [] }, { - "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6_classname", + "cssClasses": [], "type": "label:heading", + "alignment": { + "x": 15.953125, + "y": 13 + }, + "id": "532ff124-9e72-4335-9e2b-423a544f1318_classname", + "text": "Brew", "position": { - "x": 31.0, - "y": 7.0 + "x": 31, + "y": 7 }, "size": { - "width": 51.46875, - "height": 16.0 + "width": 31.90625, + "height": 16 }, - "text": "PreHeat", - "alignment": { - "x": 25.6796875, - "y": 13.0 - } + "children": [] } + ] + }, + { + "cssClasses": [ + "merge" ], - "type": "task:automated", + "type": "activityNode:merge", + "id": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", "position": { - "x": 390.0, - "y": 220.0 + "x": 500, + "y": 50 }, + "nodeType": "mergeNode", "size": { - "width": 92.46875, - "height": 30.0 + "width": 32, + "height": 32 }, - "layout": "hbox", - "layoutOptions": { - "prefWidth": 84.96875, - "paddingRight": 10.0, - "prefHeight": 30.0 - }, - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } + "children": [] }, { - "id": "d04afaf8-0894-4284-b844-22b255893107", "cssClasses": [ - "medium" + "merge" ], - "type": "edge:weighted", - "sourceId": "activityNode2", - "targetId": "4b06ed95-c9be-47df-9941-98099259be4a" + "type": "activityNode:merge", + "id": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "position": { + "x": 500, + "y": 150 + }, + "nodeType": "mergeNode", + "size": { + "width": 32, + "height": 32 + }, + "children": [] }, { - "id": "b9ef468f-2aaf-41b9-b408-29e2d08e490e", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "activityNode2", - "targetId": "80d1792f-9c7e-41c0-8eca-eeb0509397b6" + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "7b660e4a-c5cf-4746-91d6-5a7e732008de", + "sourceId": "ef349859-2a89-46b0-b394-c339a79c10e3", + "targetId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "children": [] }, { - "id": "58657bcf-6d32-4e7d-b73a-f84fd8d7158b", + "cssClasses": [], "type": "edge", - "sourceId": "4b06ed95-c9be-47df-9941-98099259be4a", - "targetId": "activityNode4", - "args": { - "edgePadding": 10.0 - } + "routingPoints": [], + "id": "2a0ddf8c-fb98-4432-9844-e9189ba2fa6c", + "sourceId": "da626312-7545-4b15-a502-766ab2da1e65", + "targetId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "children": [] }, { - "id": "39a75477-facf-4960-b0bb-1989be7fb57f", + "cssClasses": [], "type": "edge", - "sourceId": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", - "targetId": "activityNode4", - "args": { - "edgePadding": 10.0 - } + "routingPoints": [], + "id": "58114c96-6558-4b46-9d91-a480e3f9c3cd", + "sourceId": "8360c168-8f25-438a-a213-d5a84e9ae4cd", + "targetId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "children": [] + }, + { + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "7633af04-0cf0-4267-a681-646123270d54", + "sourceId": "ab6e230a-33e6-4a0c-a08b-c0930767a437", + "targetId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "children": [] + }, + { + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "9ddef18e-ba9a-40a3-8961-eaee5ab0007f", + "sourceId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "targetId": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "children": [] + }, + { + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "191fea84-e6a7-4ad6-94ee-a8e939a5058d", + "sourceId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "targetId": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "children": [] + }, + { + "cssClasses": [], + "type": "edge", + "routingPoints": [], + "id": "c8fe1b1c-54fd-4bcd-b194-acc02299a0ed", + "sourceId": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "targetId": "532ff124-9e72-4335-9e2b-423a544f1318", + "children": [] } - ], - "type": "graph", - "revision": 10 + ] } \ No newline at end of file diff --git a/packages/client/css/ghost-element.css b/packages/client/css/ghost-element.css index a9acb53..b61ef29 100644 --- a/packages/client/css/ghost-element.css +++ b/packages/client/css/ghost-element.css @@ -25,3 +25,10 @@ height: 0; visibility: hidden; } + +.sprotty-node.insert-indicator:not(.selected) { + pointer-events: none; + fill: none; + stroke: black; /* same color as default color for edges */ + stroke-width: 1.5px; +} diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 6c4d36c..106a1a9 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -38,9 +38,8 @@ import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; -import { FeedbackAwareSetModelCommand } from './feedback'; +import { FeedbackActionDispatcher, FeedbackAwareSetModelCommand } from './feedback'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; -import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index feee90d..1ecc6b4 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, GModelElement, MouseListener } from '@eclipse-glsp/sprotty'; +import { Action, Disposable, GModelElement, MouseListener, Point } from '@eclipse-glsp/sprotty'; /** * A mouse listener that is aware of prior mouse dragging. @@ -27,13 +27,28 @@ export class DragAwareMouseListener extends MouseListener implements Disposable protected _isMouseDown = false; protected _isMouseDrag = false; + protected _dragStart?: Point; + protected _dragSensitivity = 0; + + constructor(dragSensitivity = 0) { + super(); + this._dragSensitivity = dragSensitivity; + } + override mouseDown(target: GModelElement, event: MouseEvent): Action[] { this._isMouseDown = true; + this._dragStart = this._dragSensitivity > 0 ? { x: event.clientX, y: event.clientY } : undefined; return []; } override mouseMove(target: GModelElement, event: MouseEvent): Action[] { if (this._isMouseDown) { + if (this._dragStart) { + const dragDistance = Point.maxDistance(this._dragStart, { x: event.clientX, y: event.clientY }); + if (dragDistance < this._dragSensitivity) { + return this.nonDraggingMouseMove(target, event); + } + } this._isMouseDrag = true; return this.draggingMouseMove(target, event); } @@ -50,6 +65,7 @@ export class DragAwareMouseListener extends MouseListener implements Disposable override mouseUp(element: GModelElement, event: MouseEvent): Action[] { this._isMouseDown = false; + this._dragStart = undefined; if (this._isMouseDrag) { this._isMouseDrag = false; return this.draggingMouseUp(element, event); diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts b/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts new file mode 100644 index 0000000..2bc703c --- /dev/null +++ b/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2019-2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + Action, + ActionHandlerRegistry, + Command, + CommandActionHandler, + CommandExecutionContext, + Disposable, + GModelElement, + IActionDispatcher, + ICommand, + ILogger, + TYPES, + toTypeGuard +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { getFeedbackRank } from './feedback-command'; +import { FeedbackEmitter } from './feedback-emitter'; +import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; + +@injectable() +export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { + protected registeredFeedback: Map = new Map(); + + @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; + @inject(TYPES.ILogger) protected logger: ILogger; + + @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; + + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { + if (feedbackEmitter instanceof GModelElement) { + this.logger.log( + this, + // eslint-disable-next-line max-len + 'GModelElements as feedback emitters are discouraged, as they usually change between model updates and are considered unstable.' + ); + } + if (feedbackActions.length > 0) { + this.registeredFeedback.set(feedbackEmitter, feedbackActions); + this.dispatchFeedback(feedbackActions, feedbackEmitter); + } + return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); + } + + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void { + this.registeredFeedback.delete(feedbackEmitter); + const actions = MaybeActions.asArray(cleanupActions); + if (actions.length > 0) { + this.dispatchFeedback(actions, feedbackEmitter); + } + } + + getRegisteredFeedback(): Action[] { + const result: Action[] = []; + this.registeredFeedback.forEach(actions => result.push(...actions)); + return result; + } + + getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { + const result: IFeedbackEmitter[] = []; + this.registeredFeedback.forEach((actions, emitter) => { + if (actions.includes(action)) { + result.push(emitter); + } + }); + return result; + } + + getFeedbackCommands(): Command[] { + return this.getRegisteredFeedback() + .flatMap(action => this.actionToCommands(action)) + .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise { + const feedbackCommands = this.getFeedbackCommands() ?? []; + if (feedbackCommands?.length > 0) { + const results = feedbackCommands.map(command => command.execute(context)); + await Promise.all(results); + } + } + + protected actionToCommands(action: Action): ICommand[] { + return ( + this.actionHandlerRegistry + .get(action.kind) + .filter(toTypeGuard(CommandActionHandler)) + .map(handler => handler.handle(action)) ?? [] + ); + } + + createEmitter(): FeedbackEmitter { + return new FeedbackEmitter(this); + } + + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { + try { + const actionDispatcher = await this.actionDispatcher(); + await actionDispatcher.dispatchAll(actions); + this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); + } catch (reason) { + this.logger.error(this, 'Failed to dispatch feedback actions', reason); + } + } +} diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index cf6911b..e3e658a 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -13,26 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - ActionHandlerRegistry, - Command, - CommandActionHandler, - CommandExecutionContext, - Disposable, - GModelElement, - IActionDispatcher, - ICommand, - ILogger, - MaybeFunction, - TYPES, - call, - asArray as toArray, - toTypeGuard -} from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; -import { getFeedbackRank } from './feedback-command'; -import { FeedbackEmitter } from './feeback-emitter'; +import { Action, Command, CommandExecutionContext, Disposable, MaybeFunction, call, asArray as toArray } from '@eclipse-glsp/sprotty'; +import { FeedbackEmitter } from './feedback-emitter'; export interface IFeedbackEmitter {} @@ -104,89 +86,3 @@ export interface IFeedbackActionDispatcher { */ createEmitter(): FeedbackEmitter; } - -@injectable() -export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { - protected registeredFeedback: Map = new Map(); - - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - @inject(TYPES.ILogger) protected logger: ILogger; - - @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; - - registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { - if (feedbackEmitter instanceof GModelElement) { - this.logger.log( - this, - // eslint-disable-next-line max-len - 'GModelElements as feedback emitters are discouraged, as they usually change between model updates and are considered unstable.' - ); - } - if (feedbackActions.length > 0) { - this.registeredFeedback.set(feedbackEmitter, feedbackActions); - this.dispatchFeedback(feedbackActions, feedbackEmitter); - } - return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); - } - - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void { - this.registeredFeedback.delete(feedbackEmitter); - const actions = MaybeActions.asArray(cleanupActions); - if (actions.length > 0) { - this.dispatchFeedback(actions, feedbackEmitter); - } - } - - getRegisteredFeedback(): Action[] { - const result: Action[] = []; - this.registeredFeedback.forEach(actions => result.push(...actions)); - return result; - } - - getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { - const result: IFeedbackEmitter[] = []; - this.registeredFeedback.forEach((actions, emitter) => { - if (actions.includes(action)) { - result.push(emitter); - } - }); - return result; - } - - getFeedbackCommands(): Command[] { - return this.getRegisteredFeedback() - .flatMap(action => this.actionToCommands(action)) - .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); - } - - async applyFeedbackCommands(context: CommandExecutionContext): Promise { - const feedbackCommands = this.getFeedbackCommands() ?? []; - if (feedbackCommands?.length > 0) { - const results = feedbackCommands.map(command => command.execute(context)); - await Promise.all(results); - } - } - - protected actionToCommands(action: Action): ICommand[] { - return ( - this.actionHandlerRegistry - .get(action.kind) - .filter(toTypeGuard(CommandActionHandler)) - .map(handler => handler.handle(action)) ?? [] - ); - } - - createEmitter(): FeedbackEmitter { - return new FeedbackEmitter(this); - } - - protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { - try { - const actionDispatcher = await this.actionDispatcher(); - await actionDispatcher.dispatchAll(actions); - this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); - } catch (reason) { - this.logger.error(this, 'Failed to dispatch feedback actions', reason); - } - } -} diff --git a/packages/client/src/base/feedback/feedback-emitter.spec.ts b/packages/client/src/base/feedback/feedback-emitter.spec.ts new file mode 100644 index 0000000..b07e0c9 --- /dev/null +++ b/packages/client/src/base/feedback/feedback-emitter.spec.ts @@ -0,0 +1,176 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, Command, CommandExecutionContext, Disposable } from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback-action-dispatcher'; +import { FeedbackEmitter } from './feedback-emitter'; + +class MockFeedbackActionDispatcher implements IFeedbackActionDispatcher { + protected feedbackEmitters: Map = new Map(); + + registerFeedback(feedbackEmitter: IFeedbackEmitter, actions: Action[]): Disposable { + this.feedbackEmitters.set(feedbackEmitter, actions); + return Disposable.create(() => this.deregisterFeedback(feedbackEmitter)); + } + + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, _actions?: Action[]): void { + this.feedbackEmitters.delete(feedbackEmitter); + } + + getRegisteredFeedback(): Action[] { + const result: Action[] = []; + this.feedbackEmitters.forEach((value, key) => result.push(...value)); + return result; + } + + getFeedbackCommands(): Command[] { + return []; + } + + createEmitter(): FeedbackEmitter { + return new FeedbackEmitter(this); + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise {} +} + +describe('FeedbackEmitter', () => { + describe('Initial State', () => { + it('On creation nothing should be submitted.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + feedback.submit(); + expect(feedbackActionDispatcher['feedbackEmitters']).to.be.empty; + }); + }); + + describe('Adding Feedback', () => { + it('Should add an action as part of the emitter feedback.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + feedback.add(action); + expect(feedback['feedbackActions']).to.deep.equal([action]); + }); + + it('Should add an action and cleanup action as part of the emitter feedback.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + const cleanupAction: Action = { kind: 'cleanup' }; + feedback.add(action, cleanupAction); + expect(feedback['feedbackActions']).to.deep.equal([action]); + expect(feedback['cleanupActions']).to.deep.equal([cleanupAction]); + }); + }); + + describe('Merging Feedback', () => { + it('Should merge the feedback of another emitter into this emitter.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback1 = feedbackActionDispatcher.createEmitter(); + const feedback2 = feedbackActionDispatcher.createEmitter(); + const action1: Action = { kind: 'action1' }; + const action2: Action = { kind: 'action2' }; + feedback1.add(action1); + feedback2.add(action2); + feedback1.merge(feedback2); + expect(feedback1['feedbackActions']).to.deep.equal([action1, action2]); + }); + }); + + describe('Removing Feedback', () => { + it('Should remove the action from the emitter feedback.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + feedback.add(action); + feedback.remove(action); + expect(feedback['feedbackActions']).to.be.empty; + }); + + it('Should remove the action together with the cleanup action from the emitter feedback.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + const cleanupAction: Action = { kind: 'cleanup' }; + feedback.add(action, cleanupAction); + feedback.remove(action); + expect(feedback['feedbackActions']).to.be.empty; + expect(feedback['cleanupActions']).to.be.empty; + }); + }); + + describe('Clearing Feedback', () => { + it('Should clear any pending feedback actions and cleanup actions.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + const cleanupAction: Action = { kind: 'cleanup' }; + feedback.add(action, cleanupAction); + feedback.clear(); + expect(feedback['feedbackActions']).to.be.empty; + expect(feedback['cleanupActions']).to.be.empty; + }); + }); + + describe('Submitting Feedback', () => { + it('Should register any pending actions as feedback.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + feedback.add(action); + feedback.submit(); + expect(feedbackActionDispatcher['feedbackEmitters'].size).to.equal(1); + expect(feedbackActionDispatcher['feedbackEmitters'].get(feedback)).to.deep.equal([action]); + }); + }); + + describe('Discarding Feedback', () => { + it('Should remove the registered feedback without calling any cleanup actions.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + feedback.add(action); + feedback.submit(); + feedback.discard(); + expect(feedbackActionDispatcher['feedbackEmitters']).to.be.empty; + }); + }); + + describe('Reverting Feedback', () => { + it('Should remove the registered feedback and call the registered cleanup actions.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + const cleanupAction: Action = { kind: 'cleanup' }; + feedback.add(action, cleanupAction); + feedback.submit(); + feedback.revert(); + expect(feedbackActionDispatcher['feedbackEmitters']).to.be.empty; + }); + }); + + describe('Disposing Feedback', () => { + it('Should dispose the registered feedback and any pending feedback actions.', () => { + const feedbackActionDispatcher = new MockFeedbackActionDispatcher(); + const feedback = feedbackActionDispatcher.createEmitter(); + const action: Action = { kind: 'action' }; + feedback.add(action); + feedback.dispose(); + expect(feedbackActionDispatcher['feedbackEmitters']).to.be.empty; + }); + }); +}); diff --git a/packages/client/src/base/feedback/feeback-emitter.ts b/packages/client/src/base/feedback/feedback-emitter.ts similarity index 83% rename from packages/client/src/base/feedback/feeback-emitter.ts rename to packages/client/src/base/feedback/feedback-emitter.ts index 0833f3f..f39a8bf 100644 --- a/packages/client/src/base/feedback/feeback-emitter.ts +++ b/packages/client/src/base/feedback/feedback-emitter.ts @@ -17,10 +17,14 @@ import { Action, Disposable, arrayOf } from '@eclipse-glsp/sprotty'; import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; +// counter for internal id, mainly useful for debugging +let idCounter = 0; + /** * A helper object to collect, submit and undo feedback consisting of several actions. */ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { + protected id = idCounter++; protected feedbackActions: (Action | undefined)[] = []; protected cleanupActions: MaybeActions[] = []; protected deregistration?: Disposable; @@ -46,6 +50,17 @@ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { return this; } + /** + * Merges the feedback of another emitter into this emitter. + * + * @param feedback feedback to merge + */ + merge(feedback: FeedbackEmitter): this { + this.feedbackActions.push(...feedback.feedbackActions); + this.cleanupActions.push(...feedback.cleanupActions); + return this; + } + /** * Removes the action as part of this emitters feedback. If the action cannot be found, this is a no-op. * Please note that this also removed the corresponding cleanup action. @@ -56,9 +71,9 @@ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { */ remove(action: Action): this { const idx = this.feedbackActions.indexOf(action); - if (idx) { - delete this.feedbackActions[idx]; - delete this.cleanupActions[idx]; + if (idx >= 0) { + this.feedbackActions.splice(idx, 1); + this.cleanupActions.splice(idx, 1); } return this; } @@ -79,8 +94,12 @@ export class FeedbackEmitter implements IFeedbackEmitter, Disposable { // with 'arrayOf' we skip undefined entries that are created for non-cleanup actions or cleanup-only actions const actions = arrayOf(...this.feedbackActions); const cleanupActions = arrayOf(...this.cleanupActions); - this.deregistration = this.feedbackDispatcher.registerFeedback(this, actions, () => cleanupActions.flatMap(MaybeActions.asArray)); - this.clear(); + if (actions.length > 0 || cleanupActions.length > 0) { + this.deregistration = this.feedbackDispatcher.registerFeedback(this, actions, () => + cleanupActions.flatMap(MaybeActions.asArray) + ); + this.clear(); + } return this; } diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts index 73b366b..a05db2a 100644 --- a/packages/client/src/base/feedback/index.ts +++ b/packages/client/src/base/feedback/index.ts @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export * from './css-feedback'; -export * from './feeback-emitter'; export * from './feedback-action-dispatcher'; +export * from './feedback-action-dispatcher-default'; export * from './feedback-command'; +export * from './feedback-emitter'; export * from './set-model-command'; export * from './update-model-command'; diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index a153b4a..6142e54 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -18,8 +18,8 @@ import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; import * as sinon from 'sinon'; import { defaultModule } from './default.module'; -import { FeedbackEmitter } from './feedback/feeback-emitter'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from './feedback/feedback-emitter'; import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service'; @injectable() diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index f48f32f..7e41ba3 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -41,7 +41,7 @@ export class MovementKeyTool implements Tool { @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(TYPES.Grid) @optional() protected grid: Grid; - @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; + @inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; get id(): string { return MovementKeyTool.ID; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index c6d283b..9ecf1f5 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { Bounds, + Direction, GChildElement, GModelElement, GParentElement, @@ -47,6 +48,20 @@ export enum ResizeHandleLocation { } export namespace ResizeHandleLocation { + export const CORNERS: ResizeHandleLocation[] = [ + ResizeHandleLocation.TopLeft, + ResizeHandleLocation.TopRight, + ResizeHandleLocation.BottomRight, + ResizeHandleLocation.BottomLeft + ]; + export const CROSS: ResizeHandleLocation[] = [ + ResizeHandleLocation.Top, + ResizeHandleLocation.Right, + ResizeHandleLocation.Bottom, + ResizeHandleLocation.Left + ]; + export const ALL = Object.values(ResizeHandleLocation).filter(value => typeof value !== 'function') as ResizeHandleLocation[]; + export function opposite(location: ResizeHandleLocation): ResizeHandleLocation { switch (location) { case ResizeHandleLocation.TopLeft: @@ -67,6 +82,27 @@ export namespace ResizeHandleLocation { return ResizeHandleLocation.Right; } } + + export function direction(location: ResizeHandleLocation): Direction[] { + switch (location) { + case ResizeHandleLocation.TopLeft: + return [Direction.Up, Direction.Left]; + case ResizeHandleLocation.Top: + return [Direction.Up]; + case ResizeHandleLocation.TopRight: + return [Direction.Up, Direction.Right]; + case ResizeHandleLocation.Right: + return [Direction.Right]; + case ResizeHandleLocation.BottomRight: + return [Direction.Down, Direction.Right]; + case ResizeHandleLocation.Bottom: + return [Direction.Down]; + case ResizeHandleLocation.BottomLeft: + return [Direction.Down, Direction.Left]; + case ResizeHandleLocation.Left: + return [Direction.Left]; + } + } } export function isBoundsAwareMoveable(element: GModelElement): element is BoundsAwareModelElement & MoveableElement { @@ -182,19 +218,8 @@ export class SResizeHandle extends GChildElement implements Hoverable { } } -export function addResizeHandles( - element: ResizableModelElement, - locations: ResizeHandleLocation[] = [ - ResizeHandleLocation.TopLeft, - ResizeHandleLocation.Top, - ResizeHandleLocation.BottomLeft, - ResizeHandleLocation.BottomRight - ] -): void { - for (const location of Object.values(ResizeHandleLocation)) { - if (typeof location === 'function') { - continue; - } +export function addResizeHandles(element: ResizableModelElement, locations: ResizeHandleLocation[] = ResizeHandleLocation.CORNERS): void { + for (const location of ResizeHandleLocation.ALL) { const existing = element.children.find(child => child instanceof SResizeHandle && child.location === location); if (locations.includes(location) && !existing) { // add missing handle diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 8158d2f..54b8db9 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, GModelElement, GNode, GParentElement, Point } from '@eclipse-glsp/sprotty'; +import { Bounds, Dimension, GModelElement, GNode, GParentElement, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { BoundsAwareModelElement } from '../../utils'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { SResizeHandle, isBoundsAwareMoveable } from './model'; +import { SResizeHandle } from './model'; /** * A `MovementRestrictor` is an optional service that can be used by tools to validate @@ -48,27 +48,27 @@ export class NoOverlapMovementRestrictor implements IMovementRestrictor { cssClasses = ['movement-not-allowed']; validate(element: GModelElement, newLocation?: Point): boolean { - if (!isBoundsAwareMoveable(element) || !newLocation) { + if (!isMoveable(element) || !newLocation) { return false; } // Create ghost element at the newLocation + const dimensions: Dimension = isBoundsAware(element) ? element.bounds : { width: 1, height: 1 }; const ghostElement = Object.create(element) as BoundsAwareModelElement; - ghostElement.bounds = { - x: newLocation.x, - y: newLocation.y, - width: element.bounds.width, - height: element.bounds.height - }; + ghostElement.bounds = { ...dimensions, ...newLocation }; ghostElement.type = 'Ghost'; ghostElement.id = element.id; return !Array.from( element.root.index .all() - .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof GNode) - .map(e => e as BoundsAwareModelElement) + .filter(node => node.id !== ghostElement.id && node !== ghostElement.root && node instanceof GNode) + .map(node => node as BoundsAwareModelElement) ).some(e => this.areOverlapping(e, ghostElement)); } + protected isBoundsRelevant(element: GModelElement, ghostElement: BoundsAwareModelElement): element is BoundsAwareModelElement { + return element.id !== ghostElement.id && element !== ghostElement.root && element instanceof GNode && isBoundsAware(element); + } + protected areOverlapping(element1: BoundsAwareModelElement, element2: BoundsAwareModelElement): boolean { return Bounds.overlap(toAbsoluteBounds(element1), toAbsoluteBounds(element2)); } diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx index 07380f3..58a2b45 100644 --- a/packages/client/src/features/debug/debug-bounds-decorator.tsx +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ -import { Bounds, GModelElement, IVNodePostprocessor, Point, TYPES, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; +import { Bounds, GModelElement, IVNodePostprocessor, Point, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GGraph } from '../../model'; @@ -29,7 +29,7 @@ export const CSS_DEBUG_BOUNDS = 'debug-bounds'; @injectable() export class DebugBoundsDecorator implements IVNodePostprocessor { - @inject(TYPES.IDebugManager) @optional() protected debugManager?: DebugManager; + @inject(DebugManager) @optional() protected debugManager?: DebugManager; decorate(vnode: VNode, element: GModelElement): VNode { if (!this.debugManager?.isDebugEnabled) { @@ -87,7 +87,7 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { } protected renderTopLeftCorner(element: BoundsAwareModelElement): VNode { - const position = Bounds.topLeft(element.bounds); + const position = Bounds.position(element.bounds); const topLeft = Bounds.topLeft(element.bounds); const corner = Point.subtract(topLeft, position); return ( @@ -104,7 +104,7 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { } protected renderTopRightCorner(element: BoundsAwareModelElement): VNode { - const position = Bounds.topLeft(element.bounds); + const position = Bounds.position(element.bounds); const topRight = Bounds.topRight(element.bounds); const corner = Point.subtract(topRight, position); return ( @@ -121,7 +121,7 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { } protected renderBottomLeftCorner(element: BoundsAwareModelElement): VNode { - const position = Bounds.topLeft(element.bounds); + const position = Bounds.position(element.bounds); const bottomLeft = Bounds.bottomLeft(element.bounds); const corner = Point.subtract(bottomLeft, position); return ( @@ -138,7 +138,7 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { } protected renderBottomRightCorner(element: BoundsAwareModelElement): VNode { - const position = Bounds.topLeft(element.bounds); + const position = Bounds.position(element.bounds); const bottomRight = Bounds.bottomRight(element.bounds); const corner = Point.subtract(bottomRight, position); return ( @@ -156,7 +156,7 @@ export class DebugBoundsDecorator implements IVNodePostprocessor { protected renderCenter(element: BoundsAwareModelElement): VNode { const bounds = element.bounds; - const position = Bounds.topLeft(bounds); + const position = Bounds.position(bounds); const center = Bounds.center(bounds); const corner = Point.subtract(center, position); return ( diff --git a/packages/client/src/features/debug/debug-module.ts b/packages/client/src/features/debug/debug-module.ts index 203558a..0b633e7 100644 --- a/packages/client/src/features/debug/debug-module.ts +++ b/packages/client/src/features/debug/debug-module.ts @@ -25,7 +25,7 @@ export const debugModule = new FeatureModule((bind, unbind, isBound, rebind) => configureCommand(context, EnableDebugModeCommand); - bindAsService(bind, TYPES.IDebugManager, DebugManager); + bind(DebugManager).toSelf().inSingletonScope(); configureActionHandler(context, EnableDebugModeAction.KIND, DebugManager); bindAsService(context, TYPES.IVNodePostprocessor, DebugBoundsDecorator); diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index af5c9bd..a4809ed 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -17,16 +17,12 @@ import { Action, Dimension, GModelElement, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; -import { FeedbackEmitter } from '../../base/feedback/feeback-emitter'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { MoveableElement, getAbsolutePosition } from '../../utils'; -import { - CSS_RESIZE_MODE, - ChangeBoundsManager, - ChangeBoundsTracker, - FeedbackAwareTool, - MoveFinishedEventAction, - TrackedElementMove -} from '../tools'; +import { FeedbackAwareTool } from '../tools/base-tools'; +import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; +import { MoveFinishedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; +import { ChangeBoundsTracker, TrackedMove } from '../tools/change-bounds/change-bounds-tracker'; export interface PositioningTool extends FeedbackAwareTool { readonly changeBoundsManager: ChangeBoundsManager; @@ -51,16 +47,16 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener return !element || !isMoveable(element) ? undefined : element; } - override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - super.mouseMove(target, event); - const element = this.getTrackedElement(target, event); + override mouseMove(ctx: GModelElement, event: MouseEvent): Action[] { + super.mouseMove(ctx, event); + const element = this.getTrackedElement(ctx, event); if (!element) { return []; } if (!this.tracker.isTracking()) { - this.initialize(element, target, event); + this.initialize(element, ctx, event); } - const move = this.tracker.moveElements([element], { snap: event, restrict: event, validate: true }); + const move = this.tracker.moveElements([element], { snap: event, restrict: event }); const elementMove = move.elementMoves[0]; if (!elementMove) { return []; @@ -71,7 +67,7 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener MoveAction.create([{ elementId: this.elementId, toPosition: elementMove.toPosition }], { animate: false }), MoveFinishedEventAction.create() ); - this.addMoveFeeback(elementMove); + this.addMoveFeedback(move, ctx, event); this.moveGhostFeedback.submit(); this.tracker.updateTrackingPosition(elementMove.moveVector); return []; @@ -89,13 +85,9 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener : mousePosition; } - protected addMoveFeeback(move: TrackedElementMove): void { - this.tool.changeBoundsManager.addRestrictionFeedback(this.moveGhostFeedback, move); - this.moveGhostFeedback.add(ModifyCSSFeedbackAction.create({ elements: [move.element.id], remove: [CSS_HIDDEN] })); - this.moveGhostFeedback.add( - ModifyCSSFeedbackAction.create({ add: [CSS_RESIZE_MODE] }), - ModifyCSSFeedbackAction.create({ remove: [CSS_RESIZE_MODE] }) - ); + protected addMoveFeedback(move: TrackedMove, ctx: GModelElement, event: MouseEvent): void { + this.moveGhostFeedback.add(ModifyCSSFeedbackAction.create({ elements: [this.elementId], remove: [CSS_HIDDEN] })); + this.tool.changeBoundsManager.addMoveFeedback(this.moveGhostFeedback, move, ctx, event); } override dispose(): void { diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index 44966bb..75bfa4b 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; +import { FeatureModule, TYPES, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import '../../../css/grid.css'; import { GridManager } from './grid-manager'; import { ShowGridAction, ShowGridCommand } from './grid-model'; @@ -27,7 +27,7 @@ export const gridModule = new FeatureModule((bind, unbind, isBound, rebind) => { configureCommand(context, ShowGridCommand); - bindAsService(bind, TYPES.IGridManager, GridManager); + bind(GridManager).toSelf().inSingletonScope(); configureActionHandler(context, ShowGridAction.KIND, GridManager); bind(TYPES.ISnapper).to(GridSnapper); diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 55fd201..cce6d6c 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -30,6 +30,7 @@ import { FeedbackEmitter } from '../../base'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; +import { ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; import { Grid } from '../grid'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { @@ -165,32 +166,87 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH if (!isSnap) { return 0; } - return direction === Direction.Left || direction === Direction.Right - ? this.options.minimumMoveDelta.x - : this.options.minimumMoveDelta.y; + const minimumMoveDelta = this.options.minimumMoveDelta; + return direction === Direction.Left || direction === Direction.Right ? minimumMoveDelta.x : minimumMoveDelta.y; } - getMinimumMoveVector(element: GModelElement, isSnap: boolean, directions: Direction[]): Vector | undefined { + getMinimumMoveVector(element: GModelElement, isSnap: boolean, move: Direction[]): Vector | undefined { if (!isSnap) { return undefined; } - const helperLines = element.root.children.filter(child => isHelperLine(child)) as HelperLine[]; - if (helperLines.length === 0) { + const state = this.getHelperLineState(element); + if (state.helperLines.length === 0) { return undefined; } const minimum: Writable = { ...Vector.ZERO }; - if (directions.includes(Direction.Left) && helperLines.some(line => line.isLeft || line.isCenter)) { + const resize = + element instanceof SResizeHandle + ? ResizeHandleLocation.direction(element.location) + : [Direction.Left, Direction.Right, Direction.Up, Direction.Down]; + + if ((state.types.left || state.types.center) && move.includes(Direction.Left) && resize.includes(Direction.Left)) { minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Left); - } else if (directions.includes(Direction.Right) && helperLines.some(line => line.isRight || line.isCenter)) { + } else if ((state.types.right || state.types.center) && move.includes(Direction.Right) && resize.includes(Direction.Right)) { minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Right); } - if (directions.includes(Direction.Up) && helperLines.some(line => line.isTop || line.isMiddle)) { + if ((state.types.top || state.types.middle) && move.includes(Direction.Up) && resize.includes(Direction.Up)) { minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Up); - } else if (directions.includes(Direction.Down) && helperLines.some(line => line.isBottom || line.isMiddle)) { + } else if ((state.types.bottom || state.types.middle) && move.includes(Direction.Down) && resize.includes(Direction.Down)) { minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Down); } return Vector.isZero(minimum) ? undefined : minimum; } + + protected getHelperLineState(element: GModelElement): HelperLineState { + const helperLines = element.root.children.filter(isHelperLine) || []; + const types = { + left: false, + right: false, + top: false, + bottom: false, + center: false, + middle: false + }; + for (const line of helperLines) { + switch (line.lineType) { + case HelperLineType.Left: + case HelperLineType.LeftRight: + types.left = true; + break; + case HelperLineType.Right: + case HelperLineType.RightLeft: + types.right = true; + break; + case HelperLineType.Top: + case HelperLineType.TopBottom: + types.top = true; + break; + case HelperLineType.Bottom: + case HelperLineType.BottomTop: + types.bottom = true; + break; + case HelperLineType.Center: + types.center = true; + break; + case HelperLineType.Middle: + types.middle = true; + break; + } + } + return { helperLines, types }; + } +} + +export interface HelperLineState { + helperLines: HelperLine[]; + types: { + left: boolean; + right: boolean; + top: boolean; + bottom: boolean; + center: boolean; + middle: boolean; + }; } diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index 2e29dbe..b1433ab 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -24,7 +24,12 @@ export interface Containable { isContainableElement(input: GModelElement | GModelElementSchema | string): boolean; } -export function isContainable(element: GModelElement): element is GModelElement & Containable { +/** + * A union type for all elements that can contain other elements. + */ +export type ContainerElement = GModelElement & Containable; + +export function isContainable(element: GModelElement): element is ContainerElement { return element.hasFeature(containerFeature); } diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index 458f512..345d089 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -37,7 +37,7 @@ import { Container } from 'inversify'; import * as sinon from 'sinon'; import { FeedbackEmitter } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher-default'; import { GEdge } from '../../model'; import { isResizable } from '../change-bounds/model'; import { isReconnectable } from '../reconnect/model'; diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 6717fc8..19b25d0 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { Ranked } from '../../base/ranked'; import { SelectableElement } from '../../utils'; @@ -28,7 +28,7 @@ import { ChangeBoundsManager } from '../tools'; export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ - @inject(TYPES.IChangeBoundsManager) @optional() readonly changeBoundsManager?: ChangeBoundsManager; + @inject(ChangeBoundsManager) @optional() readonly changeBoundsManager?: ChangeBoundsManager; protected override handleSelectTarget( selectableTarget: SelectableElement, diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 968d53c..9fd3060 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -26,7 +26,6 @@ import { SetContextActions, SetModelAction, SetUIExtensionVisibilityAction, - TYPES, TriggerNodeCreationAction, UpdateModelAction, codiconCSSClasses, @@ -78,11 +77,11 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, @inject(FocusTracker) protected focusTracker: FocusTracker; - @inject(TYPES.IGridManager) + @inject(GridManager) @optional() protected gridManager?: GridManager; - @inject(TYPES.IDebugManager) + @inject(DebugManager) @optional() protected debugManager?: DebugManager; diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 25917bf..02359e1 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -17,8 +17,8 @@ import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { FeedbackEmitter } from '../../base/feedback/feeback-emitter'; import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; import { GLSPKeyTool } from '../../base/view/key-tool'; import { GLSPMouseTool } from '../../base/view/mouse-tool'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts index 38a5a8c..699d00e 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts @@ -28,19 +28,32 @@ import { isLocateable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { FeedbackEmitter } from '../../../base'; +import { + CursorCSS, + FeedbackEmitter, + ModifyCSSFeedbackAction, + applyCssClasses, + cursorFeedbackAction, + deleteCssClasses, + toggleCssClasses +} from '../../../base'; import { isValidMove, minDimensions } from '../../../utils'; import { LayoutAware } from '../../bounds/layout-data'; import { IMovementRestrictor, ResizeHandleLocation, + SResizeHandle, movementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds'; +import { GridManager } from '../../grid'; import { IHelperLineManager } from '../../helper-lines'; -import { ChangeBoundsTracker, TrackedElementMove } from './change-bounds-tracker'; +import { InsertIndicator } from '../node-creation/insert-indicator'; +import { ChangeBoundsTracker, TrackedElementMove, TrackedElementResize, TrackedMove, TrackedResize } from './change-bounds-tracker'; export const CSS_RESIZE_MODE = 'resize-mode'; +export const CSS_RESTRICTED_RESIZE = 'resize-not-allowed'; +export const CSS_ACTIVE_HANDLE = 'active'; @injectable() export class ChangeBoundsManager { @@ -48,7 +61,8 @@ export class ChangeBoundsManager { @inject(MousePositionTracker) readonly positionTracker: MousePositionTracker, @optional() @inject(TYPES.IMovementRestrictor) readonly movementRestrictor?: IMovementRestrictor, @optional() @inject(TYPES.ISnapper) readonly snapper?: ISnapper, - @optional() @inject(TYPES.IHelperLineManager) readonly helperLineManager?: IHelperLineManager + @optional() @inject(TYPES.IHelperLineManager) readonly helperLineManager?: IHelperLineManager, + @optional() @inject(GridManager) protected gridManager?: GridManager ) {} unsnapModifier(): KeyboardModifier | undefined { @@ -102,7 +116,7 @@ export class ChangeBoundsManager { } restrictMovement(element: GModelElement, movement: Movement): Movement { - const minimumMovement = this.helperLineManager?.getMinimumMoveVector(element, true, movement.direction); + const minimumMovement = this.getMinimumMovement(element, movement); if (!minimumMovement) { return movement; } @@ -116,23 +130,67 @@ export class ChangeBoundsManager { return Point.move(movement.from, targetPosition); } - addRestrictionFeedback(feedback: FeedbackEmitter, move: TrackedElementMove): FeedbackEmitter { + protected getMinimumMovement(element: GModelElement, movement: Movement): Vector | undefined { + return element instanceof InsertIndicator && this.gridManager + ? this.gridManager.grid + : this.helperLineManager?.getMinimumMoveVector(element, true, movement.direction); + } + + addMoveFeedback(feedback: FeedbackEmitter, trackedMove: TrackedMove, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter { + // cursor feedback on graph + feedback.add(cursorFeedbackAction(CursorCSS.MOVE), cursorFeedbackAction(CursorCSS.DEFAULT)); + + // restriction feedback on each element + trackedMove.elementMoves.forEach(move => this.addMoveRestrictionFeedback(feedback, move, ctx, event)); + + return feedback; + } + + addResizeFeedback(feedback: FeedbackEmitter, resize: TrackedResize, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter { + // graph feedback + feedback.add( + ModifyCSSFeedbackAction.create({ add: [CSS_RESIZE_MODE] }), + ModifyCSSFeedbackAction.create({ remove: [CSS_RESIZE_MODE] }) + ); + + // cursor feedback on graph + const cursorClass = SResizeHandle.getCursorCss(resize.handleMove.element); + feedback.add(cursorFeedbackAction(cursorClass), cursorFeedbackAction(CursorCSS.DEFAULT)); + + // handle feedback + const handle = resize.handleMove.element; + feedback.add(applyCssClasses(handle, CSS_ACTIVE_HANDLE), deleteCssClasses(handle, CSS_ACTIVE_HANDLE)); + feedback.add(toggleCssClasses(handle, !resize.valid.size, CSS_RESTRICTED_RESIZE), deleteCssClasses(handle, CSS_RESTRICTED_RESIZE)); + + // restriction feedback on each element + resize.elementResizes.forEach(elementResize => { + this.addMoveRestrictionFeedback(feedback, elementResize, ctx, event); + feedback.add( + toggleCssClasses(elementResize.element, !elementResize.valid.size, CSS_RESTRICTED_RESIZE), + deleteCssClasses(elementResize.element, CSS_RESTRICTED_RESIZE) + ); + }); + return feedback; + } + + addMoveRestrictionFeedback( + feedback: FeedbackEmitter, + change: TrackedElementResize | TrackedElementMove, + ctx?: GModelElement, + event?: MouseEvent + ): FeedbackEmitter { if (this.movementRestrictor) { + const valid = TrackedElementMove.is(change) ? change.valid : change.valid.move; feedback.add( - movementRestrictionFeedback(move.element, this.movementRestrictor!, move.valid), - removeMovementRestrictionFeedback(move.element, this.movementRestrictor!) + movementRestrictionFeedback(change.element, this.movementRestrictor, valid), + removeMovementRestrictionFeedback(change.element, this.movementRestrictor) ); } return feedback; } defaultResizeLocations(): ResizeHandleLocation[] { - return [ - ResizeHandleLocation.TopLeft, - ResizeHandleLocation.TopRight, - ResizeHandleLocation.BottomRight, - ResizeHandleLocation.BottomLeft - ]; + return ResizeHandleLocation.CORNERS; } useSymmetricResize(arg: MouseEvent | KeyboardEvent | any): boolean { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index 9cce984..dcbee52 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -71,7 +71,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { static readonly KIND = ShowChangeBoundsToolResizeFeedbackAction.KIND; @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; - @inject(TYPES.IChangeBoundsManager) protected changeBoundsManager: ChangeBoundsManager; + @inject(ChangeBoundsManager) protected changeBoundsManager: ChangeBoundsManager; execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; @@ -81,7 +81,10 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { if (this.action.elementId) { const resizeElement = index.getById(this.action.elementId); if (resizeElement && isResizable(resizeElement)) { - addResizeHandles(resizeElement, this.action.resizeLocations ?? this.changeBoundsManager.defaultResizeLocations()); + addResizeHandles( + resizeElement, + this.action.resizeLocations ?? resizeElement.resizeLocations ?? this.changeBoundsManager.defaultResizeLocations() + ); } } return context.root; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index 7bfb651..34e34c4 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -23,7 +23,7 @@ import { SResizeHandleView } from './view'; export const changeBoundsToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IChangeBoundsManager, ChangeBoundsManager); + bind(ChangeBoundsManager).toSelf().inSingletonScope(); bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index 49c02dd..b86ecf0 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -19,7 +19,7 @@ import { DebouncedFunc, debounce } from 'lodash'; import { ChangeBoundsTracker, TrackedMove } from '.'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; -import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { MoveableElement, filter, getElements, isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils'; import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; @@ -128,7 +128,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { // cancel any pending move this.pendingMoveInitialized?.cancel(); this.moveFeedback.add(this.createMoveAction(move), () => this.resetElementPositions(target)); - this.addMovementFeedback(move, target, event); + this.addMoveFeedback(move, target, event); this.tracker.updateTrackingPosition(move); this.moveFeedback.submit(); return []; @@ -142,12 +142,8 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { ); } - protected addMovementFeedback(trackedMove: TrackedMove, ctx: GModelElement, event: MouseEvent): void { - // cursor feedback - this.moveFeedback.add(cursorFeedbackAction(CursorCSS.MOVE), cursorFeedbackAction(CursorCSS.DEFAULT)); - - // restriction feedback - trackedMove.elementMoves.forEach(move => this.tool.changeBoundsManager.addRestrictionFeedback(this.moveFeedback, move)); + protected addMoveFeedback(trackedMove: TrackedMove, ctx: GModelElement, event: MouseEvent): void { + this.tool.changeBoundsManager.addMoveFeedback(this.moveFeedback, trackedMove, ctx, event); } protected initializeElementsToMove(root: GModelRoot): void { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index fa2306b..37721e6 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -37,17 +37,8 @@ import { TYPES, findParentByFeature } from '@eclipse-glsp/sprotty'; -import { CSS_RESIZE_MODE, ChangeBoundsManager, ChangeBoundsTracker, TrackedElementResize, TrackedResize } from '..'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; -import { - CursorCSS, - ModifyCSSFeedbackAction, - applyCssClasses, - cursorFeedbackAction, - deleteCssClasses, - toggleCssClasses -} from '../../../base/feedback/css-feedback'; -import { FeedbackEmitter } from '../../../base/feedback/feeback-emitter'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { BoundsAwareModelElement, @@ -60,18 +51,16 @@ import { } from '../../../utils/gmodel-util'; import { LocalRequestBoundsAction, SetBoundsFeedbackAction } from '../../bounds'; import { SResizeHandle, isResizable } from '../../change-bounds/model'; -import { - IMovementRestrictor, - movementRestrictionFeedback, - removeMovementRestrictionFeedback -} from '../../change-bounds/movement-restrictor'; +import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { BaseEditTool } from '../base-tools'; +import { CSS_ACTIVE_HANDLE, ChangeBoundsManager } from './change-bounds-manager'; import { HideChangeBoundsToolResizeFeedbackAction, MoveFinishedEventAction, ShowChangeBoundsToolResizeFeedbackAction } from './change-bounds-tool-feedback'; import { FeedbackMoveMouseListener } from './change-bounds-tool-move-feedback'; +import { ChangeBoundsTracker, TrackedElementResize, TrackedResize } from './change-bounds-tracker'; /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. @@ -93,7 +82,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; - @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; + @inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; get id(): string { return ChangeBoundsTool.ID; @@ -133,7 +122,7 @@ export class ChangeBoundsTool extends BaseEditTool { } export class ChangeBoundsListener extends DragAwareMouseListener implements ISelectionListener { - static readonly CSS_CLASS_ACTIVE = 'active'; + static readonly CSS_CLASS_ACTIVE = CSS_ACTIVE_HANDLE; // members for calculating the correct position change protected initialBounds: ElementAndBounds | undefined; @@ -206,15 +195,13 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel // consider resize handles ourselves if (this.activeResizeHandle && this.tracker.isTracking()) { const resize = this.tracker.resizeElements(this.activeResizeHandle, { snap: event, symmetric: event, restrict: event }); - this.addResizeFeedback(resize, target, event); const resizeAction = this.resizeBoundsAction(resize); if (resizeAction.bounds.length > 0) { this.resizeFeedback.add(resizeAction, () => this.resetBounds()); this.tracker.updateTrackingPosition(resize.handleMove.moveVector); - } else { - this.resizeFeedback.add(undefined, () => this.resetBounds()); + this.addResizeFeedback(resize, target, event); + this.resizeFeedback.submit(); } - this.resizeFeedback.submit(); } return super.draggingMouseMove(target, event); } @@ -234,40 +221,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } protected addResizeFeedback(resize: TrackedResize, target: GModelElement, event: MouseEvent): void { - const handle = resize.handleMove.element; - // handle feedback - this.resizeFeedback.add( - applyCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE), - deleteCssClasses(handle, ChangeBoundsListener.CSS_CLASS_ACTIVE) - ); - - // graph feedback - this.resizeFeedback.add( - ModifyCSSFeedbackAction.create({ add: [CSS_RESIZE_MODE] }), - ModifyCSSFeedbackAction.create({ remove: [CSS_RESIZE_MODE] }) - ); - - // cursor feedback - const cursorClass = SResizeHandle.getCursorCss(resize.handleMove.element); - this.resizeFeedback.add(cursorFeedbackAction(cursorClass), cursorFeedbackAction(CursorCSS.DEFAULT)); - - const movementRestrictor = this.tool.changeBoundsManager.movementRestrictor; - resize.elementResizes.forEach(elementResize => { - if (movementRestrictor) { - this.resizeFeedback.add( - movementRestrictionFeedback(elementResize.element, movementRestrictor, elementResize.valid.move), - removeMovementRestrictionFeedback(elementResize.element, movementRestrictor) - ); - } - this.resizeFeedback.add( - toggleCssClasses(elementResize.element, !elementResize.valid.size, 'resize-not-allowed'), - deleteCssClasses(elementResize.element, 'resize-not-allowed') - ); - }); - this.resizeFeedback.add( - toggleCssClasses(resize.handleMove.element, !resize.valid.size, 'resize-not-allowed'), - deleteCssClasses(resize.handleMove.element, 'resize-not-allowed') - ); + this.tool.changeBoundsManager.addResizeFeedback(this.resizeFeedback, resize, target, event); } protected resetBounds(): Action[] { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts index 63efd0c..1121d7b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts @@ -27,11 +27,13 @@ import { Vector, Writable, hasBooleanProp, + hasObjectProp, + isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; -import { ChangeBoundsManager } from '..'; import { BoundsAwareModelElement, MoveableElement, ResizableModelElement, getElements } from '../../../utils'; import { DiagramMovementCalculator, ResizeHandleLocation, SResizeHandle } from '../../change-bounds'; +import { ChangeBoundsManager } from './change-bounds-manager'; export interface ElementTrackingOptions { /** Snap position. Default: true. */ @@ -73,6 +75,17 @@ export interface TrackedElementMove extends ResolvedElementMove { valid: boolean; } +export namespace TrackedElementMove { + export function is(obj: any): obj is TrackedElementMove { + return ( + hasObjectProp(obj, 'element') && + hasObjectProp(obj, 'fromPosition') && + hasObjectProp(obj, 'toPosition') && + hasBooleanProp(obj, 'valid') + ); + } +} + export type TypedElementMove = TrackedElementMove & { element: T }; export interface TrackedMove extends Movement { @@ -134,6 +147,14 @@ export interface TrackedElementResize { }; } +export namespace TrackedElementResize { + export function is(obj: any): obj is TrackedElementResize { + return ( + isBoundsAware(obj.element) && hasObjectProp(obj, 'fromBounds') && hasObjectProp(obj, 'toBounds') && hasObjectProp(obj, 'valid') + ); + } +} + export interface TrackedResize extends Movement { handleMove: TrackedHandleMove; elementResizes: TrackedElementResize[]; @@ -179,7 +200,7 @@ export class ChangeBoundsTracker { const update = this.calculateDiagramMovement(); const move: TrackedMove = { ...update, elementMoves: [], valid: true, options }; - if (Vector.isZero(update.vector)) { + if (Vector.isZero(update.vector) && options.skipStatic) { // no movement detected so elements won't be moved, exit early return move; } @@ -260,13 +281,13 @@ export class ChangeBoundsTracker { const update = this.calculateDiagramMovement(); const handleMove = this.calculateHandleMove(new MoveableResizeHandle(handle), update.vector, options); const resize: TrackedResize = { ...update, valid: { move: true, size: true }, options, handleMove, elementResizes: [] }; - if (Vector.isZero(handleMove.moveVector)) { + if (Vector.isZero(handleMove.moveVector) && options.skipStatic) { // no movement detected so elements won't be moved, exit early return resize; } // calculate resize for each element (typically only one element is resized at a time but customizations are possible) - const elementsToResize = this.getResizeableElements(handle, options); + const elementsToResize = this.getResizableElements(handle, options); for (const element of elementsToResize) { const elementResize = this.calculateElementResize(element, handleMove, options); if (!this.skipElementResize(elementResize, options)) { @@ -293,7 +314,7 @@ export class ChangeBoundsTracker { return this.calculateElementMove(handle, diagramMovement, moveOptions); } - protected getResizeableElements(handle: SResizeHandle, options: ResizeOptions): ResizableModelElement[] { + protected getResizableElements(handle: SResizeHandle, options: ResizeOptions): ResizableModelElement[] { return [handle.parent]; } diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 2c91414..3f8f376 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -25,13 +25,14 @@ import { isConnectable, isCtrlOrCmd } from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, optional } from 'inversify'; import { FeedbackEmitter } from '../../../base'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; +import { Grid } from '../../grid'; import { ITypeHintProvider } from '../../hints/type-hint-provider'; import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; @@ -45,8 +46,8 @@ export class EdgeCreationTool extends BaseCreationTool { + /** Flag to indicate whether the location within a container needs to be valid. Default: false */ + validateLocationInContainer?: boolean; + + /** Overwrite for location validation if validation result is already known. Default: undefined */ + validLocationOverwrite?: boolean; + + /** Mouse event to be used for finding the container. Default: undefined */ + evt?: MouseEvent; +} + +export const DEFAULT_INSERT_OPTIONS: InsertOptions = { + validateLocationInContainer: false +}; + +@injectable() +export class ContainerManager { + @inject(ChangeBoundsManager) protected readonly changeBoundsManager: ChangeBoundsManager; + + insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial): TrackedInsert { + const options = { ...DEFAULT_INSERT_OPTIONS, ...opts }; + const container = this.findContainer(location, proxy, opts?.evt); + let valid = !container || container.isContainableElement(elementTypeId); + if (valid && (!container || options.validateLocationInContainer)) { + // we need to check whether the location is valid either because we do not have a container or the option is set + valid = opts?.validLocationOverwrite ?? this.changeBoundsManager.hasValidPosition(proxy, location); + } + return { elementTypeId, container, location, valid }; + } + + findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined { + // reverse order of children to find the innermost, top-rendered containers first + return findChildrenAtPosition(ctx.root, location) + .reverse() + .find(element => isContainable(element) && !element.cssClasses?.includes(CSS_GHOST_ELEMENT)) as ContainerElement | undefined; + } + + addInsertFeedback(feedback: FeedbackEmitter, trackedInsert: TrackedInsert, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter { + // cursor feedback + if (!trackedInsert.valid) { + feedback.add(cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED), cursorFeedbackAction(CursorCSS.DEFAULT)); + } else { + feedback.add(cursorFeedbackAction(CursorCSS.NODE_CREATION), cursorFeedbackAction()); + } + return feedback; + } +} diff --git a/packages/client/src/features/tools/node-creation/index.ts b/packages/client/src/features/tools/node-creation/index.ts index 4cf07d2..39635ca 100644 --- a/packages/client/src/features/tools/node-creation/index.ts +++ b/packages/client/src/features/tools/node-creation/index.ts @@ -13,5 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './container-manager'; +export * from './insert-indicator'; export * from './node-creation-module'; export * from './node-creation-tool'; +export * from './node-creation-views'; diff --git a/packages/client/src/features/tools/node-creation/insert-indicator.ts b/packages/client/src/features/tools/node-creation/insert-indicator.ts new file mode 100644 index 0000000..7964dcf --- /dev/null +++ b/packages/client/src/features/tools/node-creation/insert-indicator.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Args, Dimension, FeatureSet, GNode, boundsFeature, createFeatureSet, moveFeature } from '@eclipse-glsp/sprotty'; +import { v4 as uuid } from 'uuid'; +import { ArgsAware, argsFeature } from '../../../base/args-feature'; + +export const ARG_LENGTH = 'length'; + +export class InsertIndicator extends GNode implements ArgsAware { + static override readonly DEFAULT_FEATURES = [boundsFeature, moveFeature, argsFeature]; + + static TYPE = 'node:insert-indicator'; + + override id: string = uuid(); + override type: string = InsertIndicator.TYPE; + override features?: FeatureSet = createFeatureSet(InsertIndicator.DEFAULT_FEATURES); + override cssClasses: string[] = ['insert-indicator', 'sprotty-node']; + override size: Dimension = { width: 1, height: 1 }; + args: Args = {}; + + constructor(length?: number) { + super(); + if (length) { + this.args = { [ARG_LENGTH]: length }; + } + } +} diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index 3691616..b6eef8e 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -13,15 +13,27 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, TriggerNodeCreationAction, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { + FeatureModule, + TYPES, + TriggerNodeCreationAction, + bindAsService, + configureActionHandler, + configureModelElement +} from '@eclipse-glsp/sprotty'; import { elementTemplateModule } from '../../element-template/element-template-module'; +import { ContainerManager } from './container-manager'; +import { InsertIndicator } from './insert-indicator'; import { NodeCreationTool } from './node-creation-tool'; +import { InsertIndicatorView } from './node-creation-views'; export const nodeCreationToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; + bind(ContainerManager).toSelf().inSingletonScope(); bindAsService(context, TYPES.ITool, NodeCreationTool); configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); + configureModelElement(context, InsertIndicator.TYPE, InsertIndicator, InsertIndicatorView); }, { requires: elementTemplateModule } ); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index f34fe5b..1673cf6 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -16,13 +16,14 @@ import { Action, CreateNodeOperation, + Disposable, + DisposableCollection, GModelElement, - GNode, GhostElement, + IModelFactory, Point, TYPES, TriggerNodeCreationAction, - findParentByFeature, isCtrlOrCmd, isMoveable } from '@eclipse-glsp/sprotty'; @@ -32,14 +33,14 @@ import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; -import { MoveableElement, isValidMove } from '../../../utils'; -import { getAbsolutePosition } from '../../../utils/viewpoint-util'; -import { RemoveTemplateElementsAction } from '../../element-template'; +import { MoveableElement } from '../../../utils/gmodel-util'; import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; -import { Containable, isContainable } from '../../hints/model'; +import { RemoveTemplateElementsAction } from '../../element-template/remove-template-element'; import { BaseCreationTool } from '../base-tools'; -import { ChangeBoundsManager } from '../change-bounds'; +import { ChangeBoundsManager, TrackedMove } from '../change-bounds'; +import { ContainerManager, TrackedInsert } from './container-manager'; +import { InsertIndicator } from './insert-indicator'; @injectable() export class NodeCreationTool extends BaseCreationTool implements PositioningTool { @@ -47,52 +48,80 @@ export class NodeCreationTool extends BaseCreationTool, context: RenderingContext): VNode | undefined { + if (context.targetKind === 'hidden') { + return undefined; + } + const size = this.getSize(model); + const node = ( + + + + + ); + model.cssClasses.forEach(cl => setClass(node, cl, true)); + return node; + } + + protected getSize(model: Readonly): number { + return GArgument.getNumber(model, ARG_LENGTH) ?? this.grid?.x ?? 10; + } +} diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index 5ad5329..c8fa274 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -40,6 +40,7 @@ import { isSelected, remove } from '@eclipse-glsp/sprotty'; +import { ResizeHandleLocation } from '../features'; /** * Helper type to represent a filter predicate for {@link GModelElement}s. This is used to retrieve @@ -253,7 +254,9 @@ export type MoveableElement = GModelElement & Locateable; export interface Resizable extends BoundsAware, Selectable {} -export interface ResizableModelElement extends GParentElement, Resizable {} +export interface ResizableModelElement extends GParentElement, Resizable { + resizeLocations?: ResizeHandleLocation[]; +} /** * Helper function to translate a given {@link GModelElement} into its corresponding {@link ElementAndBounds} representation. diff --git a/packages/client/src/views/ggraph-view.tsx b/packages/client/src/views/ggraph-view.tsx index 7c2c9f1..db0f32d 100644 --- a/packages/client/src/views/ggraph-view.tsx +++ b/packages/client/src/views/ggraph-view.tsx @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Dimension, Point, RenderingContext, SGraphImpl, SGraphView, TYPES, Writable } from '@eclipse-glsp/sprotty'; +import { Bounds, Dimension, Point, RenderingContext, SGraphImpl, SGraphView, Writable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GridManager, GridStyle } from '../features'; @injectable() export class GGraphView extends SGraphView { - @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; + @inject(GridManager) @optional() protected gridManager?: GridManager; override render(model: Readonly, context: RenderingContext): VNode { const graph = super.render(model, context); @@ -31,7 +31,7 @@ export class GGraphView extends SGraphView { } protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { - if (!this.gridManager?.isGridVisible) { + if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { return {}; } const bounds = this.getBackgroundBounds(model, context, this.gridManager); diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 69ee9ce..23f7ce6 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -25,7 +25,6 @@ import { ProjectionParams, RenderingContext, SGraphImpl, - TYPES, ViewProjection, Writable, html, @@ -45,7 +44,7 @@ const JSX = { createElement: html }; @injectable() export class GLSPProjectionView extends ProjectedViewportView { @inject(EdgeRouterRegistry) edgeRouterRegistry: EdgeRouterRegistry; - @inject(TYPES.IGridManager) @optional() protected gridManager?: GridManager; + @inject(GridManager) @optional() protected gridManager?: GridManager; override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { const rootNode: VNode = ( @@ -73,7 +72,7 @@ export class GLSPProjectionView extends ProjectedViewportView { } protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { - if (!this.gridManager?.isGridVisible) { + if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { return {}; } const bounds = this.getBackgroundBounds(model, context, this.gridManager); diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 349405c..b80f027 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -16,4 +16,5 @@ export * from './action-override'; export * from './feature-modules'; export * from './re-exports'; +export * from './svg-views-override'; export * from './types'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 5978e58..e6f0ea2 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -292,7 +292,7 @@ export { RectangularPort } from 'sprotty/lib/lib/model'; export * from 'sprotty/lib/lib/modules'; -export * from 'sprotty/lib/lib/svg-views'; +export { EmptyGroupView, RectangularNodeView, SvgViewportView } from 'sprotty/lib/lib/svg-views'; // ------------------ Model Source ------------------ export * from 'sprotty/lib/model-source/commit-model'; diff --git a/packages/glsp-sprotty/src/svg-views-override.tsx b/packages/glsp-sprotty/src/svg-views-override.tsx new file mode 100644 index 0000000..da3769a --- /dev/null +++ b/packages/glsp-sprotty/src/svg-views-override.tsx @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Bounds } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { + ATTR_BBOX_ELEMENT, + BoundsAware, + Hoverable, + IViewArgs, + RenderingContext, + SShapeElementImpl, + Selectable, + CircularNodeView as SprottyCircularNodeView, + DiamondNodeView as SprottyDiamondNodeView, + svg +} from 'sprotty'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const JSX = { createElement: svg }; + +@injectable() +export class DiamondNodeView extends SprottyDiamondNodeView { + override render( + node: Readonly, + context: RenderingContext, + args?: IViewArgs + ): VNode | undefined { + // The super class may change the bounds of the given node as browsers may calculate larger bounds for the rendered node (BBox). + // To ensure that the node's bounds remains unchanged, we render a rectangle with the correct bounds during hidden rendering. + return applyHiddenBoundingRect(node, context, super.render(node, context, args)); + } +} + +@injectable() +export class CircularNodeView extends SprottyCircularNodeView { + override render( + node: Readonly, + context: RenderingContext, + args?: IViewArgs + ): VNode | undefined { + // The super class may change the bounds of the given node as browsers may calculate larger bounds for the rendered node (BBox). + // To ensure that the node's bounds remains unchanged, we render a rectangle with the correct bounds during hidden rendering. + return applyHiddenBoundingRect(node, context, super.render(node, context, args)); + } +} + +/** + * Creates a hidden rectangle with the bounds of the given element. + * @param withBounds The element to create the hidden rectangle for. + * @returns The hidden rectangle. + */ +export function hiddenBoundingRect(withBounds: BoundsAware): VNode { + // an element with attribute ATTR_BBOX_ELEMENT is used by the hidden bounds updater to determine the bounds if it is within a g-element + // we set the fill to transparent since the SVG export uses the hidden rendering to generate the image and we do not want to be seen + return ; +} + +/** + * Applies a hidden bounding rectangle to the given view if we are in the hidden rendering context where sizes are being determined. + * + * @param withBounds The element to apply the hidden bounding rectangle to. + * @param context The rendering context. + * @param view The view to apply the hidden bounding rectangle to. + * @returns The view with the hidden bounding rectangle applied, wrapped in a group element if necessary. + */ +export function applyHiddenBoundingRect(withBounds: BoundsAware, context: RenderingContext, view: V): V { + if (view && context.targetKind === 'hidden') { + const parent = view.sel === 'g' ? view : {view}; + parent.children?.unshift(hiddenBoundingRect(withBounds)); + return parent; + } + return view; +} diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 704e354..9f0034f 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -48,8 +48,5 @@ export const TYPES = { IDiagramOptions: Symbol('IDiagramOptions'), IDiagramStartup: Symbol('IDiagramStartup'), IToolManager: Symbol('IToolManager'), - IDebugManager: Symbol('IDebugManager'), - Grid: Symbol('Grid'), - IGridManager: Symbol('IGridManager'), - IChangeBoundsManager: Symbol('IChangeBoundsManager') + Grid: Symbol('Grid') }; diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index ce8c281..4bf1a07 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; -import { GModelRootSchema } from '..'; +import { GModelRootSchema } from '../model'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; import { Args, ElementAndAlignment, ElementAndBounds, ElementAndLayoutData, ElementAndRoutingPoints } from './types'; diff --git a/packages/protocol/src/sprotty-geometry-bounds.spec.ts b/packages/protocol/src/sprotty-geometry-bounds.spec.ts index ac3348d..22f450b 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.spec.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.spec.ts @@ -18,6 +18,20 @@ import { Dimension, Point } from 'sprotty-protocol'; import { Bounds } from './sprotty-geometry-bounds'; describe('Bounds', () => { + describe('is', () => { + it('should return true if the given object is a bounds', () => { + const bounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.is(bounds); + expect(result).to.be.true; + }); + + it('should return false if the given object is not a bounds', () => { + const bounds = { y: 0, width: 100, height: 100 }; + const result = Bounds.is(bounds); + expect(result).to.be.false; + }); + }); + describe('encompasses', () => { it('should return true if the outer bounds completely encompass the inner bounds', () => { const outer: Bounds = { x: 0, y: 0, width: 100, height: 100 }; @@ -274,6 +288,22 @@ describe('Bounds', () => { }); }); + describe('position', () => { + it('should return the position of the bounds', () => { + const bounds: Bounds = { x: 10, y: 20, width: 30, height: 40 }; + const result = Bounds.position(bounds); + expect(result).to.deep.equal({ x: 10, y: 20 }); + }); + }); + + describe('dimension', () => { + it('should create a new dimension from the given bounds', () => { + const bounds = { x: 10, y: 20, width: 30, height: 40 }; + const dimension = Bounds.dimension(bounds); + expect(dimension).to.deep.equal({ width: 30, height: 40 }); + }); + }); + describe('sortBy', () => { it('should sort the bounds based on the rank function', () => { const bounds1: Bounds = { x: 0, y: 0, width: 100, height: 100 }; diff --git a/packages/protocol/src/sprotty-geometry-bounds.ts b/packages/protocol/src/sprotty-geometry-bounds.ts index 8e8a6b6..02d60b5 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.ts @@ -15,7 +15,10 @@ ********************************************************************************/ /* eslint-disable @typescript-eslint/no-shadow */ -import { Bounds, Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; +import { Bounds } from 'sprotty-protocol/lib/utils/geometry'; +import { Dimension } from './sprotty-geometry-dimension'; +import { Point } from './sprotty-geometry-point'; +import { AnyObject, hasNumberProp } from './utils'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Bounds { @@ -24,6 +27,12 @@ declare module 'sprotty-protocol/lib/utils/geometry' { */ const ZERO: Bounds; + /** + * Type guard to check if the given object is a bound. + * @param bounds the object to be checked + */ + function is(bounds: any): bounds is Bounds; + /** * Checks whether the inner bounds are compeletely encompassed by the outer bounds. * @@ -208,6 +217,21 @@ declare module 'sprotty-protocol/lib/utils/geometry' { */ function from(topLeft: Point, bottomRight: Point): Bounds; + /** + * Creates a new point from the given bounds by removing the `width` and `height` of the bounds. + * This is the same as the top-left point but this method may carry more semantics. + * @param bounds the bounds + * @returns new point + */ + function position(bounds: Bounds): Point; + + /** + * Creates a new dimension from the given bounds by removing the `x` and `y` of the bounds. + * @param bounds the bounds + * @returns new dimension + */ + function dimension(bounds: Bounds): Dimension; + /** * Sorts the given bounds by the given rank function. * @param rankFunc the rank function @@ -241,6 +265,13 @@ declare module 'sprotty-protocol/lib/utils/geometry' { height: 0 }); +Bounds.is = (bounds: any): bounds is Bounds => + AnyObject.is(bounds) && + hasNumberProp(bounds, 'x') && + hasNumberProp(bounds, 'y') && + hasNumberProp(bounds, 'width') && + hasNumberProp(bounds, 'height'); + Bounds.encompasses = (outer: Bounds, inner: Bounds): boolean => Bounds.includes(outer, Bounds.topLeft(inner)) && Bounds.includes(outer, Bounds.bottomRight(inner)); @@ -311,6 +342,10 @@ Bounds.from = (topLeft: Point, bottomRight: Point): Bounds => ({ height: bottomRight.y - topLeft.y }); +Bounds.position = Bounds.topLeft; + +Bounds.dimension = (bounds: Bounds): Dimension => ({ width: bounds.width, height: bounds.height }); + Bounds.move = Bounds.translate; Bounds.resize = (bounds: Bounds, delta: Dimension): Bounds => ({ diff --git a/packages/protocol/src/sprotty-geometry-dimension.spec.ts b/packages/protocol/src/sprotty-geometry-dimension.spec.ts index a9256d1..02fde1b 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.spec.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.spec.ts @@ -17,6 +17,24 @@ import { expect } from 'chai'; import { Dimension } from './sprotty-geometry-dimension'; describe('Dimension', () => { + describe('ZERO', () => { + it('should have width and height set to 0', () => { + expect(Dimension.ZERO).to.deep.equal({ width: 0, height: 0 }); + }); + }); + + describe('is', () => { + it('should return true if the given object is a dimension', () => { + const dimension: Dimension = { width: 10, height: 20 }; + expect(Dimension.is(dimension)).to.be.true; + }); + + it('should return false if the given object is not a dimension', () => { + const dimension = { height: 20 }; + expect(Dimension.is(dimension)).to.be.false; + }); + }); + describe('map', () => { it('should apply the given function to width and height', () => { const dimension: Dimension = { width: 10, height: 20 }; diff --git a/packages/protocol/src/sprotty-geometry-dimension.ts b/packages/protocol/src/sprotty-geometry-dimension.ts index 2c9e0e8..8b56bc4 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.ts @@ -16,6 +16,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; +import { AnyObject, hasNumberProp } from './utils'; import { equalUpTo } from './utils/math-util'; declare module 'sprotty-protocol/lib/utils/geometry' { @@ -25,6 +26,12 @@ declare module 'sprotty-protocol/lib/utils/geometry' { */ const ZERO: Dimension; + /** + * Type guard to check if the given object is a bound. + * @param dimension the object to be checked + */ + function is(dimension: any): dimension is Dimension; + /** * Applies the given function to the `width` and `height` of the given dimensional object to create a new dimensional object. * @@ -33,6 +40,7 @@ declare module 'sprotty-protocol/lib/utils/geometry' { * @returns new dimension */ function map(dimension: T, callbackfn: (value: number, key: keyof Dimension) => number): T; + /** * Returns the center point of the given dimension. * @@ -104,6 +112,8 @@ declare module 'sprotty-protocol/lib/utils/geometry' { height: 0 }); +Dimension.is = (dimension: any): dimension is Dimension => + AnyObject.is(dimension) && hasNumberProp(dimension, 'width') && hasNumberProp(dimension, 'height'); Dimension.center = (d: Dimension): Point => ({ x: d.width * 0.5, y: d.height * 0.5 }); Dimension.add = (d: Dimension, a: Dimension): Dimension => ({ width: d.width + a.width, height: d.height + a.height }); Dimension.subtract = (d: Dimension, a: Dimension): Dimension => ({ width: d.width - a.width, height: d.height - a.height }); diff --git a/packages/protocol/src/sprotty-geometry-point.ts b/packages/protocol/src/sprotty-geometry-point.ts index fcb5206..c32843d 100644 --- a/packages/protocol/src/sprotty-geometry-point.ts +++ b/packages/protocol/src/sprotty-geometry-point.ts @@ -16,8 +16,10 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { Point } from 'sprotty-protocol/lib/utils/geometry'; -import { AnyObject, Movement, Vector, hasNumberProp } from './utils'; +import { Movement } from './utils/geometry-movement'; +import { Vector } from './utils/geometry-vector'; import { equalUpTo } from './utils/math-util'; +import { AnyObject, hasNumberProp } from './utils/type-util'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Point { diff --git a/packages/protocol/src/utils/geometry-movement.ts b/packages/protocol/src/utils/geometry-movement.ts index 71c3f15..cb6c07a 100644 --- a/packages/protocol/src/utils/geometry-movement.ts +++ b/packages/protocol/src/utils/geometry-movement.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from 'sprotty-protocol/lib/utils/geometry'; +import { Point } from '../sprotty-geometry-point'; import { Direction } from './geometry-util'; import { Vector } from './geometry-vector'; import { AnyObject, hasObjectProp } from './type-util'; From 2881ed4c2368d2d57318264e6d7198b9e93f9886 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 27 May 2024 11:28:27 +0200 Subject: [PATCH 337/566] GLSP-1346: Use root level index (#349) Switch to root level index generation. - Remove all sub directory index.ts files - Update `generate:Index` script to single file - Update import paths - (Check and fix copyright headers) Fixes https://github.com/eclipse-glsp/glsp/issues/1346 Co-authored-by: Martin Fleck --- examples/workflow-glsp/package.json | 2 +- .../src/direct-task-editing/index.ts | 17 -- examples/workflow-glsp/src/index.ts | 5 +- examples/workflow-glsp/src/model.ts | 2 +- packages/client/package.json | 2 +- .../client/src/base/action-dispatcher.spec.ts | 2 +- .../client/src/base/auto-complete/index.ts | 20 -- packages/client/src/base/default.module.ts | 3 +- .../src/base/drag-aware-mouse-listener.ts | 2 +- packages/client/src/base/feedback/index.ts | 22 -- packages/client/src/base/focus/index.ts | 17 -- packages/client/src/base/index.ts | 33 --- packages/client/src/base/model/index.ts | 19 -- packages/client/src/base/selection-service.ts | 2 +- .../client/src/base/tool-manager/index.ts | 17 -- .../src/base/tool-manager/tool-manager.ts | 2 +- .../client/src/base/ui-extension/index.ts | 17 -- .../ui-extension/ui-extension-registry.ts | 2 +- packages/client/src/base/view/index.ts | 18 -- packages/client/src/base/view/key-tool.ts | 2 +- packages/client/src/base/view/mouse-tool.ts | 2 +- .../accessibility/edge-autocomplete/index.ts | 19 -- .../accessibility/element-navigation/index.ts | 21 -- .../accessibility/focus-tracker/index.ts | 17 -- .../src/features/accessibility/index.ts | 30 --- .../accessibility/key-shortcut/index.ts | 18 -- .../accessibility/keyboard-grid/index.ts | 20 -- .../accessibility/keyboard-pointer/index.ts | 21 -- .../keyboard-tool-palette/index.ts | 17 -- .../features/accessibility/move-zoom/index.ts | 18 -- .../accessibility/move-zoom/move-handler.ts | 4 +- .../accessibility/resize-key-tool/index.ts | 18 -- .../resize-key-tool/resize-key-handler.ts | 6 +- .../features/accessibility/search/index.ts | 18 -- .../src/features/accessibility/toast/index.ts | 18 -- .../accessibility/view-key-tools/index.ts | 19 -- .../view-key-tools/movement-key-tool.ts | 6 +- packages/client/src/features/bounds/index.ts | 24 -- .../bounds/set-bounds-feedback-command.ts | 2 +- .../src/features/change-bounds/index.ts | 21 -- .../src/features/change-bounds/model.ts | 6 +- .../change-bounds/movement-restrictor.ts | 4 +- .../point-position-updater.spec.ts | 2 +- .../src/features/command-palette/index.ts | 19 -- .../client/src/features/context-menu/index.ts | 19 -- .../client/src/features/copy-paste/index.ts | 19 -- .../features/debug/debug-bounds-decorator.tsx | 2 +- .../src/features/debug/debug-manager.ts | 3 +- .../client/src/features/debug/debug-model.ts | 4 +- packages/client/src/features/debug/index.ts | 19 -- .../client/src/features/decoration/index.ts | 17 -- .../src/features/element-template/index.ts | 19 -- ...ouse-tracking-element-position-listener.ts | 5 +- packages/client/src/features/export/index.ts | 18 -- .../client/src/features/grid/grid-manager.ts | 3 +- .../client/src/features/grid/grid-model.ts | 4 +- .../client/src/features/grid/grid-module.ts | 2 +- packages/client/src/features/grid/index.ts | 20 -- .../helper-lines/helper-line-feedback.ts | 2 +- .../helper-line-manager-default.ts | 4 +- .../client/src/features/helper-lines/index.ts | 21 -- packages/client/src/features/hints/index.ts | 18 -- packages/client/src/features/hints/model.ts | 2 +- .../features/hints/type-hint-provider.spec.ts | 4 +- .../src/features/hints/type-hint-provider.ts | 2 +- packages/client/src/features/hover/index.ts | 17 -- packages/client/src/features/index.ts | 45 ---- .../src/features/label-edit-ui/index.ts | 17 -- .../client/src/features/label-edit/index.ts | 18 -- packages/client/src/features/layout/index.ts | 17 -- .../client/src/features/navigation/index.ts | 18 -- .../client/src/features/reconnect/index.ts | 16 -- packages/client/src/features/routing/index.ts | 17 -- packages/client/src/features/save/index.ts | 17 -- packages/client/src/features/select/index.ts | 18 -- .../features/select/select-mouse-listener.ts | 8 +- .../features/source-model-watcher/index.ts | 17 -- packages/client/src/features/status/index.ts | 17 -- .../client/src/features/svg-metadata/index.ts | 17 -- .../client/src/features/tool-palette/index.ts | 17 -- .../src/features/tool-palette/tool-palette.ts | 8 +- .../client/src/features/tools/base-tools.ts | 2 +- .../change-bounds/change-bounds-manager.ts | 16 +- .../change-bounds-tool-module.ts | 2 +- .../change-bounds-tool-move-feedback.ts | 12 +- .../tools/change-bounds/change-bounds-tool.ts | 5 +- .../change-bounds/change-bounds-tracker.ts | 5 +- .../src/features/tools/change-bounds/index.ts | 22 -- .../src/features/tools/deletion/index.ts | 17 -- .../edge-creation/dangling-edge-feedback.ts | 4 +- .../edge-creation-tool-feedback.ts | 2 +- .../tools/edge-creation/edge-creation-tool.ts | 6 +- .../src/features/tools/edge-creation/index.ts | 20 -- .../edge-edit/edge-edit-tool-feedback.ts | 7 +- .../tools/edge-edit/edge-edit-tool.ts | 6 +- .../src/features/tools/edge-edit/index.ts | 18 -- packages/client/src/features/tools/index.ts | 23 -- .../features/tools/marquee-selection/index.ts | 22 -- .../marquee-selection/marquee-mouse-tool.ts | 4 +- .../tools/node-creation/container-manager.ts | 8 +- .../src/features/tools/node-creation/index.ts | 20 -- .../node-creation/node-creation-module.ts | 2 +- .../tools/node-creation/node-creation-tool.ts | 7 +- .../node-creation/node-creation-views.tsx | 4 +- .../client/src/features/undo-redo/index.ts | 17 -- .../client/src/features/validation/index.ts | 19 -- .../src/features/validation/validate.ts | 2 +- .../client/src/features/viewport/index.ts | 20 -- .../src/features/viewport/viewport-handler.ts | 2 +- packages/client/src/index.ts | 228 +++++++++++++++++- packages/client/src/utils/gmodel-util.ts | 2 +- packages/client/src/utils/index.ts | 22 -- packages/client/src/views/ggraph-view.tsx | 2 +- .../client/src/views/glsp-projection-view.tsx | 4 +- packages/client/src/views/index.ts | 23 -- packages/glsp-sprotty/package.json | 2 +- packages/glsp-sprotty/src/index.ts | 2 +- packages/protocol/package.json | 2 +- .../protocol/src/action-protocol/index.ts | 36 --- .../src/action-protocol/model-layout.ts | 4 +- .../src/client-server-protocol/glsp-client.ts | 6 +- .../src/client-server-protocol/glsp-server.ts | 4 +- .../src/client-server-protocol/index.ts | 20 -- .../jsonrpc/base-jsonrpc-glsp-client.ts | 4 +- .../client-server-protocol/jsonrpc/index.ts | 21 -- .../src/client-server-protocol/types.ts | 4 +- .../src/di/container-configuration.ts | 3 +- packages/protocol/src/di/feature-module.ts | 2 +- packages/protocol/src/di/lazy-injector.ts | 3 +- packages/protocol/src/index.ts | 47 +++- packages/protocol/src/model/index.ts | 17 -- .../protocol/src/sprotty-geometry-bounds.ts | 2 +- .../src/sprotty-geometry-dimension.ts | 2 +- packages/protocol/src/utils/index.ts | 23 -- 134 files changed, 401 insertions(+), 1425 deletions(-) delete mode 100644 examples/workflow-glsp/src/direct-task-editing/index.ts delete mode 100644 packages/client/src/base/auto-complete/index.ts delete mode 100644 packages/client/src/base/feedback/index.ts delete mode 100644 packages/client/src/base/focus/index.ts delete mode 100644 packages/client/src/base/index.ts delete mode 100644 packages/client/src/base/model/index.ts delete mode 100644 packages/client/src/base/tool-manager/index.ts delete mode 100644 packages/client/src/base/ui-extension/index.ts delete mode 100644 packages/client/src/base/view/index.ts delete mode 100644 packages/client/src/features/accessibility/edge-autocomplete/index.ts delete mode 100644 packages/client/src/features/accessibility/element-navigation/index.ts delete mode 100644 packages/client/src/features/accessibility/focus-tracker/index.ts delete mode 100644 packages/client/src/features/accessibility/index.ts delete mode 100644 packages/client/src/features/accessibility/key-shortcut/index.ts delete mode 100644 packages/client/src/features/accessibility/keyboard-grid/index.ts delete mode 100644 packages/client/src/features/accessibility/keyboard-pointer/index.ts delete mode 100644 packages/client/src/features/accessibility/keyboard-tool-palette/index.ts delete mode 100644 packages/client/src/features/accessibility/move-zoom/index.ts delete mode 100644 packages/client/src/features/accessibility/resize-key-tool/index.ts delete mode 100644 packages/client/src/features/accessibility/search/index.ts delete mode 100644 packages/client/src/features/accessibility/toast/index.ts delete mode 100644 packages/client/src/features/accessibility/view-key-tools/index.ts delete mode 100644 packages/client/src/features/bounds/index.ts delete mode 100644 packages/client/src/features/change-bounds/index.ts delete mode 100644 packages/client/src/features/command-palette/index.ts delete mode 100644 packages/client/src/features/context-menu/index.ts delete mode 100644 packages/client/src/features/copy-paste/index.ts delete mode 100644 packages/client/src/features/debug/index.ts delete mode 100644 packages/client/src/features/decoration/index.ts delete mode 100644 packages/client/src/features/element-template/index.ts delete mode 100644 packages/client/src/features/export/index.ts delete mode 100644 packages/client/src/features/grid/index.ts delete mode 100644 packages/client/src/features/helper-lines/index.ts delete mode 100644 packages/client/src/features/hints/index.ts delete mode 100644 packages/client/src/features/hover/index.ts delete mode 100644 packages/client/src/features/index.ts delete mode 100644 packages/client/src/features/label-edit-ui/index.ts delete mode 100644 packages/client/src/features/label-edit/index.ts delete mode 100644 packages/client/src/features/layout/index.ts delete mode 100644 packages/client/src/features/navigation/index.ts delete mode 100644 packages/client/src/features/reconnect/index.ts delete mode 100644 packages/client/src/features/routing/index.ts delete mode 100644 packages/client/src/features/save/index.ts delete mode 100644 packages/client/src/features/select/index.ts delete mode 100644 packages/client/src/features/source-model-watcher/index.ts delete mode 100644 packages/client/src/features/status/index.ts delete mode 100644 packages/client/src/features/svg-metadata/index.ts delete mode 100644 packages/client/src/features/tool-palette/index.ts delete mode 100644 packages/client/src/features/tools/change-bounds/index.ts delete mode 100644 packages/client/src/features/tools/deletion/index.ts delete mode 100644 packages/client/src/features/tools/edge-creation/index.ts delete mode 100644 packages/client/src/features/tools/edge-edit/index.ts delete mode 100644 packages/client/src/features/tools/index.ts delete mode 100644 packages/client/src/features/tools/marquee-selection/index.ts delete mode 100644 packages/client/src/features/tools/node-creation/index.ts delete mode 100644 packages/client/src/features/undo-redo/index.ts delete mode 100644 packages/client/src/features/validation/index.ts delete mode 100644 packages/client/src/features/viewport/index.ts delete mode 100644 packages/client/src/utils/index.ts delete mode 100644 packages/client/src/views/index.ts delete mode 100644 packages/protocol/src/action-protocol/index.ts delete mode 100644 packages/protocol/src/client-server-protocol/index.ts delete mode 100644 packages/protocol/src/client-server-protocol/jsonrpc/index.ts delete mode 100644 packages/protocol/src/model/index.ts delete mode 100644 packages/protocol/src/utils/index.ts diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 6295352..cabe4e1 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -35,7 +35,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf *.tsbuildinfo lib", - "generate:index": "glsp generateIndex src -f", + "generate:index": "glsp generateIndex src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "watch": "tsc -w" }, diff --git a/examples/workflow-glsp/src/direct-task-editing/index.ts b/examples/workflow-glsp/src/direct-task-editing/index.ts deleted file mode 100644 index 48791ce..0000000 --- a/examples/workflow-glsp/src/direct-task-editing/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './di.config'; -export * from './direct-task-editor'; diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index f5d9dc1..74f104b 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './direct-task-editing'; +export * from './direct-task-editing/di.config'; +export * from './direct-task-editing/direct-task-editor'; export * from './model'; export * from './workflow-diagram-module'; export * from './workflow-snapper'; diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index 9e6c7b3..b6475f0 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/package.json b/packages/client/package.json index 0e94333..a77c877 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -38,7 +38,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", - "generate:index": "glsp generateIndex src -f", + "generate:index": "glsp generateIndex src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/client/src/base/action-dispatcher.spec.ts b/packages/client/src/base/action-dispatcher.spec.ts index 248cf71..3a23aa8 100644 --- a/packages/client/src/base/action-dispatcher.spec.ts +++ b/packages/client/src/base/action-dispatcher.spec.ts @@ -19,7 +19,7 @@ import { Container } from 'inversify'; import { GLSPActionDispatcher } from './action-dispatcher'; import { defaultModule } from './default.module'; -import { IDiagramOptions } from './model'; +import { IDiagramOptions } from './model/diagram-loader'; const container = new Container(); container.load(defaultModule); diff --git a/packages/client/src/base/auto-complete/index.ts b/packages/client/src/base/auto-complete/index.ts deleted file mode 100644 index fb5f7b3..0000000 --- a/packages/client/src/base/auto-complete/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './auto-complete-actions'; -export * from './auto-complete-widget'; -export * from './autocomplete-suggestion-providers'; -export * from './base-autocomplete-palette'; -export * from './validation-decorator'; diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 106a1a9..c4a47d1 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -38,8 +38,9 @@ import { GLSPActionDispatcher } from './action-dispatcher'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; -import { FeedbackActionDispatcher, FeedbackAwareSetModelCommand } from './feedback'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; +import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher-default'; +import { FeedbackAwareSetModelCommand } from './feedback/set-model-command'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index 1ecc6b4..2d291de 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/index.ts b/packages/client/src/base/feedback/index.ts deleted file mode 100644 index a05db2a..0000000 --- a/packages/client/src/base/feedback/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './css-feedback'; -export * from './feedback-action-dispatcher'; -export * from './feedback-action-dispatcher-default'; -export * from './feedback-command'; -export * from './feedback-emitter'; -export * from './set-model-command'; -export * from './update-model-command'; diff --git a/packages/client/src/base/focus/index.ts b/packages/client/src/base/focus/index.ts deleted file mode 100644 index 4de0e8f..0000000 --- a/packages/client/src/base/focus/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './focus-state-change-action'; -export * from './focus-tracker'; diff --git a/packages/client/src/base/index.ts b/packages/client/src/base/index.ts deleted file mode 100644 index cc79046..0000000 --- a/packages/client/src/base/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './action-dispatcher'; -export * from './action-handler-registry'; -export * from './args-feature'; -export * from './auto-complete'; -export * from './command-stack'; -export * from './default.module'; -export * from './drag-aware-mouse-listener'; -export * from './editor-context-service'; -export * from './feedback'; -export * from './focus'; -export * from './model'; -export * from './mouse-position-tracker'; -export * from './ranked'; -export * from './selection-clearing-mouse-listener'; -export * from './selection-service'; -export * from './tool-manager'; -export * from './ui-extension'; -export * from './view'; diff --git a/packages/client/src/base/model/index.ts b/packages/client/src/base/model/index.ts deleted file mode 100644 index 75f2f15..0000000 --- a/packages/client/src/base/model/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './diagram-loader'; -export * from './glsp-model-source'; -export * from './model-initialization-constraint'; -export * from './model-registry'; diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index 1c5dd23..f1a46cc 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -39,7 +39,7 @@ import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { SelectableElement, getElements, getMatchingElements } from '../utils/gmodel-util'; import { IGModelRootListener } from './editor-context-service'; import { IFeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; -import { IDiagramStartup } from './model'; +import { IDiagramStartup } from './model/diagram-loader'; export interface ISelectionListener { selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; diff --git a/packages/client/src/base/tool-manager/index.ts b/packages/client/src/base/tool-manager/index.ts deleted file mode 100644 index 1a8a894..0000000 --- a/packages/client/src/base/tool-manager/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './tool'; -export * from './tool-manager'; diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts index b3755b1..14230dd 100644 --- a/packages/client/src/base/tool-manager/tool-manager.ts +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -29,7 +29,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService, IEditModeListener } from '../editor-context-service'; -import { IDiagramStartup } from '../model'; +import { IDiagramStartup } from '../model/diagram-loader'; import { Ranked } from '../ranked'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from './tool'; /** diff --git a/packages/client/src/base/ui-extension/index.ts b/packages/client/src/base/ui-extension/index.ts deleted file mode 100644 index da1662b..0000000 --- a/packages/client/src/base/ui-extension/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './ui-extension'; -export * from './ui-extension-registry'; diff --git a/packages/client/src/base/ui-extension/ui-extension-registry.ts b/packages/client/src/base/ui-extension/ui-extension-registry.ts index 803c63a..9e26880 100644 --- a/packages/client/src/base/ui-extension/ui-extension-registry.ts +++ b/packages/client/src/base/ui-extension/ui-extension-registry.ts @@ -16,7 +16,7 @@ import { IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { IDiagramStartup } from '../model'; +import { IDiagramStartup } from '../model/diagram-loader'; @injectable() export class GLSPUIExtensionRegistry extends UIExtensionRegistry implements IDiagramStartup { diff --git a/packages/client/src/base/view/index.ts b/packages/client/src/base/view/index.ts deleted file mode 100644 index fb4efbe..0000000 --- a/packages/client/src/base/view/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './key-tool'; -export * from './mouse-tool'; -export * from './view-registry'; diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts index 10e4a07..b6492ee 100644 --- a/packages/client/src/base/view/key-tool.ts +++ b/packages/client/src/base/view/key-tool.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { Disposable, KeyListener, KeyTool, LazyInjector, MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { IDiagramStartup } from '../model'; +import { IDiagramStartup } from '../model/diagram-loader'; @injectable() export class GLSPKeyTool extends KeyTool implements IDiagramStartup { diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index f6739ba..1e9ed16 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -25,7 +25,7 @@ import { TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { IDiagramStartup } from '../model'; +import { IDiagramStartup } from '../model/diagram-loader'; import { Ranked } from '../ranked'; /** diff --git a/packages/client/src/features/accessibility/edge-autocomplete/index.ts b/packages/client/src/features/accessibility/edge-autocomplete/index.ts deleted file mode 100644 index b6f9eee..0000000 --- a/packages/client/src/features/accessibility/edge-autocomplete/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './action'; -export * from './edge-autocomplete-context'; -export * from './edge-autocomplete-palette'; -export * from './edge-autocomplete-tool'; diff --git a/packages/client/src/features/accessibility/element-navigation/index.ts b/packages/client/src/features/accessibility/element-navigation/index.ts deleted file mode 100644 index 2f30cda..0000000 --- a/packages/client/src/features/accessibility/element-navigation/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './diagram-navigation-tool'; -export * from './element-navigation-module'; -export * from './element-navigator'; -export * from './left-right-top-bottom-navigator'; -export * from './local-element-navigator'; -export * from './position-navigator'; diff --git a/packages/client/src/features/accessibility/focus-tracker/index.ts b/packages/client/src/features/accessibility/focus-tracker/index.ts deleted file mode 100644 index aefa9ea..0000000 --- a/packages/client/src/features/accessibility/focus-tracker/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './focus-tracker-module'; -export * from './focus-tracker-tool'; diff --git a/packages/client/src/features/accessibility/index.ts b/packages/client/src/features/accessibility/index.ts deleted file mode 100644 index 8135cf7..0000000 --- a/packages/client/src/features/accessibility/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './accessibility-module'; -export * from './actions'; -export * from './edge-autocomplete'; -export * from './element-navigation'; -export * from './focus-tracker'; -export * from './global-keylistener-tool'; -export * from './key-shortcut'; -export * from './keyboard-grid'; -export * from './keyboard-pointer'; -export * from './keyboard-tool-palette'; -export * from './move-zoom'; -export * from './resize-key-tool'; -export * from './search'; -export * from './toast'; -export * from './view-key-tools'; diff --git a/packages/client/src/features/accessibility/key-shortcut/index.ts b/packages/client/src/features/accessibility/key-shortcut/index.ts deleted file mode 100644 index 43df88f..0000000 --- a/packages/client/src/features/accessibility/key-shortcut/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './accessible-key-shortcut'; -export * from './accessible-key-shortcut-tool'; -export * from './di.config'; diff --git a/packages/client/src/features/accessibility/keyboard-grid/index.ts b/packages/client/src/features/accessibility/keyboard-grid/index.ts deleted file mode 100644 index 7b6e618..0000000 --- a/packages/client/src/features/accessibility/keyboard-grid/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './action'; -export * from './constants'; -export * from './keyboard-grid'; -export * from './keyboard-grid-search-palette'; -export * from './keyboard-node-grid'; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/index.ts b/packages/client/src/features/accessibility/keyboard-pointer/index.ts deleted file mode 100644 index d6f812e..0000000 --- a/packages/client/src/features/accessibility/keyboard-pointer/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './actions'; -export * from './constants'; -export * from './keyboard-pointer'; -export * from './keyboard-pointer-listener'; -export * from './keyboard-pointer-module'; -export * from './keyboard-pointer-position'; diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts deleted file mode 100644 index b8b1621..0000000 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './keyboard-tool-palette'; -export * from './keyboard-tool-palette-module'; diff --git a/packages/client/src/features/accessibility/move-zoom/index.ts b/packages/client/src/features/accessibility/move-zoom/index.ts deleted file mode 100644 index 55c7171..0000000 --- a/packages/client/src/features/accessibility/move-zoom/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './move-handler'; -export * from './move-zoom-module'; -export * from './zoom-handler'; diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/accessibility/move-zoom/move-handler.ts index 0018f87..c0f34e3 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -35,9 +35,9 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; -import { FeedbackEmitter } from '../../../base'; import { EditorContextService } from '../../../base/editor-context-service'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; import { isValidMove } from '../../../utils/layout-utils'; import { outsideOfViewport } from '../../../utils/viewpoint-util'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/index.ts b/packages/client/src/features/accessibility/resize-key-tool/index.ts deleted file mode 100644 index 60481ad..0000000 --- a/packages/client/src/features/accessibility/resize-key-tool/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './resize-key-handler'; -export * from './resize-key-module'; -export * from './resize-key-tool'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts index d9e2251..6979803 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -31,12 +31,12 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; -import { FeedbackEmitter } from '../../../base'; import { EditorContextService } from '../../../base/editor-context-service'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { Resizable, SelectableBoundsAware, getElements, isSelectableAndBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; import { isValidMove, isValidSize, minHeight, minWidth } from '../../../utils/layout-utils'; -import { Grid } from '../../grid'; +import { Grid } from '../../grid/grid'; export enum ResizeType { Increase, diff --git a/packages/client/src/features/accessibility/search/index.ts b/packages/client/src/features/accessibility/search/index.ts deleted file mode 100644 index 30b4bf1..0000000 --- a/packages/client/src/features/accessibility/search/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './search-palette'; -export * from './search-palette-module'; -export * from './search-tool'; diff --git a/packages/client/src/features/accessibility/toast/index.ts b/packages/client/src/features/accessibility/toast/index.ts deleted file mode 100644 index a15b255..0000000 --- a/packages/client/src/features/accessibility/toast/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './toast-handler'; -export * from './toast-module'; -export * from './toast-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/index.ts b/packages/client/src/features/accessibility/view-key-tools/index.ts deleted file mode 100644 index 9333700..0000000 --- a/packages/client/src/features/accessibility/view-key-tools/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './deselect-key-tool'; -export * from './movement-key-tool'; -export * from './view-key-tools-module'; -export * from './zoom-key-tool'; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 7e41ba3..1eebf5b 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,8 +20,8 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; -import { Grid } from '../../grid'; -import { ChangeBoundsManager } from '../../tools'; +import { Grid } from '../../grid/grid'; +import { ChangeBoundsManager } from '../../tools/change-bounds/change-bounds-manager'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; diff --git a/packages/client/src/features/bounds/index.ts b/packages/client/src/features/bounds/index.ts deleted file mode 100644 index 4bd0493..0000000 --- a/packages/client/src/features/bounds/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './bounds-module'; -export * from './freeform-layout'; -export * from './glsp-hidden-bounds-updater'; -export * from './hbox-layout'; -export * from './layout-data'; -export * from './layouter'; -export * from './local-bounds'; -export * from './set-bounds-feedback-command'; -export * from './vbox-layout'; diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index 604a84d..99d992b 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -24,9 +24,9 @@ import { isLayoutContainer } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Ranked } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { Ranked } from '../../base/ranked'; import { LocalRequestBoundsAction } from './local-bounds'; export interface SetBoundsFeedbackAction extends Omit { diff --git a/packages/client/src/features/change-bounds/index.ts b/packages/client/src/features/change-bounds/index.ts deleted file mode 100644 index dc4fc97..0000000 --- a/packages/client/src/features/change-bounds/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './model'; -export * from './movement-restrictor'; -export * from './point-position-updater'; -export * from './position-snapper'; -export * from './snap'; -export * from './tracker'; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 9ecf1f5..daac5e7 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,8 +26,8 @@ import { isMoveable, isSelectable } from '@eclipse-glsp/sprotty'; -import { CursorCSS } from '../../base'; -import { BoundsAwareModelElement, MoveableElement, ResizableModelElement } from '../../utils'; +import { CursorCSS } from '../../base/feedback/css-feedback'; +import { BoundsAwareModelElement, MoveableElement, ResizableModelElement } from '../../utils/gmodel-util'; export const resizeFeature = Symbol('resizeFeature'); diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 54b8db9..1080b44 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { Bounds, Dimension, GModelElement, GNode, GParentElement, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; -import { BoundsAwareModelElement } from '../../utils'; +import { BoundsAwareModelElement } from '../../utils/gmodel-util'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { SResizeHandle } from './model'; diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index 743e4da..b6e92bf 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -18,7 +18,7 @@ import { GModelElement } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; -import { GridSnapper } from '../grid'; +import { GridSnapper } from './../grid/grid-snapper'; import { PointPositionUpdater } from './point-position-updater'; describe('PointPositionUpdater', () => { diff --git a/packages/client/src/features/command-palette/index.ts b/packages/client/src/features/command-palette/index.ts deleted file mode 100644 index 312a6c5..0000000 --- a/packages/client/src/features/command-palette/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './command-palette'; -export * from './command-palette-module'; -export * from './command-palette-tool'; -export * from './server-command-palette-provider'; diff --git a/packages/client/src/features/context-menu/index.ts b/packages/client/src/features/context-menu/index.ts deleted file mode 100644 index 5cf038b..0000000 --- a/packages/client/src/features/context-menu/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './context-menu-module'; -export * from './delete-element-context-menu'; -export * from './glsp-context-menu-mouse-listener'; -export * from './server-context-menu-provider'; diff --git a/packages/client/src/features/copy-paste/index.ts b/packages/client/src/features/copy-paste/index.ts deleted file mode 100644 index e533d25..0000000 --- a/packages/client/src/features/copy-paste/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './copy-paste-context-menu'; -export * from './copy-paste-handler'; -export * from './copy-paste-modules'; -export * from './copy-paste-standalone'; diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx index 58a2b45..df073f2 100644 --- a/packages/client/src/features/debug/debug-bounds-decorator.tsx +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -19,7 +19,7 @@ import { Bounds, GModelElement, IVNodePostprocessor, Point, isDecoration, isSize import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GGraph } from '../../model'; -import { BoundsAwareModelElement } from '../../utils'; +import { BoundsAwareModelElement } from '../../utils/gmodel-util'; import { DebugManager } from './debug-manager'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/debug/debug-manager.ts b/packages/client/src/features/debug/debug-manager.ts index ccd4775..f0b5533 100644 --- a/packages/client/src/features/debug/debug-manager.ts +++ b/packages/client/src/features/debug/debug-manager.ts @@ -16,7 +16,8 @@ import { IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct } from 'inversify'; -import { FeedbackEmitter, IFeedbackActionDispatcher } from '../../base'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { EnableDebugModeAction } from './debug-model'; @injectable() diff --git a/packages/client/src/features/debug/debug-model.ts b/packages/client/src/features/debug/debug-model.ts index cea8313..5835e24 100644 --- a/packages/client/src/features/debug/debug-model.ts +++ b/packages/client/src/features/debug/debug-model.ts @@ -16,8 +16,8 @@ import { Action, CommandExecutionContext, CommandReturn, GModelRoot, TYPES, hasBooleanProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { FeedbackCommand } from '../../base'; -import { addCssClasses, removeCssClasses } from '../../utils'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { addCssClasses, removeCssClasses } from '../../utils/gmodel-util'; export interface EnableDebugModeAction extends Action { kind: typeof EnableDebugModeAction.KIND; diff --git a/packages/client/src/features/debug/index.ts b/packages/client/src/features/debug/index.ts deleted file mode 100644 index f63e8a6..0000000 --- a/packages/client/src/features/debug/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 Axon Ivy AG and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './debug-bounds-decorator'; -export * from './debug-manager'; -export * from './debug-model'; -export * from './debug-module'; diff --git a/packages/client/src/features/decoration/index.ts b/packages/client/src/features/decoration/index.ts deleted file mode 100644 index d994863..0000000 --- a/packages/client/src/features/decoration/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './decoration-module'; -export * from './decoration-placer'; diff --git a/packages/client/src/features/element-template/index.ts b/packages/client/src/features/element-template/index.ts deleted file mode 100644 index 841060f..0000000 --- a/packages/client/src/features/element-template/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './add-template-element'; -export * from './element-template-module'; -export * from './mouse-tracking-element-position-listener'; -export * from './remove-template-element'; diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index a4809ed..4dcc937 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,8 @@ import { Action, Dimension, GModelElement, MoveAction, Point, isBoundsAware, isM import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; -import { MoveableElement, getAbsolutePosition } from '../../utils'; +import { MoveableElement } from '../../utils/gmodel-util'; +import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackAwareTool } from '../tools/base-tools'; import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; import { MoveFinishedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; diff --git a/packages/client/src/features/export/index.ts b/packages/client/src/features/export/index.ts deleted file mode 100644 index 378fddb..0000000 --- a/packages/client/src/features/export/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './export-modules'; -export * from './export-svg-action-handler'; -export * from './glsp-svg-exporter'; diff --git a/packages/client/src/features/grid/grid-manager.ts b/packages/client/src/features/grid/grid-manager.ts index 29f2e9e..eb48d0b 100644 --- a/packages/client/src/features/grid/grid-manager.ts +++ b/packages/client/src/features/grid/grid-manager.ts @@ -16,7 +16,8 @@ import { IActionHandler, PropertiesOfType, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct } from 'inversify'; -import { FeedbackEmitter, IFeedbackActionDispatcher } from '../../base'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { Grid } from './grid'; import { ShowGridAction } from './grid-model'; diff --git a/packages/client/src/features/grid/grid-model.ts b/packages/client/src/features/grid/grid-model.ts index 8be2ff0..a9459db 100644 --- a/packages/client/src/features/grid/grid-model.ts +++ b/packages/client/src/features/grid/grid-model.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { Action, CommandExecutionContext, CommandReturn, GModelRoot, TYPES, hasBooleanProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { FeedbackCommand } from '../../base'; -import { addCssClasses, removeCssClasses } from '../../utils'; +import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { addCssClasses, removeCssClasses } from '../../utils/gmodel-util'; export interface ShowGridAction extends Action { kind: typeof ShowGridCommand.KIND; diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index 75bfa4b..becda00 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Axon Ivy AG and others. + * Copyright (c) 2023-2024 Axon Ivy AG and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/index.ts b/packages/client/src/features/grid/index.ts deleted file mode 100644 index 16699e0..0000000 --- a/packages/client/src/features/grid/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 Axon Ivy AG and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './grid'; -export * from './grid-manager'; -export * from './grid-model'; -export * from './grid-module'; -export * from './grid-snapper'; diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index 4ff9437..403eba9 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -46,7 +46,7 @@ import { isVisibleOnCanvas } from '../../utils/gmodel-util'; import { getViewportBounds, toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { feedbackEdgeEndId, feedbackEdgeId } from '../tools'; +import { feedbackEdgeEndId, feedbackEdgeId } from '../tools/edge-creation/dangling-edge-feedback'; import { HelperLine, HelperLineType, SelectionBounds, isHelperLine, isSelectionBounds } from './model'; export type ViewportLineType = typeof HelperLineType.Center | typeof HelperLineType.Middle | string; diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index cce6d6c..1fa093c 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -26,12 +26,12 @@ import { Writable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; -import { FeedbackEmitter } from '../../base'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; import { ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; -import { Grid } from '../grid'; +import { Grid } from '../grid/grid'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { AlignmentElementFilter, diff --git a/packages/client/src/features/helper-lines/index.ts b/packages/client/src/features/helper-lines/index.ts deleted file mode 100644 index 18e60b0..0000000 --- a/packages/client/src/features/helper-lines/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './helper-line-feedback'; -export * from './helper-line-manager'; -export * from './helper-line-manager-default'; -export * from './helper-line-module'; -export * from './model'; -export * from './view'; diff --git a/packages/client/src/features/hints/index.ts b/packages/client/src/features/hints/index.ts deleted file mode 100644 index 2effe7c..0000000 --- a/packages/client/src/features/hints/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './model'; -export * from './type-hint-provider'; -export * from './type-hints-module'; diff --git a/packages/client/src/features/hints/model.ts b/packages/client/src/features/hints/model.ts index b1433ab..75c1b45 100644 --- a/packages/client/src/features/hints/model.ts +++ b/packages/client/src/features/hints/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index 345d089..330df15 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -35,7 +35,6 @@ import { import { expect } from 'chai'; import { Container } from 'inversify'; import * as sinon from 'sinon'; -import { FeedbackEmitter } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher-default'; import { GEdge } from '../../model'; @@ -43,6 +42,7 @@ import { isResizable } from '../change-bounds/model'; import { isReconnectable } from '../reconnect/model'; import { Containable, isContainable, isReparentable } from './model'; import { ApplyTypeHintsAction, ApplyTypeHintsCommand, ITypeHintProvider, TypeHintProvider } from './type-hint-provider'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; describe('TypeHintProvider', () => { const container = new Container(); container.bind(GLSPActionDispatcher).toConstantValue(sinon.createStubInstance(GLSPActionDispatcher)); diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 51f46e8..6b7bfd8 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -37,10 +37,10 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct } from 'inversify'; -import { FeedbackEmitter } from '../../base'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { GEdge } from '../../model'; import { getElementTypeId } from '../../utils/gmodel-util'; diff --git a/packages/client/src/features/hover/index.ts b/packages/client/src/features/hover/index.ts deleted file mode 100644 index 869f1f1..0000000 --- a/packages/client/src/features/hover/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './hover'; -export * from './hover-module'; diff --git a/packages/client/src/features/index.ts b/packages/client/src/features/index.ts deleted file mode 100644 index 8f8fad3..0000000 --- a/packages/client/src/features/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './accessibility'; -export * from './bounds'; -export * from './change-bounds'; -export * from './command-palette'; -export * from './context-menu'; -export * from './copy-paste'; -export * from './debug'; -export * from './decoration'; -export * from './element-template'; -export * from './export'; -export * from './grid'; -export * from './helper-lines'; -export * from './hints'; -export * from './hover'; -export * from './label-edit'; -export * from './label-edit-ui'; -export * from './layout'; -export * from './navigation'; -export * from './reconnect'; -export * from './routing'; -export * from './save'; -export * from './select'; -export * from './source-model-watcher'; -export * from './status'; -export * from './svg-metadata'; -export * from './tool-palette'; -export * from './tools'; -export * from './undo-redo'; -export * from './validation'; -export * from './viewport'; diff --git a/packages/client/src/features/label-edit-ui/index.ts b/packages/client/src/features/label-edit-ui/index.ts deleted file mode 100644 index 58af763..0000000 --- a/packages/client/src/features/label-edit-ui/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './label-edit-ui'; -export * from './label-edit-ui-module'; diff --git a/packages/client/src/features/label-edit/index.ts b/packages/client/src/features/label-edit/index.ts deleted file mode 100644 index 340592d..0000000 --- a/packages/client/src/features/label-edit/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './edit-label-tool'; -export * from './edit-label-validator'; -export * from './label-edit-module'; diff --git a/packages/client/src/features/layout/index.ts b/packages/client/src/features/layout/index.ts deleted file mode 100644 index 03966eb..0000000 --- a/packages/client/src/features/layout/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './layout-elements-action'; -export * from './layout-module'; diff --git a/packages/client/src/features/navigation/index.ts b/packages/client/src/features/navigation/index.ts deleted file mode 100644 index 93c96cc..0000000 --- a/packages/client/src/features/navigation/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './navigation-action-handler'; -export * from './navigation-module'; -export * from './navigation-target-resolver'; diff --git a/packages/client/src/features/reconnect/index.ts b/packages/client/src/features/reconnect/index.ts deleted file mode 100644 index 0886faa..0000000 --- a/packages/client/src/features/reconnect/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './model'; diff --git a/packages/client/src/features/routing/index.ts b/packages/client/src/features/routing/index.ts deleted file mode 100644 index 87ced36..0000000 --- a/packages/client/src/features/routing/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './edge-router'; -export * from './routing-module'; diff --git a/packages/client/src/features/save/index.ts b/packages/client/src/features/save/index.ts deleted file mode 100644 index d9c79be..0000000 --- a/packages/client/src/features/save/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './save-keylistener'; -export * from './save-module'; diff --git a/packages/client/src/features/select/index.ts b/packages/client/src/features/select/index.ts deleted file mode 100644 index 0030448..0000000 --- a/packages/client/src/features/select/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './select-feedback-command'; -export * from './select-module'; -export * from './select-mouse-listener'; diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 19b25d0..47f2a52 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,9 +16,9 @@ import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { Ranked } from '../../base/ranked'; -import { SelectableElement } from '../../utils'; -import { SResizeHandle } from '../change-bounds'; -import { ChangeBoundsManager } from '../tools'; +import { SelectableElement } from '../../utils/gmodel-util'; +import { SResizeHandle } from '../change-bounds/model'; +import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. diff --git a/packages/client/src/features/source-model-watcher/index.ts b/packages/client/src/features/source-model-watcher/index.ts deleted file mode 100644 index caa3abd..0000000 --- a/packages/client/src/features/source-model-watcher/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './source-model-changed-action-handler'; -export * from './source-model-watcher-module'; diff --git a/packages/client/src/features/status/index.ts b/packages/client/src/features/status/index.ts deleted file mode 100644 index 51e2efe..0000000 --- a/packages/client/src/features/status/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './status-module'; -export * from './status-overlay'; diff --git a/packages/client/src/features/svg-metadata/index.ts b/packages/client/src/features/svg-metadata/index.ts deleted file mode 100644 index db153a7..0000000 --- a/packages/client/src/features/svg-metadata/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './metadata-placer'; -export * from './svg-metadata-module'; diff --git a/packages/client/src/features/tool-palette/index.ts b/packages/client/src/features/tool-palette/index.ts deleted file mode 100644 index de20986..0000000 --- a/packages/client/src/features/tool-palette/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './tool-palette'; -export * from './tool-palette-module'; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 9fd3060..2202c93 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -37,11 +37,11 @@ import { EditorContextService, IEditModeListener } from '../../base/editor-conte import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; -import { DebugManager } from '../debug'; -import { GridManager } from '../grid'; +import { DebugManager } from '../debug/debug-manager'; +import { GridManager } from '../grid/grid-manager'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; -import { OriginViewportAction } from '../viewport'; +import { OriginViewportAction } from '../viewport/origin-viewport'; const CLICKED_CSS_CLASS = 'clicked'; const SEARCH_ICON_ID = 'search'; diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 02359e1..c062bd5 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts index 699d00e..4747041 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts @@ -28,26 +28,26 @@ import { isLocateable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; + import { CursorCSS, - FeedbackEmitter, ModifyCSSFeedbackAction, applyCssClasses, cursorFeedbackAction, deleteCssClasses, toggleCssClasses -} from '../../../base'; -import { isValidMove, minDimensions } from '../../../utils'; +} from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; +import { isValidMove, minDimensions } from '../../../utils/layout-utils'; import { LayoutAware } from '../../bounds/layout-data'; +import { ResizeHandleLocation, SResizeHandle } from '../../change-bounds/model'; import { IMovementRestrictor, - ResizeHandleLocation, - SResizeHandle, movementRestrictionFeedback, removeMovementRestrictionFeedback -} from '../../change-bounds'; -import { GridManager } from '../../grid'; -import { IHelperLineManager } from '../../helper-lines'; +} from '../../change-bounds/movement-restrictor'; +import { GridManager } from '../../grid/grid-manager'; +import { IHelperLineManager } from '../../helper-lines/helper-line-manager'; import { InsertIndicator } from '../node-creation/insert-indicator'; import { ChangeBoundsTracker, TrackedElementMove, TrackedElementResize, TrackedMove, TrackedResize } from './change-bounds-tracker'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index 34e34c4..e20655c 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index b86ecf0..e2669b6 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,14 +16,20 @@ import { Action, ElementMove, GModelElement, GModelRoot, MoveAction, Point, findParentByFeature } from '@eclipse-glsp/sprotty'; import { DebouncedFunc, debounce } from 'lodash'; -import { ChangeBoundsTracker, TrackedMove } from '.'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; -import { MoveableElement, filter, getElements, isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils'; +import { + MoveableElement, + filter, + getElements, + isNonRoutableSelectedMovableBoundsAware, + removeDescendants +} from '../../../utils/gmodel-util'; import { SResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { MoveFinishedEventAction, MoveInitializedEventAction } from './change-bounds-tool-feedback'; +import { ChangeBoundsTracker, TrackedMove } from './change-bounds-tracker'; /** * This mouse listener provides visual feedback for moving by sending client-side diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 37721e6..3979845 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -49,7 +49,8 @@ import { isNonRoutableSelectedMovableBoundsAware, toElementAndBounds } from '../../../utils/gmodel-util'; -import { LocalRequestBoundsAction, SetBoundsFeedbackAction } from '../../bounds'; +import { LocalRequestBoundsAction } from '../../bounds/local-bounds'; +import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-command'; import { SResizeHandle, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { BaseEditTool } from '../base-tools'; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts index 1121d7b..5f77dd5 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts @@ -31,8 +31,9 @@ import { isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; -import { BoundsAwareModelElement, MoveableElement, ResizableModelElement, getElements } from '../../../utils'; -import { DiagramMovementCalculator, ResizeHandleLocation, SResizeHandle } from '../../change-bounds'; +import { BoundsAwareModelElement, MoveableElement, ResizableModelElement, getElements } from '../../../utils/gmodel-util'; +import { ResizeHandleLocation, SResizeHandle } from '../../change-bounds/model'; +import { DiagramMovementCalculator } from '../../change-bounds/tracker'; import { ChangeBoundsManager } from './change-bounds-manager'; export interface ElementTrackingOptions { diff --git a/packages/client/src/features/tools/change-bounds/index.ts b/packages/client/src/features/tools/change-bounds/index.ts deleted file mode 100644 index be79d32..0000000 --- a/packages/client/src/features/tools/change-bounds/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './change-bounds-manager'; -export * from './change-bounds-tool'; -export * from './change-bounds-tool-feedback'; -export * from './change-bounds-tool-module'; -export * from './change-bounds-tool-move-feedback'; -export * from './change-bounds-tracker'; -export * from './view'; diff --git a/packages/client/src/features/tools/deletion/index.ts b/packages/client/src/features/tools/deletion/index.ts deleted file mode 100644 index f33c825..0000000 --- a/packages/client/src/features/tools/deletion/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './delete-tool'; -export * from './deletion-tool-module'; diff --git a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts index 0a909bf..348fc60 100644 --- a/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/dangling-edge-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -32,7 +32,7 @@ import { isConnectable } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { feedbackFeature } from '../../../base'; +import { feedbackFeature } from '../../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { isRoutable } from '../../../utils/gmodel-util'; import { toAbsolutePosition } from '../../../utils/viewpoint-util'; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts index 0a57ed8..fbbb336 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool-feedback.ts @@ -29,8 +29,8 @@ import { isBoundsAware, isConnectable } from '@eclipse-glsp/sprotty'; -import { FeedbackEmitter } from '../../../base'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { absoluteToParent, getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { FeedbackEdgeEnd, feedbackEdgeEndId } from './dangling-edge-feedback'; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 3f8f376..702872c 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,13 +26,13 @@ import { isCtrlOrCmd } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { FeedbackEmitter } from '../../../base'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; -import { Grid } from '../../grid'; +import { Grid } from '../../grid/grid'; import { ITypeHintProvider } from '../../hints/type-hint-provider'; import { BaseCreationTool } from '../base-tools'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction } from './dangling-edge-feedback'; diff --git a/packages/client/src/features/tools/edge-creation/index.ts b/packages/client/src/features/tools/edge-creation/index.ts deleted file mode 100644 index 21b73ae..0000000 --- a/packages/client/src/features/tools/edge-creation/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './dangling-edge-feedback'; -export * from './edege-creation-module'; -export * from './edge-creation-tool'; -export * from './edge-creation-tool-feedback'; -export * from './view'; diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index e57992b..6d3cc9f 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -41,13 +41,14 @@ import { typeGuard } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { FeedbackEmitter } from '../../../base'; import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../../base/feedback/feedback-command'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { forEachElement, getMatchingElements, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; -import { ChangeBoundsManager, ChangeBoundsTracker, MoveableRoutingHandle } from '../change-bounds'; +import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { ChangeBoundsTracker, MoveableRoutingHandle } from '../change-bounds/change-bounds-tracker'; import { FeedbackEdgeEnd, feedbackEdgeEndId, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from '../edge-creation/edge-creation-tool-feedback'; diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index d94855b..292a6b3 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,14 +30,14 @@ import { isSelected } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { GReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; import { BaseEditTool } from '../base-tools'; -import { ChangeBoundsManager } from '../change-bounds'; +import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { DrawFeedbackEdgeSourceAction, diff --git a/packages/client/src/features/tools/edge-edit/index.ts b/packages/client/src/features/tools/edge-edit/index.ts deleted file mode 100644 index 5e69905..0000000 --- a/packages/client/src/features/tools/edge-edit/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './edge-edit-module'; -export * from './edge-edit-tool'; -export * from './edge-edit-tool-feedback'; diff --git a/packages/client/src/features/tools/index.ts b/packages/client/src/features/tools/index.ts deleted file mode 100644 index 9b34389..0000000 --- a/packages/client/src/features/tools/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './base-tools'; -export * from './change-bounds'; -export * from './deletion'; -export * from './edge-creation'; -export * from './edge-edit'; -export * from './marquee-selection'; -export * from './node-creation'; -export * from './tool-focus-loss-module'; diff --git a/packages/client/src/features/tools/marquee-selection/index.ts b/packages/client/src/features/tools/marquee-selection/index.ts deleted file mode 100644 index 548217a..0000000 --- a/packages/client/src/features/tools/marquee-selection/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './marquee-behavior'; -export * from './marquee-mouse-tool'; -export * from './marquee-selection-module'; -export * from './marquee-tool'; -export * from './marquee-tool-feedback'; -export * from './model'; -export * from './view'; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 45edec3..7794919 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,7 +30,7 @@ import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener' import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; -import { BoundsAwareModelElement, SelectableBoundsAware, isSelectableAndBoundsAware } from '../../../utils'; +import { BoundsAwareModelElement, SelectableBoundsAware, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { BaseEditTool } from '../base-tools'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index 191eb6f..7f2345b 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -16,10 +16,10 @@ import { GModelElement, Point, findChildrenAtPosition } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { CSS_GHOST_ELEMENT, CursorCSS, FeedbackEmitter, cursorFeedbackAction } from '../../../base'; -import { ContainerElement, isContainable } from '../../hints'; -import { ChangeBoundsManager } from '../change-bounds'; - +import { CSS_GHOST_ELEMENT, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; +import { ContainerElement, isContainable } from '../../hints/model'; +import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; export interface TrackedInsert { elementTypeId: string; location: Point; diff --git a/packages/client/src/features/tools/node-creation/index.ts b/packages/client/src/features/tools/node-creation/index.ts deleted file mode 100644 index 39635ca..0000000 --- a/packages/client/src/features/tools/node-creation/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './container-manager'; -export * from './insert-indicator'; -export * from './node-creation-module'; -export * from './node-creation-tool'; -export * from './node-creation-views'; diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index b6eef8e..6d17ad5 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 1673cf6..fad6c52 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,16 +29,17 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import '../../../../css/ghost-element.css'; -import { FeedbackEmitter } from '../../../base'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; +import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { MoveableElement } from '../../../utils/gmodel-util'; import { AddTemplateElementsAction, getTemplateElementId } from '../../element-template/add-template-element'; import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; import { RemoveTemplateElementsAction } from '../../element-template/remove-template-element'; import { BaseCreationTool } from '../base-tools'; -import { ChangeBoundsManager, TrackedMove } from '../change-bounds'; +import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { TrackedMove } from '../change-bounds/change-bounds-tracker'; import { ContainerManager, TrackedInsert } from './container-manager'; import { InsertIndicator } from './insert-indicator'; diff --git a/packages/client/src/features/tools/node-creation/node-creation-views.tsx b/packages/client/src/features/tools/node-creation/node-creation-views.tsx index 3698c62..9a51445 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-views.tsx +++ b/packages/client/src/features/tools/node-creation/node-creation-views.tsx @@ -17,8 +17,8 @@ import { IView, RenderingContext, TYPES, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; -import { GArgument } from '../../../utils'; -import { Grid } from '../../grid'; +import { GArgument } from '../../../utils/argument-utils'; +import { Grid } from '../../grid/grid'; import { ARG_LENGTH, InsertIndicator } from './insert-indicator'; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/client/src/features/undo-redo/index.ts b/packages/client/src/features/undo-redo/index.ts deleted file mode 100644 index e63ab56..0000000 --- a/packages/client/src/features/undo-redo/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './undo-redo-key-listener'; -export * from './undo-redo-module'; diff --git a/packages/client/src/features/validation/index.ts b/packages/client/src/features/validation/index.ts deleted file mode 100644 index 39c13c2..0000000 --- a/packages/client/src/features/validation/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './issue-marker'; -export * from './marker-navigator'; -export * from './validate'; -export * from './validation-modules'; diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 0f9815b..6d00269 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -28,10 +28,10 @@ import { hasArrayProp } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { FeedbackEmitter } from '../../base'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { removeCssClasses } from '../../utils/gmodel-util'; import { GIssueMarker, createGIssue, getGIssueMarker, getOrCreateGIssueMarker, getSeverity } from './issue-marker'; diff --git a/packages/client/src/features/viewport/index.ts b/packages/client/src/features/viewport/index.ts deleted file mode 100644 index 0ccb5ef..0000000 --- a/packages/client/src/features/viewport/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './glsp-scroll-mouse-listener'; -export * from './origin-viewport'; -export * from './reposition'; -export * from './viewport-handler'; -export * from './viewport-modules'; diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 986dd55..21e5498 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { Action, DOMHelper, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EditorContextService } from '../../base'; +import { EditorContextService } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 35dcac6..7dca4ef 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,229 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './base'; +export * from './base/action-dispatcher'; +export * from './base/action-handler-registry'; +export * from './base/args-feature'; +export * from './base/auto-complete/auto-complete-actions'; +export * from './base/auto-complete/auto-complete-widget'; +export * from './base/auto-complete/autocomplete-suggestion-providers'; +export * from './base/auto-complete/base-autocomplete-palette'; +export * from './base/auto-complete/validation-decorator'; +export * from './base/command-stack'; +export * from './base/default.module'; +export * from './base/drag-aware-mouse-listener'; +export * from './base/editor-context-service'; +export * from './base/feedback/css-feedback'; +export * from './base/feedback/feedback-action-dispatcher'; +export * from './base/feedback/feedback-action-dispatcher-default'; +export * from './base/feedback/feedback-command'; +export * from './base/feedback/feedback-emitter'; +export * from './base/feedback/set-model-command'; +export * from './base/feedback/update-model-command'; +export * from './base/focus/focus-state-change-action'; +export * from './base/focus/focus-tracker'; +export * from './base/model/diagram-loader'; +export * from './base/model/glsp-model-source'; +export * from './base/model/model-initialization-constraint'; +export * from './base/model/model-registry'; +export * from './base/mouse-position-tracker'; +export * from './base/ranked'; +export * from './base/selection-clearing-mouse-listener'; +export * from './base/selection-service'; +export * from './base/tool-manager/tool'; +export * from './base/tool-manager/tool-manager'; +export * from './base/ui-extension/ui-extension'; +export * from './base/ui-extension/ui-extension-registry'; +export * from './base/view/key-tool'; +export * from './base/view/mouse-tool'; +export * from './base/view/view-registry'; export * from './default-modules'; -export * from './features'; +export * from './features/accessibility/accessibility-module'; +export * from './features/accessibility/actions'; +export * from './features/accessibility/edge-autocomplete/action'; +export * from './features/accessibility/edge-autocomplete/edge-autocomplete-context'; +export * from './features/accessibility/edge-autocomplete/edge-autocomplete-palette'; +export * from './features/accessibility/edge-autocomplete/edge-autocomplete-tool'; +export * from './features/accessibility/element-navigation/diagram-navigation-tool'; +export * from './features/accessibility/element-navigation/element-navigation-module'; +export * from './features/accessibility/element-navigation/element-navigator'; +export * from './features/accessibility/element-navigation/left-right-top-bottom-navigator'; +export * from './features/accessibility/element-navigation/local-element-navigator'; +export * from './features/accessibility/element-navigation/position-navigator'; +export * from './features/accessibility/focus-tracker/focus-tracker-module'; +export * from './features/accessibility/focus-tracker/focus-tracker-tool'; +export * from './features/accessibility/global-keylistener-tool'; +export * from './features/accessibility/key-shortcut/accessible-key-shortcut'; +export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; +export * from './features/accessibility/key-shortcut/di.config'; +export * from './features/accessibility/keyboard-grid/action'; +export * from './features/accessibility/keyboard-grid/constants'; +export * from './features/accessibility/keyboard-grid/keyboard-grid'; +export * from './features/accessibility/keyboard-grid/keyboard-grid-search-palette'; +export * from './features/accessibility/keyboard-grid/keyboard-node-grid'; +export * from './features/accessibility/keyboard-pointer/actions'; +export * from './features/accessibility/keyboard-pointer/constants'; +export * from './features/accessibility/keyboard-pointer/keyboard-pointer'; +export * from './features/accessibility/keyboard-pointer/keyboard-pointer-listener'; +export * from './features/accessibility/keyboard-pointer/keyboard-pointer-module'; +export * from './features/accessibility/keyboard-pointer/keyboard-pointer-position'; +export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette'; +export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module'; +export * from './features/accessibility/move-zoom/move-handler'; +export * from './features/accessibility/move-zoom/move-zoom-module'; +export * from './features/accessibility/move-zoom/zoom-handler'; +export * from './features/accessibility/resize-key-tool/resize-key-handler'; +export * from './features/accessibility/resize-key-tool/resize-key-module'; +export * from './features/accessibility/resize-key-tool/resize-key-tool'; +export * from './features/accessibility/search/search-palette'; +export * from './features/accessibility/search/search-palette-module'; +export * from './features/accessibility/search/search-tool'; +export * from './features/accessibility/toast/toast-handler'; +export * from './features/accessibility/toast/toast-module'; +export * from './features/accessibility/toast/toast-tool'; +export * from './features/accessibility/view-key-tools/deselect-key-tool'; +export * from './features/accessibility/view-key-tools/movement-key-tool'; +export * from './features/accessibility/view-key-tools/view-key-tools-module'; +export * from './features/accessibility/view-key-tools/zoom-key-tool'; +export * from './features/bounds/bounds-module'; +export * from './features/bounds/freeform-layout'; +export * from './features/bounds/glsp-hidden-bounds-updater'; +export * from './features/bounds/hbox-layout'; +export * from './features/bounds/layout-data'; +export * from './features/bounds/layouter'; +export * from './features/bounds/local-bounds'; +export * from './features/bounds/set-bounds-feedback-command'; +export * from './features/bounds/vbox-layout'; +export * from './features/change-bounds/model'; +export * from './features/change-bounds/movement-restrictor'; +export * from './features/change-bounds/point-position-updater'; +export * from './features/change-bounds/position-snapper'; +export * from './features/change-bounds/snap'; +export * from './features/change-bounds/tracker'; +export * from './features/command-palette/command-palette'; +export * from './features/command-palette/command-palette-module'; +export * from './features/command-palette/command-palette-tool'; +export * from './features/command-palette/server-command-palette-provider'; +export * from './features/context-menu/context-menu-module'; +export * from './features/context-menu/delete-element-context-menu'; +export * from './features/context-menu/glsp-context-menu-mouse-listener'; +export * from './features/context-menu/server-context-menu-provider'; +export * from './features/copy-paste/copy-paste-context-menu'; +export * from './features/copy-paste/copy-paste-handler'; +export * from './features/copy-paste/copy-paste-modules'; +export * from './features/copy-paste/copy-paste-standalone'; +export * from './features/debug/debug-bounds-decorator'; +export * from './features/debug/debug-manager'; +export * from './features/debug/debug-model'; +export * from './features/debug/debug-module'; +export * from './features/decoration/decoration-module'; +export * from './features/decoration/decoration-placer'; +export * from './features/element-template/add-template-element'; +export * from './features/element-template/element-template-module'; +export * from './features/element-template/mouse-tracking-element-position-listener'; +export * from './features/element-template/remove-template-element'; +export * from './features/export/export-modules'; +export * from './features/export/export-svg-action-handler'; +export * from './features/export/glsp-svg-exporter'; +export * from './features/grid/grid'; +export * from './features/grid/grid-manager'; +export * from './features/grid/grid-model'; +export * from './features/grid/grid-module'; +export * from './features/grid/grid-snapper'; +export * from './features/helper-lines/helper-line-feedback'; +export * from './features/helper-lines/helper-line-manager'; +export * from './features/helper-lines/helper-line-manager-default'; +export * from './features/helper-lines/helper-line-module'; +export * from './features/helper-lines/model'; +export * from './features/helper-lines/view'; +export * from './features/hints/model'; +export * from './features/hints/type-hint-provider'; +export * from './features/hints/type-hints-module'; +export * from './features/hover/hover'; +export * from './features/hover/hover-module'; +export * from './features/label-edit-ui/label-edit-ui'; +export * from './features/label-edit-ui/label-edit-ui-module'; +export * from './features/label-edit/edit-label-tool'; +export * from './features/label-edit/edit-label-validator'; +export * from './features/label-edit/label-edit-module'; +export * from './features/layout/layout-elements-action'; +export * from './features/layout/layout-module'; +export * from './features/navigation/navigation-action-handler'; +export * from './features/navigation/navigation-module'; +export * from './features/navigation/navigation-target-resolver'; +export * from './features/reconnect/model'; +export * from './features/routing/edge-router'; +export * from './features/routing/routing-module'; +export * from './features/save/save-keylistener'; +export * from './features/save/save-module'; +export * from './features/select/select-feedback-command'; +export * from './features/select/select-module'; +export * from './features/select/select-mouse-listener'; +export * from './features/source-model-watcher/source-model-changed-action-handler'; +export * from './features/source-model-watcher/source-model-watcher-module'; +export * from './features/status/status-module'; +export * from './features/status/status-overlay'; +export * from './features/svg-metadata/metadata-placer'; +export * from './features/svg-metadata/svg-metadata-module'; +export * from './features/tool-palette/tool-palette'; +export * from './features/tool-palette/tool-palette-module'; +export * from './features/tools/base-tools'; +export * from './features/tools/change-bounds/change-bounds-manager'; +export * from './features/tools/change-bounds/change-bounds-tool'; +export * from './features/tools/change-bounds/change-bounds-tool-feedback'; +export * from './features/tools/change-bounds/change-bounds-tool-module'; +export * from './features/tools/change-bounds/change-bounds-tool-move-feedback'; +export * from './features/tools/change-bounds/change-bounds-tracker'; +export * from './features/tools/change-bounds/view'; +export * from './features/tools/deletion/delete-tool'; +export * from './features/tools/deletion/deletion-tool-module'; +export * from './features/tools/edge-creation/dangling-edge-feedback'; +export * from './features/tools/edge-creation/edege-creation-module'; +export * from './features/tools/edge-creation/edge-creation-tool'; +export * from './features/tools/edge-creation/edge-creation-tool-feedback'; +export * from './features/tools/edge-creation/view'; +export * from './features/tools/edge-edit/edge-edit-module'; +export * from './features/tools/edge-edit/edge-edit-tool'; +export * from './features/tools/edge-edit/edge-edit-tool-feedback'; +export * from './features/tools/marquee-selection/marquee-behavior'; +export * from './features/tools/marquee-selection/marquee-mouse-tool'; +export * from './features/tools/marquee-selection/marquee-selection-module'; +export * from './features/tools/marquee-selection/marquee-tool'; +export * from './features/tools/marquee-selection/marquee-tool-feedback'; +export * from './features/tools/marquee-selection/model'; +export * from './features/tools/marquee-selection/view'; +export * from './features/tools/node-creation/container-manager'; +export * from './features/tools/node-creation/insert-indicator'; +export * from './features/tools/node-creation/node-creation-module'; +export * from './features/tools/node-creation/node-creation-tool'; +export * from './features/tools/node-creation/node-creation-views'; +export * from './features/tools/tool-focus-loss-module'; +export * from './features/undo-redo/undo-redo-key-listener'; +export * from './features/undo-redo/undo-redo-module'; +export * from './features/validation/issue-marker'; +export * from './features/validation/marker-navigator'; +export * from './features/validation/validate'; +export * from './features/validation/validation-modules'; +export * from './features/viewport/glsp-scroll-mouse-listener'; +export * from './features/viewport/origin-viewport'; +export * from './features/viewport/reposition'; +export * from './features/viewport/viewport-handler'; +export * from './features/viewport/viewport-modules'; export * from './model'; export * from './re-exports'; export * from './standalone-modules'; -export * from './utils'; -export * from './views'; +export * from './utils/argument-utils'; +export * from './utils/geometry-util'; +export * from './utils/gmodel-util'; +export * from './utils/html-utils'; +export * from './utils/layout-utils'; +export * from './utils/marker'; +export * from './utils/viewpoint-util'; +export * from './views/base-view-module'; +export * from './views/compartments'; +export * from './views/gedge-view'; +export * from './views/ggraph-view'; +export * from './views/glsp-projection-view'; +export * from './views/issue-marker-view'; +export * from './views/rounded-corner'; +export * from './views/rounded-corner-view'; diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index c8fa274..2261e5f 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -40,7 +40,7 @@ import { isSelected, remove } from '@eclipse-glsp/sprotty'; -import { ResizeHandleLocation } from '../features'; +import { ResizeHandleLocation } from '../features/change-bounds/model'; /** * Helper type to represent a filter predicate for {@link GModelElement}s. This is used to retrieve diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts deleted file mode 100644 index ffccc99..0000000 --- a/packages/client/src/utils/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './argument-utils'; -export * from './geometry-util'; -export * from './gmodel-util'; -export * from './html-utils'; -export * from './layout-utils'; -export * from './marker'; -export * from './viewpoint-util'; diff --git a/packages/client/src/views/ggraph-view.tsx b/packages/client/src/views/ggraph-view.tsx index db0f32d..a32b77c 100644 --- a/packages/client/src/views/ggraph-view.tsx +++ b/packages/client/src/views/ggraph-view.tsx @@ -16,7 +16,7 @@ import { Bounds, Dimension, Point, RenderingContext, SGraphImpl, SGraphView, Writable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; -import { GridManager, GridStyle } from '../features'; +import { GridManager, GridStyle } from '../features/grid/grid-manager'; @injectable() export class GGraphView extends SGraphView { diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 23f7ce6..d72cd03 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2023 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,7 +33,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode, VNodeStyle, h } from 'snabbdom'; -import { GridManager, GridStyle } from '../features'; +import { GridManager, GridStyle } from '../features/grid/grid-manager'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: html }; diff --git a/packages/client/src/views/index.ts b/packages/client/src/views/index.ts deleted file mode 100644 index 29e3d75..0000000 --- a/packages/client/src/views/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './base-view-module'; -export * from './compartments'; -export * from './gedge-view'; -export * from './ggraph-view'; -export * from './glsp-projection-view'; -export * from './issue-marker-view'; -export * from './rounded-corner'; -export * from './rounded-corner-view'; diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 1ae3902..c2733bd 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -28,7 +28,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo", - "generate:index": "glsp generateIndex src -f", + "generate:index": "glsp generateIndex src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "watch": "tsc -w" }, diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index b80f027..9fc7ab5 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/package.json b/packages/protocol/package.json index ddf9cb0..0c6660b 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -37,7 +37,7 @@ "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", - "generate:index": "glsp generateIndex src -f", + "generate:index": "glsp generateIndex src/di src -f -s", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/protocol/src/action-protocol/index.ts b/packages/protocol/src/action-protocol/index.ts deleted file mode 100644 index 4913dd1..0000000 --- a/packages/protocol/src/action-protocol/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './base-protocol'; -export * from './client-notification'; -export * from './clipboard'; -export * from './contexts'; -export * from './edge-modification'; -export * from './element-creation'; -export * from './element-hover'; -export * from './element-navigation'; -export * from './element-selection'; -export * from './element-text-editing'; -export * from './element-type-hints'; -export * from './element-validation'; -export * from './model-data'; -export * from './model-edit-mode'; -export * from './model-layout'; -export * from './model-saving'; -export * from './node-modification'; -export * from './tool-palette'; -export * from './types'; -export * from './undo-redo'; -export * from './viewport'; diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index 4bf1a07..dc3c229 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import * as sprotty from 'sprotty-protocol/lib/actions'; -import { GModelRootSchema } from '../model'; +import { GModelRootSchema } from '../model/model-schema'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; import { Args, ElementAndAlignment, ElementAndBounds, ElementAndLayoutData, ElementAndRoutingPoints } from './types'; diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 7b2f95c..177380d 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,11 +15,11 @@ ********************************************************************************/ import * as uuid from 'uuid'; -import { ActionMessage } from '../action-protocol'; +import { ActionMessage } from '../action-protocol/base-protocol'; import { Disposable } from '../utils/disposable'; import { Event } from '../utils/event'; -import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; import { AnyObject, hasStringProp } from '../utils/type-util'; +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export class ApplicationIdProvider { private static _applicationId?: string; diff --git a/packages/protocol/src/client-server-protocol/glsp-server.ts b/packages/protocol/src/client-server-protocol/glsp-server.ts index f670c87..8f2ac17 100644 --- a/packages/protocol/src/client-server-protocol/glsp-server.ts +++ b/packages/protocol/src/client-server-protocol/glsp-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from '../action-protocol'; +import { ActionMessage } from '../action-protocol/base-protocol'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; /** diff --git a/packages/protocol/src/client-server-protocol/index.ts b/packages/protocol/src/client-server-protocol/index.ts deleted file mode 100644 index dd8fa48..0000000 --- a/packages/protocol/src/client-server-protocol/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './base-glsp-client'; -export * from './glsp-client'; -export * from './glsp-server'; -export * from './jsonrpc'; -export * from './types'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 9cc1adc..be58166 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; -import { ActionMessage } from '../../action-protocol'; +import { ActionMessage } from '../../action-protocol/base-protocol'; import { Emitter, Event } from '../../utils/event'; import { ActionMessageHandler, ClientState, GLSPClient } from '../glsp-client'; import { GLSPClientProxy } from '../glsp-server'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/index.ts b/packages/protocol/src/client-server-protocol/jsonrpc/index.ts deleted file mode 100644 index 38dc79f..0000000 --- a/packages/protocol/src/client-server-protocol/jsonrpc/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './base-jsonrpc-glsp-client'; -export * from './glsp-jsonrpc-client'; -export * from './glsp-jsonrpc-server'; -export * from './websocket-connection'; -export * from './worker-connection-provider'; -export * from './ws-connection-provider'; diff --git a/packages/protocol/src/client-server-protocol/types.ts b/packages/protocol/src/client-server-protocol/types.ts index 862c089..adb26f8 100644 --- a/packages/protocol/src/client-server-protocol/types.ts +++ b/packages/protocol/src/client-server-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args } from '../action-protocol'; +import { Args } from '../action-protocol/types'; /** * A key-value pair structure to map a diagramType to its server-handled action kinds. diff --git a/packages/protocol/src/di/container-configuration.ts b/packages/protocol/src/di/container-configuration.ts index ed3ff45..230af51 100644 --- a/packages/protocol/src/di/container-configuration.ts +++ b/packages/protocol/src/di/container-configuration.ts @@ -15,7 +15,8 @@ ********************************************************************************/ import { Container, ContainerModule } from 'inversify'; -import { MaybeArray, asArray, distinctAdd, hasFunctionProp, hasNumberProp, remove } from '../utils'; +import { MaybeArray, asArray, distinctAdd, remove } from '../utils/array-util'; +import { hasFunctionProp, hasNumberProp } from '../utils/type-util'; /** * Initializes a container with the given {@link ContainerConfiguration}. The container configuration diff --git a/packages/protocol/src/di/feature-module.ts b/packages/protocol/src/di/feature-module.ts index 8139b08..233cb2f 100644 --- a/packages/protocol/src/di/feature-module.ts +++ b/packages/protocol/src/di/feature-module.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { ContainerModule, interfaces } from 'inversify'; -import { MaybeArray, asArray } from '../utils'; +import { MaybeArray, asArray } from '../utils/array-util'; import { BindingContext } from './inversify-util'; /** diff --git a/packages/protocol/src/di/lazy-injector.ts b/packages/protocol/src/di/lazy-injector.ts index 469d451..814d56e 100644 --- a/packages/protocol/src/di/lazy-injector.ts +++ b/packages/protocol/src/di/lazy-injector.ts @@ -32,7 +32,8 @@ import { getServiceIdentifierAsString, interfaces } from 'inversify'; import { NOT_REGISTERED } from 'inversify/lib/constants/error_msgs'; -import { AnyObject, MaybeArray } from '../utils'; +import { MaybeArray } from '../utils/array-util'; +import { AnyObject } from '../utils/type-util'; import { BindingContext } from './inversify-util'; /** diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index bab6cb5..e60fcf2 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,49 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './action-protocol'; -export * from './client-server-protocol'; -export * from './model'; +export * from './action-protocol/base-protocol'; +export * from './action-protocol/client-notification'; +export * from './action-protocol/clipboard'; +export * from './action-protocol/contexts'; +export * from './action-protocol/edge-modification'; +export * from './action-protocol/element-creation'; +export * from './action-protocol/element-hover'; +export * from './action-protocol/element-navigation'; +export * from './action-protocol/element-selection'; +export * from './action-protocol/element-text-editing'; +export * from './action-protocol/element-type-hints'; +export * from './action-protocol/element-validation'; +export * from './action-protocol/model-data'; +export * from './action-protocol/model-edit-mode'; +export * from './action-protocol/model-layout'; +export * from './action-protocol/model-saving'; +export * from './action-protocol/node-modification'; +export * from './action-protocol/tool-palette'; +export * from './action-protocol/types'; +export * from './action-protocol/undo-redo'; +export * from './action-protocol/viewport'; +export * from './client-server-protocol/base-glsp-client'; +export * from './client-server-protocol/glsp-client'; +export * from './client-server-protocol/glsp-server'; +export * from './client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; +export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-client'; +export * from './client-server-protocol/jsonrpc/glsp-jsonrpc-server'; +export * from './client-server-protocol/jsonrpc/websocket-connection'; +export * from './client-server-protocol/jsonrpc/worker-connection-provider'; +export * from './client-server-protocol/jsonrpc/ws-connection-provider'; +export * from './client-server-protocol/types'; +export * from './model/default-types'; +export * from './model/model-schema'; export * from './re-exports'; export * from './sprotty-actions'; export * from './sprotty-geometry-bounds'; export * from './sprotty-geometry-dimension'; export * from './sprotty-geometry-point'; -export * from './utils'; +export * from './utils/array-util'; +export * from './utils/disposable'; +export * from './utils/event'; +export * from './utils/geometry-movement'; +export * from './utils/geometry-util'; +export * from './utils/geometry-vector'; +export * from './utils/math-util'; +export * from './utils/type-util'; diff --git a/packages/protocol/src/model/index.ts b/packages/protocol/src/model/index.ts deleted file mode 100644 index d372746..0000000 --- a/packages/protocol/src/model/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './default-types'; -export * from './model-schema'; diff --git a/packages/protocol/src/sprotty-geometry-bounds.ts b/packages/protocol/src/sprotty-geometry-bounds.ts index 02d60b5..18d4c5b 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.ts @@ -18,7 +18,7 @@ import { Bounds } from 'sprotty-protocol/lib/utils/geometry'; import { Dimension } from './sprotty-geometry-dimension'; import { Point } from './sprotty-geometry-point'; -import { AnyObject, hasNumberProp } from './utils'; +import { AnyObject, hasNumberProp } from './utils/type-util'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Bounds { diff --git a/packages/protocol/src/sprotty-geometry-dimension.ts b/packages/protocol/src/sprotty-geometry-dimension.ts index 8b56bc4..c67e9c5 100644 --- a/packages/protocol/src/sprotty-geometry-dimension.ts +++ b/packages/protocol/src/sprotty-geometry-dimension.ts @@ -16,8 +16,8 @@ /* eslint-disable @typescript-eslint/no-shadow */ import { Dimension, Point } from 'sprotty-protocol/lib/utils/geometry'; -import { AnyObject, hasNumberProp } from './utils'; import { equalUpTo } from './utils/math-util'; +import { AnyObject, hasNumberProp } from './utils/type-util'; declare module 'sprotty-protocol/lib/utils/geometry' { namespace Dimension { diff --git a/packages/protocol/src/utils/index.ts b/packages/protocol/src/utils/index.ts deleted file mode 100644 index d5d3ba3..0000000 --- a/packages/protocol/src/utils/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -export * from './array-util'; -export * from './disposable'; -export * from './event'; -export * from './geometry-movement'; -export * from './geometry-util'; -export * from './geometry-vector'; -export * from './math-util'; -export * from './type-util'; From 4355e9a67033bdc0a463e679aacd3cf72d3e6ccb Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 27 May 2024 18:14:02 +0200 Subject: [PATCH 338/566] Ensure that align feature is exported from sprotty (#350) --- packages/glsp-sprotty/src/re-exports.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index e6f0ea2..c847bb8 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -105,6 +105,7 @@ export { LayoutContainer, LayoutableChild, ModelLayoutOptions, + alignFeature, boundsFeature, findChildrenAtPosition, getAbsoluteBounds, From 6c50438b62a92e5f2a6b98dcb7ce616e067b51a1 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 27 May 2024 16:14:56 +0000 Subject: [PATCH 339/566] Minor fix in ResizeHandleLocation.ALL (#351) --- examples/workflow-glsp/src/model.ts | 5 +---- packages/client/src/features/change-bounds/model.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index b6475f0..2518a46 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -24,7 +24,6 @@ import { Nameable, RectangularNode, ResizableModelElement, - ResizeHandleLocation, WithEditableLabel, boundsFeature, connectableFeature, @@ -90,9 +89,7 @@ export class ControlNode extends DiamondNode implements ResizableModelElement { override strokeWidth = 1; } -export class BranchingNode extends ControlNode { - resizeLocations = ResizeHandleLocation.CROSS; -} +export class BranchingNode extends ControlNode {} export class SynchronizationNode extends ControlNode {} diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index daac5e7..daeba5d 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -60,7 +60,7 @@ export namespace ResizeHandleLocation { ResizeHandleLocation.Bottom, ResizeHandleLocation.Left ]; - export const ALL = Object.values(ResizeHandleLocation).filter(value => typeof value !== 'function') as ResizeHandleLocation[]; + export const ALL: ResizeHandleLocation[] = [...ResizeHandleLocation.CORNERS, ...ResizeHandleLocation.CROSS]; export function opposite(location: ResizeHandleLocation): ResizeHandleLocation { switch (location) { From ce1d0b6887eccb8c1242d8d4ccd3db3bfa496acd Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 27 May 2024 20:57:11 +0000 Subject: [PATCH 340/566] Add Resizable with 'resizeLocations' to shape elements (#84) --- .../handler/create-decision-node-handler.ts | 4 +- .../handler/create-merge-node-handler.ts | 4 +- packages/graph/src/gresizable.ts | 60 +++++++++++++++++++ packages/graph/src/gshape-element.ts | 9 ++- packages/graph/src/index.ts | 1 + 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 packages/graph/src/gresizable.ts diff --git a/examples/workflow-server/src/common/handler/create-decision-node-handler.ts b/examples/workflow-server/src/common/handler/create-decision-node-handler.ts index 3c89b38..6836d0a 100644 --- a/examples/workflow-server/src/common/handler/create-decision-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-decision-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/server'; +import { GResizeLocation, Point } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -25,6 +25,6 @@ export class CreateDecisionNodeHandler extends CreateActivityNodeHandler { label = 'Decision Node'; protected override builder(point?: Point): ActivityNodeBuilder { - return super.builder(point).addCssClass('decision'); + return super.builder(point).addCssClass('decision').resizeLocations(GResizeLocation.CROSS); } } diff --git a/examples/workflow-server/src/common/handler/create-merge-node-handler.ts b/examples/workflow-server/src/common/handler/create-merge-node-handler.ts index 194bdae..688b9a6 100644 --- a/examples/workflow-server/src/common/handler/create-merge-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-merge-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/server'; +import { GResizeLocation, Point } from '@eclipse-glsp/server'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -25,6 +25,6 @@ export class CreateMergeNodeHandler extends CreateActivityNodeHandler { label = 'Merge Node'; protected override builder(point: Point | undefined): ActivityNodeBuilder { - return super.builder(point).addCssClass('merge'); + return super.builder(point).addCssClass('merge').resizeLocations(GResizeLocation.CROSS); } } diff --git a/packages/graph/src/gresizable.ts b/packages/graph/src/gresizable.ts new file mode 100644 index 0000000..e50a9c3 --- /dev/null +++ b/packages/graph/src/gresizable.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement, GModelElementBuilder } from './gmodel-element'; + +export const GResizable = Symbol('GResizable'); + +export interface GResizable { + resizeLocations?: GResizeLocation[]; + [GResizable]: boolean; +} + +export function isGResizable(element: G): element is G & GResizable { + return GResizable in element && element[GResizable] === true; +} + +export type GResizableBuilder = GModelElementBuilder; + +export namespace GResizableBuilder { + export function resizeLocations(builder: B, resizeLocations?: GResizeLocation[]): B { + const proxy = builder['proxy']; + proxy.resizeLocations = resizeLocations; + return builder; + } +} + +export enum GResizeLocation { + TopLeft = 'top-left', + Top = 'top', + TopRight = 'top-right', + Right = 'right', + BottomRight = 'bottom-right', + Bottom = 'bottom', + BottomLeft = 'bottom-left', + Left = 'left' +} + +export namespace GResizeLocation { + export const CORNERS: GResizeLocation[] = [ + GResizeLocation.TopLeft, + GResizeLocation.TopRight, + GResizeLocation.BottomRight, + GResizeLocation.BottomLeft + ]; + export const CROSS: GResizeLocation[] = [GResizeLocation.Top, GResizeLocation.Right, GResizeLocation.Bottom, GResizeLocation.Left]; + export const ALL: GResizeLocation[] = [...GResizeLocation.CORNERS, ...GResizeLocation.CROSS]; +} diff --git a/packages/graph/src/gshape-element.ts b/packages/graph/src/gshape-element.ts index ceec7ca..aa53aab 100644 --- a/packages/graph/src/gshape-element.ts +++ b/packages/graph/src/gshape-element.ts @@ -17,13 +17,16 @@ import { Args, Dimension, JsonPrimitive, Point } from '@eclipse-glsp/protocol'; import { GBoundsAware, GBoundsAwareBuilder } from './gbounds-aware'; import { GLayoutable, GLayoutableBuilder } from './glayoutable'; import { GModelElement, GModelElementBuilder } from './gmodel-element'; +import { GResizable, GResizableBuilder, GResizeLocation } from './gresizable'; -export abstract class GShapeElement extends GModelElement implements GBoundsAware, GLayoutable { +export abstract class GShapeElement extends GModelElement implements GBoundsAware, GLayoutable, GResizable { layoutOptions?: Args; position: Point; size: Dimension; + resizeLocations?: GResizeLocation[]; [GBoundsAware] = true; [GLayoutable] = true; + [GResizable] = true; } export class GShapeElementBuilder extends GModelElementBuilder { @@ -48,4 +51,8 @@ export class GShapeElementBuilder extends GModelElement addLayoutOptions(layoutOptions: Args | Map): this { return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); } + + resizeLocations(resizeLocations?: GResizeLocation[]): this { + return GResizableBuilder.resizeLocations(this, resizeLocations); + } } diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index fd68dcd..498b794 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -32,5 +32,6 @@ export * from './gmodel-util'; export * from './gnode'; export * from './gport'; export * from './gpre-rendered-element'; +export * from './gresizable'; export * from './gshape-element'; export * from './gshaped-prerendered-element'; From 0567257edb631c6a956d91a33e7c192d25936fd6 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 29 May 2024 11:46:49 +0000 Subject: [PATCH 341/566] Improve some aspects of change bounds, feedback and positioning (#352) - Ensure GridManager properly tracks state if action is used on startup - Ensure DebugManager properly tracks state if action is used on startup - Avoid cursor feedback interfering with edge edit feedback in disposal - Avoid unnecessary disposal calls by calling mouseUp on mouseMove - Fix issue with non-resizable elements not being movable - Fix calculation of position on diagram for nested HTML elements - More flexibility for hiddenBoundingRect - Make EdgeCreationTool more customizable --- packages/client/css/change-bounds.css | 9 ------ .../client/src/base/mouse-position-tracker.ts | 27 ++++++++++++++-- .../src/features/debug/debug-manager.ts | 16 +++++----- .../client/src/features/grid/grid-manager.ts | 8 +++-- .../change-bounds-tool-move-feedback.ts | 8 ++--- .../tools/change-bounds/change-bounds-tool.ts | 22 ++++++------- .../tools/edge-creation/edge-creation-tool.ts | 26 ++++++++++------ .../edge-edit/edge-edit-tool-feedback.ts | 31 +++++++++++++------ .../tools/edge-edit/edge-edit-tool.ts | 8 +++-- .../glsp-sprotty/src/svg-views-override.tsx | 13 ++++++-- 10 files changed, 108 insertions(+), 60 deletions(-) diff --git a/packages/client/css/change-bounds.css b/packages/client/css/change-bounds.css index 099647d..9b48e62 100644 --- a/packages/client/css/change-bounds.css +++ b/packages/client/css/change-bounds.css @@ -38,12 +38,3 @@ stroke: var(--glsp-error-foreground); stroke-width: 1.5px; } - -.move-mode .sprotty-projection-bar, -.resize-mode .sprotty-projection-bar { - /** - * We are using mouse events (offsetX, offsetY) in the GLSPMousePositionTracker to calculate the diagram position relative to the parent. - * Other elements result in relative coordinates different from the graph and will therefore interfere with the correct position calculation. - */ - pointer-events: none; -} diff --git a/packages/client/src/base/mouse-position-tracker.ts b/packages/client/src/base/mouse-position-tracker.ts index f66fcf6..1041a4c 100644 --- a/packages/client/src/base/mouse-position-tracker.ts +++ b/packages/client/src/base/mouse-position-tracker.ts @@ -14,12 +14,35 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { MousePositionTracker } from '@eclipse-glsp/sprotty'; -import { injectable } from 'inversify'; +import { + Action, + DOMHelper, + GModelElement, + MousePositionTracker, + Point, + TYPES, + ViewerOptions, + getAbsoluteClientBounds +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { Ranked } from './ranked'; @injectable() export class GLSPMousePositionTracker extends MousePositionTracker implements Ranked { /* we want to be executed before all default mouse listeners since we are just tracking the position and others may need it */ rank = Ranked.DEFAULT_RANK - 200; + + @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; + @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; + + override mouseMove(target: GModelElement, event: MouseEvent): (Action | Promise)[] { + // we cannot simply use the offsetX and offsetY properties of the event since they also consider nested HTML elements + // such as foreignObjects or the projection bars divs. Instead, we manually translate the client coordinates to the diagram + const clientToRoot = getAbsoluteClientBounds(target.root, this.domHelper, this.viewerOptions); + const clientToPosition = { x: event.clientX, y: event.clientY }; + const rootToPosition = Point.subtract(clientToPosition, clientToRoot); + const positionOnDiagram = target.root.parentToLocal(rootToPosition); + this.lastPosition = positionOnDiagram; + return []; + } } diff --git a/packages/client/src/features/debug/debug-manager.ts b/packages/client/src/features/debug/debug-manager.ts index f0b5533..aa68734 100644 --- a/packages/client/src/features/debug/debug-manager.ts +++ b/packages/client/src/features/debug/debug-manager.ts @@ -32,19 +32,21 @@ export class DebugManager implements IActionHandler { return this._debugEnabled; } - handle(action: EnableDebugModeAction): void { - this._debugEnabled = action.enable; - } - @postConstruct() protected init(): void { this.debugFeedback = this.feedbackDispatcher.createEmitter(); } - setDebugEnabled(visible: boolean): void { - if (!visible) { + handle(action: EnableDebugModeAction): void { + this.setDebugEnabled(action.enable); + } + + setDebugEnabled(enabled: boolean): void { + if (this._debugEnabled && !enabled) { + this._debugEnabled = false; this.debugFeedback.dispose(); - } else { + } else if (!this._debugEnabled && enabled) { + this._debugEnabled = true; this.debugFeedback .add(EnableDebugModeAction.create({ enable: true }), EnableDebugModeAction.create({ enable: false })) .submit(); diff --git a/packages/client/src/features/grid/grid-manager.ts b/packages/client/src/features/grid/grid-manager.ts index eb48d0b..5d2aacb 100644 --- a/packages/client/src/features/grid/grid-manager.ts +++ b/packages/client/src/features/grid/grid-manager.ts @@ -44,13 +44,15 @@ export class GridManager implements IActionHandler { } handle(action: ShowGridAction): void { - this._gridVisible = action.show; + this.setGridVisible(action.show); } setGridVisible(visible: boolean): void { - if (!visible) { + if (this._gridVisible && !visible) { + this._gridVisible = false; this.gridFeedback.dispose(); - } else { + } else if (!this._gridVisible && visible) { + this._gridVisible = true; this.gridFeedback.add(ShowGridAction.create({ show: true }), ShowGridAction.create({ show: false })).submit(); } } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index e2669b6..a0efe64 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -112,11 +112,9 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { return []; } - override mouseMove(target: GModelElement, event: MouseEvent): Action[] { - super.mouseMove(target, event); - if (event.buttons === 0) { - return this.mouseUp(target, event); - } else if (this.tracker.isTracking()) { + override draggingMouseMove(target: GModelElement, event: MouseEvent): Action[] { + super.draggingMouseMove(target, event); + if (this.tracker.isTracking()) { return this.moveElements(target, event); } return []; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 3979845..36ec4fc 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -180,7 +180,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel this.handleFeedback.submit(); return true; } else { - this.dispose(); + this.disposeResize(); return false; } } @@ -233,21 +233,19 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { - if (!this.tracker.isTracking()) { - return []; - } const actions: Action[] = []; if (this.activeResizeHandle) { actions.push(...this.handleResizeOnServer(this.activeResizeHandle)); } else { + // since the move feedback is handled by another class we just see whether there is something to move actions.push(...this.handleMoveOnServer(target)); } - this.disposeAllButHandles(); + this.disposeResize({ keepHandles: true }); return actions; } override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { - this.disposeAllButHandles(); + this.disposeResize({ keepHandles: true }); return super.nonDraggingMouseUp(element, event); } @@ -347,19 +345,19 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return element !== undefined && this.activeResizeElement !== undefined && element.id === this.activeResizeElement.id; } - protected disposeAllButHandles(): void { - // We do not dispose the handle feedback as we want to keep showing the handles on selected elements - // this.handleFeedback.dispose(); + protected disposeResize(opts: { keepHandles: boolean } = { keepHandles: false }): void { + if (!opts.keepHandles) { + this.handleFeedback.dispose(); + } this.resizeFeedback.dispose(); this.tracker.dispose(); this.activeResizeElement = undefined; this.activeResizeHandle = undefined; this.initialBounds = undefined; - super.dispose(); } override dispose(): void { - this.handleFeedback.dispose(); - this.disposeAllButHandles(); + this.disposeResize(); + super.dispose(); } } diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 702872c..ea363dd 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -56,24 +56,32 @@ export class EdgeCreationTool extends BaseCreationTool this.edgeEditListener.selectionChanged(change.root, change.selectedElements)) ); } @@ -110,10 +111,12 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti // active reconnect handle data protected reconnectMode?: 'NEW_SOURCE' | 'NEW_TARGET'; + protected cursorFeedback: FeedbackEmitter; protected editFeedback: FeedbackEmitter; constructor(protected tool: EdgeEditTool) { super(); + this.cursorFeedback = this.tool.createFeedbackEmitter(); this.editFeedback = this.tool.createFeedbackEmitter(); } @@ -264,11 +267,11 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti (this.reconnectMode === 'NEW_SOURCE' && currentTarget.canConnect(this.edge, 'source')) || (this.reconnectMode === 'NEW_TARGET' && currentTarget.canConnect(this.edge, 'target')) ) { - this.editFeedback.add(cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), cursorFeedbackAction()).submit(); + this.cursorFeedback.add(cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), cursorFeedbackAction()).submit(); return []; } } - this.editFeedback.add(cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED), cursorFeedbackAction()).submit(); + this.cursorFeedback.add(cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED), cursorFeedbackAction()).submit(); } } return []; @@ -307,6 +310,7 @@ export class EdgeEditListener extends DragAwareMouseListener implements ISelecti this.reconnectMode = undefined; this.newConnectable = undefined; this.routingHandle = undefined; + this.cursorFeedback.dispose(); this.editFeedback.dispose(); this.tool.deregisterFeedbackListeners(); super.dispose(); diff --git a/packages/glsp-sprotty/src/svg-views-override.tsx b/packages/glsp-sprotty/src/svg-views-override.tsx index da3769a..e3f7f11 100644 --- a/packages/glsp-sprotty/src/svg-views-override.tsx +++ b/packages/glsp-sprotty/src/svg-views-override.tsx @@ -61,13 +61,22 @@ export class CircularNodeView extends SprottyCircularNodeView { /** * Creates a hidden rectangle with the bounds of the given element. + * This is typically used to fixate the size of an element and avoid size changes that may happen during rendering. + * Changes can happen if the resulting bounding box (BBox) of the rendered element is different form the given bounds. + * The created bounding rect needs to be placed within a root SVG group (g) element to take effect. + * * @param withBounds The element to create the hidden rectangle for. + * @param context The rendering context. If provided, a rect is only created if the context is 'hidden'. * @returns The hidden rectangle. */ -export function hiddenBoundingRect(withBounds: BoundsAware): VNode { +export function hiddenBoundingRect(withBounds: BoundsAware): VNode; +export function hiddenBoundingRect(withBounds: BoundsAware, context: RenderingContext): VNode | undefined; +export function hiddenBoundingRect(withBounds: BoundsAware, context?: RenderingContext): VNode | undefined { // an element with attribute ATTR_BBOX_ELEMENT is used by the hidden bounds updater to determine the bounds if it is within a g-element // we set the fill to transparent since the SVG export uses the hidden rendering to generate the image and we do not want to be seen - return ; + return !context || context.targetKind === 'hidden' ? ( + + ) : undefined; } /** From 341c3699e253b56a2594265604bcb58a441aa4aa Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 4 Jun 2024 21:53:44 +0000 Subject: [PATCH 342/566] Minor Improvements (#353) * Minor Improvements - Ensure we render/unhide tracked ghost element as soon as possible - Add ghost element bounds data to arguments sent to server * PR Feedback - Remove snapper argument - New: Add utility function to check if a bound object is valid - New: Make SVG exporter a little bit more flexible --- ...ouse-tracking-element-position-listener.ts | 5 +- .../src/features/export/glsp-svg-exporter.ts | 105 +++++++++++++++--- .../tools/node-creation/container-manager.ts | 16 +-- .../tools/node-creation/insert-indicator.ts | 2 +- .../tools/node-creation/node-creation-tool.ts | 28 ++++- .../src/sprotty-geometry-bounds.spec.ts | 20 ++++ .../protocol/src/sprotty-geometry-bounds.ts | 10 ++ 7 files changed, 154 insertions(+), 32 deletions(-) diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index 4dcc937..fb127dc 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -54,10 +54,11 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener if (!element) { return []; } - if (!this.tracker.isTracking()) { + const isInitializing = !this.tracker.isTracking(); + if (isInitializing) { this.initialize(element, ctx, event); } - const move = this.tracker.moveElements([element], { snap: event, restrict: event }); + const move = this.tracker.moveElements([element], { snap: event, restrict: event, skipStatic: !isInitializing }); const elementMove = move.elementMoves[0]; if (!elementMove) { return []; diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 169412b..dd76397 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -13,38 +13,107 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ExportSvgAction, GModelRoot, RequestAction, SvgExporter } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { v4 as uuid } from 'uuid'; -import { ExportSvgAction, RequestAction, GModelRoot, SvgExporter } from '@eclipse-glsp/sprotty'; @injectable() export class GLSPSvgExporter extends SvgExporter { - override export(root: GModelRoot, _request?: RequestAction): void { + override export(root: GModelRoot, request?: RequestAction): void { if (typeof document !== 'undefined') { - const svgElement = this.findSvgElement(); + let svgElement = this.findSvgElement(); if (svgElement) { - // createSvg requires the svg to have a non-empty id, so we generate one if necessary - const originalId = svgElement.id; - try { - svgElement.id = originalId || uuid(); - // provide generated svg code with respective sizing for proper viewing in browser and remove undesired border - const bounds = this.getBounds(root); - const svg = this.createSvg(svgElement, root).replace( - 'style="', - `style="width: ${bounds.width}px !important;height: ${bounds.height}px !important;border: none !important;` - ); - // do not give request/response id here as otherwise the action is treated as an unrequested response - this.actionDispatcher.dispatch(ExportSvgAction.create(svg)); - } finally { - svgElement.id = originalId; - } + svgElement = this.prepareSvgElement(svgElement, root, request); + const serializedSvg = this.createSvg(svgElement, root); + const svgExport = this.getSvgExport(serializedSvg, svgElement, root, request); + // do not give request/response id here as otherwise the action is treated as an unrequested response + this.actionDispatcher.dispatch(ExportSvgAction.create(svgExport)); } } } + protected override createSvg(svgElement: SVGSVGElement, root: GModelRoot): string { + // createSvg requires the svg to have a non-empty id, so we generate one if necessary + const originalId = svgElement.id; + try { + svgElement.id = originalId || uuid(); + return super.createSvg(svgElement, root); + } finally { + svgElement.id = originalId; + } + } + protected findSvgElement(): SVGSVGElement | null { const div = document.getElementById(this.options.hiddenDiv); // search for first svg element as hierarchy within Sprotty might change return div && div.querySelector('svg'); } + + protected prepareSvgElement(svgElement: SVGSVGElement, root: GModelRoot, request?: RequestAction): SVGSVGElement { + return svgElement; + } + + protected override copyStyles(source: Element, target: Element, skippedProperties: string[]): void { + this.copyStyle(source, target, skippedProperties); + + // IE doesn't retrun anything on source.children + for (let i = 0; i < source.childNodes.length; ++i) { + const sourceChild = source.childNodes[i]; + const targetChild = target.childNodes[i]; + if (sourceChild instanceof Element) { + this.copyStyles(sourceChild, targetChild as Element, []); + } + } + } + + protected copyStyle(source: Element, target: Element, skippedProperties: string[]): void { + const sourceStyle = getComputedStyle(source); + const targetStyle = getComputedStyle(target); + + let style = ''; + for (let i = 0; i < sourceStyle.length; i++) { + const propertyName = sourceStyle[i]; + if (!skippedProperties.includes(propertyName)) { + const propertyValue = sourceStyle.getPropertyValue(propertyName); + const propertyPriority = sourceStyle.getPropertyPriority(propertyName); + if (targetStyle.getPropertyValue(propertyName) !== propertyValue) { + if (isElementCSSInlineStyle(target)) { + // rather set the property directly on the element to keep other values intact + target.style.setProperty(propertyName, propertyValue); + } else { + // collect all properties to set them at once + style += `${propertyName}: ${propertyValue}${propertyPriority ? ' !' + propertyPriority : ''}; `; + } + } + } + } + if (style !== '') { + target.setAttribute('style', style.trim()); + } + } + + protected getSvgExport( + serializedSvgElement: string, + svgElement: SVGElement, + root: GModelRoot, + request?: RequestAction + ): string { + const svgExportStyle = this.getSvgExportStyle(svgElement, root, request); + return svgExportStyle ? serializedSvgElement.replace('style="', `style="${svgExportStyle}`) : serializedSvgElement; + } + + protected getSvgExportStyle(svgElement: SVGElement, root: GModelRoot, request?: RequestAction): string | undefined { + // provide generated svg code with respective sizing for proper viewing in browser and remove undesired border + const bounds = this.getBounds(root); + return ( + `width: ${bounds.width}px !important;` + + `height: ${bounds.height}px !important;` + + 'border: none !important;' + + 'cursor: default !important;' + ); + } +} + +export function isElementCSSInlineStyle(element: any): element is ElementCSSInlineStyle { + return 'style' in element && element.style instanceof CSSStyleDeclaration; } diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index 7f2345b..397126c 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -20,12 +20,6 @@ import { CSS_GHOST_ELEMENT, CursorCSS, cursorFeedbackAction } from '../../../bas import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { ContainerElement, isContainable } from '../../hints/model'; import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; -export interface TrackedInsert { - elementTypeId: string; - location: Point; - container?: GModelElement; - valid: boolean; -} export interface InsertOptions extends Record { /** Flag to indicate whether the location within a container needs to be valid. Default: false */ @@ -42,6 +36,14 @@ export const DEFAULT_INSERT_OPTIONS: InsertOptions = { validateLocationInContainer: false }; +export interface TrackedInsert { + elementTypeId: string; + location: Point; + container?: GModelElement; + valid: boolean; + options: InsertOptions; +} + @injectable() export class ContainerManager { @inject(ChangeBoundsManager) protected readonly changeBoundsManager: ChangeBoundsManager; @@ -54,7 +56,7 @@ export class ContainerManager { // we need to check whether the location is valid either because we do not have a container or the option is set valid = opts?.validLocationOverwrite ?? this.changeBoundsManager.hasValidPosition(proxy, location); } - return { elementTypeId, container, location, valid }; + return { elementTypeId, container, location, valid, options }; } findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined { diff --git a/packages/client/src/features/tools/node-creation/insert-indicator.ts b/packages/client/src/features/tools/node-creation/insert-indicator.ts index 7964dcf..c2f16a9 100644 --- a/packages/client/src/features/tools/node-creation/insert-indicator.ts +++ b/packages/client/src/features/tools/node-creation/insert-indicator.ts @@ -29,7 +29,7 @@ export class InsertIndicator extends GNode implements ArgsAware { override type: string = InsertIndicator.TYPE; override features?: FeatureSet = createFeatureSet(InsertIndicator.DEFAULT_FEATURES); override cssClasses: string[] = ['insert-indicator', 'sprotty-node']; - override size: Dimension = { width: 1, height: 1 }; + override size: Dimension = Dimension.ZERO; args: Args = {}; constructor(length?: number) { diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index fad6c52..679c1d5 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -15,7 +15,10 @@ ********************************************************************************/ import { Action, + Args, + Bounds, CreateNodeOperation, + Dimension, Disposable, DisposableCollection, GModelElement, @@ -24,6 +27,7 @@ import { Point, TYPES, TriggerNodeCreationAction, + isBoundsAware, isCtrlOrCmd, isMoveable } from '@eclipse-glsp/sprotty'; @@ -129,16 +133,16 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { return this.triggerAction.elementTypeId; } - override nonDraggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { + override nonDraggingMouseUp(ctx: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; - const insert = this.getTrackedInsert(target, event); + const insert = this.getTrackedInsert(ctx, event); if (insert.valid) { result.push( CreateNodeOperation.create(this.elementTypeId, { location: insert.location, containerId: insert.container?.id, - args: this.triggerAction.args + args: this.getCreateNodeOperationArgs(insert, ctx, event) }) ); } @@ -158,6 +162,22 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { return result; } + protected getCreateNodeOperationArgs(insert: TrackedInsert, ctx: GModelElement, event: MouseEvent): Args | undefined { + let args = { ...this.triggerAction.args }; + const ghostElement = this.getGhostElement(ctx, event); + if (ghostElement) { + const ghostDimensions = isBoundsAware(ghostElement) ? Bounds.dimension(ghostElement.bounds) : Dimension.ZERO; + args = { + ...args, + 'ghost-x': ghostElement.position.x, + 'ghost-y': ghostElement.position.y, + 'ghost-width': ghostDimensions.width, + 'ghost-height': ghostDimensions.height + }; + } + return args; + } + protected createGhostElement(ghostElement: GhostElement): string { const templates = [ghostElement.template]; this.ghostElementFeedback.add( @@ -176,7 +196,7 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { protected getTrackedInsert(ctx: GModelElement, event: MouseEvent): TrackedInsert { const ghostElement = this.getGhostElement(ctx, event); if (!ghostElement) { - return { elementTypeId: this.elementTypeId, location: Point.ORIGIN, valid: false }; + return { elementTypeId: this.elementTypeId, location: Point.ORIGIN, valid: false, options: { evt: event } }; } return this.tool.containerManager.insert(ghostElement, ghostElement.position, this.elementTypeId, { evt: event }); } diff --git a/packages/protocol/src/sprotty-geometry-bounds.spec.ts b/packages/protocol/src/sprotty-geometry-bounds.spec.ts index 22f450b..c63bd4c 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.spec.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.spec.ts @@ -32,6 +32,26 @@ describe('Bounds', () => { }); }); + describe('isValid', () => { + it('should return true if the bounds are valid', () => { + const bounds: Bounds = { x: 0, y: 0, width: 100, height: 100 }; + const result = Bounds.isValid(bounds); + expect(result).to.be.true; + }); + + it('should return false if a dimension is not valid', () => { + const bounds = { x: 0, y: 0, width: 100, height: -1 }; + const result = Bounds.isValid(bounds); + expect(result).to.be.false; + }); + + it('should return false if a coordinate is not valid', () => { + const bounds = { x: 0, y: NaN, width: 100, height: 0 }; + const result = Bounds.isValid(bounds); + expect(result).to.be.false; + }); + }); + describe('encompasses', () => { it('should return true if the outer bounds completely encompass the inner bounds', () => { const outer: Bounds = { x: 0, y: 0, width: 100, height: 100 }; diff --git a/packages/protocol/src/sprotty-geometry-bounds.ts b/packages/protocol/src/sprotty-geometry-bounds.ts index 18d4c5b..4de583b 100644 --- a/packages/protocol/src/sprotty-geometry-bounds.ts +++ b/packages/protocol/src/sprotty-geometry-bounds.ts @@ -33,6 +33,14 @@ declare module 'sprotty-protocol/lib/utils/geometry' { */ function is(bounds: any): bounds is Bounds; + /** + * Type guard to check if a bound is valid. For a bound to be valid it needs to be defined and have valid + * coordinates and dimensions. + * + * @param bounds the bounds to be checked for validity + */ + function isValid(bounds?: Bounds): bounds is Bounds; + /** * Checks whether the inner bounds are compeletely encompassed by the outer bounds. * @@ -272,6 +280,8 @@ Bounds.is = (bounds: any): bounds is Bounds => hasNumberProp(bounds, 'width') && hasNumberProp(bounds, 'height'); +Bounds.isValid = (bounds?: Bounds): bounds is Bounds => bounds !== undefined && Dimension.isValid(bounds) && Point.isValid(bounds); + Bounds.encompasses = (outer: Bounds, inner: Bounds): boolean => Bounds.includes(outer, Bounds.topLeft(inner)) && Bounds.includes(outer, Bounds.bottomRight(inner)); From c918fe0c25bf61fa4821b8f943c9ff28584593d1 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 5 Jun 2024 07:30:35 +0000 Subject: [PATCH 343/566] Fix style handling in SVG exporter (#354) For some reason our created target elements are not actually instanceof Element even though everything is in their prototype chain. So instead, we now check whether it is an element with tag (HTML, SVG, Math) and whether it has a style property. --- .../client/src/features/export/glsp-svg-exporter.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index dd76397..d4877b1 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -77,7 +77,7 @@ export class GLSPSvgExporter extends SvgExporter { const propertyValue = sourceStyle.getPropertyValue(propertyName); const propertyPriority = sourceStyle.getPropertyPriority(propertyName); if (targetStyle.getPropertyValue(propertyName) !== propertyValue) { - if (isElementCSSInlineStyle(target)) { + if (this.shouldUpdateStyle(target)) { // rather set the property directly on the element to keep other values intact target.style.setProperty(propertyName, propertyValue); } else { @@ -92,6 +92,11 @@ export class GLSPSvgExporter extends SvgExporter { } } + protected shouldUpdateStyle(element: any): element is ElementCSSInlineStyle { + // we want to simply update the style of elements and keep other values intact if they have a style property + return 'tagName' in element && 'style' in element; + } + protected getSvgExport( serializedSvgElement: string, svgElement: SVGElement, @@ -113,7 +118,3 @@ export class GLSPSvgExporter extends SvgExporter { ); } } - -export function isElementCSSInlineStyle(element: any): element is ElementCSSInlineStyle { - return 'style' in element && element.style instanceof CSSStyleDeclaration; -} From b106afbf891f879fbc0117263f9100fbc0429456 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 7 Jun 2024 00:44:09 +0200 Subject: [PATCH 344/566] Configure fork and join nodes as not resizable (#85) * Configure fork and join nodes as not resizable Update type hints configuration for workflow example to make fork and join nodes not resizable => we have test cases for both resizable and non-resizable elements in the diagram Refactor createDefault hint functions to allow partial overriding of default properties * Align edge-creation-checker condition with java server i..e only allow decision nodes as sources for weighted edges --- .../common/workflow-diagram-configuration.ts | 39 ++++++++++--------- .../common/workflow-edge-creation-checker.ts | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/examples/workflow-server/src/common/workflow-diagram-configuration.ts b/examples/workflow-server/src/common/workflow-diagram-configuration.ts index 784c9bf..2a4daf9 100644 --- a/examples/workflow-server/src/common/workflow-diagram-configuration.ts +++ b/examples/workflow-server/src/common/workflow-diagram-configuration.ts @@ -48,33 +48,26 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration { return [ createDefaultShapeTypeHint(types.MANUAL_TASK), createDefaultShapeTypeHint(types.AUTOMATED_TASK), - createDefaultShapeTypeHint(types.FORK_NODE), - createDefaultShapeTypeHint(types.JOIN_NODE), + createDefaultShapeTypeHint({ elementTypeId: types.FORK_NODE, resizable: false }), + createDefaultShapeTypeHint({ elementTypeId: types.JOIN_NODE, resizable: false }), createDefaultShapeTypeHint(types.DECISION_NODE), createDefaultShapeTypeHint(types.MERGE_NODE), - { + createDefaultShapeTypeHint({ elementTypeId: types.CATEGORY, - repositionable: true, - deletable: true, - resizable: true, - reparentable: true, containableElementTypeIds: [types.TASK, types.ACTIVITY_NODE, types.CATEGORY] - } + }) ]; } get edgeTypeHints(): EdgeTypeHint[] { return [ createDefaultEdgeTypeHint(DefaultTypes.EDGE), - { - elementTypeId: types.WEIGHTED_EDGE, - repositionable: true, - deletable: true, - routable: true, + createDefaultEdgeTypeHint({ + elementTypeId: DefaultTypes.EDGE, dynamic: true, sourceElementTypeIds: [types.ACTIVITY_NODE], targetElementTypeIds: [types.TASK, types.ACTIVITY_NODE] - } + }) ]; } @@ -83,13 +76,20 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration { animatedUpdate = true; } -export function createDefaultShapeTypeHint(elementId: string): ShapeTypeHint { - return { elementTypeId: elementId, repositionable: true, deletable: true, resizable: true, reparentable: true }; +export function createDefaultShapeTypeHint(template: { elementTypeId: string } & Partial): ShapeTypeHint; +export function createDefaultShapeTypeHint(elementId: string): ShapeTypeHint; +export function createDefaultShapeTypeHint( + elementIdOrTemplate: string | ({ elementTypeId: string } & Partial) +): ShapeTypeHint { + const template = typeof elementIdOrTemplate === 'string' ? { elementTypeId: elementIdOrTemplate } : elementIdOrTemplate; + return { repositionable: true, deletable: true, resizable: true, reparentable: true, ...template }; } -export function createDefaultEdgeTypeHint(elementId: string): EdgeTypeHint { +export function createDefaultEdgeTypeHint(template: { elementTypeId: string } & Partial): EdgeTypeHint; +export function createDefaultEdgeTypeHint(elementId: string): EdgeTypeHint; +export function createDefaultEdgeTypeHint(elementIdOrTemplate: string | ({ elementTypeId: string } & Partial)): EdgeTypeHint { + const template = typeof elementIdOrTemplate === 'string' ? { elementTypeId: elementIdOrTemplate } : elementIdOrTemplate; return { - elementTypeId: elementId, repositionable: true, deletable: true, routable: true, @@ -110,6 +110,7 @@ export function createDefaultEdgeTypeHint(elementId: string): EdgeTypeHint { types.FORK_NODE, types.JOIN_NODE, types.CATEGORY - ] + ], + ...template }; } diff --git a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts index 32080cd..f61a87c 100644 --- a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts +++ b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts @@ -21,7 +21,7 @@ import { ModelTypes } from './util/model-types'; @injectable() export class WorkflowEdgeCreationChecker implements EdgeCreationChecker { isValidSource(edgeType: string, sourceElement: GModelElement): boolean { - return edgeType !== ModelTypes.WEIGHTED_EDGE || sourceElement.type === ModelTypes.DECISION_NODE; + return edgeType !== ModelTypes.WEIGHTED_EDGE && sourceElement.type === ModelTypes.DECISION_NODE; } isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean { return ( From c482ef4197593fbe45f262824c995f9cd4b0ca4c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 10 Jun 2024 00:17:54 +0200 Subject: [PATCH 345/566] GLSP-1349 Improve container configuration (#355) - Add `replace` property to `ModulConfiguration` interface. When resolving feature modules of the replace property, any potential already configured module with the same id will be replaced with the defined module. If there is nothing to replace, the module will be added to the end of the resolved configuration - Add check for non-unique feature module ids to `resolveContainerConfiguration` function. - Add option to enable additional debug logging when loading feature modules. This is usefull during development to get insights about why the container modules are not loaded as expected - Add dedicated `featureIds` to all default modules. If no featureId is provided the number id of the module is reused for creating the featureId. While this works perfectly fine, it makes debugging harder because feature modules are hard to indentify based on their featureid. With this change its now easier to derive the corresponding feature module from the featureid. Fixes https://github.com/eclipse-glsp/glsp/issues/1349 --- .../src/direct-task-editing/di.config.ts | 9 +- .../src/workflow-diagram-module.ts | 4 +- packages/client/src/base/default.module.ts | 153 +++++++++--------- .../accessibility/accessibility-module.ts | 41 ++--- .../keyboard-pointer-module.ts | 33 ++-- .../keyboard-tool-palette-module.ts | 11 +- .../move-zoom/move-zoom-module.ts | 11 +- .../resize-key-tool/resize-key-module.ts | 11 +- .../search/search-palette-module.ts | 11 +- .../view-key-tools/view-key-tools-module.ts | 15 +- .../src/features/bounds/bounds-module.ts | 37 +++-- .../command-palette/command-palette-module.ts | 15 +- .../context-menu/context-menu-module.ts | 29 ++-- .../features/copy-paste/copy-paste-modules.ts | 16 +- .../client/src/features/debug/debug-module.ts | 17 +- .../features/decoration/decoration-module.ts | 9 +- .../element-template-module.ts | 13 +- .../src/features/export/export-modules.ts | 17 +- .../client/src/features/grid/grid-module.ts | 19 ++- .../helper-lines/helper-line-module.ts | 29 ++-- .../src/features/hints/type-hints-module.ts | 17 +- .../client/src/features/hover/hover-module.ts | 53 +++--- .../label-edit-ui/label-edit-ui-module.ts | 15 +- .../features/label-edit/label-edit-module.ts | 15 +- .../src/features/layout/layout-module.ts | 13 +- .../features/navigation/navigation-module.ts | 19 ++- .../src/features/routing/routing-module.ts | 39 ++--- .../client/src/features/save/save-module.ts | 9 +- .../src/features/select/select-module.ts | 19 ++- .../source-model-watcher-module.ts | 9 +- .../src/features/status/status-module.ts | 15 +- .../svg-metadata/svg-metadata-module.ts | 11 +- .../tool-palette/tool-palette-module.ts | 17 +- .../change-bounds-tool-module.ts | 19 ++- .../tools/deletion/deletion-tool-module.ts | 13 +- .../edge-creation/edege-creation-module.ts | 15 +- .../tools/edge-edit/edge-edit-module.ts | 25 +-- .../marquee-selection-module.ts | 19 ++- .../node-creation/node-creation-module.ts | 5 +- .../features/tools/tool-focus-loss-module.ts | 11 +- .../features/validation/validation-modules.ts | 32 ++-- .../src/features/viewport/viewport-modules.ts | 37 +++-- packages/client/src/standalone-modules.ts | 21 +-- packages/client/src/views/base-view-module.ts | 11 +- packages/glsp-sprotty/package.json | 2 +- packages/glsp-sprotty/src/feature-modules.ts | 22 +-- .../src/di/container-configuration.spec.ts | 73 +++++---- .../src/di/container-configuration.ts | 47 +++++- .../protocol/src/di/feature-module.spec.ts | 5 + packages/protocol/src/di/feature-module.ts | 34 +++- 50 files changed, 683 insertions(+), 459 deletions(-) diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts index 855b48f..df8e823 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/di.config.ts @@ -16,6 +16,9 @@ import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/client'; import { TaskEditor } from './direct-task-editor'; -export const directTaskEditor = new FeatureModule((bind, _unbind, _isBound) => { - bindAsService(bind, TYPES.IUIExtension, TaskEditor); -}); +export const directTaskEditor = new FeatureModule( + (bind, _unbind, _isBound) => { + bindAsService(bind, TYPES.IUIExtension, TaskEditor); + }, + { featureId: Symbol('directTaskEditor') } +); diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 3c0942e..c73a436 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -99,11 +99,11 @@ export function createWorkflowDiagramContainer(...containerConfiguration: Contai export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { return initializeDiagramContainer( container, + ...containerConfiguration, directTaskEditor, helperLineModule, gridModule, debugModule, - workflowDiagramModule, - ...containerConfiguration + workflowDiagramModule ); } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index c4a47d1..f6351b6 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -62,76 +62,83 @@ import { GViewRegistry } from './view/view-registry'; * The default module provides all of GLSP's base functionality and services. * It builds on top of sprotty's default module {@link `sprottyDefaultModule`}. */ -export const defaultModule = new FeatureModule((bind, unbind, isBound, rebind, ...rest) => { - // load bindings from sprotty's default module to avoid code duplication - sprottyDefaultModule.registry(bind, unbind, isBound, rebind, ...rest); - const context = { bind, unbind, isBound, rebind }; - - bindLazyInjector(context); - - bind(EditorContextService).toSelf().inSingletonScope(); - bind(TYPES.IDiagramStartup).toService(EditorContextService); - bind(TYPES.IEditorContextServiceProvider).toProvider(ctx => async () => ctx.container.get(EditorContextService)); - - configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); - configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); - - bind(FocusTracker).toSelf().inSingletonScope(); - configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); - - // Model update initialization ------------------------------------ - bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); - configureCommand(context, FeedbackAwareUpdateModelCommand); - rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); - - bind(GLSPMouseTool).toSelf().inSingletonScope(); - bindOrRebind(context, MouseTool).toService(GLSPMouseTool); - bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); - bind(GLSPMousePositionTracker).toSelf().inSingletonScope(); - bindOrRebind(context, MousePositionTracker).toService(GLSPMousePositionTracker); - bind(GLSPKeyTool).toSelf().inSingletonScope(); - bindOrRebind(context, KeyTool).toService(GLSPKeyTool); - bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); - - bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); - bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); - bind(GLSPActionDispatcher).toSelf().inSingletonScope(); - bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); - - bindOrRebind(context, ActionHandlerRegistry).to(GLSPActionHandlerRegistry).inSingletonScope(); - - bindAsService(context, TYPES.ModelSource, GLSPModelSource); - bind(DiagramLoader).toSelf().inSingletonScope(); - bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); - - // support re-registration of model elements and views - bindOrRebind(context, TYPES.SModelRegistry).to(GModelRegistry).inSingletonScope(); - bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); - - bind(SelectionService).toSelf().inSingletonScope(); - bind(TYPES.IGModelRootListener).toService(SelectionService); - bind(TYPES.IDiagramStartup).toService(SelectionService); - - // Feedback Support ------------------------------------ - // Generic re-usable feedback modifying css classes - configureCommand(context, ModifyCssFeedbackCommand); - // We support using sprotty's MoveCommand as client-side visual feedback - configureCommand(context, MoveCommand); - - bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); - bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); - - // Tool manager initialization ------------------------------------ - bind(TYPES.IToolManager).to(ToolManager).inSingletonScope(); - bind(TYPES.IDiagramStartup).toService(TYPES.IToolManager); - bind(TYPES.IEditModeListener).toService(TYPES.IToolManager); - bind(DefaultToolsEnablingKeyListener).toSelf().inSingletonScope(); - bind(TYPES.KeyListener).toService(DefaultToolsEnablingKeyListener); - bind(ToolManagerActionHandler).toSelf().inSingletonScope(); - configureActionHandler(context, EnableDefaultToolsAction.KIND, ToolManagerActionHandler); - configureActionHandler(context, EnableToolsAction.KIND, ToolManagerActionHandler); - - bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); - bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); - bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); -}); +export const defaultModule = new FeatureModule( + (bind, unbind, isBound, rebind, ...rest) => { + // load bindings from sprotty's default module to avoid code duplication + sprottyDefaultModule.registry(bind, unbind, isBound, rebind, ...rest); + const context = { bind, unbind, isBound, rebind }; + + bindLazyInjector(context); + + bind(EditorContextService).toSelf().inSingletonScope(); + bind(TYPES.IDiagramStartup).toService(EditorContextService); + bind(TYPES.IEditorContextServiceProvider).toProvider( + ctx => async () => ctx.container.get(EditorContextService) + ); + + configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); + configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); + + bind(FocusTracker).toSelf().inSingletonScope(); + configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); + + // Model update initialization ------------------------------------ + bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); + configureCommand(context, FeedbackAwareUpdateModelCommand); + rebind(SetModelCommand).to(FeedbackAwareSetModelCommand); + + bind(GLSPMouseTool).toSelf().inSingletonScope(); + bindOrRebind(context, MouseTool).toService(GLSPMouseTool); + bind(TYPES.IDiagramStartup).toService(GLSPMouseTool); + bind(GLSPMousePositionTracker).toSelf().inSingletonScope(); + bindOrRebind(context, MousePositionTracker).toService(GLSPMousePositionTracker); + bind(GLSPKeyTool).toSelf().inSingletonScope(); + bindOrRebind(context, KeyTool).toService(GLSPKeyTool); + bind(TYPES.IDiagramStartup).toService(GLSPKeyTool); + + bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); + bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); + bind(GLSPActionDispatcher).toSelf().inSingletonScope(); + bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); + + bindOrRebind(context, ActionHandlerRegistry).to(GLSPActionHandlerRegistry).inSingletonScope(); + + bindAsService(context, TYPES.ModelSource, GLSPModelSource); + bind(DiagramLoader).toSelf().inSingletonScope(); + bind(ModelInitializationConstraint).to(DefaultModelInitializationConstraint).inSingletonScope(); + + // support re-registration of model elements and views + bindOrRebind(context, TYPES.SModelRegistry).to(GModelRegistry).inSingletonScope(); + bindOrRebind(context, TYPES.ViewRegistry).to(GViewRegistry).inSingletonScope(); + + bind(SelectionService).toSelf().inSingletonScope(); + bind(TYPES.IGModelRootListener).toService(SelectionService); + bind(TYPES.IDiagramStartup).toService(SelectionService); + + // Feedback Support ------------------------------------ + // Generic re-usable feedback modifying css classes + configureCommand(context, ModifyCssFeedbackCommand); + // We support using sprotty's MoveCommand as client-side visual feedback + configureCommand(context, MoveCommand); + + bindAsService(context, TYPES.IVNodePostprocessor, LocationPostprocessor); + bind(TYPES.HiddenVNodePostprocessor).toService(LocationPostprocessor); + + // Tool manager initialization ------------------------------------ + bind(TYPES.IToolManager).to(ToolManager).inSingletonScope(); + bind(TYPES.IDiagramStartup).toService(TYPES.IToolManager); + bind(TYPES.IEditModeListener).toService(TYPES.IToolManager); + bind(DefaultToolsEnablingKeyListener).toSelf().inSingletonScope(); + bind(TYPES.KeyListener).toService(DefaultToolsEnablingKeyListener); + bind(ToolManagerActionHandler).toSelf().inSingletonScope(); + configureActionHandler(context, EnableDefaultToolsAction.KIND, ToolManagerActionHandler); + configureActionHandler(context, EnableToolsAction.KIND, ToolManagerActionHandler); + + bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); + bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); + bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); + }, + { + featureId: Symbol('default') + } +); diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 1a5b68d..5665ce8 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -15,30 +15,35 @@ ********************************************************************************/ import { FeatureModule } from '@eclipse-glsp/sprotty'; +import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; +import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; import { configureShortcutHelpTool } from './key-shortcut/di.config'; +import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; +import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; -import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; -import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; -import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; -import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; -import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; import { configureToastTool } from './toast/toast-module'; +import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; /** * Enables the accessibility tools for a keyboard-only-usage */ -export const accessibilityModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureResizeTools(context); - configureViewKeyTools(context); - configureMoveZoom(context); - configureSearchPaletteModule(context); - configureShortcutHelpTool(context); - configureKeyboardToolPaletteTool(context); - configureKeyboardControlTools(context); - configureElementNavigationTool(context); - configureFocusTrackerTool(context); - configureToastTool(context); -}); +export const accessibilityModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureResizeTools(context); + configureViewKeyTools(context); + configureMoveZoom(context); + configureSearchPaletteModule(context); + configureShortcutHelpTool(context); + configureKeyboardToolPaletteTool(context); + configureKeyboardControlTools(context); + configureElementNavigationTool(context); + configureFocusTrackerTool(context); + configureToastTool(context); + }, + { + featureId: Symbol('accessibility') + } +); diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index 6d09dff..decbe60 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -14,32 +14,35 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + BindingContext, + FeatureModule, TYPES, - bindAsService, - configureActionHandler, + TriggerEdgeCreationAction, TriggerNodeCreationAction, - FeatureModule, - BindingContext, - TriggerEdgeCreationAction + bindAsService, + configureActionHandler } from '@eclipse-glsp/sprotty'; -import { GlobalKeyListenerTool } from '../global-keylistener-tool'; -import { KeyboardPointer } from './keyboard-pointer'; -import { KeyboardGrid } from '../keyboard-grid/keyboard-grid'; -import { SetKeyboardPointerRenderPositionAction } from './actions'; +import { SetEdgeTargetSelectionAction } from '../edge-autocomplete/action'; import { EdgeAutocompletePalette } from '../edge-autocomplete/edge-autocomplete-palette'; import { EdgeAutocompletePaletteTool } from '../edge-autocomplete/edge-autocomplete-tool'; -import { SetEdgeTargetSelectionAction } from '../edge-autocomplete/action'; +import { GlobalKeyListenerTool } from '../global-keylistener-tool'; +import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; +import { KeyboardGrid } from '../keyboard-grid/keyboard-grid'; import { GridSearchPalette } from '../keyboard-grid/keyboard-grid-search-palette'; import { KeyboardNodeGrid } from '../keyboard-grid/keyboard-node-grid'; -import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; +import { SetKeyboardPointerRenderPositionAction } from './actions'; +import { KeyboardPointer } from './keyboard-pointer'; /** * Handles the pointer used via grid to position new elements. */ -export const keyboardControlModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureKeyboardControlTools(context); -}); +export const keyboardControlModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureKeyboardControlTools(context); + }, + { featureId: Symbol('keyboardControl') } +); export function configureKeyboardControlTools(context: BindingContext): void { bindAsService(context, TYPES.IDefaultTool, GlobalKeyListenerTool); diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index 9f26fb0..b9477a9 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -29,10 +29,13 @@ import { EnableToolPaletteAction } from '../../tool-palette/tool-palette'; import { FocusDomAction } from '../actions'; import { KeyboardToolPalette } from './keyboard-tool-palette'; -export const keyboardToolPaletteModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureKeyboardToolPaletteTool(context); -}); +export const keyboardToolPaletteModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureKeyboardToolPaletteTool(context); + }, + { featureId: Symbol('keyboardToolPalette') } +); export function configureKeyboardToolPaletteTool(context: BindingContext): void { bindAsService(context, TYPES.IUIExtension, KeyboardToolPalette); diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index c9ff29a..d8b8eb8 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -21,10 +21,13 @@ import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewport /** * Handles move and zoom actions. */ -export const moveZoomModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureMoveZoom(context); -}); +export const moveZoomModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureMoveZoom(context); + }, + { featureId: Symbol('moveZoom') } +); export function configureMoveZoom(context: BindingContext): void { context.bind(MoveViewportHandler).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index 41e3c6b..7a29779 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -21,10 +21,13 @@ import { ResizeKeyTool } from './resize-key-tool'; /** * Handles resize actions. */ -export const resizeKeyModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureResizeTools(context); -}); +export const resizeKeyModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureResizeTools(context); + }, + { featureId: Symbol('resizeKey') } +); export function configureResizeTools(context: BindingContext): void { context.bind(ResizeElementHandler).toSelf().inSingletonScope(); diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 371d2b0..c2bf03a 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -18,10 +18,13 @@ import { bindAsService, BindingContext, FeatureModule, TYPES } from '@eclipse-gl import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; -export const searchPaletteModule = new FeatureModule((bind, _unbind, isBound, rebind) => { - const context = { bind, isBound, rebind }; - configureSearchPaletteModule(context); -}); +export const searchPaletteModule = new FeatureModule( + (bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + configureSearchPaletteModule(context); + }, + { featureId: Symbol('searchPalette') } +); export function configureSearchPaletteModule(context: Pick): void { bindAsService(context, TYPES.IUIExtension, SearchAutocompletePalette); diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index f9811ab..85f616d 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -15,15 +15,20 @@ ********************************************************************************/ import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import { KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; import { DeselectKeyTool } from './deselect-key-tool'; import { MovementKeyTool } from './movement-key-tool'; import { ZoomKeyTool } from './zoom-key-tool'; -import { KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; -export const viewKeyToolsModule = new FeatureModule((bind, _unbind, isBound, rebind) => { - const context = { bind, isBound, rebind }; - configureViewKeyTools(context); -}); +export const viewKeyToolsModule = new FeatureModule( + (bind, _unbind, isBound, rebind) => { + const context = { bind, isBound, rebind }; + configureViewKeyTools(context); + }, + { + featureId: Symbol('viewKeyTools') + } +); export function configureViewKeyTools(context: Pick): void { bindAsService(context, TYPES.IDefaultTool, MovementKeyTool); diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 0cd9891..d686ab8 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -35,24 +35,27 @@ import { LocalComputedBoundsCommand } from './local-bounds'; import { SetBoundsFeedbackCommand } from './set-bounds-feedback-command'; import { VBoxLayouterExt } from './vbox-layout'; -export const boundsModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { - const context = { bind, isBound }; - configureCommand(context, SetBoundsCommand); - configureCommand(context, RequestBoundsCommand); - bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); - bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); +export const boundsModule = new FeatureModule( + (bind, _unbind, isBound, _rebind) => { + const context = { bind, isBound }; + configureCommand(context, SetBoundsCommand); + configureCommand(context, RequestBoundsCommand); + bind(HiddenBoundsUpdater).toSelf().inSingletonScope(); + bindAsService(context, TYPES.HiddenVNodePostprocessor, GLSPHiddenBoundsUpdater); - configureCommand(context, LocalComputedBoundsCommand); - configureCommand(context, SetBoundsFeedbackCommand); + configureCommand(context, LocalComputedBoundsCommand); + configureCommand(context, SetBoundsFeedbackCommand); - bind(TYPES.Layouter).to(LayouterExt).inSingletonScope(); - bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); + bind(TYPES.Layouter).to(LayouterExt).inSingletonScope(); + bind(TYPES.LayoutRegistry).to(LayoutRegistry).inSingletonScope(); - configureLayout(context, VBoxLayouter.KIND, VBoxLayouterExt); - configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); - configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); + configureLayout(context, VBoxLayouter.KIND, VBoxLayouterExt); + configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); + configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); - // backwards compatibility - // eslint-disable-next-line deprecation/deprecation - bind(PositionSnapper).toSelf(); -}); + // backwards compatibility + // eslint-disable-next-line deprecation/deprecation + bind(PositionSnapper).toSelf(); + }, + { featureId: Symbol('bounds') } +); diff --git a/packages/client/src/features/command-palette/command-palette-module.ts b/packages/client/src/features/command-palette/command-palette-module.ts index 3bbecd8..61f4228 100644 --- a/packages/client/src/features/command-palette/command-palette-module.ts +++ b/packages/client/src/features/command-palette/command-palette-module.ts @@ -19,9 +19,12 @@ import { GlspCommandPalette } from './command-palette'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; -export const commandPaletteModule = new FeatureModule(bind => { - bindAsService(bind, TYPES.IUIExtension, GlspCommandPalette); - bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); - bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); - bindAsService(bind, TYPES.IDefaultTool, CommandPaletteTool); -}); +export const commandPaletteModule = new FeatureModule( + bind => { + bindAsService(bind, TYPES.IUIExtension, GlspCommandPalette); + bind(TYPES.ICommandPaletteActionProviderRegistry).to(CommandPaletteActionProviderRegistry).inSingletonScope(); + bindAsService(bind, TYPES.ICommandPaletteActionProvider, ServerCommandPaletteActionProvider); + bindAsService(bind, TYPES.IDefaultTool, CommandPaletteTool); + }, + { featureId: Symbol('commandPalette') } +); diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 1ae7c37..46c6999 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -17,17 +17,20 @@ import { ContextMenuProviderRegistry, FeatureModule, IContextMenuService, TYPES, import { GLSPContextMenuMouseListener } from './glsp-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; -export const contextMenuModule = new FeatureModule(bind => { - bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => async () => { - if (ctx.container.isBound(TYPES.IContextMenuService)) { - return ctx.container.get(TYPES.IContextMenuService); - } - console.warn("'TYPES.IContextMenuService' is not bound. Use no-op implementation instead"); - // eslint-disable-next-line @typescript-eslint/no-empty-function - return { show: () => {} }; - }); +export const contextMenuModule = new FeatureModule( + bind => { + bind(TYPES.IContextMenuServiceProvider).toProvider(ctx => async () => { + if (ctx.container.isBound(TYPES.IContextMenuService)) { + return ctx.container.get(TYPES.IContextMenuService); + } + console.warn("'TYPES.IContextMenuService' is not bound. Use no-op implementation instead"); + // eslint-disable-next-line @typescript-eslint/no-empty-function + return { show: () => {} }; + }); - bindAsService(bind, TYPES.MouseListener, GLSPContextMenuMouseListener); - bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); - bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); -}); + bindAsService(bind, TYPES.MouseListener, GLSPContextMenuMouseListener); + bind(TYPES.IContextMenuProviderRegistry).to(ContextMenuProviderRegistry); + bindAsService(bind, TYPES.IContextMenuItemProvider, ServerContextMenuItemProvider); + }, + { featureId: Symbol('contextMenu') } +); diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 435a150..1bb8d4f 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -17,10 +17,13 @@ import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; import { CopyPasteStartup } from './copy-paste-standalone'; -export const copyPasteModule = new FeatureModule((bind, _unbind, isBound) => { - bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); - bind(TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); -}); +export const copyPasteModule = new FeatureModule( + (bind, _unbind, isBound) => { + bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); + bind(TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); + }, + { featureId: Symbol('copyPaste') } +); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -31,5 +34,8 @@ export const standaloneCopyPasteModule = new FeatureModule( (bind, _unbind, isBound) => { bindAsService(bind, TYPES.IDiagramStartup, CopyPasteStartup); }, - { requires: copyPasteModule } + { + featureId: Symbol('standaloneCopyPaste'), + requires: copyPasteModule + } ); diff --git a/packages/client/src/features/debug/debug-module.ts b/packages/client/src/features/debug/debug-module.ts index 0b633e7..5b2bdde 100644 --- a/packages/client/src/features/debug/debug-module.ts +++ b/packages/client/src/features/debug/debug-module.ts @@ -20,13 +20,16 @@ import { DebugBoundsDecorator } from './debug-bounds-decorator'; import { DebugManager } from './debug-manager'; import { EnableDebugModeAction, EnableDebugModeCommand } from './debug-model'; -export const debugModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; +export const debugModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; - configureCommand(context, EnableDebugModeCommand); + configureCommand(context, EnableDebugModeCommand); - bind(DebugManager).toSelf().inSingletonScope(); - configureActionHandler(context, EnableDebugModeAction.KIND, DebugManager); + bind(DebugManager).toSelf().inSingletonScope(); + configureActionHandler(context, EnableDebugModeAction.KIND, DebugManager); - bindAsService(context, TYPES.IVNodePostprocessor, DebugBoundsDecorator); -}); + bindAsService(context, TYPES.IVNodePostprocessor, DebugBoundsDecorator); + }, + { featureId: Symbol('debug') } +); diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index c4865bd..ad0fd8e 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -17,6 +17,9 @@ import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import '../../../css/decoration.css'; import { GlspDecorationPlacer } from './decoration-placer'; -export const decorationModule = new FeatureModule(bind => { - bindAsService(bind, TYPES.IVNodePostprocessor, GlspDecorationPlacer); -}); +export const decorationModule = new FeatureModule( + bind => { + bindAsService(bind, TYPES.IVNodePostprocessor, GlspDecorationPlacer); + }, + { featureId: Symbol('decoration') } +); diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index 3008231..7325b7f 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -17,8 +17,11 @@ import { FeatureModule, configureCommand } from '@eclipse-glsp/sprotty'; import { AddTemplateElementsFeedbackCommand } from './add-template-element'; import { RemoveTemplateElementsFeedbackCommand } from './remove-template-element'; -export const elementTemplateModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureCommand(context, AddTemplateElementsFeedbackCommand); - configureCommand(context, RemoveTemplateElementsFeedbackCommand); -}); +export const elementTemplateModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureCommand(context, AddTemplateElementsFeedbackCommand); + configureCommand(context, RemoveTemplateElementsFeedbackCommand); + }, + { featureId: Symbol('elementTemplate') } +); diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 43bac9d..4467372 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -27,12 +27,15 @@ import { import { ExportSvgActionHandler } from './export-svg-action-handler'; import { GLSPSvgExporter } from './glsp-svg-exporter'; -export const exportModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); - configureCommand(context, ExportSvgCommand); - bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); -}); +export const exportModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); + configureCommand(context, ExportSvgCommand); + bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); + }, + { featureId: Symbol('export') } +); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -46,5 +49,5 @@ export const standaloneExportModule = new FeatureModule( bind(ExportSvgActionHandler).toSelf().inSingletonScope(); configureActionHandler(context, ExportSvgAction.KIND, ExportSvgActionHandler); }, - { requires: exportModule } + { featureId: Symbol('standaloneExport'), requires: exportModule } ); diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index becda00..1b20830 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -20,15 +20,18 @@ import { GridManager } from './grid-manager'; import { ShowGridAction, ShowGridCommand } from './grid-model'; import { GridSnapper } from './grid-snapper'; -export const gridModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; +export const gridModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; - bind(TYPES.Grid).toConstantValue({ x: 10, y: 10 }); + bind(TYPES.Grid).toConstantValue({ x: 10, y: 10 }); - configureCommand(context, ShowGridCommand); + configureCommand(context, ShowGridCommand); - bind(GridManager).toSelf().inSingletonScope(); - configureActionHandler(context, ShowGridAction.KIND, GridManager); + bind(GridManager).toSelf().inSingletonScope(); + configureActionHandler(context, ShowGridAction.KIND, GridManager); - bind(TYPES.ISnapper).to(GridSnapper); -}); + bind(TYPES.ISnapper).to(GridSnapper); + }, + { featureId: Symbol('grid') } +); diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index 03436c9..200dc16 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -31,17 +31,20 @@ import { HelperLineManager } from './helper-line-manager-default'; import { HELPER_LINE, HelperLine, SELECTION_BOUNDS, SelectionBounds } from './model'; import { HelperLineView, SelectionBoundsView } from './view'; -export const helperLineModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureModelElement(context, HELPER_LINE, HelperLine, HelperLineView); - configureModelElement(context, SELECTION_BOUNDS, SelectionBounds, SelectionBoundsView); - configureCommand(context, DrawHelperLinesFeedbackCommand); - configureCommand(context, RemoveHelperLinesFeedbackCommand); +export const helperLineModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureModelElement(context, HELPER_LINE, HelperLine, HelperLineView); + configureModelElement(context, SELECTION_BOUNDS, SelectionBounds, SelectionBoundsView); + configureCommand(context, DrawHelperLinesFeedbackCommand); + configureCommand(context, RemoveHelperLinesFeedbackCommand); - bindAsService(bind, TYPES.IHelperLineManager, HelperLineManager); - configureActionHandler(context, SetBoundsAction.KIND, TYPES.IHelperLineManager); - configureActionHandler(context, SetBoundsFeedbackAction.KIND, TYPES.IHelperLineManager); - configureActionHandler(context, MoveAction.KIND, TYPES.IHelperLineManager); - configureActionHandler(context, MoveInitializedEventAction.KIND, TYPES.IHelperLineManager); - configureActionHandler(context, MoveFinishedEventAction.KIND, TYPES.IHelperLineManager); -}); + bindAsService(bind, TYPES.IHelperLineManager, HelperLineManager); + configureActionHandler(context, SetBoundsAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, SetBoundsFeedbackAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveInitializedEventAction.KIND, TYPES.IHelperLineManager); + configureActionHandler(context, MoveFinishedEventAction.KIND, TYPES.IHelperLineManager); + }, + { featureId: Symbol('helperLine') } +); diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 0da5be7..970b2b7 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -16,10 +16,13 @@ import { FeatureModule, SetTypeHintsAction, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hint-provider'; -export const typeHintsModule = new FeatureModule((bind, unbind, isBound) => { - const context = { bind, unbind, isBound }; - bindAsService(context, TYPES.ITypeHintProvider, TypeHintProvider); - bind(TYPES.IDiagramStartup).toService(TypeHintProvider); - configureActionHandler(context, SetTypeHintsAction.KIND, TypeHintProvider); - configureCommand(context, ApplyTypeHintsCommand); -}); +export const typeHintsModule = new FeatureModule( + (bind, unbind, isBound) => { + const context = { bind, unbind, isBound }; + bindAsService(context, TYPES.ITypeHintProvider, TypeHintProvider); + bind(TYPES.IDiagramStartup).toService(TypeHintProvider); + configureActionHandler(context, SetTypeHintsAction.KIND, TypeHintProvider); + configureCommand(context, ApplyTypeHintsCommand); + }, + { featureId: Symbol('typeHints') } +); diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 454141e..360f4fa 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -35,30 +35,33 @@ import { FocusStateChangedAction } from '../../base/focus/focus-state-change-act import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GlspHoverMouseListener } from './hover'; -export const hoverModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - bindAsService(context, TYPES.PopupVNodePostprocessor, PopupPositionUpdater); - bindAsService(context, TYPES.MouseListener, GlspHoverMouseListener); - bindAsService(context, TYPES.PopupMouseListener, PopupHoverMouseListener); - bindAsService(context, TYPES.KeyListener, HoverKeyListener); +export const hoverModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + bindAsService(context, TYPES.PopupVNodePostprocessor, PopupPositionUpdater); + bindAsService(context, TYPES.MouseListener, GlspHoverMouseListener); + bindAsService(context, TYPES.PopupMouseListener, PopupHoverMouseListener); + bindAsService(context, TYPES.KeyListener, HoverKeyListener); - bind(TYPES.HoverState).toConstantValue({ - mouseOverTimer: undefined, - mouseOutTimer: undefined, - popupOpen: false, - previousPopupElement: undefined - }); - bind(ClosePopupActionHandler).toSelf().inSingletonScope(); + bind(TYPES.HoverState).toConstantValue({ + mouseOverTimer: undefined, + mouseOutTimer: undefined, + popupOpen: false, + previousPopupElement: undefined + }); + bind(ClosePopupActionHandler).toSelf().inSingletonScope(); - configureCommand(context, HoverFeedbackCommand); - configureCommand(context, SetPopupModelCommand); - configureActionHandler(context, SetPopupModelCommand.KIND, ClosePopupActionHandler); - configureActionHandler(context, FitToScreenCommand.KIND, ClosePopupActionHandler); - configureActionHandler(context, CenterCommand.KIND, ClosePopupActionHandler); - configureActionHandler(context, SetViewportCommand.KIND, ClosePopupActionHandler); - configureActionHandler(context, MoveCommand.KIND, ClosePopupActionHandler); - configureActionHandler(context, FocusStateChangedAction.KIND, ClosePopupActionHandler); - configureActionHandler(context, EnableToolsAction.KIND, GlspHoverMouseListener); - configureActionHandler(context, EnableDefaultToolsAction.KIND, GlspHoverMouseListener); - configureActionHandler(context, FocusStateChangedAction.KIND, GlspHoverMouseListener); -}); + configureCommand(context, HoverFeedbackCommand); + configureCommand(context, SetPopupModelCommand); + configureActionHandler(context, SetPopupModelCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, FitToScreenCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, CenterCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, SetViewportCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, MoveCommand.KIND, ClosePopupActionHandler); + configureActionHandler(context, FocusStateChangedAction.KIND, ClosePopupActionHandler); + configureActionHandler(context, EnableToolsAction.KIND, GlspHoverMouseListener); + configureActionHandler(context, EnableDefaultToolsAction.KIND, GlspHoverMouseListener); + configureActionHandler(context, FocusStateChangedAction.KIND, GlspHoverMouseListener); + }, + { featureId: Symbol('hover') } +); diff --git a/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts b/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts index 3b204b1..0a4a384 100644 --- a/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts +++ b/packages/client/src/features/label-edit-ui/label-edit-ui-module.ts @@ -16,9 +16,12 @@ import { EditLabelAction, EditLabelActionHandler, FeatureModule, TYPES, configureActionHandler } from '@eclipse-glsp/sprotty'; import { GlspEditLabelUI } from './label-edit-ui'; -export const labelEditUiModule = new FeatureModule((bind, unbind, isBound, rebind, ...rest) => { - const context = { bind, unbind, isBound, rebind }; - configureActionHandler(context, EditLabelAction.KIND, EditLabelActionHandler); - bind(GlspEditLabelUI).toSelf().inSingletonScope(); - bind(TYPES.IUIExtension).toService(GlspEditLabelUI); -}); +export const labelEditUiModule = new FeatureModule( + (bind, unbind, isBound, rebind, ...rest) => { + const context = { bind, unbind, isBound, rebind }; + configureActionHandler(context, EditLabelAction.KIND, EditLabelActionHandler); + bind(GlspEditLabelUI).toSelf().inSingletonScope(); + bind(TYPES.IUIExtension).toService(GlspEditLabelUI); + }, + { featureId: Symbol('labelEditUi') } +); diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 2e2ed1a..90460ab 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -17,9 +17,12 @@ import { ApplyLabelEditCommand, FeatureModule, TYPES, bindAsService, configureCo import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; -export const labelEditModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { - bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); - bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); - bindAsService(bind, TYPES.IDefaultTool, DirectLabelEditTool); - configureCommand({ bind, isBound }, ApplyLabelEditCommand); -}); +export const labelEditModule = new FeatureModule( + (bind, _unbind, isBound, _rebind) => { + bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); + bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); + bindAsService(bind, TYPES.IDefaultTool, DirectLabelEditTool); + configureCommand({ bind, isBound }, ApplyLabelEditCommand); + }, + { featureId: Symbol('labelEdit') } +); diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index a05828d..8b7df50 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -21,8 +21,11 @@ import { ResizeElementsActionHandler } from './layout-elements-action'; -export const layoutModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); - configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); -}); +export const layoutModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); + configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); + }, + { featureId: Symbol('layout') } +); diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 36d1024..40fe7bf 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -17,11 +17,14 @@ import { FeatureModule, NavigateToExternalTargetAction, NavigateToTargetAction, import { NavigateAction, NavigationActionHandler, ProcessNavigationArgumentsAction } from './navigation-action-handler'; import { NavigationTargetResolver } from './navigation-target-resolver'; -export const navigationModule = new FeatureModule((bind, _unbind, isBound) => { - bind(NavigationTargetResolver).toSelf().inSingletonScope(); - bind(NavigationActionHandler).toSelf().inSingletonScope(); - configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); - configureActionHandler({ bind, isBound }, NavigateToTargetAction.KIND, NavigationActionHandler); - configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); - configureActionHandler({ bind, isBound }, NavigateToExternalTargetAction.KIND, NavigationActionHandler); -}); +export const navigationModule = new FeatureModule( + (bind, _unbind, isBound) => { + bind(NavigationTargetResolver).toSelf().inSingletonScope(); + bind(NavigationActionHandler).toSelf().inSingletonScope(); + configureActionHandler({ bind, isBound }, NavigateAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, NavigateToTargetAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, ProcessNavigationArgumentsAction.KIND, NavigationActionHandler); + configureActionHandler({ bind, isBound }, NavigateToExternalTargetAction.KIND, NavigationActionHandler); + }, + { featureId: Symbol('navigation') } +); diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index a31e4e7..7985178 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -33,25 +33,28 @@ import { } from '@eclipse-glsp/sprotty'; import { GLSPBezierEdgeRouter, GLSPManhattanEdgeRouter, GLSPPolylineEdgeRouter } from './edge-router'; -export const routingModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bind(EdgeRouterRegistry).toSelf().inSingletonScope(); - bind(AnchorComputerRegistry).toSelf().inSingletonScope(); +export const routingModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(EdgeRouterRegistry).toSelf().inSingletonScope(); + bind(AnchorComputerRegistry).toSelf().inSingletonScope(); - bindAsService(context, TYPES.IEdgeRouter, GLSPManhattanEdgeRouter); - bindAsService(context, TYPES.IAnchorComputer, ManhattanEllipticAnchor); - bindAsService(context, TYPES.IAnchorComputer, ManhattanRectangularAnchor); - bindAsService(context, TYPES.IAnchorComputer, ManhattanDiamondAnchor); + bindAsService(context, TYPES.IEdgeRouter, GLSPManhattanEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, ManhattanEllipticAnchor); + bindAsService(context, TYPES.IAnchorComputer, ManhattanRectangularAnchor); + bindAsService(context, TYPES.IAnchorComputer, ManhattanDiamondAnchor); - bindAsService(context, TYPES.IEdgeRouter, GLSPPolylineEdgeRouter); - bindAsService(context, TYPES.IAnchorComputer, EllipseAnchor); - bindAsService(context, TYPES.IAnchorComputer, RectangleAnchor); - bindAsService(context, TYPES.IAnchorComputer, DiamondAnchor); + bindAsService(context, TYPES.IEdgeRouter, GLSPPolylineEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, EllipseAnchor); + bindAsService(context, TYPES.IAnchorComputer, RectangleAnchor); + bindAsService(context, TYPES.IAnchorComputer, DiamondAnchor); - bindAsService(context, TYPES.IEdgeRouter, GLSPBezierEdgeRouter); - bindAsService(context, TYPES.IAnchorComputer, BezierEllipseAnchor); - bindAsService(context, TYPES.IAnchorComputer, BezierRectangleAnchor); - bindAsService(context, TYPES.IAnchorComputer, BezierDiamondAnchor); + bindAsService(context, TYPES.IEdgeRouter, GLSPBezierEdgeRouter); + bindAsService(context, TYPES.IAnchorComputer, BezierEllipseAnchor); + bindAsService(context, TYPES.IAnchorComputer, BezierRectangleAnchor); + bindAsService(context, TYPES.IAnchorComputer, BezierDiamondAnchor); - configureCommand({ bind, isBound }, AddRemoveBezierSegmentCommand); -}); + configureCommand({ bind, isBound }, AddRemoveBezierSegmentCommand); + }, + { featureId: Symbol('routing') } +); diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index c030660..5a97c2d 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -21,6 +21,9 @@ import { SaveModelKeyboardListener } from './save-keylistener'; * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced * with an application native module. */ -export const saveModule = new FeatureModule(bind => { - bindAsService(bind, TYPES.KeyListener, SaveModelKeyboardListener); -}); +export const saveModule = new FeatureModule( + bind => { + bindAsService(bind, TYPES.KeyListener, SaveModelKeyboardListener); + }, + { featureId: Symbol('save') } +); diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index ef57880..6bf147d 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -18,13 +18,16 @@ import { SelectAllCommand, SelectCommand } from '../../base/selection-service'; import { SelectFeedbackCommand } from './select-feedback-command'; import { RankedSelectMouseListener } from './select-mouse-listener'; -export const selectModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - configureCommand(context, SelectCommand); - configureCommand(context, SelectAllCommand); - configureCommand(context, SelectFeedbackCommand); - bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); -}); +export const selectModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + configureCommand(context, SelectCommand); + configureCommand(context, SelectAllCommand); + configureCommand(context, SelectFeedbackCommand); + bindAsService(context, TYPES.MouseListener, RankedSelectMouseListener); + }, + { featureId: Symbol('select') } +); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -35,5 +38,5 @@ export const standaloneSelectModule = new FeatureModule( bind => { bindAsService(bind, TYPES.KeyListener, SelectKeyboardListener); }, - { requires: selectModule } + { featureId: Symbol('standaloneSelect'), requires: selectModule } ); diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index 747cae2..212cbf4 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -16,6 +16,9 @@ import { FeatureModule, SourceModelChangedAction, configureActionHandler } from '@eclipse-glsp/sprotty'; import { SourceModelChangedActionHandler } from './source-model-changed-action-handler'; -export const sourceModelWatcherModule = new FeatureModule((bind, _unbind, isBound) => { - configureActionHandler({ bind, isBound }, SourceModelChangedAction.KIND, SourceModelChangedActionHandler); -}); +export const sourceModelWatcherModule = new FeatureModule( + (bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, SourceModelChangedAction.KIND, SourceModelChangedActionHandler); + }, + { featureId: Symbol('sourceModelWatcher') } +); diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index f9ee341..3ec9672 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -18,9 +18,12 @@ import { FeatureModule, StatusAction, TYPES, bindAsService, configureActionHandl import '../../../css/status-overlay.css'; import { StatusOverlay } from './status-overlay'; -export const statusModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IUIExtension, StatusOverlay); - bind(TYPES.IDiagramStartup).toService(StatusOverlay); - configureActionHandler(context, StatusAction.KIND, StatusOverlay); -}); +export const statusModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IUIExtension, StatusOverlay); + bind(TYPES.IDiagramStartup).toService(StatusOverlay); + configureActionHandler(context, StatusAction.KIND, StatusOverlay); + }, + { featureId: Symbol('status') } +); diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index 2d28205..cbab06a 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -17,7 +17,10 @@ import { FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { MetadataPlacer } from './metadata-placer'; -export const svgMetadataModule = new FeatureModule(bind => { - bind(MetadataPlacer).toSelf().inSingletonScope(); - bind(TYPES.IVNodePostprocessor).toService(MetadataPlacer); -}); +export const svgMetadataModule = new FeatureModule( + bind => { + bind(MetadataPlacer).toSelf().inSingletonScope(); + bind(TYPES.IVNodePostprocessor).toService(MetadataPlacer); + }, + { featureId: Symbol('svgMetadata') } +); diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index a8a4f4b..f7dbf86 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -18,10 +18,13 @@ import '../../../css/tool-palette.css'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { ToolPalette } from './tool-palette'; -export const toolPaletteModule = new FeatureModule((bind, _unbind, isBound, _rebind) => { - bindAsService(bind, TYPES.IUIExtension, ToolPalette); - bind(TYPES.IDiagramStartup).toService(ToolPalette); - configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); - configureActionHandler({ bind, isBound }, UpdateModelAction.KIND, ToolPalette); - configureActionHandler({ bind, isBound }, SetModelAction.KIND, ToolPalette); -}); +export const toolPaletteModule = new FeatureModule( + (bind, _unbind, isBound, _rebind) => { + bindAsService(bind, TYPES.IUIExtension, ToolPalette); + bind(TYPES.IDiagramStartup).toService(ToolPalette); + configureActionHandler({ bind, isBound }, EnableDefaultToolsAction.KIND, ToolPalette); + configureActionHandler({ bind, isBound }, UpdateModelAction.KIND, ToolPalette); + configureActionHandler({ bind, isBound }, SetModelAction.KIND, ToolPalette); + }, + { featureId: Symbol('toolPalette') } +); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index e20655c..9ca32eb 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -21,11 +21,14 @@ import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { SResizeHandleView } from './view'; -export const changeBoundsToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bind(ChangeBoundsManager).toSelf().inSingletonScope(); - bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); - configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); - configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); - configureView(context, SResizeHandle.TYPE, SResizeHandleView); -}); +export const changeBoundsToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(ChangeBoundsManager).toSelf().inSingletonScope(); + bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); + configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); + configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); + configureView(context, SResizeHandle.TYPE, SResizeHandleView); + }, + { featureId: Symbol('changeBoundsTool') } +); diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 1581148..444a5cb 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -16,8 +16,11 @@ import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; -export const deletionToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); - bindAsService(context, TYPES.ITool, MouseDeleteTool); -}); +export const deletionToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, DelKeyDeleteTool); + bindAsService(context, TYPES.ITool, MouseDeleteTool); + }, + { featureId: Symbol('deletionTool') } +); diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index 78d2ee5..dd605a7 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -17,9 +17,12 @@ import { FeatureModule, TYPES, TriggerEdgeCreationAction, bindAsService, configu import { configureDanglingFeedbackEdge } from './dangling-edge-feedback'; import { EdgeCreationTool } from './edge-creation-tool'; -export const edgeCreationToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.ITool, EdgeCreationTool); - configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); - configureDanglingFeedbackEdge(context); -}); +export const edgeCreationToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.ITool, EdgeCreationTool); + configureActionHandler(context, TriggerEdgeCreationAction.KIND, EdgeCreationTool); + configureDanglingFeedbackEdge(context); + }, + { featureId: Symbol('edgeCreationTool') } +); diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index f7c7752..42aa7a2 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -23,16 +23,19 @@ import { SwitchRoutingModeCommand } from './edge-edit-tool-feedback'; -export const edgeEditToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); +export const edgeEditToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, EdgeEditTool); - // reconnect edge tool feedback - configureCommand(context, ShowEdgeReconnectHandlesFeedbackCommand); - configureCommand(context, HideEdgeReconnectHandlesFeedbackCommand); - configureCommand(context, DrawFeedbackEdgeSourceCommand); - configureCommand(context, SwitchRoutingModeCommand); + // reconnect edge tool feedback + configureCommand(context, ShowEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, HideEdgeReconnectHandlesFeedbackCommand); + configureCommand(context, DrawFeedbackEdgeSourceCommand); + configureCommand(context, SwitchRoutingModeCommand); - // dangling edge feedback - configureDanglingFeedbackEdge(context); -}); + // dangling edge feedback + configureDanglingFeedbackEdge(context); + }, + { featureId: Symbol('edgeEditTool') } +); diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index 6a7998b..43a4a2b 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -20,13 +20,16 @@ import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-too import { MarqueeNode } from './model'; import { MarqueeView } from './view'; -export const marqueeSelectionToolModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bindAsService(context, TYPES.IDefaultTool, MarqueeTool); - bindAsService(context, TYPES.ITool, MarqueeMouseTool); +export const marqueeSelectionToolModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.IDefaultTool, MarqueeTool); + bindAsService(context, TYPES.ITool, MarqueeMouseTool); - configureCommand(context, DrawMarqueeCommand); - configureCommand(context, RemoveMarqueeCommand); + configureCommand(context, DrawMarqueeCommand); + configureCommand(context, RemoveMarqueeCommand); - configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); -}); + configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); + }, + { featureId: Symbol('marqueeSelectionTool') } +); diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index 6d17ad5..d26e945 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -35,5 +35,8 @@ export const nodeCreationToolModule = new FeatureModule( configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); configureModelElement(context, InsertIndicator.TYPE, InsertIndicator, InsertIndicatorView); }, - { requires: elementTemplateModule } + { + featureId: Symbol('nodeCreationTool'), + requires: elementTemplateModule + } ); diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index 1f740a5..e83a781 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { Action, FeatureModule, IActionHandler, ICommand, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; @@ -34,6 +34,9 @@ export class EnableDefaultToolsOnFocusLossHandler implements IActionHandler { /** * Enables the default tools in the tool manager if the diagram looses focus. */ -export const toolFocusLossModule = new FeatureModule((bind, _unbind, isBound) => { - configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); -}); +export const toolFocusLossModule = new FeatureModule( + (bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); + }, + { featureId: Symbol('toolFocusLoss ') } +); diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 3f78560..6dacf57 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -25,19 +25,25 @@ import { } from './marker-navigator'; import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersActionHandler, ValidationFeedbackEmitter } from './validate'; -export const validationModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - configureActionHandler(context, SetMarkersAction.KIND, SetMarkersActionHandler); - configureCommand(context, ApplyMarkersCommand); - configureCommand(context, DeleteMarkersCommand); - bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); -}); +export const validationModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + configureActionHandler(context, SetMarkersAction.KIND, SetMarkersActionHandler); + configureCommand(context, ApplyMarkersCommand); + configureCommand(context, DeleteMarkersCommand); + bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); + }, + { featureId: Symbol('validation') } +); -export const markerNavigatorModule = new FeatureModule((bind, _unbind, isBound) => { - bind(GModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); - bind(MarkerNavigator).toSelf().inSingletonScope(); - configureActionHandler({ bind, isBound }, NavigateToMarkerAction.KIND, NavigateToMarkerActionHandler); -}); +export const markerNavigatorModule = new FeatureModule( + (bind, _unbind, isBound) => { + bind(GModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); + bind(MarkerNavigator).toSelf().inSingletonScope(); + configureActionHandler({ bind, isBound }, NavigateToMarkerAction.KIND, NavigateToMarkerActionHandler); + }, + { featureId: Symbol('markerNavigator') } +); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -49,5 +55,5 @@ export const standaloneMarkerNavigatorModule = new FeatureModule( bindAsService(bind, TYPES.IContextMenuProvider, MarkerNavigatorContextMenuItemProvider); bindAsService(bind, TYPES.KeyListener, MarkerNavigatorKeyListener); }, - { requires: markerNavigatorModule } + { featureId: Symbol('standaloneMarkerNavigator'), requires: markerNavigatorModule } ); diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index e2ea673..7c86c7c 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -33,25 +33,28 @@ import { OriginViewportCommand } from './origin-viewport'; import { RepositionCommand } from './reposition'; import { RestoreViewportHandler } from './viewport-handler'; -export const viewportModule = new FeatureModule((bind, _unbind, isBound) => { - const context = { bind, isBound }; - configureCommand(context, CenterCommand); - configureCommand(context, FitToScreenCommand); - configureCommand(context, GetViewportCommand); - configureCommand(context, SetViewportCommand); - configureCommand(context, RepositionCommand); - configureCommand(context, OriginViewportCommand); +export const viewportModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + configureCommand(context, CenterCommand); + configureCommand(context, FitToScreenCommand); + configureCommand(context, GetViewportCommand); + configureCommand(context, SetViewportCommand); + configureCommand(context, RepositionCommand); + configureCommand(context, OriginViewportCommand); - bindAsService(context, TYPES.MouseListener, ZoomMouseListener); - bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); + bindAsService(context, TYPES.MouseListener, ZoomMouseListener); + bindAsService(context, TYPES.MouseListener, GLSPScrollMouseListener); - configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); - configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); + configureActionHandler(context, EnableToolsAction.KIND, GLSPScrollMouseListener); + configureActionHandler(context, EnableDefaultToolsAction.KIND, GLSPScrollMouseListener); - bindAsService(context, TYPES.IDiagramStartup, RestoreViewportHandler); - configureActionHandler(context, EnableDefaultToolsAction.KIND, RestoreViewportHandler); - configureActionHandler(context, FocusDomAction.KIND, RestoreViewportHandler); -}); + bindAsService(context, TYPES.IDiagramStartup, RestoreViewportHandler); + configureActionHandler(context, EnableDefaultToolsAction.KIND, RestoreViewportHandler); + configureActionHandler(context, FocusDomAction.KIND, RestoreViewportHandler); + }, + { featureId: Symbol('viewport') } +); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -63,5 +66,5 @@ export const standaloneViewportModule = new FeatureModule( const context = { bind, isBound }; bindAsService(context, TYPES.KeyListener, CenterKeyboardListener); }, - { requires: viewportModule } + { featureId: Symbol('standaloneViewport'), requires: viewportModule } ); diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index cbbb874..2c2a467 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -42,15 +42,18 @@ import { undoRedoModule } from './features/undo-redo/undo-redo-module'; import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; import { standaloneViewportModule } from './features/viewport/viewport-modules'; -export const standaloneDefaultModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bind(FallbackActionHandler).toSelf().inSingletonScope(); - configureActionHandler(context, MessageAction.KIND, FallbackActionHandler); - configureActionHandler(context, StartProgressAction.KIND, FallbackActionHandler); - configureActionHandler(context, UpdateProgressAction.KIND, FallbackActionHandler); - configureActionHandler(context, EndProgressAction.KIND, FallbackActionHandler); - bindAsService(context, TYPES.KeyListener, LayoutKeyListener); -}); +export const standaloneDefaultModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(FallbackActionHandler).toSelf().inSingletonScope(); + configureActionHandler(context, MessageAction.KIND, FallbackActionHandler); + configureActionHandler(context, StartProgressAction.KIND, FallbackActionHandler); + configureActionHandler(context, UpdateProgressAction.KIND, FallbackActionHandler); + configureActionHandler(context, EndProgressAction.KIND, FallbackActionHandler); + bindAsService(context, TYPES.KeyListener, LayoutKeyListener); + }, + { featureId: Symbol('standaloneDefault') } +); /** * A fallback action handler for actions sent by features that are currently not supported by diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 3e198cf..647fccc 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -53,10 +53,13 @@ import { GGraphView } from './ggraph-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; -export const baseViewModule = new FeatureModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureDefaultModelElements(context); -}); +export const baseViewModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureDefaultModelElements(context); + }, + { featureId: Symbol('baseView') } +); export function configureDefaultModelElements(context: Pick): void { // HTML elements diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index c2733bd..1103593 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "^2.2.0-next", + "@eclipse-glsp/protocol": "2.2.0-next", "autocompleter": "^9.1.0", "snabbdom": "^3.5.1", "sprotty": "1.0.0", diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts index aed1524..169ec80 100644 --- a/packages/glsp-sprotty/src/feature-modules.ts +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -28,16 +28,16 @@ import sprottyUpdateModule from 'sprotty/lib/features/update/di.config'; import sprottyZorderModule from 'sprotty/lib/features/zorder/di.config'; import sprottyModelSourceModule from 'sprotty/lib/model-source/di.config'; -export const buttonModule = new FeatureModule(sprottyButtonModule.registry); -export const edgeEditModule = new FeatureModule(sprottyEdgeEditModule.registry); -export const edgeIntersectionModule = new FeatureModule(sprottyEdgeIntersectionModule.registry); -export const edgeLayoutModule = new FeatureModule(sprottyEdgeLayoutModule.registry); -export const expandModule = new FeatureModule(sprottyExpandModule.registry); -export const fadeModule = new FeatureModule(sprottyFadeModule.registry); -export const modelSourceModule = new FeatureModule(sprottyModelSourceModule.registry); -export const moveModule = new FeatureModule(sprottyMoveModule.registry); -export const openModule = new FeatureModule(sprottyOpenModule.registry); -export const updateModule = new FeatureModule(sprottyUpdateModule.registry); -export const zorderModule = new FeatureModule(sprottyZorderModule.registry); +export const buttonModule = new FeatureModule(sprottyButtonModule.registry, { featureId: Symbol('button') }); +export const edgeEditModule = new FeatureModule(sprottyEdgeEditModule.registry, { featureId: Symbol('edgeEdit') }); +export const edgeIntersectionModule = new FeatureModule(sprottyEdgeIntersectionModule.registry, { featureId: Symbol('edgeIntersection') }); +export const edgeLayoutModule = new FeatureModule(sprottyEdgeLayoutModule.registry, { featureId: Symbol('edgeLayout') }); +export const expandModule = new FeatureModule(sprottyExpandModule.registry, { featureId: Symbol('expand') }); +export const fadeModule = new FeatureModule(sprottyFadeModule.registry, { featureId: Symbol('fade') }); +export const modelSourceModule = new FeatureModule(sprottyModelSourceModule.registry, { featureId: Symbol('modelSource') }); +export const moveModule = new FeatureModule(sprottyMoveModule.registry, { featureId: Symbol('move') }); +export const openModule = new FeatureModule(sprottyOpenModule.registry, { featureId: Symbol('open') }); +export const updateModule = new FeatureModule(sprottyUpdateModule.registry, { featureId: Symbol('update') }); +export const zorderModule = new FeatureModule(sprottyZorderModule.registry, { featureId: Symbol('zorder') }); export { sprottyDefaultModule }; diff --git a/packages/protocol/src/di/container-configuration.spec.ts b/packages/protocol/src/di/container-configuration.spec.ts index 178b72c..586c9dd 100644 --- a/packages/protocol/src/di/container-configuration.spec.ts +++ b/packages/protocol/src/di/container-configuration.spec.ts @@ -15,53 +15,56 @@ ********************************************************************************/ import { expect } from 'chai'; import { Container } from 'inversify'; -import * as sinon from 'sinon'; -import { initializeContainer } from './container-configuration'; +import { resolveContainerConfiguration } from './container-configuration'; import { FeatureModule } from './feature-module'; -const sandbox = sinon.createSandbox(); -const container = new Container(); -const loadSpy = sandbox.spy(container, 'load'); -container.snapshot(); +const moduleA = new FeatureModule(() => {}, { featureId: Symbol('moduleA') }); +const moduleB = new FeatureModule(() => {}, { featureId: Symbol('moduleB') }); +const moduleC = new FeatureModule(() => {}, { featureId: Symbol('moduleC'), requires: [moduleA, moduleB] }); -const moduleA = new FeatureModule(() => {}); -const moduleB = new FeatureModule(() => {}); -const moduleC = new FeatureModule(() => {}); +FeatureModule.DEBUG_LOG_ENABLED = true; +const container = new Container(); +container.load(moduleC); describe('Container configuration', () => { - describe('initializeContainer', () => { - beforeEach(() => { - sandbox.reset(); - container.restore(); - container.snapshot(); - }); - it('should load the given container modules', () => { - initializeContainer(container, moduleA, moduleB, moduleC); - expect(loadSpy.calledOnce).to.equal(true); - expect(loadSpy.firstCall.args).to.deep.equal([moduleA, moduleB, moduleC]); + describe('resolveContainerConfiguration', () => { + it('should resolve the given container modules in incoming order', () => { + const result = resolveContainerConfiguration(moduleA, moduleB, moduleC); + expect(result).to.deep.equal([moduleA, moduleB, moduleC]); }); - it('should load the same container module only once', () => { - initializeContainer(container, moduleA, moduleA); - expect(loadSpy.calledOnce).to.equal(true); - expect(loadSpy.firstCall.args).to.deep.equal([moduleA]); + it('should resolve the same container module only once', () => { + const result = resolveContainerConfiguration(moduleA, moduleA); + expect(result).to.deep.equal([moduleA]); }); - it('should load the given container modules and add configurations', () => { - initializeContainer(container, moduleA, { add: [moduleB, moduleC] }); - expect(loadSpy.calledOnce).to.equal(true); - expect(loadSpy.firstCall.args).to.deep.equal([moduleA, moduleB, moduleC]); + it('should resolve the given container modules and add configurations', () => { + const result = resolveContainerConfiguration(moduleA, { add: [moduleB, moduleC] }); + expect(result).to.deep.equal([moduleA, moduleB, moduleC]); }); - it('should load the given container modules/add configurations and not load modules from remove configurations', () => { - initializeContainer(container, moduleA, { + it('should resolve the given container modules/add configurations and not load modules from remove configurations', () => { + const result = resolveContainerConfiguration(moduleA, { add: [moduleB, moduleC], remove: moduleA }); - expect(loadSpy.calledOnce).to.equal(true); - expect(loadSpy.firstCall.args).to.deep.equal([moduleB, moduleC]); + expect(result).to.deep.equal([moduleB, moduleC]); + }); + it('should resolve a module from a remove configuration if it is re-added with a subsequent add configuration', () => { + const result = resolveContainerConfiguration(moduleA, { remove: moduleA }, moduleA); + expect(result).to.deep.equal([moduleA]); + }); + it('should resolve a module from a replace configuration instead of a prior added module with the same feature id', () => { + const replaceModule = new FeatureModule(() => {}, { featureId: moduleA.featureId }); + const result = resolveContainerConfiguration(moduleA, moduleB, { replace: replaceModule }); + expect(result).to.deep.equal([replaceModule, moduleB]); + }); + // eslint-disable-next-line max-len + it('should still resolve a module from a replace configuration if there is no prior added module with the same featureId to replace', () => { + const replaceModule = new FeatureModule(() => {}, { featureId: Symbol('replaceModule') }); + const result = resolveContainerConfiguration(moduleA, moduleB, { replace: replaceModule }); + expect(result).to.deep.equal([moduleA, moduleB, replaceModule]); }); - it('should load a module from a remove configuration if it is added later again', () => { - initializeContainer(container, moduleA, { remove: moduleA }, moduleA); - expect(loadSpy.calledOnce).to.equal(true); - expect(loadSpy.firstCall.args).to.deep.equal([moduleA]); + it('should throw an error for a configuration that resolves to multiple feature modules with the same featureId', () => { + const duplicateModule = new FeatureModule(() => {}, { featureId: moduleA.featureId }); + expect(() => resolveContainerConfiguration(moduleA, duplicateModule)).to.throw(); }); }); }); diff --git a/packages/protocol/src/di/container-configuration.ts b/packages/protocol/src/di/container-configuration.ts index 230af51..26fbf62 100644 --- a/packages/protocol/src/di/container-configuration.ts +++ b/packages/protocol/src/di/container-configuration.ts @@ -17,6 +17,7 @@ import { Container, ContainerModule } from 'inversify'; import { MaybeArray, asArray, distinctAdd, remove } from '../utils/array-util'; import { hasFunctionProp, hasNumberProp } from '../utils/type-util'; +import { FeatureModule } from './feature-module'; /** * Initializes a container with the given {@link ContainerConfiguration}. The container configuration @@ -36,8 +37,10 @@ export function initializeContainer(container: Container, ...containerConfigurat /** * Processes the given container configurations and returns the corresponding set of {@link ContainerModule}s. * Container configurations are processed in the order they are passed. If a module is configured to be removed - * it can be added again in a later configuration. - * @param containerConfigurations The container configurations to resolves + * it can be added again in a later configuration. This also means in case of `replace` configurations that affect the same feature id + * the last configuration wins. + * @param containerConfigurations The container configurations to resolve + * @throws An error if featureModule ids are not unique in the resolved module array * @returns an Array of resolved container modules */ export function resolveContainerConfiguration(...containerConfigurations: ContainerConfiguration): ContainerModule[] { @@ -52,8 +55,39 @@ export function resolveContainerConfiguration(...containerConfigurations: Contai if (config.add) { distinctAdd(modules, ...asArray(config.add)); } + if (config.replace) { + asArray(config.replace).forEach(replace => { + const existingIndex = modules.findIndex(m => m instanceof FeatureModule && m.featureId === replace.featureId); + if (existingIndex >= 0) { + modules[existingIndex] = replace; + } else { + console.warn( + `Could not find module to replace with feature id ${replace.featureId.toString()}.` + + 'Adding replacement module to the end of the resolved configurations.' + ); + distinctAdd(modules, replace); + } + }); + } + } + }); + + // Check for duplicate feature ids in resolved modules + const featureIds = new Set(); + const duplicates: FeatureModule[] = []; + modules.forEach(module => { + if (module instanceof FeatureModule) { + if (featureIds.has(module.featureId)) { + duplicates.push(module); + } else { + featureIds.add(module.featureId); + } } }); + if (duplicates.length > 0) { + const culprits = duplicates.map(m => m.featureId).join(', '); + throw new Error(`Could not resolve container configuration. Non-unique feature ids found in container configuration: ${culprits}`); + } return modules; } /** @@ -76,8 +110,15 @@ export type ContainerConfiguration = Array; /** Set of modules that should be loaded into the container */ remove?: MaybeArray; + /** + * Set of feature modules that should be loaded into the container and + * replace potential already configured modules with the same feature id. + * When resolving the replacement module will be added at the index of the module it replaces. + * If there is no module to replace, the replacement module will be added to the end of the list (i.e. behaves like `add`). + */ + replace?: MaybeArray; } diff --git a/packages/protocol/src/di/feature-module.spec.ts b/packages/protocol/src/di/feature-module.spec.ts index 0c562ea..8210fc1 100644 --- a/packages/protocol/src/di/feature-module.spec.ts +++ b/packages/protocol/src/di/feature-module.spec.ts @@ -70,6 +70,11 @@ describe('FeatureModule', () => { expect(container.isBound(moduleWithUnmetRequirements.featureId)).to.be.false; expect(container.isBound('Foo')).to.be.false; }); + it('Should throw an error if a feature module with the same featureId is already loaded', () => { + const loadedModule = new FeatureModule(() => {}, { featureId: moduleA.featureId }); + container.load(loadedModule); + expect(() => container.load(moduleA)).to.throw(); + }); }); describe('Multiple required modules', () => { it('Should load feature module with loaded required modules', () => { diff --git a/packages/protocol/src/di/feature-module.ts b/packages/protocol/src/di/feature-module.ts index 233cb2f..a326b72 100644 --- a/packages/protocol/src/di/feature-module.ts +++ b/packages/protocol/src/di/feature-module.ts @@ -31,6 +31,7 @@ export interface FeatureModuleOptions { */ featureId?: symbol; } + /** * A `FeatureModule` is a specialized {@link ContainerModule} that can declare dependencies to other {@link FeatureModule}. * A feature module will only be loaded into a container if all of its required modules haven been loaded before. T @@ -39,6 +40,11 @@ export interface FeatureModuleOptions { */ export class FeatureModule extends ContainerModule { + /** + * Global flag to enable/disable additional debug log output when loading feature modules + * Default is `false`. + */ + public static DEBUG_LOG_ENABLED = false; readonly featureId: symbol; readonly requires?: MaybeArray; @@ -47,6 +53,7 @@ export class FeatureModule extends ContainerModule { super((bind, unbind, isBound, ...rest) => { if (this.configure(bind, isBound)) { registry(bind, unbind, isBound, ...rest); + this.debugLog(`Loading of feature module with id '${this.featureId.toString()}' completed`); } }); this.featureId = options.featureId ?? this.createFeatureId(); @@ -65,20 +72,45 @@ export class FeatureModule extends ContainerModule { * @returns `true` if all requirements are met and the module is loaded. `false` otherwise */ configure(bind: interfaces.Bind, isBound: interfaces.IsBound): boolean { + this.debugLog(`Trying to load feature module with id '${this.featureId.toString()}'`); + if (this.isLoaded({ isBound })) { + const message = `Could not load feature module. Another module with id '${this.featureId.toString()}' is already loaded`; + this.debugLog(message); + throw new Error(message); + } if (this.checkRequirements(isBound)) { + this.debugLog(`Requirements are met, continue loading of feature module with id '${this.featureId.toString()}'`); bind(this.featureId).toConstantValue(this.featureId); return true; } return false; } + protected debugLog(message?: any, ...optionalParams: any[]): void { + if (FeatureModule.DEBUG_LOG_ENABLED) { + console.log(message, ...optionalParams); + } + } + /** * Checks if all required {@link FeatureModule}s are already loaded/bound in the container. * @param isBound The `isBound` property of the module callback. Used to check the required modules. * @returns `true` if all requirements are met, `false` otherwise */ protected checkRequirements(isBound: interfaces.IsBound): boolean { - return this.requires ? asArray(this.requires).every(module => isBound(module.featureId)) : true; + const requires = asArray(this.requires ?? []); + if (requires.length === 0) { + return true; + } + const missing = requires.filter(module => !module.isLoaded({ isBound })); + if (missing.length > 0) { + this.debugLog( + // eslint-disable-next-line max-len + `Could not load feature module. Required modules are not loaded. Feature ids: ${missing.map(m => m.featureId.toString()).join(', ')}` + ); + return false; + } + return true; } isLoaded(context: Pick): boolean { From 13ccbda75b1a3368c0dadf6da62dfec07474e6d8 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 12 Jun 2024 13:39:53 +0200 Subject: [PATCH 346/566] Cleanup & depreaction (#356) - Adjust name of `SResizeHandle` and `SResizeHandleview` to the gmodel namespace and reexport under the old name as deprecated for backwards compatibility. - Ensure that all diagram modules are constructed as `FeatureModules` instead of plain `ContainerModules` - Rename modules that still have the glsp prefix and also reexport under old deprecated name --- .../src/workflow-diagram-module.ts | 72 ++++++++++--------- .../accessibility/accessibility-module.ts | 4 +- .../element-navigation-module.ts | 27 ++++--- .../focus-tracker/focus-tracker-module.ts | 21 ++++-- .../{di.config.ts => shortcut-help-module.ts} | 21 ++++-- .../accessibility/toast/toast-module.ts | 21 ++++-- .../view-key-tools/deselect-key-tool.ts | 16 ++--- .../src/features/change-bounds/model.ts | 29 ++++---- .../change-bounds/movement-restrictor.ts | 6 +- .../helper-line-manager-default.ts | 4 +- .../features/select/select-mouse-listener.ts | 4 +- .../change-bounds/change-bounds-manager.ts | 4 +- .../change-bounds-tool-module.ts | 6 +- .../change-bounds-tool-move-feedback.ts | 6 +- .../tools/change-bounds/change-bounds-tool.ts | 8 +-- .../change-bounds/change-bounds-tracker.ts | 12 ++-- .../src/features/tools/change-bounds/view.tsx | 17 +++-- .../features/undo-redo/undo-redo-module.ts | 14 ++-- packages/client/src/index.ts | 2 +- 19 files changed, 169 insertions(+), 125 deletions(-) rename packages/client/src/features/accessibility/key-shortcut/{di.config.ts => shortcut-help-module.ts} (74%) diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index c73a436..f913e12 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -20,6 +20,7 @@ import { DefaultTypes, DeleteElementContextMenuItemProvider, DiamondNodeView, + FeatureModule, GCompartment, GCompartmentView, GEdge, @@ -45,7 +46,7 @@ import { initializeDiagramContainer } from '@eclipse-glsp/client'; import 'balloon-css/balloon.min.css'; -import { Container, ContainerModule } from 'inversify'; +import { Container } from 'inversify'; import 'sprotty/css/edit-label.css'; import '../css/diagram.css'; import { directTaskEditor } from './direct-task-editing/di.config'; @@ -54,43 +55,46 @@ import { WorkflowSnapper } from './workflow-snapper'; import { WorkflowStartup } from './workflow-startup'; import { IconView, WorkflowEdgeView } from './workflow-views'; -export const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; +export const workflowDiagramModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; - bindOrRebind(context, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); - bindOrRebind(context, TYPES.LogLevel).toConstantValue(LogLevel.warn); - bindAsService(context, TYPES.ICommandPaletteActionProvider, RevealNamedElementActionProvider); - bindAsService(context, TYPES.IContextMenuItemProvider, DeleteElementContextMenuItemProvider); + bindOrRebind(context, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); + bindOrRebind(context, TYPES.LogLevel).toConstantValue(LogLevel.warn); + bindAsService(context, TYPES.ICommandPaletteActionProvider, RevealNamedElementActionProvider); + bindAsService(context, TYPES.IContextMenuItemProvider, DeleteElementContextMenuItemProvider); - configureDefaultModelElements(context); - configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); - configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); - configureModelElement(context, 'label:heading', GLabel, GLabelView, { enable: [editLabelFeature] }); - configureModelElement(context, 'comp:comp', GCompartment, GCompartmentView); - configureModelElement(context, 'comp:header', GCompartment, GCompartmentView); - configureModelElement(context, 'label:icon', GLabel, GLabelView); - configureModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); - configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); - configureModelElement(context, 'icon', Icon, IconView); - configureModelElement(context, 'activityNode:merge', BranchingNode, DiamondNodeView); - configureModelElement(context, 'activityNode:decision', BranchingNode, DiamondNodeView); - configureModelElement(context, 'activityNode:fork', SynchronizationNode, RectangularNodeView); - configureModelElement(context, 'activityNode:join', SynchronizationNode, RectangularNodeView); - configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); - configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); - configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); + configureDefaultModelElements(context); + configureModelElement(context, 'task:automated', TaskNode, RoundedCornerNodeView); + configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); + configureModelElement(context, 'label:heading', GLabel, GLabelView, { enable: [editLabelFeature] }); + configureModelElement(context, 'comp:comp', GCompartment, GCompartmentView); + configureModelElement(context, 'comp:header', GCompartment, GCompartmentView); + configureModelElement(context, 'label:icon', GLabel, GLabelView); + configureModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); + configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); + configureModelElement(context, 'icon', Icon, IconView); + configureModelElement(context, 'activityNode:merge', BranchingNode, DiamondNodeView); + configureModelElement(context, 'activityNode:decision', BranchingNode, DiamondNodeView); + configureModelElement(context, 'activityNode:fork', SynchronizationNode, RectangularNodeView); + configureModelElement(context, 'activityNode:join', SynchronizationNode, RectangularNodeView); + configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); + configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); + configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); - bind(TYPES.IHelperLineOptions).toDynamicValue(ctx => { - const options: IHelperLineOptions = {}; - // skip icons for alignment as well as compartments which are only used for structure - options.alignmentElementFilter = element => - DEFAULT_ALIGNABLE_ELEMENT_FILTER(element) && !(element instanceof Icon) && !(element instanceof GCompartment); - return options; - }); + bind(TYPES.IHelperLineOptions).toDynamicValue(ctx => { + const options: IHelperLineOptions = {}; + // skip icons for alignment as well as compartments which are only used for structure + options.alignmentElementFilter = element => + DEFAULT_ALIGNABLE_ELEMENT_FILTER(element) && !(element instanceof Icon) && !(element instanceof GCompartment); + return options; + }); - bindAsService(context, TYPES.IDiagramStartup, WorkflowStartup); - bindOrRebind(context, TYPES.ISnapper).to(WorkflowSnapper); -}); + bindAsService(context, TYPES.IDiagramStartup, WorkflowStartup); + bindOrRebind(context, TYPES.ISnapper).to(WorkflowSnapper); + }, + { featureId: Symbol('workflowDiagram') } +); export function createWorkflowDiagramContainer(...containerConfiguration: ContainerConfiguration): Container { return initializeWorkflowDiagramContainer(new Container(), ...containerConfiguration); diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 5665ce8..3f962ed 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,7 +17,7 @@ import { FeatureModule } from '@eclipse-glsp/sprotty'; import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; -import { configureShortcutHelpTool } from './key-shortcut/di.config'; +import { configureShortcutHelpTool } from './key-shortcut/shortcut-help-module'; import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; import { configureMoveZoom } from './move-zoom/move-zoom-module'; diff --git a/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts index 3017777..c301d29 100644 --- a/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts +++ b/packages/client/src/features/accessibility/element-navigation/element-navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,24 +13,31 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { BindingContext, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; -import { PositionNavigator } from './position-navigator'; -import { LocalElementNavigator } from './local-element-navigator'; -import { ElementNavigatorTool } from './diagram-navigation-tool'; +import { BindingContext, FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import '../../../../css/navigation.css'; +import { ElementNavigatorTool } from './diagram-navigation-tool'; +import { LocalElementNavigator } from './local-element-navigator'; +import { PositionNavigator } from './position-navigator'; /** * Handles element navigation actions. */ -export const glspElementNavigationModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureElementNavigationTool(context); -}); +export const elementNavigationModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureElementNavigationTool(context); + }, + { featureId: Symbol('elementNavigation') } +); export function configureElementNavigationTool(context: BindingContext): void { bindAsService(context, TYPES.IDefaultTool, ElementNavigatorTool); bindAsService(context, TYPES.IElementNavigator, PositionNavigator); bindAsService(context, TYPES.ILocalElementNavigator, LocalElementNavigator); } + +export { + /** Deprecated use {@link elementNavigationModule} instead */ + elementNavigationModule as glspElementNavigationModule +}; diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts index b40db5c..be777b8 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,19 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; +import { BindingContext, FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; import { FocusTrackerTool } from './focus-tracker-tool'; -import { BindingContext, TYPES, bindAsService } from '@eclipse-glsp/sprotty'; /** * Handles actions for tracking the focus of the cursor. */ -export const glspFocusTrackerModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureFocusTrackerTool(context); -}); +export const focusTrackerModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureFocusTrackerTool(context); + }, + { featureId: Symbol('focusTracker') } +); export function configureFocusTrackerTool(context: BindingContext): void { bindAsService(context, TYPES.IDefaultTool, FocusTrackerTool); } + +export { + /** Deprecated use {@link focusTrackerModule} instead */ + focusTrackerModule as glspFocusTrackerModule +}; diff --git a/packages/client/src/features/accessibility/key-shortcut/di.config.ts b/packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts similarity index 74% rename from packages/client/src/features/accessibility/key-shortcut/di.config.ts rename to packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts index d92459e..e3c3c64 100644 --- a/packages/client/src/features/accessibility/key-shortcut/di.config.ts +++ b/packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, configureActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import '../../../../css/key-shortcut.css'; import { KeyShortcutUIExtension, SetAccessibleKeyShortcutAction } from './accessible-key-shortcut'; import { AccessibleKeyShortcutTool } from './accessible-key-shortcut-tool'; @@ -23,13 +22,21 @@ import { AccessibleKeyShortcutTool } from './accessible-key-shortcut-tool'; /** * Handles actions for displaying help/information about keyboard shortcuts. */ -export const glspShortcutHelpModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureShortcutHelpTool(context); -}); +export const shortcutHelpModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureShortcutHelpTool(context); + }, + { featureId: Symbol('shortcutHelp') } +); export function configureShortcutHelpTool(context: BindingContext): void { bindAsService(context, TYPES.IDefaultTool, AccessibleKeyShortcutTool); bindAsService(context, TYPES.IUIExtension, KeyShortcutUIExtension); configureActionHandler(context, SetAccessibleKeyShortcutAction.KIND, KeyShortcutUIExtension); } + +export { + /** Deprecated use {@link shortcutHelpModule} instead */ + shortcutHelpModule as glspShortcutHelpModule +}; diff --git a/packages/client/src/features/accessibility/toast/toast-module.ts b/packages/client/src/features/accessibility/toast/toast-module.ts index 56db1a3..6c70e6a 100644 --- a/packages/client/src/features/accessibility/toast/toast-module.ts +++ b/packages/client/src/features/accessibility/toast/toast-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, BindingContext, configureActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import '../../../../css/toast.css'; import { HideToastAction, ShowToastMessageAction } from './toast-handler'; import { Toast } from './toast-tool'; @@ -23,10 +22,13 @@ import { Toast } from './toast-tool'; * Handles toast/user notification actions. */ -export const glspToastModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureToastTool(context); -}); +export const toastModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureToastTool(context); + }, + { featureId: Symbol('toast') } +); export function configureToastTool(context: BindingContext): void { bindAsService(context, TYPES.IUIExtension, Toast); @@ -34,3 +36,8 @@ export function configureToastTool(context: BindingContext): void { configureActionHandler(context, ShowToastMessageAction.KIND, Toast); configureActionHandler(context, HideToastAction.KIND, Toast); } + +export { + /** Deprecated use {@link toastModule} instead */ + toastModule as glspToastModule +}; diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index 69b0293..ef340fa 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,21 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, - KeyListener, - KeyTool, GModelElement, GRoutableElement, + KeyListener, + KeyTool, SelectAction, SwitchEditModeAction, isSelectable } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { toArray } from 'sprotty/lib/utils/iterable'; +import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Tool } from '../../../base/tool-manager/tool'; -import { SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle } from '../../change-bounds/model'; /** * Deselects the element if there is no interaction possible with element. @@ -59,7 +59,7 @@ export class DeselectKeyTool implements Tool { export class DeselectKeyListener extends KeyListener { override keyDown(target: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesDeselectKeystroke(event)) { - const isResizeHandleActive = toArray(target.root.index.all().filter(el => el instanceof SResizeHandle)).length > 0; + const isResizeHandleActive = toArray(target.root.index.all().filter(el => el instanceof GResizeHandle)).length > 0; if (isResizeHandleActive) { return []; diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index daeba5d..1216e7c 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -109,14 +109,14 @@ export function isBoundsAwareMoveable(element: GModelElement): element is Bounds return isMoveable(element) && isBoundsAware(element); } -export class SResizeHandle extends GChildElement implements Hoverable { +export class GResizeHandle extends GChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; override readonly parent: ResizableModelElement; constructor( readonly location: ResizeHandleLocation, - override readonly type: string = SResizeHandle.TYPE, + override readonly type: string = GResizeHandle.TYPE, readonly hoverFeedback: boolean = false ) { super(); @@ -166,12 +166,12 @@ export class SResizeHandle extends GChildElement implements Hoverable { return this.isNeResize() || this.isSwResize(); } - static getHandlePosition(handle: SResizeHandle): Point; + static getHandlePosition(handle: GResizeHandle): Point; static getHandlePosition(parent: ResizableModelElement, location: ResizeHandleLocation): Point; static getHandlePosition(bounds: Bounds, location: ResizeHandleLocation): Point; - static getHandlePosition(first: ResizableModelElement | SResizeHandle | Bounds, second?: ResizeHandleLocation): Point { - const bounds = SResizeHandle.is(first) ? first.parent.bounds : first instanceof GModelElement ? first.bounds : first; - const location = SResizeHandle.is(first) ? first.location : second!; + static getHandlePosition(first: ResizableModelElement | GResizeHandle | Bounds, second?: ResizeHandleLocation): Point { + const bounds = GResizeHandle.is(first) ? first.parent.bounds : first instanceof GModelElement ? first.bounds : first; + const location = GResizeHandle.is(first) ? first.location : second!; switch (location) { case ResizeHandleLocation.TopLeft: return Bounds.topLeft(bounds); @@ -192,7 +192,7 @@ export class SResizeHandle extends GChildElement implements Hoverable { } } - static getCursorCss(handle: SResizeHandle): string { + static getCursorCss(handle: GResizeHandle): string { switch (handle.location) { case ResizeHandleLocation.TopLeft: return CursorCSS.RESIZE_NW; @@ -213,17 +213,17 @@ export class SResizeHandle extends GChildElement implements Hoverable { } } - static is(handle: unknown): handle is SResizeHandle { - return typeof handle === 'object' && !!handle && 'type' in handle && handle.type === SResizeHandle.TYPE; + static is(handle: unknown): handle is GResizeHandle { + return typeof handle === 'object' && !!handle && 'type' in handle && handle.type === GResizeHandle.TYPE; } } export function addResizeHandles(element: ResizableModelElement, locations: ResizeHandleLocation[] = ResizeHandleLocation.CORNERS): void { for (const location of ResizeHandleLocation.ALL) { - const existing = element.children.find(child => child instanceof SResizeHandle && child.location === location); + const existing = element.children.find(child => child instanceof GResizeHandle && child.location === location); if (locations.includes(location) && !existing) { // add missing handle - element.add(new SResizeHandle(location)); + element.add(new GResizeHandle(location)); } else if (!locations.includes(location) && existing) { // remove existing handle element.remove(existing); @@ -232,5 +232,10 @@ export function addResizeHandles(element: ResizableModelElement, locations: Resi } export function removeResizeHandles(element: GParentElement): void { - element.removeAll(child => child instanceof SResizeHandle); + element.removeAll(child => child instanceof GResizeHandle); } + +export { + /** @deprecated Use {@link GResizeHandle} instead */ + GResizeHandle as SResizeHandle +}; diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 1080b44..844be69 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -18,7 +18,7 @@ import { injectable } from 'inversify'; import { ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { BoundsAwareModelElement } from '../../utils/gmodel-util'; import { toAbsoluteBounds } from '../../utils/viewpoint-util'; -import { SResizeHandle } from './model'; +import { GResizeHandle } from './model'; /** * A `MovementRestrictor` is an optional service that can be used by tools to validate @@ -86,7 +86,7 @@ export function createMovementRestrictionFeedback( ): ModifyCSSFeedbackAction { const elements: GModelElement[] = [element]; if (element instanceof GParentElement) { - element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); + element.children.filter(child => child instanceof GResizeHandle).forEach(e => elements.push(e)); } return ModifyCSSFeedbackAction.create({ elements, add: movementRestrictor.cssClasses }); } @@ -103,7 +103,7 @@ export function removeMovementRestrictionFeedback( ): ModifyCSSFeedbackAction { const elements: GModelElement[] = [element]; if (element instanceof GParentElement) { - element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); + element.children.filter(child => child instanceof GResizeHandle).forEach(e => elements.push(e)); } return ModifyCSSFeedbackAction.create({ elements, remove: movementRestrictor.cssClasses }); diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 1fa093c..0d9c45f 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -30,7 +30,7 @@ import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-d import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { ISelectionListener, SelectionService } from '../../base/selection-service'; import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; -import { ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; +import { GResizeHandle, ResizeHandleLocation } from '../change-bounds/model'; import { Grid } from '../grid/grid'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { @@ -182,7 +182,7 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH const minimum: Writable = { ...Vector.ZERO }; const resize = - element instanceof SResizeHandle + element instanceof GResizeHandle ? ResizeHandleLocation.direction(element.location) : [Direction.Left, Direction.Right, Direction.Up, Direction.Down]; diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 47f2a52..9671c17 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -17,7 +17,7 @@ import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseLis import { inject, injectable, optional } from 'inversify'; import { Ranked } from '../../base/ranked'; import { SelectableElement } from '../../utils/gmodel-util'; -import { SResizeHandle } from '../change-bounds/model'; +import { GResizeHandle } from '../change-bounds/model'; import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; /** @@ -60,7 +60,7 @@ export class RankedSelectMouseListener extends SelectMouseListener implements Ra } protected override handleButton(target: GModelElement, event: MouseEvent): (Action | Promise)[] | undefined { - if (target instanceof SResizeHandle && this.changeBoundsManager?.useSymmetricResize(event)) { + if (target instanceof GResizeHandle && this.changeBoundsManager?.useSymmetricResize(event)) { // avoid de-selecting elements when resizing with a modifier key return []; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts index 4747041..28bb579 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts @@ -40,7 +40,7 @@ import { import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { isValidMove, minDimensions } from '../../../utils/layout-utils'; import { LayoutAware } from '../../bounds/layout-data'; -import { ResizeHandleLocation, SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle, ResizeHandleLocation } from '../../change-bounds/model'; import { IMovementRestrictor, movementRestrictionFeedback, @@ -154,7 +154,7 @@ export class ChangeBoundsManager { ); // cursor feedback on graph - const cursorClass = SResizeHandle.getCursorCss(resize.handleMove.element); + const cursorClass = GResizeHandle.getCursorCss(resize.handleMove.element); feedback.add(cursorFeedbackAction(cursorClass), cursorFeedbackAction(CursorCSS.DEFAULT)); // handle feedback diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index 9ca32eb..c35ae26 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -15,11 +15,11 @@ ********************************************************************************/ import { FeatureModule, TYPES, bindAsService, configureCommand, configureView } from '@eclipse-glsp/sprotty'; import '../../../../css/change-bounds.css'; -import { SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsManager } from './change-bounds-manager'; import { ChangeBoundsTool } from './change-bounds-tool'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; -import { SResizeHandleView } from './view'; +import { GResizeHandleView } from './view'; export const changeBoundsToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { @@ -28,7 +28,7 @@ export const changeBoundsToolModule = new FeatureModule( bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); - configureView(context, SResizeHandle.TYPE, SResizeHandleView); + configureView(context, GResizeHandle.TYPE, GResizeHandleView); }, { featureId: Symbol('changeBoundsTool') } ); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index a0efe64..c946741 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -26,7 +26,7 @@ import { isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils/gmodel-util'; -import { SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle } from '../../change-bounds/model'; import { ChangeBoundsTool } from './change-bounds-tool'; import { MoveFinishedEventAction, MoveInitializedEventAction } from './change-bounds-tool-feedback'; import { ChangeBoundsTracker, TrackedMove } from './change-bounds-tracker'; @@ -66,7 +66,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { } protected initializeMove(target: GModelElement, event: MouseEvent): void { - if (target instanceof SResizeHandle) { + if (target instanceof GResizeHandle) { // avoid conflict with resize tool return; } @@ -102,7 +102,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { } protected isValidMoveable(element?: GModelElement): element is MoveableElement { - return !!element && isNonRoutableSelectedMovableBoundsAware(element) && !(element instanceof SResizeHandle); + return !!element && isNonRoutableSelectedMovableBoundsAware(element) && !(element instanceof GResizeHandle); } override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 36ec4fc..ce59ade 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -51,7 +51,7 @@ import { } from '../../../utils/gmodel-util'; import { LocalRequestBoundsAction } from '../../bounds/local-bounds'; import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-command'; -import { SResizeHandle, isResizable } from '../../change-bounds/model'; +import { GResizeHandle, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { BaseEditTool } from '../base-tools'; import { CSS_ACTIVE_HANDLE, ChangeBoundsManager } from './change-bounds-manager'; @@ -131,7 +131,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel // members for resize mode protected activeResizeElement?: ResizableModelElement; - protected activeResizeHandle?: SResizeHandle; + protected activeResizeHandle?: GResizeHandle; protected handleFeedback: FeedbackEmitter; protected resizeFeedback: FeedbackEmitter; @@ -155,7 +155,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel } protected updateResizeElement(target: GModelElement, event?: MouseEvent): boolean { - this.activeResizeHandle = target instanceof SResizeHandle ? target : undefined; + this.activeResizeHandle = target instanceof GResizeHandle ? target : undefined; this.activeResizeElement = this.activeResizeHandle?.parent ?? this.findResizeElement(target); if (this.activeResizeElement) { if (event) { @@ -301,7 +301,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return newRoutingPoints.length > 0 ? [ChangeRoutingPointsOperation.create(newRoutingPoints)] : []; } - protected handleResizeOnServer(activeResizeHandle: SResizeHandle): Action[] { + protected handleResizeOnServer(activeResizeHandle: GResizeHandle): Action[] { if (this.initialBounds && this.isValidResize(activeResizeHandle.parent)) { const elementAndBounds = toElementAndBounds(activeResizeHandle.parent); if (!this.initialBounds.newPosition || !elementAndBounds.newPosition) { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts index 5f77dd5..1e48bfd 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tracker.ts @@ -32,7 +32,7 @@ import { isMoveable } from '@eclipse-glsp/sprotty'; import { BoundsAwareModelElement, MoveableElement, ResizableModelElement, getElements } from '../../../utils/gmodel-util'; -import { ResizeHandleLocation, SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle, ResizeHandleLocation } from '../../change-bounds/model'; import { DiagramMovementCalculator } from '../../change-bounds/tracker'; import { ChangeBoundsManager } from './change-bounds-manager'; @@ -277,7 +277,7 @@ export class ChangeBoundsTracker { // RESIZE // - resizeElements(handle: SResizeHandle, opts?: Partial): TrackedResize { + resizeElements(handle: GResizeHandle, opts?: Partial): TrackedResize { const options = this.resolveResizeOptions(opts); const update = this.calculateDiagramMovement(); const handleMove = this.calculateHandleMove(new MoveableResizeHandle(handle), update.vector, options); @@ -315,7 +315,7 @@ export class ChangeBoundsTracker { return this.calculateElementMove(handle, diagramMovement, moveOptions); } - protected getResizableElements(handle: SResizeHandle, options: ResizeOptions): ResizableModelElement[] { + protected getResizableElements(handle: GResizeHandle, options: ResizeOptions): ResizableModelElement[] { return [handle.parent]; } @@ -447,11 +447,11 @@ export class ChangeBoundsTracker { } } -export class MoveableResizeHandle extends SResizeHandle implements Locateable { +export class MoveableResizeHandle extends GResizeHandle implements Locateable { constructor( - protected handle: SResizeHandle, + protected handle: GResizeHandle, override location: ResizeHandleLocation = handle.location, - readonly position = SResizeHandle.getHandlePosition(handle.parent, location) + readonly position = GResizeHandle.getHandlePosition(handle.parent, location) ) { super(location, handle.type, handle.hoverFeedback); this.id = handle.id; diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index 2571b81..63d4ef6 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,14 +16,14 @@ import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { SResizeHandle } from '../../change-bounds/model'; +import { GResizeHandle } from '../../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: svg }; @injectable() -export class SResizeHandleView implements IView { - render(handle: SResizeHandle, context: RenderingContext): VNode | undefined { +export class GResizeHandleView implements IView { + render(handle: GResizeHandle, context: RenderingContext): VNode | undefined { if (context.targetKind === 'hidden') { return undefined; } @@ -45,11 +45,16 @@ export class SResizeHandleView implements IView { return ; } - protected getPosition(handle: SResizeHandle): Point | undefined { - return Point.subtract(SResizeHandle.getHandlePosition(handle), handle.parent.bounds); + protected getPosition(handle: GResizeHandle): Point | undefined { + return Point.subtract(GResizeHandle.getHandlePosition(handle), handle.parent.bounds); } getRadius(): number { return 7; } } + +export { + /** @deprecated Use {@link GResizeHandleView} instead */ + GResizeHandleView as SResizeHandleView +}; diff --git a/packages/client/src/features/undo-redo/undo-redo-module.ts b/packages/client/src/features/undo-redo/undo-redo-module.ts index c3fdf78..956d2b2 100644 --- a/packages/client/src/features/undo-redo/undo-redo-module.ts +++ b/packages/client/src/features/undo-redo/undo-redo-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ContainerModule } from 'inversify'; -import { bindAsService, TYPES } from '@eclipse-glsp/sprotty'; +import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { GLSPUndoRedoKeyListener } from './undo-redo-key-listener'; /** @@ -23,6 +22,9 @@ import { GLSPUndoRedoKeyListener } from './undo-redo-key-listener'; * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced * with an application native module. */ -export const undoRedoModule = new ContainerModule((bind, unbind, isBound, rebind) => { - bindAsService(bind, TYPES.KeyListener, GLSPUndoRedoKeyListener); -}); +export const undoRedoModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + bindAsService(bind, TYPES.KeyListener, GLSPUndoRedoKeyListener); + }, + { featureId: Symbol('undoRedo') } +); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7dca4ef..866956e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -67,7 +67,7 @@ export * from './features/accessibility/focus-tracker/focus-tracker-tool'; export * from './features/accessibility/global-keylistener-tool'; export * from './features/accessibility/key-shortcut/accessible-key-shortcut'; export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; -export * from './features/accessibility/key-shortcut/di.config'; +export * from './features/accessibility/key-shortcut/shortcut-help-module'; export * from './features/accessibility/keyboard-grid/action'; export * from './features/accessibility/keyboard-grid/constants'; export * from './features/accessibility/keyboard-grid/keyboard-grid'; From 10ae156eee64f680850259087f216de17b592c24 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 13 Jun 2024 22:20:10 +0200 Subject: [PATCH 347/566] Update to Sprotty 1.2.0 (#357) Mostly non-breaking changes. Some definitions have moved to the sprotty-protocol package. However, we can mitigate all potential breaks with our @eclipse-glsp/sprotty reexport. Fixes https://github.com/eclipse-glsp/glsp/issues/1253 --- examples/workflow-standalone/tsconfig.json | 1 + .../client/src/base/model/model-registry.ts | 6 ++- .../src/features/validation/issue-marker.ts | 11 ++--- packages/glsp-sprotty/package.json | 6 +-- packages/glsp-sprotty/src/feature-modules.ts | 2 + packages/glsp-sprotty/src/re-exports.ts | 32 +++++++-------- .../glsp-sprotty/src/svg-views-override.tsx | 6 +-- packages/protocol/package.json | 2 +- packages/protocol/src/re-exports.ts | 40 ++++++++++++++++++- 9 files changed, 74 insertions(+), 32 deletions(-) diff --git a/examples/workflow-standalone/tsconfig.json b/examples/workflow-standalone/tsconfig.json index edcac2a..55c9b67 100644 --- a/examples/workflow-standalone/tsconfig.json +++ b/examples/workflow-standalone/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "src", + "resolveJsonModule": true, "outDir": "lib" }, "include": ["src", "lib", "css", "app"], diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index 71e082e..26e26da 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -32,7 +32,11 @@ export class GModelRegistry extends SModelRegistry { } if (this.hasKey(key)) { // do not throw error but log overwriting - console.log(`Factory for model element '${key}' will be overwritten.`); + console.log(`Key is already registered: ${key}.Factory for model element '${key}' will be overwritten.`); + console.warn( + 'Implicit overwriting by registering the same key twice is deprecated' + + "\n Use 'overrideModelElement()' instead of 'configureModelElement()' for explicit overwriting." + ); } this.elements.set(key, factory); } diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index b3758c4..19f1c75 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -22,11 +22,11 @@ import { Marker, MarkerKind, Projectable, - SIssueMarker, + SIssueMarkerImpl, isBoundsAware } from '@eclipse-glsp/sprotty'; -export class GIssueMarker extends SIssueMarker implements Projectable { +export class GIssueMarker extends SIssueMarkerImpl implements Projectable { constructor() { super(); this.features = new Set(GDecoration.DEFAULT_FEATURES); @@ -90,9 +90,10 @@ export function getGIssueMarker(modelElement: GParentElement): GIssueMarker | un * @returns the created `GIssue`. */ export function createGIssue(marker: Marker, parent?: GParentElement): GIssue { - const issue = new GIssue(); - issue.message = marker.description; - + const issue: GIssue = { + message: marker.description, + severity: 'info' + }; switch (marker.kind) { case MarkerKind.ERROR: { issue.severity = 'error'; diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 1103593..b1a5369 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -35,9 +35,9 @@ "dependencies": { "@eclipse-glsp/protocol": "2.2.0-next", "autocompleter": "^9.1.0", - "snabbdom": "^3.5.1", - "sprotty": "1.0.0", - "sprotty-protocol": "1.0.0" + "snabbdom": "~3.5.1", + "sprotty": "1.2.0", + "sprotty-protocol": "1.2.0" }, "devDependencies": { "@types/jsdom": "^21.1.3", diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts index 169ec80..5b8631e 100644 --- a/packages/glsp-sprotty/src/feature-modules.ts +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -18,6 +18,7 @@ import { FeatureModule } from '@eclipse-glsp/protocol/lib/di'; import sprottyDefaultModule from 'sprotty/lib/base/di.config'; import sprottyButtonModule from 'sprotty/lib/features/button/di.config'; import sprottyEdgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; +import sprottyEdgeJunctionModule from 'sprotty/lib/features/edge-junction/di.config'; import sprottyEdgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; import { edgeEditModule as sprottyEdgeEditModule } from 'sprotty/lib/features/edit/di.config'; import sprottyExpandModule from 'sprotty/lib/features/expand/di.config'; @@ -39,5 +40,6 @@ export const moveModule = new FeatureModule(sprottyMoveModule.registry, { featur export const openModule = new FeatureModule(sprottyOpenModule.registry, { featureId: Symbol('open') }); export const updateModule = new FeatureModule(sprottyUpdateModule.registry, { featureId: Symbol('update') }); export const zorderModule = new FeatureModule(sprottyZorderModule.registry, { featureId: Symbol('zorder') }); +export const edgeJunctionModule = new FeatureModule(sprottyEdgeJunctionModule.registry, { featureId: Symbol('edgeJunction') }); export { sprottyDefaultModule }; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index c847bb8..bd42b46 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -97,13 +97,12 @@ export * from 'sprotty/lib/features/bounds/bounds-manipulation'; export * from 'sprotty/lib/features/bounds/hbox-layout'; export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; export * from 'sprotty/lib/features/bounds/layout'; -export * from 'sprotty/lib/features/bounds/layout-options'; +export { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; export { - Alignable, - BoundsAware, + InternalBoundsAware as BoundsAware, SShapeElementImpl as GShapeElement, - LayoutContainer, - LayoutableChild, + InternalLayoutContainer as LayoutContainer, + InternalLayoutableChild as LayoutableChild, ModelLayoutOptions, alignFeature, boundsFeature, @@ -124,7 +123,7 @@ export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; -export { SButtonImpl as GButton, SButtonSchema as GButtonSchema } from 'sprotty/lib/features/button/model'; +export { SButtonImpl as GButton } from 'sprotty/lib/features/button/model'; export { CommandPaletteActionProviderRegistry, @@ -139,7 +138,7 @@ export * from 'sprotty/lib/features/context-menu/mouse-listener'; export * from 'sprotty/lib/features/edge-layout/di.config'; export * from 'sprotty/lib/features/edge-layout/edge-layout'; -export * from 'sprotty/lib/features/edge-layout/model'; +export { DEFAULT_EDGE_PLACEMENT, checkEdgePlacement, edgeLayoutFeature, isEdgeLayoutable } from 'sprotty/lib/features/edge-layout/model'; // Exclude client-side creation features (not supported in GLSP) // export * from 'sprotty/lib/features/edit/create'; // export * from 'sprotty/lib/features/edit/create-on-drag'; @@ -151,7 +150,7 @@ export * from 'sprotty/lib/features/edit/model'; // export * from 'sprotty/lib/features/edit/reconnect'; export * from 'sprotty/lib/features/expand/expand'; -export * from 'sprotty/lib/features/expand/model'; +export { expandFeature, isExpandable } from 'sprotty/lib/features/expand/model'; export * from 'sprotty/lib/features/expand/views'; // Exclude RequestExportSvgAction. Already provided by glsp-protocol export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor } from 'sprotty/lib/features/export/export'; @@ -160,10 +159,10 @@ export * from 'sprotty/lib/features/export/model'; export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; export * from 'sprotty/lib/features/fade/fade'; -export * from 'sprotty/lib/features/fade/model'; +export { fadeFeature, isFadeable } from 'sprotty/lib/features/fade/model'; export * from 'sprotty/lib/features/hover/hover'; -export * from 'sprotty/lib/features/hover/model'; +export { hasPopupFeature, hoverFeedbackFeature, isHoverable, popupFeature } from 'sprotty/lib/features/hover/model'; export * from 'sprotty/lib/features/hover/popup-position-updater'; // Alias SModel types @@ -171,10 +170,7 @@ export * from 'sprotty/lib/features/decoration/decoration-placer'; export { Decoration, SDecoration as GDecoration, - SIssue as GIssue, - SIssueSeverity as GIssueSeverity, - // Export as is, we extend it glsp-client to `GIssueMarker` - SIssueMarker, + SIssueMarkerImpl, decorationFeature, isDecoration } from 'sprotty/lib/features/decoration/model'; @@ -183,7 +179,9 @@ export * from 'sprotty/lib/features/decoration/views'; export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; export * from 'sprotty/lib/features/edge-intersection/sweepline'; -export * from 'sprotty/lib/features/move/model'; +export * from 'sprotty/lib/features/edge-junction/junction-finder'; + +export { isLocateable, isMoveable, moveFeature } from 'sprotty/lib/features/move/model'; export * from 'sprotty/lib/features/move/move'; export * from 'sprotty/lib/features/move/snap'; @@ -192,7 +190,7 @@ export * from 'sprotty/lib/features/nameable/model'; export * from 'sprotty/lib/features/open/model'; export * from 'sprotty/lib/features/open/open'; -export * from 'sprotty/lib/features/projection/model'; +export { ViewProjection, getModelBounds, getProjectedBounds, getProjections, isProjectable } from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; export * from 'sprotty/lib/features/routing/abstract-edge-router'; @@ -222,7 +220,7 @@ export * from 'sprotty/lib/features/routing/polyline-edge-router'; export * from 'sprotty/lib/features/routing/routing'; export * from 'sprotty/lib/features/routing/views'; -export * from 'sprotty/lib/features/select/model'; +export { isSelectable, isSelected, selectFeature } from 'sprotty/lib/features/select/model'; // Alias Select commands with sprotty prefix to avoid clash with glsp-client export { GetSelectionCommand, diff --git a/packages/glsp-sprotty/src/svg-views-override.tsx b/packages/glsp-sprotty/src/svg-views-override.tsx index e3f7f11..45b82d8 100644 --- a/packages/glsp-sprotty/src/svg-views-override.tsx +++ b/packages/glsp-sprotty/src/svg-views-override.tsx @@ -14,17 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds } from '@eclipse-glsp/protocol'; +import { Bounds, Hoverable, Selectable } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { ATTR_BBOX_ELEMENT, - BoundsAware, - Hoverable, + InternalBoundsAware as BoundsAware, IViewArgs, RenderingContext, SShapeElementImpl, - Selectable, CircularNodeView as SprottyCircularNodeView, DiamondNodeView as SprottyDiamondNodeView, svg diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 0c6660b..be03dd2 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -45,7 +45,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "1.0.0", + "sprotty-protocol": "1.2.0", "uuid": "7.0.3", "vscode-jsonrpc": "^8.0.2" }, diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 84d91f5..7130bf1 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -13,7 +13,45 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export { Viewport } from 'sprotty-protocol/lib/model'; +export { + Alignable, + EdgeLayoutable, + EdgePlacement, + EdgeSide, + Expandable, + Fadeable, + SButton as GButtonSchema, + SCompartment as GCompartmentSchema, + SEdge as GEdgeSchema, + ForeignObjectElement as GForeignObjectElementSchema, + SGraph as GGraphSchema, + HtmlRoot as GHtmlRootSchema, + SIssue as GIssue, + SIssueMarker as GIssueMarkerSchema, + SIssueSeverity as GIssueSeverity, + SLabel as GLabelSchema, + SModelElement as GModelElementSchema, + SModelRoot as GModelRootSchema, + SNode as GNodeSchema, + SPort as GPortSchema, + PreRenderedElement as GPreRenderedElementSchema, + SShapeElement as GShapeElementSchema, + ShapedPreRenderedElement as GShapedPreRenderedElementSchema, + ViewportRootElement as GViewportRootElementSchema, + HAlignment, + Hoverable, + LayoutKind, + Locateable, + ModelLayoutOptions, + Projectable, + Scrollable, + Selectable, + VAlignment, + Viewport, + Zoomable, + isScrollable, + isZoomable +} from 'sprotty-protocol/lib/model'; export * from 'sprotty-protocol/lib/utils/async'; export * from 'sprotty-protocol/lib/utils/geometry'; export * from 'sprotty-protocol/lib/utils/json'; From 5295c4030f3f9c908270661a55c090ceffd06992 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 14 Jun 2024 10:50:55 +0200 Subject: [PATCH 348/566] Update next dependencies (#87) - Update next dependencies - Update changelog - Update copyright headers - Add vscode-json rpc as explicit dependency to server package. This ensures that we are still using the same version as the client side in mono repo scenarios where multiple versions of vscode-jsonrpc are resolved --- examples/workflow-server/src/browser/app.ts | 2 +- .../src/common/handler/create-decision-node-handler.ts | 2 +- .../src/common/handler/create-merge-node-handler.ts | 2 +- .../src/common/workflow-diagram-configuration.ts | 2 +- .../src/common/workflow-edge-creation-checker.ts | 2 +- examples/workflow-server/src/node/app.ts | 2 +- packages/graph/src/gshape-element.ts | 2 +- packages/server/package.json | 1 + 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/workflow-server/src/browser/app.ts b/examples/workflow-server/src/browser/app.ts index 2c80990..a25c261 100644 --- a/examples/workflow-server/src/browser/app.ts +++ b/examples/workflow-server/src/browser/app.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/handler/create-decision-node-handler.ts b/examples/workflow-server/src/common/handler/create-decision-node-handler.ts index 6836d0a..1bfa574 100644 --- a/examples/workflow-server/src/common/handler/create-decision-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-decision-node-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/handler/create-merge-node-handler.ts b/examples/workflow-server/src/common/handler/create-merge-node-handler.ts index 688b9a6..9330d4b 100644 --- a/examples/workflow-server/src/common/handler/create-merge-node-handler.ts +++ b/examples/workflow-server/src/common/handler/create-merge-node-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/workflow-diagram-configuration.ts b/examples/workflow-server/src/common/workflow-diagram-configuration.ts index 2a4daf9..d8bdd9c 100644 --- a/examples/workflow-server/src/common/workflow-diagram-configuration.ts +++ b/examples/workflow-server/src/common/workflow-diagram-configuration.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts index f61a87c..da45ef4 100644 --- a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts +++ b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/node/app.ts b/examples/workflow-server/src/node/app.ts index a0b2fa2..1bc2bfd 100644 --- a/examples/workflow-server/src/node/app.ts +++ b/examples/workflow-server/src/node/app.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gshape-element.ts b/packages/graph/src/gshape-element.ts index aa53aab..3b949f6 100644 --- a/packages/graph/src/gshape-element.ts +++ b/packages/graph/src/gshape-element.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/package.json b/packages/server/package.json index edb13db..40bf164 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -63,6 +63,7 @@ "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", + "vscode-jsonrpc": "^8.0.2", "winston": "^3.3.3", "ws": "^8.12.1" }, From ebeb2283b65bd1e698074cc13a74f15c1492f1d5 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Sat, 15 Jun 2024 15:04:10 +0000 Subject: [PATCH 349/566] Enhance Grid functionality and customizability (#359) * Enhance Grid functionality and customizability - Ensure grid is applied on normal graph view without projections - Allow grid color to be specified through CSS - Add more properties to the graph for re-use * Properly escape grid color values Color definitions with a # need to be escaped to be properly rendered when used in a background image url --- packages/client/css/grid.css | 7 +++++- .../client/src/features/grid/grid-style.ts | 25 +++++++++++++++++++ packages/client/src/index.ts | 1 + packages/client/src/views/ggraph-view.tsx | 21 ++++++++++------ .../client/src/views/glsp-projection-view.tsx | 21 ++++++++++------ 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 packages/client/src/features/grid/grid-style.ts diff --git a/packages/client/css/grid.css b/packages/client/css/grid.css index 8cad8ef..2f628e9 100644 --- a/packages/client/css/grid.css +++ b/packages/client/css/grid.css @@ -14,7 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +:root { + --grid-color: rgba(0, 0, 0, 0.1); +} + /** Control visibility of background image through CSS class on parent */ -.grid-background .sprotty-graph { +.grid-background .sprotty-graph, +.grid-background.sprotty-graph { background-image: var(--grid-background-image); } diff --git a/packages/client/src/features/grid/grid-style.ts b/packages/client/src/features/grid/grid-style.ts new file mode 100644 index 0000000..150657b --- /dev/null +++ b/packages/client/src/features/grid/grid-style.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2024 Axon Ivy AG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export namespace GridProperty { + export const GRID_BACKGROUND_X = '--grid-background-x'; + export const GRID_BACKGROUND_Y = '--grid-background-y'; + export const GRID_BACKGROUND_WIDTH = '--grid-background-width'; + export const GRID_BACKGROUND_HEIGHT = '--grid-background-height'; + export const GRID_BACKGROUND_ZOOM = '--grid-background-zoom'; + export const GRID_BACKGROUND_IMAGE = '--grid-background-image'; + export const GRID_COLOR = '--grid-color'; +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 866956e..49d62a4 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -142,6 +142,7 @@ export * from './features/grid/grid-manager'; export * from './features/grid/grid-model'; export * from './features/grid/grid-module'; export * from './features/grid/grid-snapper'; +export * from './features/grid/grid-style'; export * from './features/helper-lines/helper-line-feedback'; export * from './features/helper-lines/helper-line-manager'; export * from './features/helper-lines/helper-line-manager-default'; diff --git a/packages/client/src/views/ggraph-view.tsx b/packages/client/src/views/ggraph-view.tsx index a32b77c..aa503d2 100644 --- a/packages/client/src/views/ggraph-view.tsx +++ b/packages/client/src/views/ggraph-view.tsx @@ -17,6 +17,7 @@ import { Bounds, Dimension, Point, RenderingContext, SGraphImpl, SGraphView, Wri import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GridManager, GridStyle } from '../features/grid/grid-manager'; +import { GridProperty } from '../features/grid/grid-style'; @injectable() export class GGraphView extends SGraphView { @@ -30,16 +31,21 @@ export class GGraphView extends SGraphView { return graph; } - protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { + protected getGridStyle(viewport: Readonly, context: RenderingContext): GridStyle { if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { return {}; } - const bounds = this.getBackgroundBounds(model, context, this.gridManager); + const bounds = this.getBackgroundBounds(viewport, context, this.gridManager); return { + [GridProperty.GRID_BACKGROUND_X]: bounds.x + 'px', + [GridProperty.GRID_BACKGROUND_Y]: bounds.y + 'px', + [GridProperty.GRID_BACKGROUND_WIDTH]: bounds.width + 'px', + [GridProperty.GRID_BACKGROUND_HEIGHT]: bounds.height + 'px', + [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '', + [GridProperty.GRID_BACKGROUND_IMAGE]: this.getBackgroundImage(viewport, context, this.gridManager), backgroundPosition: `${bounds.x}px ${bounds.y}px`, - backgroundSize: `${bounds.width}px ${bounds.height}px`, - // we do not set the background image directly in the style object, because we want to toggle it on and off via CSS - '--grid-background-image': this.getBackgroundImage(model, context, this.gridManager) + backgroundSize: `${bounds.width}px ${bounds.height}px` + // we do not set the background-image directly in the style object, because we want to toggle it on and off via CSS }; } @@ -49,8 +55,9 @@ export class GGraphView extends SGraphView { return { ...position, ...size }; } - protected getBackgroundImage(model: Readonly, context: RenderingContext, gridManager: GridManager): string { + protected getBackgroundImage(viewport: Readonly, context: RenderingContext, gridManager: GridManager): string { + const color = getComputedStyle(document.documentElement).getPropertyValue(GridProperty.GRID_COLOR).trim().replace(/#/g, '%23'); // eslint-disable-next-line max-len - return `url('data:image/svg+xml;utf8, ')`; + return `url('data:image/svg+xml;utf8, ')`; } } diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index d72cd03..ca803b2 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -34,6 +34,7 @@ import { import { inject, injectable, optional } from 'inversify'; import { VNode, VNodeStyle, h } from 'snabbdom'; import { GridManager, GridStyle } from '../features/grid/grid-manager'; +import { GridProperty } from '../features/grid/grid-style'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const JSX = { createElement: html }; @@ -71,16 +72,21 @@ export class GLSPProjectionView extends ProjectedViewportView { return svg; } - protected getGridStyle(model: Readonly, context: RenderingContext): GridStyle { + protected getGridStyle(viewport: Readonly, context: RenderingContext): GridStyle { if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { return {}; } - const bounds = this.getBackgroundBounds(model, context, this.gridManager); + const bounds = this.getBackgroundBounds(viewport, context, this.gridManager); return { + [GridProperty.GRID_BACKGROUND_X]: bounds.x + 'px', + [GridProperty.GRID_BACKGROUND_Y]: bounds.y + 'px', + [GridProperty.GRID_BACKGROUND_WIDTH]: bounds.width + 'px', + [GridProperty.GRID_BACKGROUND_HEIGHT]: bounds.height + 'px', + [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '', + [GridProperty.GRID_BACKGROUND_IMAGE]: this.getBackgroundImage(viewport, context, this.gridManager), backgroundPosition: `${bounds.x}px ${bounds.y}px`, - backgroundSize: `${bounds.width}px ${bounds.height}px`, - // we do not set the background image directly in the style object, because we want to toggle it on and off via CSS - '--grid-background-image': this.getBackgroundImage(model, context, this.gridManager) + backgroundSize: `${bounds.width}px ${bounds.height}px` + // we do not set the background-image directly in the style object, because we want to toggle it on and off via CSS }; } @@ -90,9 +96,10 @@ export class GLSPProjectionView extends ProjectedViewportView { return { ...position, ...size }; } - protected getBackgroundImage(model: Readonly, context: RenderingContext, gridManager: GridManager): string { + protected getBackgroundImage(viewport: Readonly, context: RenderingContext, gridManager: GridManager): string { + const color = getComputedStyle(document.documentElement).getPropertyValue(GridProperty.GRID_COLOR).trim().replace(/#/g, '%23'); // eslint-disable-next-line max-len - return `url('data:image/svg+xml;utf8, ')`; + return `url('data:image/svg+xml;utf8, ')`; } protected override renderProjectionBar( From 7941065a7a7c1d6db70eaa3e1833371ff7cb89bb Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 17 Jun 2024 08:45:27 +0000 Subject: [PATCH 350/566] Fix issue with UI extensions (#360) - Use our GLSPAbstractUIExtension instead of AbstractUIExtension - Fix focus issue with GlspEditLabelUI --- .../direct-task-editing/direct-task-editor.ts | 6 +++--- .../base-autocomplete-palette.ts | 5 +++-- .../key-shortcut/accessible-key-shortcut.ts | 5 +++-- .../keyboard-grid/keyboard-grid.ts | 18 +++++++++--------- .../keyboard-pointer/keyboard-pointer.ts | 19 ++++++------------- .../accessibility/toast/toast-tool.ts | 9 +++++---- .../features/label-edit-ui/label-edit-ui.ts | 1 + .../src/features/status/status-overlay.ts | 5 +++-- .../src/features/tool-palette/tool-palette.ts | 4 ++-- 9 files changed, 35 insertions(+), 37 deletions(-) diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 34e8bc8..6155f92 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -14,19 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - AbstractUIExtension, Action, AutoCompleteWidget, DOMHelper, EditorContextService, + GLSPAbstractUIExtension, GLSPActionDispatcher, + GModelRoot, ILogger, LabeledAction, ModelIndexImpl, Operation, RequestContextActions, RequestEditValidationAction, - GModelRoot, SetContextActions, SetEditValidationResultAction, TYPES, @@ -85,7 +85,7 @@ export namespace EditTaskOperation { } @injectable() -export class TaskEditor extends AbstractUIExtension { +export class TaskEditor extends GLSPAbstractUIExtension { static readonly ID = 'task-editor'; readonly autoSuggestionSettings = { noSuggestionsMessage: 'No suggestions available', diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 339928d..08e9a93 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -14,10 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelRoot, LabeledAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject } from 'inversify'; -import { AbstractUIExtension, Action, LabeledAction, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import '../../../css/autocomplete-palette.css'; import { GLSPActionDispatcher } from '../action-dispatcher'; +import { GLSPAbstractUIExtension } from '../ui-extension/ui-extension'; import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete-widget'; /** @@ -25,7 +26,7 @@ import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete- * using the {@link AutoCompleteWidget}. * */ -export abstract class BaseAutocompletePalette extends AbstractUIExtension { +export abstract class BaseAutocompletePalette extends GLSPAbstractUIExtension { protected readonly autoSuggestionSettings = { noSuggestionsMessage: 'No suggestions available', suggestionsClass: 'command-palette-suggestions', diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index 6aeaf6f..b1896a5 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -14,9 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelRoot, IActionHandler, ICommand, matchesKeystroke } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { groupBy } from 'lodash'; -import { AbstractUIExtension, Action, IActionHandler, ICommand, matchesKeystroke, GModelRoot } from '@eclipse-glsp/sprotty'; +import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; export interface AccessibleKeyShortcutProvider { registerShortcutKey(): void; @@ -48,7 +49,7 @@ export namespace SetAccessibleKeyShortcutAction { } @injectable() -export class KeyShortcutUIExtension extends AbstractUIExtension implements IActionHandler { +export class KeyShortcutUIExtension extends GLSPAbstractUIExtension implements IActionHandler { static readonly ID = 'key-shortcut'; protected container: HTMLDivElement; protected shortcutsContainer: HTMLDivElement; diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index bff7ab7..55010e6 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -15,24 +15,24 @@ ********************************************************************************/ import '../../../../css/keyboard.css'; -import { inject, injectable } from 'inversify'; import { - AbstractUIExtension, + Action, ActionDispatcher, - IActionHandler, - SetUIExtensionVisibilityAction, GModelRoot, - TYPES, - Action, + IActionHandler, ICommand, - Point + Point, + SetUIExtensionVisibilityAction, + TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { KeyboardGridMetadata } from './constants'; +import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from './action'; +import { KeyboardGridMetadata } from './constants'; @injectable() -export class KeyboardGrid extends AbstractUIExtension implements IActionHandler { +export class KeyboardGrid extends GLSPAbstractUIExtension implements IActionHandler { @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: ActionDispatcher; protected triggerActions: Action[] = []; diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index 1249fd9..b804bf8 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -13,26 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelRoot, IActionDispatcher, IActionHandler, TYPES, TriggerNodeCreationAction } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { - AbstractUIExtension, - IActionDispatcher, - IActionHandler, - GModelRoot, - TYPES, - Action, - TriggerNodeCreationAction -} from '@eclipse-glsp/sprotty'; +import { EditorContextService } from '../../../base/editor-context-service'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; +import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; +import { KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; import { SetKeyboardPointerRenderPositionAction } from './actions'; import { KeyboardPointerMetadata } from './constants'; import { KeyboardPointerKeyboardListener } from './keyboard-pointer-listener'; import { KeyboardPointerPosition } from './keyboard-pointer-position'; -import { KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; -import { EditorContextService } from '../../../base/editor-context-service'; -import { CursorCSS } from '../../../base/feedback/css-feedback'; @injectable() -export class KeyboardPointer extends AbstractUIExtension implements IActionHandler { +export class KeyboardPointer extends GLSPAbstractUIExtension implements IActionHandler { protected _triggerAction: TriggerNodeCreationAction = { elementTypeId: 'task:automated', kind: 'triggerNodeCreation' diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 9ac4451..7fd8d10 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -14,18 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; -import { Action, AbstractUIExtension, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; -import { IDiagramStartup } from '../../../base/model/diagram-loader'; +import { Action, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { EditorContextService } from '../../../base/editor-context-service'; +import { IDiagramStartup } from '../../../base/model/diagram-loader'; +import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; import { HideToastAction, ShowToastMessageAction, ToastOptions } from './toast-handler'; /** * This extension is used to create customized user notifications as toast messages. */ @injectable() -export class Toast extends AbstractUIExtension implements IActionHandler, IDiagramStartup { +export class Toast extends GLSPAbstractUIExtension implements IActionHandler, IDiagramStartup { static readonly ID = 'toast'; protected messages: { [key: symbol]: ToastOptions } = {}; diff --git a/packages/client/src/features/label-edit-ui/label-edit-ui.ts b/packages/client/src/features/label-edit-ui/label-edit-ui.ts index b6846d1..2668a03 100644 --- a/packages/client/src/features/label-edit-ui/label-edit-ui.ts +++ b/packages/client/src/features/label-edit-ui/label-edit-ui.ts @@ -27,6 +27,7 @@ export class GlspEditLabelUI extends EditLabelUI { protected override setContainerVisible(visible: boolean): void { if (visible) { this.containerElement?.classList.remove(CSS_HIDDEN_EXTENSION_CLASS); + this.editControl.focus(); } else { this.containerElement?.classList.add(CSS_HIDDEN_EXTENSION_CLASS); } diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 718df1d..06c19f7 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -13,17 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { IActionHandler, StatusAction, codiconCSSClasses } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { AbstractUIExtension, IActionHandler, StatusAction, codiconCSSClasses } from '@eclipse-glsp/sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; +import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; /** * A reusable status overlay for rendering (icon + message) and handling of {@link StatusAction}'s. */ @injectable() -export class StatusOverlay extends AbstractUIExtension implements IActionHandler, IDiagramStartup { +export class StatusOverlay extends GLSPAbstractUIExtension implements IActionHandler, IDiagramStartup { static readonly ID = 'glsp.server.status.overlay'; @inject(GLSPActionDispatcher) diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 2202c93..cbd4159 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - AbstractUIExtension, Action, GModelRoot, IActionHandler, @@ -37,6 +36,7 @@ import { EditorContextService, IEditModeListener } from '../../base/editor-conte import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; +import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; import { DebugManager } from '../debug/debug-manager'; import { GridManager } from '../grid/grid-manager'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; @@ -65,7 +65,7 @@ export namespace EnableToolPaletteAction { } } @injectable() -export class ToolPalette extends AbstractUIExtension implements IActionHandler, IEditModeListener, IDiagramStartup { +export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandler, IEditModeListener, IDiagramStartup { static readonly ID = 'tool-palette'; @inject(GLSPActionDispatcher) From 8810d8d32e2ca7fa6b2fddf79e77ae90813e5f19 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 17 Jun 2024 14:30:28 +0200 Subject: [PATCH 351/566] Fix issue with change bounds tool (#361) - Ensure that the change bounds tool does not dispatch `ChangeRoutingPointsAction` if there are not valid element moves. - Ensure that duplicate detection in `resolveContainerConfiguration` properly stringifies symboles. --- .../tools/change-bounds/change-bounds-tool.ts | 21 +++++++++++-------- .../src/di/container-configuration.ts | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index ce59ade..a7f23fe 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -43,8 +43,8 @@ import { ISelectionListener, SelectionService } from '../../../base/selection-se import { BoundsAwareModelElement, ResizableModelElement, + SelectableBoundsAware, calcElementAndRoutingPoints, - forEachElement, getMatchingElements, isNonRoutableSelectedMovableBoundsAware, toElementAndBounds @@ -251,17 +251,20 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel protected handleMoveOnServer(target: GModelElement): Action[] { const operations: Operation[] = []; - operations.push(...this.handleMoveElementsOnServer(target)); - operations.push(...this.handleMoveRoutingPointsOnServer(target)); + const elementToMove = this.getElementsToMove(target); + operations.push(...this.handleMoveElementsOnServer(elementToMove)); + operations.push(...this.handleMoveRoutingPointsOnServer(elementToMove)); return operations.length > 0 ? [CompoundOperation.create(operations)] : []; } - protected handleMoveElementsOnServer(target: GModelElement): Operation[] { + protected getElementsToMove(target: GModelElement): SelectableBoundsAware[] { const selectedElements = getMatchingElements(target.index, isNonRoutableSelectedMovableBoundsAware); const selectionSet: Set = new Set(selectedElements); - const newBounds: ElementAndBounds[] = selectedElements - .filter(element => this.isValidMove(element, selectionSet)) - .map(toElementAndBounds); + return selectedElements.filter(element => this.isValidMove(element, selectionSet)); + } + + protected handleMoveElementsOnServer(elementsToMove: SelectableBoundsAware[]): Operation[] { + const newBounds = elementsToMove.map(toElementAndBounds); return newBounds.length > 0 ? [ChangeBoundsOperation.create(newBounds)] : []; } @@ -282,12 +285,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel return false; } - protected handleMoveRoutingPointsOnServer(target: GModelElement): Operation[] { + protected handleMoveRoutingPointsOnServer(elementsToMove: SelectableBoundsAware[]): Operation[] { const newRoutingPoints: ElementAndRoutingPoints[] = []; const routerRegistry = this.tool.edgeRouterRegistry; if (routerRegistry) { // If client routing is enabled -> delegate routing points of connected edges to server - forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { + elementsToMove.forEach(element => { if (element instanceof GConnectableElement) { element.incomingEdges .map(connectable => calcElementAndRoutingPoints(connectable, routerRegistry)) diff --git a/packages/protocol/src/di/container-configuration.ts b/packages/protocol/src/di/container-configuration.ts index 26fbf62..2425f37 100644 --- a/packages/protocol/src/di/container-configuration.ts +++ b/packages/protocol/src/di/container-configuration.ts @@ -85,7 +85,7 @@ export function resolveContainerConfiguration(...containerConfigurations: Contai } }); if (duplicates.length > 0) { - const culprits = duplicates.map(m => m.featureId).join(', '); + const culprits = duplicates.map(m => m.featureId.toString()).join(', '); throw new Error(`Could not resolve container configuration. Non-unique feature ids found in container configuration: ${culprits}`); } return modules; From 9925fe834593b124f4e0123960ca42ef4244376d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 18 Jun 2024 14:17:13 +0200 Subject: [PATCH 352/566] GLSP-1347: Fix autocomplete widget (#362) - Update `onSelect` method of autocomplete Widget to dispatch a `input` event instead of a `keyup` event. (Since autocompleter 8.x the suggestion update uses `input` instead of `keyup` - Enable task editor in standalone example by adding an explicit standaloneTaskEditorModule. Fixes https://github.com/eclipse-glsp/glsp/issues/1347 In addition: - Update dependencies and align with sprotty - Use fixed version of vscode-jsonrpc to avoid conflicts with Theia - Restore original container configuration order in `workflow-diagram-module` (was changed on accident in https://github.com/eclipse-glsp/glsp-client/pull/355) --- .../{di.config.ts => task-editor-module.ts} | 6 +- examples/workflow-glsp/src/index.ts | 2 +- .../src/workflow-diagram-module.ts | 8 +-- .../css/command-palette.css | 59 +++++++++++++++++++ examples/workflow-standalone/src/di.config.ts | 3 +- .../standalone-task-editor-module.ts | 31 ++++++++++ .../task-editor-key-listener.ts | 44 ++++++++++++++ packages/client/package.json | 8 ++- .../auto-complete/auto-complete-widget.ts | 4 +- packages/glsp-sprotty/package.json | 5 +- packages/protocol/package.json | 8 +-- 11 files changed, 158 insertions(+), 20 deletions(-) rename examples/workflow-glsp/src/direct-task-editing/{di.config.ts => task-editor-module.ts} (87%) create mode 100644 examples/workflow-standalone/css/command-palette.css create mode 100644 examples/workflow-standalone/src/features/direct-task-editing/standalone-task-editor-module.ts create mode 100644 examples/workflow-standalone/src/features/direct-task-editing/task-editor-key-listener.ts diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/task-editor-module.ts similarity index 87% rename from examples/workflow-glsp/src/direct-task-editing/di.config.ts rename to examples/workflow-glsp/src/direct-task-editing/task-editor-module.ts index df8e823..dba4e07 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/task-editor-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,9 +16,9 @@ import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/client'; import { TaskEditor } from './direct-task-editor'; -export const directTaskEditor = new FeatureModule( +export const taskEditorModule = new FeatureModule( (bind, _unbind, _isBound) => { bindAsService(bind, TYPES.IUIExtension, TaskEditor); }, - { featureId: Symbol('directTaskEditor') } + { featureId: Symbol('taskEditorModule') } ); diff --git a/examples/workflow-glsp/src/index.ts b/examples/workflow-glsp/src/index.ts index 74f104b..0874642 100644 --- a/examples/workflow-glsp/src/index.ts +++ b/examples/workflow-glsp/src/index.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './direct-task-editing/di.config'; export * from './direct-task-editing/direct-task-editor'; +export * from './direct-task-editing/task-editor-module'; export * from './model'; export * from './workflow-diagram-module'; export * from './workflow-snapper'; diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index f913e12..04c8ead 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -49,7 +49,7 @@ import 'balloon-css/balloon.min.css'; import { Container } from 'inversify'; import 'sprotty/css/edit-label.css'; import '../css/diagram.css'; -import { directTaskEditor } from './direct-task-editing/di.config'; +import { taskEditorModule } from './direct-task-editing/task-editor-module'; import { BranchingNode, CategoryNode, Icon, SynchronizationNode, TaskNode, WeightedEdge } from './model'; import { WorkflowSnapper } from './workflow-snapper'; import { WorkflowStartup } from './workflow-startup'; @@ -103,11 +103,11 @@ export function createWorkflowDiagramContainer(...containerConfiguration: Contai export function initializeWorkflowDiagramContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container { return initializeDiagramContainer( container, - ...containerConfiguration, - directTaskEditor, + taskEditorModule, helperLineModule, gridModule, debugModule, - workflowDiagramModule + workflowDiagramModule, + ...containerConfiguration ); } diff --git a/examples/workflow-standalone/css/command-palette.css b/examples/workflow-standalone/css/command-palette.css new file mode 100644 index 0000000..6f5f10f --- /dev/null +++ b/examples/workflow-standalone/css/command-palette.css @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +.command-palette.validation.error { + font-size: small; +} + +.command-palette.validation .validation-decorator { + position: absolute; + padding: 5px; + border-radius: 5px 5px 0px 0px; + color: white; + display: flex; + align-items: flex-start; + /* let error decoration fade in */ + -webkit-animation: fadein 0.3s; + -moz-animation: fadein 0.3s; + -ms-animation: fadein 0.3s; + -o-animation: fadein 0.3s; + animation: fadein 0.3s; +} + +.command-palette.validation .validation-decorator span { + margin-right: 5px; +} + +.command-palette.validation.error input, +.command-palette.validation.error input:focus { + color: var(--glsp-error-foregroundd); + outline-color: var(--glsp-error-foreground); +} + +.command-palette.validation.error .validation-decorator.error { + border: 1px solid var(--glsp-error-foreground); + background-color: var(--glsp-error-foreground); +} + +.command-palette.validation.warning input, +.command-palette.validation.warning input:focus { + color: var(--glsp-warning-foreground); + outline-color: var(--glsp-warning-foreground); +} + +.command-palette.validation.warning .validation-decorator.warning { + border: 1px solid var(--glsp-warning-foreground); + background-color: var(--glsp-warning-foreground); +} diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 1c0652d..7d3232d 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -29,6 +29,7 @@ import { import { Container } from 'inversify'; import { makeLoggerMiddleware } from 'inversify-logger-middleware'; import '../css/diagram.css'; +import { standaloneTaskEditorModule } from './features/direct-task-editing/standalone-task-editor-module'; import { getParameters } from './url-parameters'; export default function createContainer(options: IDiagramOptions): Container { const parameters = getParameters(); @@ -38,7 +39,7 @@ export default function createContainer(options: IDiagramOptions): Container { const container = createWorkflowDiagramContainer( createDiagramOptionsModule(options), { - add: accessibilityModule, + add: [standaloneTaskEditorModule, accessibilityModule], remove: toolPaletteModule }, STANDALONE_MODULE_CONFIG diff --git a/examples/workflow-standalone/src/features/direct-task-editing/standalone-task-editor-module.ts b/examples/workflow-standalone/src/features/direct-task-editing/standalone-task-editor-module.ts new file mode 100644 index 0000000..28f9c70 --- /dev/null +++ b/examples/workflow-standalone/src/features/direct-task-editing/standalone-task-editor-module.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { taskEditorModule } from '@eclipse-glsp-examples/workflow-glsp'; +import { FeatureModule, TYPES, bindAsService } from '@eclipse-glsp/client'; +import '../../../css/command-palette.css'; +import { TaskEditorKeyListener } from './task-editor-key-listener'; + +export const standaloneTaskEditorModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bindAsService(context, TYPES.KeyListener, TaskEditorKeyListener); + }, + { + featureId: Symbol('standaloneTaskEditor'), + requires: taskEditorModule + } +); diff --git a/examples/workflow-standalone/src/features/direct-task-editing/task-editor-key-listener.ts b/examples/workflow-standalone/src/features/direct-task-editing/task-editor-key-listener.ts new file mode 100644 index 0000000..fe3a80e --- /dev/null +++ b/examples/workflow-standalone/src/features/direct-task-editing/task-editor-key-listener.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { TaskEditor } from '@eclipse-glsp-examples/workflow-glsp'; +import { + Action, + EditorContextService, + GModelRoot, + KeyListener, + SetUIExtensionVisibilityAction, + matchesKeystroke +} from '@eclipse-glsp/client'; +import { inject, injectable } from 'inversify'; + +@injectable() +export class TaskEditorKeyListener extends KeyListener { + @inject(EditorContextService) + protected editorContext: EditorContextService; + + override keyDown(_element: GModelRoot, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'F2', 'ctrlCmd')) { + return [ + SetUIExtensionVisibilityAction.create({ + extensionId: TaskEditor.ID, + visible: true, + contextElementsId: [this.editorContext.selectedElements[0].id] + }) + ]; + } + return []; + } +} diff --git a/packages/client/package.json b/packages/client/package.json index a77c877..26e5612 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -47,16 +47,18 @@ }, "dependencies": { "@eclipse-glsp/sprotty": "2.2.0-next", - "autocompleter": "^9.1.0", + "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "lodash": "4.17.21" + "snabbdom": "~3.5.1", + "lodash": "4.17.21", + "vscode-jsonrpc": "8.2.0" }, "devDependencies": { "@types/file-saver": "^2.0.5", "@types/lodash": "4.14.191" }, "peerDependencies": { - "inversify": "^6.0.1" + "inversify": "~6.0.2" }, "publishConfig": { "access": "public" diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 4577dc4..de938e6 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -290,7 +290,7 @@ export class AutoCompleteWidget { if (AutoCompleteValue.is(item)) { this.inputElement.value = item.text; // trigger update of suggestions with an keyup event - window.setTimeout(() => this.inputElement.dispatchEvent(new Event('keyup'))); + window.setTimeout(() => this.inputElement.dispatchEvent(new Event('input'))); } else { this.executeFromSuggestion(item); this.notifyClose('submission'); diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index b1a5369..3ce7ef5 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -37,7 +37,8 @@ "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", - "sprotty-protocol": "1.2.0" + "sprotty-protocol": "1.2.0", + "vscode-jsonrpc": "8.2.0" }, "devDependencies": { "@types/jsdom": "^21.1.3", @@ -45,7 +46,7 @@ "snabbdom-to-html": "^7.1.0" }, "peerDependencies": { - "inversify": "^6.0.1" + "inversify": "~6.0.2" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/package.json b/packages/protocol/package.json index be03dd2..664d5eb 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -46,14 +46,14 @@ }, "dependencies": { "sprotty-protocol": "1.2.0", - "uuid": "7.0.3", - "vscode-jsonrpc": "^8.0.2" + "uuid": "~10.0.0", + "vscode-jsonrpc": "8.2.0" }, "devDependencies": { - "@types/uuid": "3.4.5" + "@types/uuid": "~9.0.8" }, "peerDependencies": { - "inversify": "^6.0.1" + "inversify": "~6.0.2" }, "publishConfig": { "access": "public" From 9667047cf9289f7c92d08809c36cd83b0d0c82c2 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 18 Jun 2024 14:35:21 +0200 Subject: [PATCH 353/566] GLSP-1347: Update standalone editor styling (#363) --- examples/workflow-standalone/css/command-palette.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/workflow-standalone/css/command-palette.css b/examples/workflow-standalone/css/command-palette.css index 6f5f10f..6eec88c 100644 --- a/examples/workflow-standalone/css/command-palette.css +++ b/examples/workflow-standalone/css/command-palette.css @@ -13,8 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -.command-palette.validation.error { - font-size: small; +.command-palette.validation.error, +.command-palette.validation.warning { + font-size: 10pt; } .command-palette.validation .validation-decorator { @@ -49,11 +50,11 @@ .command-palette.validation.warning input, .command-palette.validation.warning input:focus { - color: var(--glsp-warning-foreground); outline-color: var(--glsp-warning-foreground); } .command-palette.validation.warning .validation-decorator.warning { border: 1px solid var(--glsp-warning-foreground); background-color: var(--glsp-warning-foreground); + color: black; } From ea41a05ab849286f94d0b7892fe62abd0e5f3c31 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 20 Jun 2024 13:19:16 +0200 Subject: [PATCH 354/566] View improvements (#364) - Refactor grind background image rendering to a css only approach - Use @jsx annotion for view files Use @jsx annotation instead of const JSX definition for view files. Adapt eslint config to ignore unused svg and html imports to avoid explict deactivation of the rule in each view file. Reason - Sprotty base views also use annotations - Annotations are more robust. E.g. mocha tests with ts-node fail with an unused local error when using JSX const - Add tests for `initializeDiagramContainer` - Cleanup GLSPProjectionView overrides - Also add Grid properties if grid is not visible - Adjust variable naming in default grid css --- .../src/workflow-diagram-module.ts | 8 +- examples/workflow-glsp/src/workflow-views.tsx | 4 +- packages/client/css/grid.css | 14 +-- packages/client/src/default-modules.spec.ts | 56 ++++++++++++ .../features/debug/debug-bounds-decorator.tsx | 5 +- .../client/src/features/helper-lines/view.tsx | 4 +- .../src/features/tools/change-bounds/view.tsx | 4 +- .../src/features/tools/edge-creation/view.tsx | 6 +- .../features/tools/marquee-selection/view.tsx | 6 +- .../node-creation/node-creation-views.tsx | 5 +- packages/client/src/views/compartments.tsx | 6 +- packages/client/src/views/gedge-view.tsx | 6 +- packages/client/src/views/ggraph-view.tsx | 14 +-- .../client/src/views/glsp-projection-view.tsx | 89 +++---------------- .../client/src/views/issue-marker-view.tsx | 4 +- .../client/src/views/rounded-corner-view.tsx | 6 +- .../glsp-sprotty/src/svg-views-override.tsx | 5 +- 17 files changed, 100 insertions(+), 142 deletions(-) create mode 100644 packages/client/src/default-modules.spec.ts diff --git a/examples/workflow-glsp/src/workflow-diagram-module.ts b/examples/workflow-glsp/src/workflow-diagram-module.ts index 04c8ead..a89d88a 100644 --- a/examples/workflow-glsp/src/workflow-diagram-module.ts +++ b/examples/workflow-glsp/src/workflow-diagram-module.ts @@ -43,7 +43,8 @@ import { editLabelFeature, gridModule, helperLineModule, - initializeDiagramContainer + initializeDiagramContainer, + overrideModelElement } from '@eclipse-glsp/client'; import 'balloon-css/balloon.min.css'; import { Container } from 'inversify'; @@ -69,16 +70,15 @@ export const workflowDiagramModule = new FeatureModule( configureModelElement(context, 'task:manual', TaskNode, RoundedCornerNodeView); configureModelElement(context, 'label:heading', GLabel, GLabelView, { enable: [editLabelFeature] }); configureModelElement(context, 'comp:comp', GCompartment, GCompartmentView); - configureModelElement(context, 'comp:header', GCompartment, GCompartmentView); configureModelElement(context, 'label:icon', GLabel, GLabelView); - configureModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); + overrideModelElement(context, DefaultTypes.EDGE, GEdge, WorkflowEdgeView); configureModelElement(context, 'edge:weighted', WeightedEdge, WorkflowEdgeView); configureModelElement(context, 'icon', Icon, IconView); configureModelElement(context, 'activityNode:merge', BranchingNode, DiamondNodeView); configureModelElement(context, 'activityNode:decision', BranchingNode, DiamondNodeView); configureModelElement(context, 'activityNode:fork', SynchronizationNode, RectangularNodeView); configureModelElement(context, 'activityNode:join', SynchronizationNode, RectangularNodeView); - configureModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); + overrideModelElement(context, DefaultTypes.GRAPH, GGraph, GLSPProjectionView); configureModelElement(context, 'category', CategoryNode, RoundedCornerNodeView); configureModelElement(context, 'struct', GCompartment, StructureCompartmentView); diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index e014969..036a065 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ import { GEdge, Point, @@ -30,9 +31,6 @@ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { Icon, isTaskNode } from './model'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { protected override renderAdditionals(edge: GEdge, segments: Point[], context: RenderingContext): VNode[] { diff --git a/packages/client/css/grid.css b/packages/client/css/grid.css index 2f628e9..398f642 100644 --- a/packages/client/css/grid.css +++ b/packages/client/css/grid.css @@ -14,12 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -:root { - --grid-color: rgba(0, 0, 0, 0.1); -} - -/** Control visibility of background image through CSS class on parent */ .grid-background .sprotty-graph, .grid-background.sprotty-graph { - background-image: var(--grid-background-image); + --grid-stroke-width: calc(1px * var(--grid-background-zoom)); + --grid-stroke-width-half: calc(var(--grid-stroke-width) / 2); + --grid-color: rgba(0, 0, 0, 0.1); + background-image: linear-gradient(to right, var(--grid-color) var(--grid-stroke-width), transparent var(--grid-stroke-width)), + linear-gradient(to bottom, var(--grid-color) var(--grid-stroke-width), transparent var(--grid-stroke-width)); + background-size: var(--grid-background-width) var(--grid-background-height); + background-position: calc(var(--grid-background-x) - var(--grid-stroke-width-half)) + calc(var(--grid-background-y) - var(--grid-stroke-width-half)); } diff --git a/packages/client/src/default-modules.spec.ts b/packages/client/src/default-modules.spec.ts new file mode 100644 index 0000000..ee78305 --- /dev/null +++ b/packages/client/src/default-modules.spec.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { FeatureModule } from '@eclipse-glsp/sprotty'; +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { defaultModule } from './base/default.module'; +import { DEFAULT_MODULES, initializeDiagramContainer } from './default-modules'; + +describe('default-modules', () => { + // InitializeDiagramContainer internally uses `resolveContainerConfiguration` so we only test functionality + // that is not covered by the tests of `resolveContainerConfiguration`. + describe('initializeDiagramContainer', () => { + const sandbox = sinon.createSandbox(); + const container = new Container(); + const loadSpy = sandbox.spy(container, 'load'); + container.snapshot(); + + beforeEach(() => { + sandbox.reset(); + container.restore(); + container.snapshot(); + }); + it('should initialize the diagram container with the default modules in addition to the given config and load them first', () => { + const extraModule = new FeatureModule(() => {}); + initializeDiagramContainer(container, { add: extraModule }); + expect(loadSpy.calledOnce).to.be.true; + const callArgs = loadSpy.firstCall.args; + const lastModule = callArgs.pop(); + expect(callArgs).to.be.deep.equal(DEFAULT_MODULES).ordered; + expect(lastModule).to.be.equal(extraModule); + }); + it('should throw an error if the base (default) module is removed via configuration', () => { + expect(() => initializeDiagramContainer(container, { remove: defaultModule })).to.throw(/Invalid module configuration/); + }); + // eslint-disable-next-line max-len + it('should throw an error if the base (default) module is not the first module of the resolved configured (removed and added again)', () => { + expect(() => initializeDiagramContainer(container, { remove: defaultModule, add: defaultModule })).to.throw( + /Invalid module configuration/ + ); + }); + }); +}); diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx index df073f2..c8647ec 100644 --- a/packages/client/src/features/debug/debug-bounds-decorator.tsx +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable max-len */ - +/** @jsx svg */ import { Bounds, GModelElement, IVNodePostprocessor, Point, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; @@ -22,9 +22,6 @@ import { GGraph } from '../../model'; import { BoundsAwareModelElement } from '../../utils/gmodel-util'; import { DebugManager } from './debug-manager'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - export const CSS_DEBUG_BOUNDS = 'debug-bounds'; @injectable() diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index b47b6d6..afde81c 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -13,14 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ import { IViewArgs, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { HelperLine, SelectionBounds } from './model'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class HelperLineView extends ShapeView { override render(model: HelperLine, _context: RenderingContext): VNode | undefined { diff --git a/packages/client/src/features/tools/change-bounds/view.tsx b/packages/client/src/features/tools/change-bounds/view.tsx index 63d4ef6..afeecb5 100644 --- a/packages/client/src/features/tools/change-bounds/view.tsx +++ b/packages/client/src/features/tools/change-bounds/view.tsx @@ -13,14 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ import { IView, Point, RenderingContext, setAttr, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { GResizeHandle } from '../../change-bounds/model'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class GResizeHandleView implements IView { render(handle: GResizeHandle, context: RenderingContext): VNode | undefined { diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 86ac077..496431b 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -13,12 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ +import { GModelElement, IView, Point, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, Point, RenderingContext, GModelElement, svg } from '@eclipse-glsp/sprotty'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; /** * This view is used for the invisible end of the feedback edge. diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 13470d8..17e4067 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -13,14 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ +import { RectangularNodeView, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { RectangularNodeView, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { MarqueeNode } from './model'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class MarqueeView extends RectangularNodeView { override render(node: MarqueeNode, context: RenderingContext): VNode { diff --git a/packages/client/src/features/tools/node-creation/node-creation-views.tsx b/packages/client/src/features/tools/node-creation/node-creation-views.tsx index 9a51445..aac320c 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-views.tsx +++ b/packages/client/src/features/tools/node-creation/node-creation-views.tsx @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - +/** @jsx svg */ import { IView, RenderingContext, TYPES, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; @@ -21,9 +21,6 @@ import { GArgument } from '../../../utils/argument-utils'; import { Grid } from '../../grid/grid'; import { ARG_LENGTH, InsertIndicator } from './insert-indicator'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class InsertIndicatorView implements IView { @optional() @inject(TYPES.Grid) protected grid?: Grid; diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index f8680aa..c4002a7 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -13,12 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ +import { GCompartment, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { GCompartment, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; @injectable() export class StructureCompartmentView extends ShapeView { diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index d615561..e59c5b8 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -13,14 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ import { Point, PolylineEdgeView, RenderingContext, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { EdgePadding } from '../utils/argument-utils'; import { GEdge } from '../model'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; +import { EdgePadding } from '../utils/argument-utils'; @injectable() export class GEdgeView extends PolylineEdgeView { diff --git a/packages/client/src/views/ggraph-view.tsx b/packages/client/src/views/ggraph-view.tsx index aa503d2..a60d562 100644 --- a/packages/client/src/views/ggraph-view.tsx +++ b/packages/client/src/views/ggraph-view.tsx @@ -32,7 +32,7 @@ export class GGraphView extends SGraphView { } protected getGridStyle(viewport: Readonly, context: RenderingContext): GridStyle { - if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { + if (context.targetKind === 'hidden' || !this.gridManager) { return {}; } const bounds = this.getBackgroundBounds(viewport, context, this.gridManager); @@ -41,11 +41,7 @@ export class GGraphView extends SGraphView { [GridProperty.GRID_BACKGROUND_Y]: bounds.y + 'px', [GridProperty.GRID_BACKGROUND_WIDTH]: bounds.width + 'px', [GridProperty.GRID_BACKGROUND_HEIGHT]: bounds.height + 'px', - [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '', - [GridProperty.GRID_BACKGROUND_IMAGE]: this.getBackgroundImage(viewport, context, this.gridManager), - backgroundPosition: `${bounds.x}px ${bounds.y}px`, - backgroundSize: `${bounds.width}px ${bounds.height}px` - // we do not set the background-image directly in the style object, because we want to toggle it on and off via CSS + [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '' }; } @@ -54,10 +50,4 @@ export class GGraphView extends SGraphView { const size = Dimension.fromPoint(Point.multiplyScalar(gridManager.grid, viewport.zoom)); return { ...position, ...size }; } - - protected getBackgroundImage(viewport: Readonly, context: RenderingContext, gridManager: GridManager): string { - const color = getComputedStyle(document.documentElement).getPropertyValue(GridProperty.GRID_COLOR).trim().replace(/#/g, '%23'); - // eslint-disable-next-line max-len - return `url('data:image/svg+xml;utf8, ')`; - } } diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index ca803b2..0322409 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - +/** @jsx html */ import { Bounds, Dimension, @@ -36,9 +36,6 @@ import { VNode, VNodeStyle, h } from 'snabbdom'; import { GridManager, GridStyle } from '../features/grid/grid-manager'; import { GridProperty } from '../features/grid/grid-style'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: html }; - /** * Special viewport root view that renders horizontal and vertical projection bars for quick navigation. */ @@ -73,7 +70,7 @@ export class GLSPProjectionView extends ProjectedViewportView { } protected getGridStyle(viewport: Readonly, context: RenderingContext): GridStyle { - if (context.targetKind === 'hidden' || !this.gridManager?.isGridVisible) { + if (context.targetKind === 'hidden' || !this.gridManager) { return {}; } const bounds = this.getBackgroundBounds(viewport, context, this.gridManager); @@ -82,11 +79,7 @@ export class GLSPProjectionView extends ProjectedViewportView { [GridProperty.GRID_BACKGROUND_Y]: bounds.y + 'px', [GridProperty.GRID_BACKGROUND_WIDTH]: bounds.width + 'px', [GridProperty.GRID_BACKGROUND_HEIGHT]: bounds.height + 'px', - [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '', - [GridProperty.GRID_BACKGROUND_IMAGE]: this.getBackgroundImage(viewport, context, this.gridManager), - backgroundPosition: `${bounds.x}px ${bounds.y}px`, - backgroundSize: `${bounds.width}px ${bounds.height}px` - // we do not set the background-image directly in the style object, because we want to toggle it on and off via CSS + [GridProperty.GRID_BACKGROUND_ZOOM]: viewport.zoom + '' }; } @@ -96,35 +89,15 @@ export class GLSPProjectionView extends ProjectedViewportView { return { ...position, ...size }; } - protected getBackgroundImage(viewport: Readonly, context: RenderingContext, gridManager: GridManager): string { - const color = getComputedStyle(document.documentElement).getPropertyValue(GridProperty.GRID_COLOR).trim().replace(/#/g, '%23'); - // eslint-disable-next-line max-len - return `url('data:image/svg+xml;utf8, ')`; - } - protected override renderProjectionBar( projections: ViewProjection[], model: Readonly, modelBounds: Bounds, orientation: 'horizontal' | 'vertical' ): VNode { - const params: ProjectionParams = { modelBounds, orientation } as ProjectionParams; - // NOTE: Here we assume that the projection bars have the same size as the diagram canvas, - // i.e. they are drawn as overlay above the canvas. - params.factor = - orientation === 'horizontal' ? model.canvasBounds.width / modelBounds.width : model.canvasBounds.height / modelBounds.height; - params.zoomedFactor = params.factor / model.zoom; - return ( -
- {this.renderViewport(model, params)} - {projections.map(p => this.renderProjection(p, model, params))} -
- ); + const vnode = super.renderProjectionBar(projections, model, modelBounds, orientation); + setClass(vnode, 'bordered-projection-bar', true); + return vnode; } protected override renderViewport(model: Readonly, params: ProjectionParams): VNode { @@ -170,50 +143,14 @@ export class GLSPProjectionView extends ProjectedViewportView { model: Readonly, params: ProjectionParams ): VNode { - let canvasSize; - let projPos; - let projSize: number; - if (params.orientation === 'horizontal') { - canvasSize = model.canvasBounds.width; - projPos = (projection.projectedBounds.x - params.modelBounds.x) * params.factor; - projSize = projection.projectedBounds.width * params.factor; + const vnode = super.renderProjection(projection, model, params); + setClass(vnode, 'glsp-projection', true); + const style = vnode.data!.style!; + if (style.left) { + style.height = '60%'; } else { - canvasSize = model.canvasBounds.height; - projPos = (projection.projectedBounds.y - params.modelBounds.y) * params.factor; - projSize = projection.projectedBounds.height * params.factor; + style.width = '60%'; } - if (projPos < 0) { - projSize += projPos; - projPos = 0; - } else if (projPos > canvasSize) { - projPos = canvasSize; - } - if (projSize < 0) { - projSize = 0; - } else if (projPos + projSize > canvasSize) { - projSize = canvasSize - projPos; - } - const style: VNodeStyle = - params.orientation === 'horizontal' - ? { - left: `${projPos}px`, - width: `${projSize}px`, - height: '60%' - } - : { - top: `${projPos}px`, - height: `${projSize}px`, - width: '60%' - }; - const result = ( -
- ); - projection.cssClasses.forEach(cl => setClass(result, cl, true)); - return result; + return vnode; } } diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 6766b18..2059aa2 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -14,14 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable max-len */ +/** @jsx svg */ import { GIssueSeverity, IssueMarkerView, RenderingContext, setClass, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { GIssueMarker } from '../features/validation/issue-marker'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class GIssueMarkerView extends IssueMarkerView { override render(marker: GIssueMarker, _context: RenderingContext): VNode { diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 9e42614..bee784d 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -13,15 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** @jsx svg */ +import { GNode, GPort, GShapeElement, Hoverable, RectangularNodeView, RenderingContext, Selectable, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; -import { Hoverable, RectangularNodeView, RenderingContext, GNode, GPort, GShapeElement, Selectable, svg } from '@eclipse-glsp/sprotty'; import { CornerRadius } from '../utils/argument-utils'; import { RoundedCornerWrapper } from './rounded-corner'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class RoundedCornerNodeView extends RectangularNodeView { override render(node: Readonly, context: RenderingContext): VNode | undefined { diff --git a/packages/glsp-sprotty/src/svg-views-override.tsx b/packages/glsp-sprotty/src/svg-views-override.tsx index 45b82d8..9a72c91 100644 --- a/packages/glsp-sprotty/src/svg-views-override.tsx +++ b/packages/glsp-sprotty/src/svg-views-override.tsx @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - +/** @jsx svg */ import { Bounds, Hoverable, Selectable } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; @@ -28,9 +28,6 @@ import { svg } from 'sprotty'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const JSX = { createElement: svg }; - @injectable() export class DiamondNodeView extends SprottyDiamondNodeView { override render( From 3814834e4cd0083aceba8ecf556e5426f35cd615 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 21 Jun 2024 09:42:12 +0200 Subject: [PATCH 355/566] Minor cleanup (#88) - Change visibility of private fields to protected for better extensibility - Update to latest protocol version - This introduces a minor unavoidable API break because GIssue has changed from a class to an interface definition - Ensure that actions queued with `dispatchAfterNextUpdate` are also dispatched after the initial `SetModelAction` - Fix copyright header violations --- .../src/common/workflow-popup-factory.ts | 4 ++-- packages/graph/src/gedge-layoutable.ts | 5 ++--- packages/graph/src/gissue-marker.ts | 11 ++--------- packages/server/package.json | 2 +- .../src/common/actions/action-dispatcher.ts | 17 +++++++++++++---- .../model/request-model-action-handler.ts | 4 ++-- .../src/common/utils/client-options-util.ts | 4 ++-- 7 files changed, 24 insertions(+), 23 deletions(-) diff --git a/examples/workflow-server/src/common/workflow-popup-factory.ts b/examples/workflow-server/src/common/workflow-popup-factory.ts index ca775cd..d55432a 100644 --- a/examples/workflow-server/src/common/workflow-popup-factory.ts +++ b/examples/workflow-server/src/common/workflow-popup-factory.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -38,7 +38,7 @@ export class WorkflowPopupFactory implements PopupModelFactory { return undefined; } - private generateBody(task: TaskNode): string { + protected generateBody(task: TaskNode): string { return `Type: ${task.taskType} ${NL} Duration: ${task.duration} ${NL} Reference: ${task.references} ${NL}`; diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts index ec4b3a9..72d571a 100644 --- a/packages/graph/src/gedge-layoutable.ts +++ b/packages/graph/src/gedge-layoutable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { EdgeSide } from '@eclipse-glsp/protocol'; import { GModelElement, GModelElementBuilder } from './gmodel-element'; -export type EdgeSide = 'left' | 'right' | 'top' | 'bottom' | 'on'; - export interface GEdgePlacement { rotate: boolean; side: EdgeSide; diff --git a/packages/graph/src/gissue-marker.ts b/packages/graph/src/gissue-marker.ts index e470b4c..1250925 100644 --- a/packages/graph/src/gissue-marker.ts +++ b/packages/graph/src/gissue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,16 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DefaultTypes, flatPush, MaybeArray } from '@eclipse-glsp/protocol'; +import { DefaultTypes, flatPush, GIssue, GIssueSeverity, MaybeArray } from '@eclipse-glsp/protocol'; import { GShapeElement, GShapeElementBuilder } from './gshape-element'; -export type GIssueSeverity = 'error' | 'warning' | 'info'; - -export class GIssue { - message: string; - severity: GIssueSeverity; -} - export class GIssueMarker extends GShapeElement { static builder(): GIssueMarkerBuilder { return new GIssueMarkerBuilder(GIssueMarker).type(DefaultTypes.ISSUE_MARKER); diff --git a/packages/server/package.json b/packages/server/package.json index 40bf164..8084843 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -63,7 +63,7 @@ "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", - "vscode-jsonrpc": "^8.0.2", + "vscode-jsonrpc": "8.2.0", "winston": "^3.3.3", "ws": "^8.12.1" }, diff --git a/packages/server/src/common/actions/action-dispatcher.ts b/packages/server/src/common/actions/action-dispatcher.ts index 0c634f4..3f7cc82 100644 --- a/packages/server/src/common/actions/action-dispatcher.ts +++ b/packages/server/src/common/actions/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, flatPush, MaybeArray, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { + Action, + Disposable, + MaybeArray, + RequestAction, + ResponseAction, + SetModelAction, + UpdateModelAction, + flatPush +} from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ClientId } from '../di/service-identifiers'; import { GLSPServerError } from '../utils/glsp-server-error'; @@ -67,7 +76,7 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable { protected clientActionForwarder: ClientActionForwarder; @inject(Logger) - private logger: Logger; + protected logger: Logger; @inject(ClientId) protected clientId: string; @@ -98,7 +107,7 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable { responses.push(...response); } - if (UpdateModelAction.is(action) && this.postUpdateQueue.length > 0) { + if (this.postUpdateQueue.length > 0 && (UpdateModelAction.is(action) || SetModelAction.is(action))) { responses.push(...this.postUpdateQueue); this.postUpdateQueue = []; } diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 1bdbe57..093a10e 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -29,7 +29,7 @@ export class RequestModelActionHandler implements ActionHandler { actionKinds = [RequestModelAction.KIND]; @inject(Logger) - private logger: Logger; + protected logger: Logger; @inject(SourceModelStorage) protected sourceModelStorage: SourceModelStorage; diff --git a/packages/server/src/common/utils/client-options-util.ts b/packages/server/src/common/utils/client-options-util.ts index 5ed6387..80496f9 100644 --- a/packages/server/src/common/utils/client-options-util.ts +++ b/packages/server/src/common/utils/client-options-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,7 +17,7 @@ import { Args } from '@eclipse-glsp/protocol'; import { ArgsUtil } from './args-util'; export class ClientOptionsUtil { - private static FILE_PREFIX = 'file://'; + public static FILE_PREFIX = 'file://'; public static IS_RECONNECTING = 'isReconnecting'; public static adaptUri(uri: string): string { From 0f9944dd1f539d398ad93d8d6116535e20e3c48a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 21 Jun 2024 12:15:16 +0200 Subject: [PATCH 356/566] Fix workflow documentation navigation example (#89) Update the NodeDocumentationNavigationTargetProvider to not rely on the tasks name which is currently unreliable (see https://github.com/eclipse-glsp/glsp/issues/1351) and instead relies on the `text` property of the tasks label. --- .../provider/node-documentation-navigation-target-provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts index 50f43f1..32f8709 100644 --- a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts +++ b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Args, EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; -import { JsonOpenerOptions, ModelState, NavigationTargetProvider } from '@eclipse-glsp/server'; +import { GLabel, JsonOpenerOptions, ModelState, NavigationTargetProvider } from '@eclipse-glsp/server'; import { inject, injectable } from 'inversify'; import { TaskNode } from '../graph-extension'; @@ -28,7 +28,7 @@ export class NodeDocumentationNavigationTargetProvider implements NavigationTarg getTargets(editorContext: EditorContext): NavigationTarget[] { if (editorContext.selectedElementIds.length === 1) { const taskNode = this.modelState.index.findByClass(editorContext.selectedElementIds[0], TaskNode); - if (!taskNode || !(taskNode.id === 'task0')) { + if (!taskNode || !taskNode.children.some(child => child instanceof GLabel && child.text === 'Push')) { return []; } From 1b23cf248498b5b3bc01df83b49012fe4d70091a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 21 Jun 2024 12:33:25 +0200 Subject: [PATCH 357/566] Minor improvements (#366) - Update example1.wf - Remove projection specific css styles for hiding the projection bars on mouse enter/leave over the diagram. This is not a generic feature only certain integration types support it (Theia//VS Code) so it should be handled in the integration specific code base - Fix timing issue in `GLSPCommandStack` when notifying `GModelRootListeners` after a model change. Properly wait until the listeners a notified before completing the returned promise. This ensures that all model listeners have received the updated model information before the next action is processed by the dispatcher. - Introduce `dispatcherAfterNextUpdate` method in `GLSPActionDispatcher` that allows to delay the dispatching of actions until the next model update is processed from the server. (Now actions can be queued from both client and server) - Update changelog --- examples/workflow-standalone/app/example1.wf | 24 ++++++++++++++++ packages/client/css/glsp-sprotty.css | 9 ------ packages/client/src/base/action-dispatcher.ts | 28 +++++++++++++++++-- packages/client/src/base/command-stack.ts | 10 ++++--- packages/client/src/base/default.module.ts | 1 + 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 3757ab5..94bb9ff 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -253,6 +253,12 @@ "width": 32, "height": 32 }, + "resizeLocations": [ + "top", + "right", + "bottom", + "left" + ], "children": [] }, { @@ -270,6 +276,12 @@ "width": 32, "height": 32 }, + "resizeLocations": [ + "top", + "right", + "bottom", + "left" + ], "children": [] }, { @@ -704,6 +716,12 @@ "width": 32, "height": 32 }, + "resizeLocations": [ + "top", + "right", + "bottom", + "left" + ], "children": [] }, { @@ -721,6 +739,12 @@ "width": 32, "height": 32 }, + "resizeLocations": [ + "top", + "right", + "bottom", + "left" + ], "children": [] }, { diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 7f678a4..26c7677 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -312,15 +312,6 @@ border-width: 2px; } -.mouse-enter .projection-scroll-bar { - opacity: 0.3; -} - -.mouse-leave .projection-scroll-bar { - opacity: 0; - pointer-events: none; -} - .bordered-projection-bar { border-left: 1px solid rgba(212, 212, 212, 0.2); border-top: 1px solid rgba(212, 212, 212, 0.2); diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index c873058..d45cd3f 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,20 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ActionDispatcher, EMPTY_ROOT, RequestAction, ResponseAction, SetModelAction } from '@eclipse-glsp/sprotty'; +import { Action, ActionDispatcher, EMPTY_ROOT, GModelRoot, RequestAction, ResponseAction, SetModelAction } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; +import { IGModelRootListener } from './editor-context-service'; import { OptionalAction } from './model/glsp-model-source'; import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @injectable() -export class GLSPActionDispatcher extends ActionDispatcher { +export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRootListener { protected readonly timeouts: Map = new Map(); protected initializedConstraint = false; @inject(ModelInitializationConstraint) protected initializationConstraint: ModelInitializationConstraint; + protected postUpdateQueue: Action[] = []; + override initialize(): Promise { if (!this.initialized) { this.initialized = this.doInitialize(); @@ -71,6 +74,27 @@ export class GLSPActionDispatcher extends ActionDispatcher { this.initializationConstraint.onInitialized(() => this.dispatchAll(actions)); } + /** + * Processes all given actions, by dispatching them to the corresponding handlers, after the next model update. + * The given actions are queued until the next model update cycle has been completed i.e. + * the `EditorContextService.onModelRootChanged` event is triggered. + * + * @param actions The actions that should be dispatched after the next model update + */ + dispatchAfterNextUpdate(...actions: Action[]): void { + this.postUpdateQueue.push(...actions); + } + + modelRootChanged(_root: Readonly): void { + if (this.postUpdateQueue.length === 0) { + return; + } + + const toDispatch = [...this.postUpdateQueue]; + this.postUpdateQueue = []; + this.dispatchAll(toDispatch); + } + override async dispatch(action: Action): Promise { const result = await super.dispatch(action); this.initializationConstraint.notifyDispatched(action); diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 86604fd..ddb0ef3 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -67,12 +67,14 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.currentModel; } - override execute(command: ICommand): Promise { - const result = super.execute(command); + override async execute(command: ICommand): Promise { if (command instanceof SetModelCommand || command instanceof UpdateModelCommand) { - result.then(root => this.notifyListeners(root)); + const result = await super.execute(command); + this.notifyListeners(result); + return result; } - return result; + + return super.execute(command); } protected notifyListeners(root: Readonly): void { diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index f6351b6..0ad81f0 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -99,6 +99,7 @@ export const defaultModule = new FeatureModule( bindAsService(context, TYPES.MouseListener, SelectionClearingMouseListener); bindOrRebind(context, TYPES.ICommandStack).to(GLSPCommandStack).inSingletonScope(); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); + bind(TYPES.IGModelRootListener).toService(GLSPActionDispatcher); bindOrRebind(context, TYPES.IActionDispatcher).toService(GLSPActionDispatcher); bindOrRebind(context, ActionHandlerRegistry).to(GLSPActionHandlerRegistry).inSingletonScope(); From 63b39c3a5e0fd5e9b673914c7459293376604bea Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 25 Jun 2024 15:04:25 +0200 Subject: [PATCH 358/566] Improve ToolManagerAPI (#367) - Add `defaultToolsEnabled` flag to `ToolManager` which is true if currently only the default tools are enabled i.e. no tools have been enabled explicitly - Avoid (re)activation of default tools if the are already enabled - Remove unused GRID_STYLE property Co-authored-by: Martin Fleck --- .../src/base/tool-manager/tool-manager.ts | 20 ++++++++++++++++--- .../glsp-context-menu-mouse-listener.ts | 4 ++-- .../client/src/features/grid/grid-style.ts | 1 - 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/client/src/base/tool-manager/tool-manager.ts b/packages/client/src/base/tool-manager/tool-manager.ts index 14230dd..2e1078e 100644 --- a/packages/client/src/base/tool-manager/tool-manager.ts +++ b/packages/client/src/base/tool-manager/tool-manager.ts @@ -25,7 +25,8 @@ import { MaybePromise, TYPES, distinctAdd, - matchesKeystroke + matchesKeystroke, + pluck } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService, IEditModeListener } from '../editor-context-service'; @@ -49,6 +50,9 @@ export interface IToolManager { /** The currently active tools, which are either specifically enabled tools, or the default tools. */ readonly activeTools: Tool[]; + /** Flag to indicate that the default tools are enabled and no other tool was explicitly enabled. */ + readonly defaultToolsEnabled: boolean; + /** * Enables the tools with the specified `toolIds`. * Therefore, this manager first disables currently active tools and then enable the @@ -62,7 +66,7 @@ export interface IToolManager { enable(toolIds: string[]): void; /** - * Enables all default tools. + * Enables all default tools. If the default tools are already enabled, this is a no-op. */ enableDefaultTools(): void; @@ -90,6 +94,11 @@ export class ToolManager implements IToolManager, IDiagramStartup, IEditModeList readonly tools: Tool[] = []; readonly defaultTools: Tool[] = []; + protected _defaultToolsEnabled = false; + get defaultToolsEnabled(): boolean { + return this._defaultToolsEnabled; + } + preLoadDiagram(): MaybePromise { const tools: Tool[] = this.lazyInjector.getAll(TYPES.ITool); const defaultTools: Tool[] = this.lazyInjector.getAll(TYPES.IDefaultTool); @@ -119,12 +128,17 @@ export class ToolManager implements IToolManager, IDiagramStartup, IEditModeList } disableActiveTools(): void { + this._defaultToolsEnabled = false; this.actives.forEach(tool => tool.disable()); this.actives.splice(0, this.actives.length); } enableDefaultTools(): void { - this.enable(this.defaultTools.map(tool => tool.id)); + if (this.defaultToolsEnabled) { + return; + } + this.enable(pluck(this.defaultTools, 'id')); + this._defaultToolsEnabled = true; } enable(toolIds: string[]): void { diff --git a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts index b391b08..0f392f6 100644 --- a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -83,7 +83,7 @@ export class GLSPContextMenuMouseListener extends MouseListener { return FocusStateChangedAction.create(false); } - // Clear selection the context menu target is not selectable + // Clear selection if the context menu target is not selectable // Otherwise either maintain current selection if target is already selected or single select the current target. protected async handleContextElementSelection(target: GModelElement, event: MouseEvent): Promise { const selectableTarget = findParentByFeature(target, isSelectable); diff --git a/packages/client/src/features/grid/grid-style.ts b/packages/client/src/features/grid/grid-style.ts index 150657b..96eae8b 100644 --- a/packages/client/src/features/grid/grid-style.ts +++ b/packages/client/src/features/grid/grid-style.ts @@ -20,6 +20,5 @@ export namespace GridProperty { export const GRID_BACKGROUND_WIDTH = '--grid-background-width'; export const GRID_BACKGROUND_HEIGHT = '--grid-background-height'; export const GRID_BACKGROUND_ZOOM = '--grid-background-zoom'; - export const GRID_BACKGROUND_IMAGE = '--grid-background-image'; export const GRID_COLOR = '--grid-color'; } From 56f553b8ba9625b16eb2dfa99701248b53a25787 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 26 Jun 2024 14:59:32 +0200 Subject: [PATCH 359/566] Improve diagram configuration (#368) Also allow partial configuration of `ViewerOptions` when using the `createDiagramOptionsModule` utility function --- packages/client/src/default-modules.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index d4b1d2a..21384d1 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -42,6 +42,7 @@ import { elementTemplateModule } from './features/element-template/element-templ import { exportModule } from './features/export/export-modules'; import { typeHintsModule } from './features/hints/type-hints-module'; import { hoverModule } from './features/hover/hover-module'; +import { labelEditUiModule } from './features/label-edit-ui/label-edit-ui-module'; import { labelEditModule } from './features/label-edit/label-edit-module'; import { layoutModule } from './features/layout/layout-module'; import { navigationModule } from './features/navigation/navigation-module'; @@ -60,7 +61,6 @@ import { nodeCreationToolModule } from './features/tools/node-creation/node-crea import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; import { markerNavigatorModule, validationModule } from './features/validation/validation-modules'; import { viewportModule } from './features/viewport/viewport-modules'; -import { labelEditUiModule } from './features/label-edit-ui/label-edit-ui-module'; export const DEFAULT_MODULES = [ defaultModule, @@ -106,11 +106,14 @@ export const DEFAULT_MODULES = [ * Wraps the {@link configureDiagramOptions} utility function in a module. Adopters can either include this * module into the container {@link ModuleConfiguration} or configure the container after its creation * (e.g. using the {@link configureDiagramOptions} utility function). - * @param options The diagram instance specific configuration options + * @param diagramOptions The diagram instance specific configuration options + * @param viewerOptions Optional {@link ViewerOptions} that should be configured * @returns The corresponding {@link FeatureModule} */ -export function createDiagramOptionsModule(options: IDiagramOptions): FeatureModule { - return new FeatureModule((bind, unbind, isBound, rebind) => configureDiagramOptions({ bind, unbind, isBound, rebind }, options)); +export function createDiagramOptionsModule(diagramOptions: IDiagramOptions, viewerOptions?: Partial): FeatureModule { + return new FeatureModule((bind, unbind, isBound, rebind) => + configureDiagramOptions({ bind, unbind, isBound, rebind }, diagramOptions, viewerOptions) + ); } /** From 989441d0304751a84a66b2632138a595a6657c1e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 27 Jun 2024 12:00:47 +0200 Subject: [PATCH 360/566] Fix RestoreViewPortHandler (#369) Properly CSS escape the root id before using it as query selector --- packages/client/src/features/viewport/viewport-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 21e5498..aa8a49c 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -42,7 +42,7 @@ export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { } get graphSelector(): string { - const rootId = this.domHelper.createUniqueDOMElementId(this.editorContext.modelRoot); + const rootId = CSS.escape(this.domHelper.createUniqueDOMElementId(this.editorContext.modelRoot)); return `#${rootId}`; } From e35ac0d8c69763812847cf58e94d4a57e95aad86 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 30 Jun 2024 22:15:45 +0200 Subject: [PATCH 361/566] Update to composite build (#1353) --- dev-packages/cli/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index ecadcfb..2050461 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -36,7 +36,6 @@ "clean": "rimraf lib tsconfig.tsbuildinfo", "lint": "eslint --ext .ts,.tsx ./src", "lint:ci": "yarn lint -o eslint.xml -f checkstyle", - "prepare": "yarn clean && yarn build ", "start": "node --enable-source-maps lib/app.js", "watch": "tsc -w" }, From 0d1d4683f9843183de10dabb4af900c9a544b887 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 1 Jul 2024 10:14:18 +0200 Subject: [PATCH 362/566] Improve extensibility of creation tools (#370) --- .../tools/edge-creation/edge-creation-tool.ts | 39 ++++++++++++++----- .../tools/node-creation/node-creation-tool.ts | 37 +++++++++++++----- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index ea363dd..3ac3122 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -16,6 +16,7 @@ import { Action, AnchorComputerRegistry, + Args, CreateEdgeOperation, GModelElement, RequestCheckEdgeAction, @@ -109,7 +110,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.feedbackEdgeFeedback = tool.createFeedbackEmitter(); } - override nonDraggingMouseUp(_element: GModelElement, event: MouseEvent): Action[] { + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.button === 0) { if (!this.isSourceSelected()) { @@ -126,15 +127,8 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.target = this.currentTarget.id; } if (this.source && this.target) { - result.push( - CreateEdgeOperation.create({ - elementTypeId: this.triggerAction.elementTypeId, - sourceElementId: this.source, - targetElementId: this.target, - args: this.triggerAction.args - }) - ); - if (!isCtrlOrCmd(event)) { + result.push(this.getCreateOperation(element, event, this.source, this.target)); + if (!this.isContinuousMode(element, event)) { result.push(EnableDefaultToolsAction.create()); } else { this.dispose(); @@ -147,6 +141,31 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { return result; } + /** + * Determines wether the tool should run in continuous mode (also called stamp mode) or not. + * If continuous mode is enabled, the tool will stay after a successful creation. + * The user can then create more elements of the same type without having to re-trigger the tool. + * By default, continuous mode is enabled if the user holds the CTRL key. + * @param element the current model element + * @param event + */ + protected isContinuousMode(element: GModelElement, event: MouseEvent): boolean { + return isCtrlOrCmd(event); + } + + protected getCreateOperation(element: GModelElement, event: MouseEvent, sourceElementId: string, targetElementId: string): Action { + return CreateEdgeOperation.create({ + elementTypeId: this.triggerAction.elementTypeId, + sourceElementId, + targetElementId, + args: this.getCreateEdgeOperationArgs(element, event) + }); + } + + protected getCreateEdgeOperationArgs(ctx: GModelElement, event: MouseEvent): Args | undefined { + return { ...this.triggerAction.args }; + } + protected isSourceSelected(): boolean { return this.source !== undefined; } diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 679c1d5..0206548 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -92,11 +92,15 @@ export class NodeCreationTool extends BaseCreationTool Date: Mon, 1 Jul 2024 10:15:09 +0000 Subject: [PATCH 363/566] Fix: Proper alignment of ghost element after update in continuous mode (#371) Contributed on behalf of Axon Ivy AG --- ...ouse-tracking-element-position-listener.ts | 29 +++++++++++++++++-- .../tools/node-creation/node-creation-tool.ts | 9 ++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index fb127dc..e6c0e07 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -14,8 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Dimension, GModelElement, MoveAction, Point, isBoundsAware, isMoveable } from '@eclipse-glsp/sprotty'; +import { + Action, + Dimension, + DisposableCollection, + GModelElement, + GModelRoot, + MoveAction, + Point, + isBoundsAware, + isMoveable +} from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { EditorContextService } from '../../base/editor-context-service'; import { CSS_HIDDEN, ModifyCSSFeedbackAction } from '../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { MoveableElement } from '../../utils/gmodel-util'; @@ -32,15 +43,22 @@ export interface PositioningTool extends FeedbackAwareTool { export class MouseTrackingElementPositionListener extends DragAwareMouseListener { protected moveGhostFeedback: FeedbackEmitter; protected tracker: ChangeBoundsTracker; + protected toDispose = new DisposableCollection(); constructor( protected elementId: string, protected tool: PositioningTool, - protected cursorPosition: 'top-left' | 'middle' = 'top-left' + protected cursorPosition: 'top-left' | 'middle' = 'top-left', + protected editorContext?: EditorContextService ) { super(); this.tracker = this.tool.changeBoundsManager.createTracker(); this.moveGhostFeedback = this.tool.createFeedbackEmitter(); + this.toDispose.push(this.moveGhostFeedback); + const modelRootChangedListener = editorContext?.onModelRootChanged(newRoot => this.modelRootChanged(newRoot)); + if (modelRootChangedListener) { + this.toDispose.push(modelRootChangedListener); + } } protected getTrackedElement(target: GModelElement, event: MouseEvent): MoveableElement | undefined { @@ -92,8 +110,13 @@ export class MouseTrackingElementPositionListener extends DragAwareMouseListener this.tool.changeBoundsManager.addMoveFeedback(this.moveGhostFeedback, move, ctx, event); } + protected modelRootChanged(root: Readonly): void { + // stop the tracking once we receive a new model root and ensure proper alignment with next next mouse move + this.tracker.stopTracking(); + } + override dispose(): void { - this.moveGhostFeedback.dispose(); + this.toDispose.dispose(); super.dispose(); } } diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index 0206548..e66e93d 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -34,6 +34,7 @@ import { import { inject, injectable } from 'inversify'; import '../../../../css/ghost-element.css'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; +import { EditorContextService } from '../../../base/editor-context-service'; import { CSS_GHOST_ELEMENT, CSS_HIDDEN, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; @@ -77,7 +78,8 @@ export class NodeCreationTool extends BaseCreationTool Date: Thu, 4 Jul 2024 09:52:17 +0000 Subject: [PATCH 364/566] Centralize Marquee selection behavior for improved customization (#373) * Centralize Marquee selection behavior for improved customization Contributed on behalf of Axon Ivy AG * Update CHANGELOG --- .../marquee-behavior.spec.ts | 3 +- .../marquee-selection/marquee-behavior.ts | 74 +++++++++++++-- .../marquee-selection/marquee-mouse-tool.ts | 92 +++++-------------- .../marquee-selection-module.ts | 2 + packages/protocol/src/utils/type-util.ts | 5 + 5 files changed, 100 insertions(+), 76 deletions(-) diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index d1dc414..ae6cf8e 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -14,12 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { DOMHelper } from '@eclipse-glsp/sprotty'; import { expect } from 'chai'; import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; describe('MarqueeUtil', () => { function initUtil(marqueeBehavior?: IMarqueeBehavior): MarqueeUtil { - const util = new MarqueeUtil(marqueeBehavior); + const util = new MarqueeUtil(marqueeBehavior, new DOMHelper()); util.updateStartPoint({ x: 0, y: 0 }); util.updateCurrentPoint({ x: 0, y: 0 }); return util; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index f2f1611..1930802 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -14,7 +14,24 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Point, PointToPointLine } from '@eclipse-glsp/sprotty'; +import { + Bounds, + DOMHelper, + GModelElement, + GModelRoot, + GNode, + Point, + PointToPointLine, + TYPES, + TypeGuard, + isSelectable, + toTypeGuard, + typeGuard +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { GEdge } from '../../../model'; +import { BoundsAwareModelElement, getMatchingElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; +import { toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { DrawMarqueeAction } from './marquee-tool-feedback'; export interface IMarqueeBehavior { @@ -22,17 +39,36 @@ export interface IMarqueeBehavior { readonly entireEdge: boolean; } +@injectable() export class MarqueeUtil { protected startPoint: Point; protected currentPoint: Point; - protected marqueeBehavior: IMarqueeBehavior; - constructor(marqueeBehavior?: IMarqueeBehavior) { - if (marqueeBehavior) { - this.marqueeBehavior = marqueeBehavior; - } else { - this.marqueeBehavior = { entireElement: false, entireEdge: false }; - } + constructor( + @inject(TYPES.IMarqueeBehavior) + @optional() + protected marqueeBehavior: IMarqueeBehavior = { entireElement: false, entireEdge: false }, + @inject(TYPES.DOMHelper) protected domHelper: DOMHelper + ) {} + + isContinuousMode(element: GModelElement, event: MouseEvent): boolean { + return event.shiftKey; + } + + getMarkableNodes(root: GModelRoot): BoundsAwareModelElement[] { + return getMatchingElements(root.index, this.isMarkableNode()); + } + + protected isMarkableNode(): TypeGuard { + return typeGuard(toTypeGuard(GNode), isSelectableAndBoundsAware); + } + + getMarkableEdges(root: GModelRoot): GEdge[] { + return getMatchingElements(root.index, this.isMarkableEdge()); + } + + protected isMarkableEdge(): TypeGuard { + return typeGuard(toTypeGuard(GEdge), isSelectable); } updateStartPoint(position: Point): void { @@ -43,10 +79,32 @@ export class MarqueeUtil { this.currentPoint = position; } + isMarked(element: BoundsAwareModelElement | GEdge): boolean { + return element instanceof GEdge ? this.isMarkedEdge(element) : this.isMarkedNode(element); + } + drawMarqueeAction(): DrawMarqueeAction { return DrawMarqueeAction.create({ startPoint: this.startPoint, endPoint: this.currentPoint }); } + protected isMarkedEdge(edge: GEdge): boolean { + const domId = this.domHelper.createUniqueDOMElementId(edge); + const domEdge = document.getElementById(domId) as unknown as SVGElement | undefined; + if (!domEdge || domEdge.getAttribute('transform') || !domEdge.children[0]) { + return false; + } + const path = domEdge.children[0].getAttribute('d'); + return this.isEdgePathMarked(path); + } + + protected isMarkedNode(node: BoundsAwareModelElement): boolean { + return this.isNodeMarked(this.getNodeBounds(node)); + } + + protected getNodeBounds(node: BoundsAwareModelElement): Bounds { + return toAbsoluteBounds(node); + } + isEdgePathMarked(path: string | null): boolean { if (!path) { return false; diff --git a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts index 7794919..1fb90ef 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-mouse-tool.ts @@ -13,35 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - DOMHelper, - GModelElement, - GModelRoot, - GNode, - KeyListener, - SelectAction, - TYPES, - isSelectable, - isSelected -} from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { Action, GModelElement, GModelRoot, KeyListener, SelectAction, isSelected, typeGuard } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; -import { BoundsAwareModelElement, SelectableBoundsAware, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; -import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; +import { BoundsAwareModelElement, getMatchingElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; +import { getAbsolutePosition } from '../../../utils/viewpoint-util'; import { BaseEditTool } from '../base-tools'; -import { IMarqueeBehavior, MarqueeUtil } from './marquee-behavior'; +import { MarqueeUtil } from './marquee-behavior'; import { RemoveMarqueeAction } from './marquee-tool-feedback'; @injectable() export class MarqueeMouseTool extends BaseEditTool { static ID = 'glsp.marquee-mouse-tool'; - @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; - @inject(TYPES.IMarqueeBehavior) @optional() protected marqueeBehavior: IMarqueeBehavior; + @inject(MarqueeUtil) protected marqueeUtil: MarqueeUtil; protected shiftKeyListener: ShiftKeyListener = new ShiftKeyListener(); @@ -51,7 +39,7 @@ export class MarqueeMouseTool extends BaseEditTool { enable(): void { this.toDisposeOnDisable.push( - this.mouseTool.registerListener(new MarqueeMouseListener(this.domHelper, this.editorContext.modelRoot, this.marqueeBehavior)), + this.mouseTool.registerListener(new MarqueeMouseListener(this.editorContext.modelRoot, this.marqueeUtil)), this.keyTool.registerListener(this.shiftKeyListener), this.createFeedbackEmitter().add(cursorFeedbackAction(CursorCSS.MARQUEE), cursorFeedbackAction()).submit() ); @@ -63,46 +51,26 @@ export class MarqueeMouseTool extends BaseEditTool { } export class MarqueeMouseListener extends DragAwareMouseListener { - protected domHelper: DOMHelper; - protected marqueeUtil: MarqueeUtil; protected nodes: BoundsAwareModelElement[]; - protected edges: SVGGElement[]; + protected edges: GEdge[]; protected previouslySelected: string[]; protected isActive = false; - constructor(domHelper: DOMHelper, root: GModelRoot, marqueeBehavior: IMarqueeBehavior | undefined) { + constructor( + root: GModelRoot, + protected marqueeUtil: MarqueeUtil + ) { super(); - this.domHelper = domHelper; - this.marqueeUtil = new MarqueeUtil(marqueeBehavior); - this.nodes = Array.from( - root.index - .all() - .map(e => e as BoundsAwareModelElement) - .filter(e => isSelectable(e)) - .filter(e => e instanceof GNode) - ); - const sEdges = Array.from( - root.index - .all() - .filter(e => e instanceof GEdge) - .filter(e => isSelectable(e)) - .map(e => e.id) - ); - this.edges = Array.from(document.querySelectorAll('g')).filter(e => sEdges.includes(this.domHelper.findSModelIdByDOMElement(e))); + // pre-calculate all markable node and edges to improve performance + this.nodes = this.marqueeUtil.getMarkableNodes(root); + this.edges = this.marqueeUtil.getMarkableEdges(root); } override mouseDown(target: GModelElement, event: MouseEvent): Action[] { this.isActive = true; this.marqueeUtil.updateStartPoint(getAbsolutePosition(target, event)); if (event.ctrlKey) { - this.previouslySelected = Array.from( - target.root.index - .all() - .filter(isSelectableAndBoundsAware) - .map(e => e as SelectableBoundsAware) - .filter(e => isSelected(e)) - .map(e => e.id) - ); + this.previouslySelected = getMatchingElements(target.index, typeGuard(isSelectableAndBoundsAware, isSelected)).map(e => e.id); } return []; } @@ -110,30 +78,20 @@ export class MarqueeMouseListener extends DragAwareMouseListener { override mouseMove(target: GModelElement, event: MouseEvent): Action[] { this.marqueeUtil.updateCurrentPoint(getAbsolutePosition(target, event)); if (this.isActive) { - const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isNodeMarked(toAbsoluteBounds(e))).map(e => e.id); - const edgeIdsSelected = this.edges.filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e)); - const selected = nodeIdsSelected.concat(edgeIdsSelected); - return [SelectAction.setSelection(selected.concat(this.previouslySelected)), this.marqueeUtil.drawMarqueeAction()]; + const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isMarked(e)).map(e => e.id); + const edgeIdsSelected = this.edges.filter(e => this.marqueeUtil.isMarked(e)).map(e => e.id); + const currentSelected = nodeIdsSelected.concat(edgeIdsSelected); + const selection = currentSelected.concat(this.previouslySelected); + return [SelectAction.setSelection(selection), this.marqueeUtil.drawMarqueeAction()]; } return []; } - override mouseUp(_target: GModelElement, event: MouseEvent): Action[] { + override mouseUp(target: GModelElement, event: MouseEvent): Action[] { this.isActive = false; - if (event.shiftKey) { - return [RemoveMarqueeAction.create()]; - } - return [RemoveMarqueeAction.create(), EnableDefaultToolsAction.create()]; - } - - isEdgeMarked(element: SVGElement): boolean { - if (!element.getAttribute('transform')) { - if (element.children[0]) { - const path = element.children[0].getAttribute('d'); - return this.marqueeUtil.isEdgePathMarked(path); - } - } - return false; + return this.marqueeUtil.isContinuousMode(target, event) + ? [RemoveMarqueeAction.create()] + : [RemoveMarqueeAction.create(), EnableDefaultToolsAction.create()]; } } diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index 43a4a2b..27b3468 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -19,10 +19,12 @@ import { MarqueeTool } from './marquee-tool'; import { DrawMarqueeCommand, MARQUEE, RemoveMarqueeCommand } from './marquee-tool-feedback'; import { MarqueeNode } from './model'; import { MarqueeView } from './view'; +import { MarqueeUtil } from './marquee-behavior'; export const marqueeSelectionToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; + context.bind(MarqueeUtil).toSelf().inSingletonScope(); bindAsService(context, TYPES.IDefaultTool, MarqueeTool); bindAsService(context, TYPES.ITool, MarqueeMouseTool); diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index c6b70d5..9dab173 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -74,6 +74,11 @@ export function typeGuard(one: TypeGuard, other: TypeGuard): TypeGua return (element: any): element is T & G => one(element) && other(element); } +/** Utility function to combine two type guards with an OR */ +export function typeGuardOr(one: TypeGuard, other: TypeGuard): TypeGuard { + return (element: any): element is T | G => one(element) || other(element); +} + /** * Utility function that create a typeguard function for a given class constructor. * Essentially this wraps an instance of check as typeguard function. From 44ac2073c3700a93262e143aea6f0b630c39e807 Mon Sep 17 00:00:00 2001 From: Tobias Date: Thu, 4 Jul 2024 16:22:28 +0200 Subject: [PATCH 365/566] Update changelog for 2.2.0 and fix copyright header --- dev-packages/cli/src/app.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index fc3febb..12777ba 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -1,6 +1,5 @@ -#!/usr/bin/env node /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 3a374611dda1f99ae185e7de721b61823716cd5c Mon Sep 17 00:00:00 2001 From: Tobias Date: Thu, 4 Jul 2024 16:32:16 +0200 Subject: [PATCH 366/566] v2.2.0 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 2050461..1dd29eb 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.2.0-next", + "version": "2.2.0", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.2.0-next", + "@eclipse-glsp/config": "2.2.0", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 2a3cd65..ab4a778 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.2.0-next", - "@eclipse-glsp/nyc-config": "2.2.0-next", + "@eclipse-glsp/mocha-config": "2.2.0", + "@eclipse-glsp/nyc-config": "2.2.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 035bb91..98d1c8d 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.2.0-next", - "@eclipse-glsp/prettier-config": "2.2.0-next", - "@eclipse-glsp/ts-config": "2.2.0-next", + "@eclipse-glsp/eslint-config": "2.2.0", + "@eclipse-glsp/prettier-config": "2.2.0", + "@eclipse-glsp/ts-config": "2.2.0", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 8d1dcbc..824b8da 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.2.0-next", + "version": "2.2.0", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.2.0-next", - "@eclipse-glsp/config": "2.2.0-next", - "@eclipse-glsp/config-test": "2.2.0-next" + "@eclipse-glsp/cli": "2.2.0", + "@eclipse-glsp/config": "2.2.0", + "@eclipse-glsp/config-test": "2.2.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index b2a2af3..4a60f84 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 426c423..6684d9f 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 9a6c4f7..aac8b58 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 3f40489..53aea6f 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 9014dbf..1c6da50 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From 4a31e14399224c0cf3e694f82bfe077432bd3cc3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 4 Jul 2024 23:15:49 +0200 Subject: [PATCH 367/566] v2.2.0 (#374) * Fix copyright header violations * v2.2.0 --- examples/workflow-glsp/package.json | 4 ++-- .../src/direct-task-editing/direct-task-editor.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 6 +++--- .../src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/feedback/css-feedback.ts | 2 +- packages/client/src/base/model/model-registry.ts | 2 +- .../element-navigation/local-element-navigator.ts | 2 +- .../accessibility/key-shortcut/accessible-key-shortcut.ts | 2 +- .../features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../keyboard-pointer/keyboard-pointer-module.ts | 2 +- .../accessibility/keyboard-pointer/keyboard-pointer.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../features/accessibility/move-zoom/move-zoom-module.ts | 2 +- .../accessibility/resize-key-tool/resize-key-module.ts | 2 +- .../features/accessibility/search/search-palette-module.ts | 2 +- .../client/src/features/accessibility/toast/toast-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/freeform-layout.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../features/change-bounds/point-position-updater.spec.ts | 2 +- .../src/features/change-bounds/point-position-updater.ts | 2 +- packages/client/src/features/change-bounds/snap.ts | 2 +- .../client/src/features/context-menu/context-menu-module.ts | 2 +- .../client/src/features/copy-paste/copy-paste-modules.ts | 2 +- .../client/src/features/copy-paste/copy-paste-standalone.ts | 2 +- .../client/src/features/decoration/decoration-module.ts | 2 +- .../features/element-template/element-template-module.ts | 2 +- packages/client/src/features/export/export-modules.ts | 2 +- packages/client/src/features/export/glsp-svg-exporter.ts | 2 +- packages/client/src/features/grid/grid-snapper.spec.ts | 2 +- .../client/src/features/helper-lines/helper-line-module.ts | 2 +- packages/client/src/features/helper-lines/model.ts | 2 +- packages/client/src/features/helper-lines/view.tsx | 2 +- packages/client/src/features/hints/type-hints-module.ts | 2 +- packages/client/src/features/hover/hover-module.ts | 2 +- .../client/src/features/label-edit/label-edit-module.ts | 2 +- .../client/src/features/layout/layout-elements-action.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- .../client/src/features/navigation/navigation-module.ts | 2 +- packages/client/src/features/routing/routing-module.ts | 2 +- packages/client/src/features/save/save-module.ts | 2 +- packages/client/src/features/select/select-module.ts | 2 +- .../source-model-watcher/source-model-watcher-module.ts | 2 +- packages/client/src/features/status/status-module.ts | 2 +- packages/client/src/features/status/status-overlay.ts | 2 +- .../client/src/features/svg-metadata/svg-metadata-module.ts | 2 +- .../client/src/features/tool-palette/tool-palette-module.ts | 2 +- packages/client/src/features/tools/deletion/delete-tool.ts | 2 +- .../src/features/tools/deletion/deletion-tool-module.ts | 2 +- .../features/tools/edge-creation/edege-creation-module.ts | 2 +- packages/client/src/features/tools/edge-creation/view.tsx | 2 +- .../client/src/features/tools/edge-edit/edge-edit-module.ts | 2 +- .../tools/marquee-selection/marquee-behavior.spec.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.ts | 2 +- .../tools/marquee-selection/marquee-selection-module.ts | 2 +- .../client/src/features/tools/marquee-selection/view.tsx | 2 +- .../client/src/features/tools/tool-focus-loss-module.ts | 2 +- packages/client/src/features/validation/issue-marker.ts | 2 +- packages/client/src/features/validation/marker-navigator.ts | 2 +- .../client/src/features/validation/validation-modules.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/geometry-util.ts | 2 +- packages/client/src/utils/layout-utils.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/gedge-view.tsx | 2 +- packages/client/src/views/issue-marker-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- .../jsonrpc/worker-connection-provider.ts | 2 +- packages/protocol/src/re-exports.ts | 2 +- packages/protocol/src/utils/array-util.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 85 files changed, 91 insertions(+), 91 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index cabe4e1..f03ed93 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.0-next", + "version": "2.2.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp/client": "2.2.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 6155f92..d7cc8ff 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 036a065..bf027cb 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 3d8a93b..86c5578 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.0-next", + "version": "2.2.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp-examples/workflow-glsp": "2.2.0", + "@eclipse-glsp/client": "2.2.0", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..dc6c09e 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.2.0" } diff --git a/packages/client/package.json b/packages/client/package.json index 26e5612..7dc3b94 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.0-next", + "version": "2.2.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,11 +46,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.0-next", + "@eclipse-glsp/sprotty": "2.2.0", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "snabbdom": "~3.5.1", "lodash": "4.17.21", + "snabbdom": "~3.5.1", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 08e9a93..f921b14 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 713b44f..47bbe0e 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index 26e26da..fb8c4a5 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index efb5abd..2554844 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index b1896a5..8d619e6 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 55010e6..183b031 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index decbe60..ec4d0f5 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index b804bf8..5ed728d 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index b9477a9..307a540 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index e8984cc..57c7a69 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index d8b8eb8..204d282 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index 7a29779..fb3e676 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index c2bf03a..46c3217 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 7fd8d10..4d8c212 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 85f616d..b7ceaff 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index d686ab8..3ad5880 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 0cdfe78..650e9d0 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index ac0b080..991df93 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 9f6641c..4e38c66 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index b6e92bf..7e9d798 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index d09991a..aefd167 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 5716c18..eb91a0b 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 46c6999..b7bca73 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 1bb8d4f..607a156 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index f3afae8..53e9913 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index ad0fd8e..8d5cf17 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index 7325b7f..a2ebcfb 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 4467372..820dfd5 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index d4877b1..86d61d6 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts index 6e99eae..737cd3d 100644 --- a/packages/client/src/features/grid/grid-snapper.spec.ts +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index 200dc16..d12b56f 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index 7b70cd6..cadd235 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index afde81c..2822e47 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 970b2b7..c27df6f 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 360f4fa..5fd59c6 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 90460ab..b9a5786 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 7996eb3..646a6d0 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 8b7df50..5db6df3 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 40fe7bf..1cad4ea 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 7985178..2b308f2 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 5a97c2d..1094ac6 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index 6bf147d..b121e64 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index 212cbf4..c699aa8 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 3ec9672..f9adc41 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 06c19f7..f1972ef 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index cbab06a..194992e 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index f7dbf86..572bb72 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 0a58676..2505944 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 444a5cb..2ede9b6 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index dd605a7..454d5c9 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 496431b..5f85668 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index 42aa7a2..bcdf709 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index ae6cf8e..783211c 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index 1930802..e393c2f 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index 27b3468..a165cf4 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 17e4067..1ec3e83 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index e83a781..bf8e3db 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 19f1c75..f001040 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 864cca3..91f2ce0 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 6dacf57..6d46083 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index e291c77..bef6b0b 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 7c86c7c..03378fc 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 2c2a467..ce2dfa3 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index 47e6de7..28fc004 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 52c5d4d..4d9e29d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index bcdb5b3..9cab913 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 647fccc..326e1b1 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index c4002a7..e7e182c 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index e59c5b8..f79aafc 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 2059aa2..34e8b18 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index bee784d..68e02eb 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 3ce7ef5..2696bc4 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0-next", + "@eclipse-glsp/protocol": "2.2.0", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 664d5eb..2e41722 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.0-next", + "version": "2.2.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index 28d6650..d4e23bd 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts index f292443..a0b67ac 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 7130bf1..1182b13 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 7e8d23e..4a9f5e8 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 4f2bafb..5804d9d 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 9dab173..f157a5f 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 889cb567512934d643e461a733eed7a2b2e2a862 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 00:19:21 +0200 Subject: [PATCH 368/566] Revert "v2.2.0 (#374)" (#375) This reverts commit 4a31e14399224c0cf3e694f82bfe077432bd3cc3. --- examples/workflow-glsp/package.json | 4 ++-- .../src/direct-task-editing/direct-task-editor.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 6 +++--- .../src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/feedback/css-feedback.ts | 2 +- packages/client/src/base/model/model-registry.ts | 2 +- .../element-navigation/local-element-navigator.ts | 2 +- .../accessibility/key-shortcut/accessible-key-shortcut.ts | 2 +- .../features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../keyboard-pointer/keyboard-pointer-module.ts | 2 +- .../accessibility/keyboard-pointer/keyboard-pointer.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../features/accessibility/move-zoom/move-zoom-module.ts | 2 +- .../accessibility/resize-key-tool/resize-key-module.ts | 2 +- .../features/accessibility/search/search-palette-module.ts | 2 +- .../client/src/features/accessibility/toast/toast-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/freeform-layout.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../features/change-bounds/point-position-updater.spec.ts | 2 +- .../src/features/change-bounds/point-position-updater.ts | 2 +- packages/client/src/features/change-bounds/snap.ts | 2 +- .../client/src/features/context-menu/context-menu-module.ts | 2 +- .../client/src/features/copy-paste/copy-paste-modules.ts | 2 +- .../client/src/features/copy-paste/copy-paste-standalone.ts | 2 +- .../client/src/features/decoration/decoration-module.ts | 2 +- .../features/element-template/element-template-module.ts | 2 +- packages/client/src/features/export/export-modules.ts | 2 +- packages/client/src/features/export/glsp-svg-exporter.ts | 2 +- packages/client/src/features/grid/grid-snapper.spec.ts | 2 +- .../client/src/features/helper-lines/helper-line-module.ts | 2 +- packages/client/src/features/helper-lines/model.ts | 2 +- packages/client/src/features/helper-lines/view.tsx | 2 +- packages/client/src/features/hints/type-hints-module.ts | 2 +- packages/client/src/features/hover/hover-module.ts | 2 +- .../client/src/features/label-edit/label-edit-module.ts | 2 +- .../client/src/features/layout/layout-elements-action.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- .../client/src/features/navigation/navigation-module.ts | 2 +- packages/client/src/features/routing/routing-module.ts | 2 +- packages/client/src/features/save/save-module.ts | 2 +- packages/client/src/features/select/select-module.ts | 2 +- .../source-model-watcher/source-model-watcher-module.ts | 2 +- packages/client/src/features/status/status-module.ts | 2 +- packages/client/src/features/status/status-overlay.ts | 2 +- .../client/src/features/svg-metadata/svg-metadata-module.ts | 2 +- .../client/src/features/tool-palette/tool-palette-module.ts | 2 +- packages/client/src/features/tools/deletion/delete-tool.ts | 2 +- .../src/features/tools/deletion/deletion-tool-module.ts | 2 +- .../features/tools/edge-creation/edege-creation-module.ts | 2 +- packages/client/src/features/tools/edge-creation/view.tsx | 2 +- .../client/src/features/tools/edge-edit/edge-edit-module.ts | 2 +- .../tools/marquee-selection/marquee-behavior.spec.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.ts | 2 +- .../tools/marquee-selection/marquee-selection-module.ts | 2 +- .../client/src/features/tools/marquee-selection/view.tsx | 2 +- .../client/src/features/tools/tool-focus-loss-module.ts | 2 +- packages/client/src/features/validation/issue-marker.ts | 2 +- packages/client/src/features/validation/marker-navigator.ts | 2 +- .../client/src/features/validation/validation-modules.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/geometry-util.ts | 2 +- packages/client/src/utils/layout-utils.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/gedge-view.tsx | 2 +- packages/client/src/views/issue-marker-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- .../jsonrpc/worker-connection-provider.ts | 2 +- packages/protocol/src/re-exports.ts | 2 +- packages/protocol/src/utils/array-util.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 85 files changed, 91 insertions(+), 91 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index f03ed93..cabe4e1 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.0", + "version": "2.2.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.0", + "@eclipse-glsp/client": "2.2.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index d7cc8ff..6155f92 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index bf027cb..036a065 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 86c5578..3d8a93b 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.0", + "version": "2.2.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.0", - "@eclipse-glsp/client": "2.2.0", + "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", + "@eclipse-glsp/client": "2.2.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index dc6c09e..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.2.0" + "version": "next" } diff --git a/packages/client/package.json b/packages/client/package.json index 7dc3b94..26e5612 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.0", + "version": "2.2.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,11 +46,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.0", + "@eclipse-glsp/sprotty": "2.2.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "lodash": "4.17.21", "snabbdom": "~3.5.1", + "lodash": "4.17.21", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index f921b14..08e9a93 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 47bbe0e..713b44f 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index fb8c4a5..26e26da 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index 2554844..efb5abd 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index 8d619e6..b1896a5 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 183b031..55010e6 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index ec4d0f5..decbe60 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index 5ed728d..b804bf8 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index 307a540..b9477a9 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index 57c7a69..e8984cc 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index 204d282..d8b8eb8 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index fb3e676..7a29779 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 46c3217..c2bf03a 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 4d8c212..7fd8d10 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index b7ceaff..85f616d 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 3ad5880..d686ab8 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 650e9d0..0cdfe78 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index 991df93..ac0b080 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 4e38c66..9f6641c 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index 7e9d798..b6e92bf 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index aefd167..d09991a 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index eb91a0b..5716c18 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index b7bca73..46c6999 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 607a156..1bb8d4f 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index 53e9913..f3afae8 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index 8d5cf17..ad0fd8e 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index a2ebcfb..7325b7f 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 820dfd5..4467372 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 86d61d6..d4877b1 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts index 737cd3d..6e99eae 100644 --- a/packages/client/src/features/grid/grid-snapper.spec.ts +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index d12b56f..200dc16 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index cadd235..7b70cd6 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index 2822e47..afde81c 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index c27df6f..970b2b7 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 5fd59c6..360f4fa 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index b9a5786..90460ab 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 646a6d0..7996eb3 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 5db6df3..8b7df50 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 1cad4ea..40fe7bf 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 2b308f2..7985178 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 1094ac6..5a97c2d 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index b121e64..6bf147d 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index c699aa8..212cbf4 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index f9adc41..3ec9672 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index f1972ef..06c19f7 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index 194992e..cbab06a 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index 572bb72..f7dbf86 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 2505944..0a58676 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 2ede9b6..444a5cb 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index 454d5c9..dd605a7 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 5f85668..496431b 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index bcdf709..42aa7a2 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index 783211c..ae6cf8e 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index e393c2f..1930802 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index a165cf4..27b3468 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 1ec3e83..17e4067 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index bf8e3db..e83a781 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index f001040..19f1c75 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 91f2ce0..864cca3 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 6d46083..6dacf57 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index bef6b0b..e291c77 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 03378fc..7c86c7c 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index ce2dfa3..2c2a467 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index 28fc004..47e6de7 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 4d9e29d..52c5d4d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 9cab913..bcdb5b3 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 326e1b1..647fccc 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index e7e182c..c4002a7 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index f79aafc..e59c5b8 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 34e8b18..2059aa2 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 68e02eb..bee784d 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 2696bc4..3ce7ef5 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0", + "@eclipse-glsp/protocol": "2.2.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 2e41722..664d5eb 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.0", + "version": "2.2.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index d4e23bd..28d6650 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts index a0b67ac..f292443 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 1182b13..7130bf1 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 4a9f5e8..7e8d23e 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 5804d9d..4f2bafb 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index f157a5f..9dab173 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 40e6bffc6cc4e7d5eb3abfe5a6d4c1b390460fc3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 4 Jul 2024 18:16:32 +0200 Subject: [PATCH 369/566] Fix copyright header violations --- .../workflow-glsp/src/direct-task-editing/direct-task-editor.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- .../client/src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/feedback/css-feedback.ts | 2 +- packages/client/src/base/model/model-registry.ts | 2 +- .../accessibility/element-navigation/local-element-navigator.ts | 2 +- .../accessibility/key-shortcut/accessible-key-shortcut.ts | 2 +- .../src/features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../accessibility/keyboard-pointer/keyboard-pointer-module.ts | 2 +- .../features/accessibility/keyboard-pointer/keyboard-pointer.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../src/features/accessibility/move-zoom/move-zoom-module.ts | 2 +- .../features/accessibility/resize-key-tool/resize-key-module.ts | 2 +- .../src/features/accessibility/search/search-palette-module.ts | 2 +- packages/client/src/features/accessibility/toast/toast-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/freeform-layout.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../src/features/change-bounds/point-position-updater.spec.ts | 2 +- .../client/src/features/change-bounds/point-position-updater.ts | 2 +- packages/client/src/features/change-bounds/snap.ts | 2 +- .../client/src/features/context-menu/context-menu-module.ts | 2 +- packages/client/src/features/copy-paste/copy-paste-modules.ts | 2 +- .../client/src/features/copy-paste/copy-paste-standalone.ts | 2 +- packages/client/src/features/decoration/decoration-module.ts | 2 +- .../src/features/element-template/element-template-module.ts | 2 +- packages/client/src/features/export/export-modules.ts | 2 +- packages/client/src/features/export/glsp-svg-exporter.ts | 2 +- packages/client/src/features/grid/grid-snapper.spec.ts | 2 +- packages/client/src/features/helper-lines/helper-line-module.ts | 2 +- packages/client/src/features/helper-lines/model.ts | 2 +- packages/client/src/features/helper-lines/view.tsx | 2 +- packages/client/src/features/hints/type-hints-module.ts | 2 +- packages/client/src/features/hover/hover-module.ts | 2 +- packages/client/src/features/label-edit/label-edit-module.ts | 2 +- packages/client/src/features/layout/layout-elements-action.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- packages/client/src/features/navigation/navigation-module.ts | 2 +- packages/client/src/features/routing/routing-module.ts | 2 +- packages/client/src/features/save/save-module.ts | 2 +- packages/client/src/features/select/select-module.ts | 2 +- .../source-model-watcher/source-model-watcher-module.ts | 2 +- packages/client/src/features/status/status-module.ts | 2 +- packages/client/src/features/status/status-overlay.ts | 2 +- .../client/src/features/svg-metadata/svg-metadata-module.ts | 2 +- .../client/src/features/tool-palette/tool-palette-module.ts | 2 +- packages/client/src/features/tools/deletion/delete-tool.ts | 2 +- .../client/src/features/tools/deletion/deletion-tool-module.ts | 2 +- .../src/features/tools/edge-creation/edege-creation-module.ts | 2 +- packages/client/src/features/tools/edge-creation/view.tsx | 2 +- .../client/src/features/tools/edge-edit/edge-edit-module.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.spec.ts | 2 +- .../src/features/tools/marquee-selection/marquee-behavior.ts | 2 +- .../tools/marquee-selection/marquee-selection-module.ts | 2 +- packages/client/src/features/tools/marquee-selection/view.tsx | 2 +- packages/client/src/features/tools/tool-focus-loss-module.ts | 2 +- packages/client/src/features/validation/issue-marker.ts | 2 +- packages/client/src/features/validation/marker-navigator.ts | 2 +- packages/client/src/features/validation/validation-modules.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/geometry-util.ts | 2 +- packages/client/src/utils/layout-utils.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/gedge-view.tsx | 2 +- packages/client/src/views/issue-marker-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- .../jsonrpc/worker-connection-provider.ts | 2 +- packages/protocol/src/re-exports.ts | 2 +- packages/protocol/src/utils/array-util.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 79 files changed, 79 insertions(+), 79 deletions(-) diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 6155f92..d7cc8ff 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 036a065..bf027cb 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 08e9a93..f921b14 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 713b44f..47bbe0e 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index 26e26da..fb8c4a5 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index efb5abd..2554844 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index b1896a5..8d619e6 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 55010e6..183b031 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index decbe60..ec4d0f5 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index b804bf8..5ed728d 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index b9477a9..307a540 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index e8984cc..57c7a69 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index d8b8eb8..204d282 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index 7a29779..fb3e676 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index c2bf03a..46c3217 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 7fd8d10..4d8c212 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 85f616d..b7ceaff 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index d686ab8..3ad5880 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 0cdfe78..650e9d0 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index ac0b080..991df93 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 9f6641c..4e38c66 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index b6e92bf..7e9d798 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index d09991a..aefd167 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 5716c18..eb91a0b 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 46c6999..b7bca73 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 1bb8d4f..607a156 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index f3afae8..53e9913 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index ad0fd8e..8d5cf17 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index 7325b7f..a2ebcfb 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 4467372..820dfd5 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index d4877b1..86d61d6 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts index 6e99eae..737cd3d 100644 --- a/packages/client/src/features/grid/grid-snapper.spec.ts +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index 200dc16..d12b56f 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index 7b70cd6..cadd235 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index afde81c..2822e47 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 970b2b7..c27df6f 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 360f4fa..5fd59c6 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 90460ab..b9a5786 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 7996eb3..646a6d0 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 8b7df50..5db6df3 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 40fe7bf..1cad4ea 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 7985178..2b308f2 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 5a97c2d..1094ac6 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index 6bf147d..b121e64 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index 212cbf4..c699aa8 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 3ec9672..f9adc41 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 06c19f7..f1972ef 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index cbab06a..194992e 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index f7dbf86..572bb72 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 0a58676..2505944 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 444a5cb..2ede9b6 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index dd605a7..454d5c9 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 496431b..5f85668 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index 42aa7a2..bcdf709 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index ae6cf8e..783211c 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index 1930802..e393c2f 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index 27b3468..a165cf4 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 17e4067..1ec3e83 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index e83a781..bf8e3db 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 19f1c75..f001040 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 864cca3..91f2ce0 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 6dacf57..6d46083 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index e291c77..bef6b0b 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 7c86c7c..03378fc 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 2c2a467..ce2dfa3 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index 47e6de7..28fc004 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 52c5d4d..4d9e29d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index bcdb5b3..9cab913 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 647fccc..326e1b1 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index c4002a7..e7e182c 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index e59c5b8..f79aafc 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 2059aa2..34e8b18 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index bee784d..68e02eb 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index 28d6650..d4e23bd 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts index f292443..a0b67ac 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 7130bf1..1182b13 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 7e8d23e..4a9f5e8 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 4f2bafb..5804d9d 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 9dab173..f157a5f 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 698f8f864e888740c5552ccec9231e53f726584b Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 4 Jul 2024 18:16:40 +0200 Subject: [PATCH 370/566] v2.2.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 6 +++--- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index cabe4e1..f03ed93 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.0-next", + "version": "2.2.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp/client": "2.2.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 3d8a93b..86c5578 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.0-next", + "version": "2.2.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp-examples/workflow-glsp": "2.2.0", + "@eclipse-glsp/client": "2.2.0", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..dc6c09e 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.2.0" } diff --git a/packages/client/package.json b/packages/client/package.json index 26e5612..7dc3b94 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.0-next", + "version": "2.2.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,11 +46,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.0-next", + "@eclipse-glsp/sprotty": "2.2.0", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "snabbdom": "~3.5.1", "lodash": "4.17.21", + "snabbdom": "~3.5.1", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 3ce7ef5..2696bc4 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0-next", + "@eclipse-glsp/protocol": "2.2.0", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 664d5eb..2e41722 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.0-next", + "version": "2.2.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 8dc3e7143940d598a54bab73e4dfe1f59098623c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 00:45:05 +0200 Subject: [PATCH 371/566] Fix copyright header violations --- .../provider/node-documentation-navigation-target-provider.ts | 2 +- examples/workflow-server/src/common/workflow-popup-factory.ts | 2 +- packages/graph/src/gedge-layoutable.ts | 2 +- packages/server/src/common/actions/action-dispatcher.ts | 2 +- .../src/common/features/model/request-model-action-handler.ts | 2 +- packages/server/src/common/utils/client-options-util.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts index 32f8709..054bde4 100644 --- a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts +++ b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/workflow-popup-factory.ts b/examples/workflow-server/src/common/workflow-popup-factory.ts index d55432a..ea1f494 100644 --- a/examples/workflow-server/src/common/workflow-popup-factory.ts +++ b/examples/workflow-server/src/common/workflow-popup-factory.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts index 72d571a..afbf09f 100644 --- a/packages/graph/src/gedge-layoutable.ts +++ b/packages/graph/src/gedge-layoutable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/actions/action-dispatcher.ts b/packages/server/src/common/actions/action-dispatcher.ts index 3f7cc82..63fc208 100644 --- a/packages/server/src/common/actions/action-dispatcher.ts +++ b/packages/server/src/common/actions/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 093a10e..621d70a 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/client-options-util.ts b/packages/server/src/common/utils/client-options-util.ts index 80496f9..beb5d2e 100644 --- a/packages/server/src/common/utils/client-options-util.ts +++ b/packages/server/src/common/utils/client-options-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 5c5d8a43d9f8891cd026c7808ba125ede2375e33 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 00:45:14 +0200 Subject: [PATCH 372/566] v2.2.0 --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 8 ++++---- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index e291068..d4c256f 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.2.0-next", + "version": "2.2.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 44c2389..900eb50 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.2.0-next", + "version": "2.2.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,9 +56,9 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.2.0-next", - "@eclipse-glsp/server": "2.2.0-next", - "inversify": "^6.0.1" + "@eclipse-glsp/layout-elk": "2.2.0", + "@eclipse-glsp/server": "2.2.0", + "inversify": "^6.0.2" }, "devDependencies": { "source-map-loader": "^4.0.1", diff --git a/packages/graph/package.json b/packages/graph/package.json index b8a11db..dadd221 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.2.0-next", + "version": "2.2.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.2.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 0f94953..5723083 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.2.0-next", + "version": "2.2.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.2.0-next", + "@eclipse-glsp/server": "2.2.0", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 8084843..4725e07 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.2.0-next", + "version": "2.2.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.2.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.2.0", + "@eclipse-glsp/protocol": "2.2.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 4c7470165e1e572bed49399e3124a5dcf72c8703 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 11:26:07 +0200 Subject: [PATCH 373/566] Revert "V2.2.0" (#377) * Revert "v2.2.0" This reverts commit 698f8f864e888740c5552ccec9231e53f726584b. * Revert "Fix copyright header violations" This reverts commit 40e6bffc6cc4e7d5eb3abfe5a6d4c1b390460fc3. --- examples/workflow-glsp/package.json | 4 ++-- .../src/direct-task-editing/direct-task-editor.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 6 +++--- .../src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/feedback/css-feedback.ts | 2 +- packages/client/src/base/model/model-registry.ts | 2 +- .../element-navigation/local-element-navigator.ts | 2 +- .../accessibility/key-shortcut/accessible-key-shortcut.ts | 2 +- .../features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../keyboard-pointer/keyboard-pointer-module.ts | 2 +- .../accessibility/keyboard-pointer/keyboard-pointer.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../features/accessibility/move-zoom/move-zoom-module.ts | 2 +- .../accessibility/resize-key-tool/resize-key-module.ts | 2 +- .../features/accessibility/search/search-palette-module.ts | 2 +- .../client/src/features/accessibility/toast/toast-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/freeform-layout.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../features/change-bounds/point-position-updater.spec.ts | 2 +- .../src/features/change-bounds/point-position-updater.ts | 2 +- packages/client/src/features/change-bounds/snap.ts | 2 +- .../client/src/features/context-menu/context-menu-module.ts | 2 +- .../client/src/features/copy-paste/copy-paste-modules.ts | 2 +- .../client/src/features/copy-paste/copy-paste-standalone.ts | 2 +- .../client/src/features/decoration/decoration-module.ts | 2 +- .../features/element-template/element-template-module.ts | 2 +- packages/client/src/features/export/export-modules.ts | 2 +- packages/client/src/features/export/glsp-svg-exporter.ts | 2 +- packages/client/src/features/grid/grid-snapper.spec.ts | 2 +- .../client/src/features/helper-lines/helper-line-module.ts | 2 +- packages/client/src/features/helper-lines/model.ts | 2 +- packages/client/src/features/helper-lines/view.tsx | 2 +- packages/client/src/features/hints/type-hints-module.ts | 2 +- packages/client/src/features/hover/hover-module.ts | 2 +- .../client/src/features/label-edit/label-edit-module.ts | 2 +- .../client/src/features/layout/layout-elements-action.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- .../client/src/features/navigation/navigation-module.ts | 2 +- packages/client/src/features/routing/routing-module.ts | 2 +- packages/client/src/features/save/save-module.ts | 2 +- packages/client/src/features/select/select-module.ts | 2 +- .../source-model-watcher/source-model-watcher-module.ts | 2 +- packages/client/src/features/status/status-module.ts | 2 +- packages/client/src/features/status/status-overlay.ts | 2 +- .../client/src/features/svg-metadata/svg-metadata-module.ts | 2 +- .../client/src/features/tool-palette/tool-palette-module.ts | 2 +- packages/client/src/features/tools/deletion/delete-tool.ts | 2 +- .../src/features/tools/deletion/deletion-tool-module.ts | 2 +- .../features/tools/edge-creation/edege-creation-module.ts | 2 +- packages/client/src/features/tools/edge-creation/view.tsx | 2 +- .../client/src/features/tools/edge-edit/edge-edit-module.ts | 2 +- .../tools/marquee-selection/marquee-behavior.spec.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.ts | 2 +- .../tools/marquee-selection/marquee-selection-module.ts | 2 +- .../client/src/features/tools/marquee-selection/view.tsx | 2 +- .../client/src/features/tools/tool-focus-loss-module.ts | 2 +- packages/client/src/features/validation/issue-marker.ts | 2 +- packages/client/src/features/validation/marker-navigator.ts | 2 +- .../client/src/features/validation/validation-modules.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/geometry-util.ts | 2 +- packages/client/src/utils/layout-utils.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/gedge-view.tsx | 2 +- packages/client/src/views/issue-marker-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- .../jsonrpc/worker-connection-provider.ts | 2 +- packages/protocol/src/re-exports.ts | 2 +- packages/protocol/src/utils/array-util.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 85 files changed, 91 insertions(+), 91 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index f03ed93..cabe4e1 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.0", + "version": "2.2.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.0", + "@eclipse-glsp/client": "2.2.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index d7cc8ff..6155f92 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index bf027cb..036a065 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 86c5578..3d8a93b 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.0", + "version": "2.2.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.0", - "@eclipse-glsp/client": "2.2.0", + "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", + "@eclipse-glsp/client": "2.2.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index dc6c09e..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.2.0" + "version": "next" } diff --git a/packages/client/package.json b/packages/client/package.json index 7dc3b94..26e5612 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.0", + "version": "2.2.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,11 +46,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.0", + "@eclipse-glsp/sprotty": "2.2.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "lodash": "4.17.21", "snabbdom": "~3.5.1", + "lodash": "4.17.21", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index f921b14..08e9a93 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 47bbe0e..713b44f 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index fb8c4a5..26e26da 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index 2554844..efb5abd 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index 8d619e6..b1896a5 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 183b031..55010e6 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index ec4d0f5..decbe60 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index 5ed728d..b804bf8 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index 307a540..b9477a9 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index 57c7a69..e8984cc 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index 204d282..d8b8eb8 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index fb3e676..7a29779 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 46c3217..c2bf03a 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 4d8c212..7fd8d10 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index b7ceaff..85f616d 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 3ad5880..d686ab8 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 650e9d0..0cdfe78 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index 991df93..ac0b080 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 4e38c66..9f6641c 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index 7e9d798..b6e92bf 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index aefd167..d09991a 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index eb91a0b..5716c18 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index b7bca73..46c6999 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 607a156..1bb8d4f 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index 53e9913..f3afae8 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index 8d5cf17..ad0fd8e 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index a2ebcfb..7325b7f 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 820dfd5..4467372 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 86d61d6..d4877b1 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts index 737cd3d..6e99eae 100644 --- a/packages/client/src/features/grid/grid-snapper.spec.ts +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index d12b56f..200dc16 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index cadd235..7b70cd6 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index 2822e47..afde81c 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index c27df6f..970b2b7 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 5fd59c6..360f4fa 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index b9a5786..90460ab 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 646a6d0..7996eb3 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 5db6df3..8b7df50 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 1cad4ea..40fe7bf 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 2b308f2..7985178 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 1094ac6..5a97c2d 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index b121e64..6bf147d 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index c699aa8..212cbf4 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index f9adc41..3ec9672 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index f1972ef..06c19f7 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index 194992e..cbab06a 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index 572bb72..f7dbf86 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 2505944..0a58676 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 2ede9b6..444a5cb 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index 454d5c9..dd605a7 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 5f85668..496431b 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index bcdf709..42aa7a2 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index 783211c..ae6cf8e 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index e393c2f..1930802 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index a165cf4..27b3468 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 1ec3e83..17e4067 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index bf8e3db..e83a781 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index f001040..19f1c75 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 91f2ce0..864cca3 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 6d46083..6dacf57 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index bef6b0b..e291c77 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 03378fc..7c86c7c 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index ce2dfa3..2c2a467 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index 28fc004..47e6de7 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 4d9e29d..52c5d4d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 9cab913..bcdb5b3 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 326e1b1..647fccc 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index e7e182c..c4002a7 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index f79aafc..e59c5b8 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 34e8b18..2059aa2 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 68e02eb..bee784d 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 2696bc4..3ce7ef5 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0", + "@eclipse-glsp/protocol": "2.2.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 2e41722..664d5eb 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.0", + "version": "2.2.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index d4e23bd..28d6650 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 STMicroelectronics and others. + * Copyright (c) 2021-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts index a0b67ac..f292443 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 1182b13..7130bf1 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 4a9f5e8..7e8d23e 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 5804d9d..4f2bafb 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index f157a5f..9dab173 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2023 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From d5c7f023c02d590dfc61e50112d856fcacf9e9d5 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 10:47:49 +0200 Subject: [PATCH 374/566] Revert "v2.2.0" This reverts commit 3a374611dda1f99ae185e7de721b61823716cd5c. --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 1dd29eb..2050461 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.2.0", + "version": "2.2.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.2.0", + "@eclipse-glsp/config": "2.2.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index ab4a778..2a3cd65 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.2.0", - "@eclipse-glsp/nyc-config": "2.2.0", + "@eclipse-glsp/mocha-config": "2.2.0-next", + "@eclipse-glsp/nyc-config": "2.2.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 98d1c8d..035bb91 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.2.0", - "@eclipse-glsp/prettier-config": "2.2.0", - "@eclipse-glsp/ts-config": "2.2.0", + "@eclipse-glsp/eslint-config": "2.2.0-next", + "@eclipse-glsp/prettier-config": "2.2.0-next", + "@eclipse-glsp/ts-config": "2.2.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 824b8da..8d1dcbc 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.2.0", + "version": "2.2.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.2.0", - "@eclipse-glsp/config": "2.2.0", - "@eclipse-glsp/config-test": "2.2.0" + "@eclipse-glsp/cli": "2.2.0-next", + "@eclipse-glsp/config": "2.2.0-next", + "@eclipse-glsp/config-test": "2.2.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 4a60f84..b2a2af3 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 6684d9f..426c423 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index aac8b58..9a6c4f7 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 53aea6f..3f40489 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 1c6da50..9014dbf 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From c2f310ba605f9e449e38b60542903025c6a0d52e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 10:47:49 +0200 Subject: [PATCH 375/566] Revert "Update changelog for 2.2.0" This reverts commit 44ac2073c3700a93262e143aea6f0b630c39e807. --- dev-packages/cli/src/app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-packages/cli/src/app.ts b/dev-packages/cli/src/app.ts index 12777ba..fc3febb 100644 --- a/dev-packages/cli/src/app.ts +++ b/dev-packages/cli/src/app.ts @@ -1,5 +1,6 @@ +#!/usr/bin/env node /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 8bb182c7227431849cdaa461d6cce812187fb105 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 10:44:34 +0200 Subject: [PATCH 376/566] Revert "v2.2.0" This reverts commit 5c5d8a43d9f8891cd026c7808ba125ede2375e33. --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 8 ++++---- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index d4c256f..e291068 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.2.0", + "version": "2.2.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 900eb50..44c2389 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.2.0", + "version": "2.2.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,9 +56,9 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.2.0", - "@eclipse-glsp/server": "2.2.0", - "inversify": "^6.0.2" + "@eclipse-glsp/layout-elk": "2.2.0-next", + "@eclipse-glsp/server": "2.2.0-next", + "inversify": "^6.0.1" }, "devDependencies": { "source-map-loader": "^4.0.1", diff --git a/packages/graph/package.json b/packages/graph/package.json index dadd221..b8a11db 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.2.0", + "version": "2.2.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 5723083..0f94953 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.2.0", + "version": "2.2.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.2.0", + "@eclipse-glsp/server": "2.2.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 4725e07..8084843 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.2.0", + "version": "2.2.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.2.0", - "@eclipse-glsp/protocol": "2.2.0", + "@eclipse-glsp/graph": "2.2.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 2735286d9aa6982ff5efbee9e41d70d4b3e55b9f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 10:44:34 +0200 Subject: [PATCH 377/566] Revert "Fix copyright header violations" This reverts commit 8dc3e7143940d598a54bab73e4dfe1f59098623c. --- .../provider/node-documentation-navigation-target-provider.ts | 2 +- examples/workflow-server/src/common/workflow-popup-factory.ts | 2 +- packages/graph/src/gedge-layoutable.ts | 2 +- packages/server/src/common/actions/action-dispatcher.ts | 2 +- .../src/common/features/model/request-model-action-handler.ts | 2 +- packages/server/src/common/utils/client-options-util.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts index 054bde4..32f8709 100644 --- a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts +++ b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/workflow-popup-factory.ts b/examples/workflow-server/src/common/workflow-popup-factory.ts index ea1f494..d55432a 100644 --- a/examples/workflow-server/src/common/workflow-popup-factory.ts +++ b/examples/workflow-server/src/common/workflow-popup-factory.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts index afbf09f..72d571a 100644 --- a/packages/graph/src/gedge-layoutable.ts +++ b/packages/graph/src/gedge-layoutable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/actions/action-dispatcher.ts b/packages/server/src/common/actions/action-dispatcher.ts index 63fc208..3f7cc82 100644 --- a/packages/server/src/common/actions/action-dispatcher.ts +++ b/packages/server/src/common/actions/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 621d70a..093a10e 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/client-options-util.ts b/packages/server/src/common/utils/client-options-util.ts index beb5d2e..80496f9 100644 --- a/packages/server/src/common/utils/client-options-util.ts +++ b/packages/server/src/common/utils/client-options-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From d4323c5961e6cc701175d472679672fb01eefc9a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 5 Jul 2024 14:17:44 +0200 Subject: [PATCH 378/566] GLSP-1361: Fix di registration of selection listeners (#378) Fixes https://github.com/eclipse-glsp/glsp/issues/1361 --- packages/client/src/base/selection-service.spec.ts | 9 +++++++++ packages/client/src/base/selection-service.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/client/src/base/selection-service.spec.ts b/packages/client/src/base/selection-service.spec.ts index 6142e54..398f9e2 100644 --- a/packages/client/src/base/selection-service.spec.ts +++ b/packages/client/src/base/selection-service.spec.ts @@ -248,6 +248,15 @@ describe('SelectionService', () => { selectionService.updateSelection(root, ['not-existing'], []); assertListener(listener, root, ['node1'], [], 1); }); + it('Bindings of TYPES.ISelectionListener should be registered as listener in `preLoadDiagram`', () => { + const container = createContainer(); + const selectionListener = sandbox.createStubInstance(MockSelectionListener); + container.bind(TYPES.ISelectionListener).toConstantValue(selectionListener); + const testSelectionService = container.get(SelectionService); + testSelectionService.preLoadDiagram(); + testSelectionService.updateSelection(root, ['node1', 'node1'], []); + assertListener(selectionListener, root, ['node1'], [], 1); + }); }); function createRoot(...nodes: string[]): GModelRoot { diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index f1a46cc..c371f5e 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -74,7 +74,7 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr preLoadDiagram(): void { this.lazyInjector - .getAll('selectionListener') + .getAll(TYPES.ISelectionListener) .forEach(listener => this.onSelectionChanged(change => listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements) From 03480e7e09b5de8fa94a58ca6a86bafb791aa80d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 15 Jul 2024 17:45:35 +0200 Subject: [PATCH 379/566] GLSP-1369: Update example1.wf (#379) - Update example1.wf file to consistently use easy human readable ids to fix currently failing playwright tests and make testing overall easier - Update `DeselectKeyListener`. Remove condition to check for active resize handles. Resize handles are now longer reseteted on default tool activatin/focus switch. As a consequence the `DeselectKeyListener` no longer has to consider them and can just always trigger deselection Part of https://github.com/eclipse-glsp/glsp/issues/1369 --- examples/workflow-standalone/app/example1.wf | 176 +++++++++--------- .../view-key-tools/deselect-key-tool.ts | 25 +-- 2 files changed, 94 insertions(+), 107 deletions(-) diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 94bb9ff..5500e81 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -6,7 +6,7 @@ "y": 0 }, "id": "sprotty", - "revision": 14, + "revision": 1, "children": [ { "cssClasses": [ @@ -14,7 +14,7 @@ "automated" ], "type": "task:automated", - "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd", + "id": "task_ChkTp", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -26,7 +26,7 @@ "x": 220, "y": 150 }, - "name": "AutomatedTask8", + "name": "ChkTp", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -41,7 +41,7 @@ { "cssClasses": [], "type": "icon", - "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd_icon", + "id": "task_ChkTp_icon", "position": { "x": 5, "y": 5 @@ -59,7 +59,7 @@ "x": 20.6171875, "y": 13 }, - "id": "b300bdb8-52c3-4903-ba09-36dff9b281fd_classname", + "id": "task_ChkTp_label", "text": "ChkTp", "position": { "x": 31, @@ -79,7 +79,7 @@ "manual" ], "type": "task:manual", - "id": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d", + "id": "task_Push", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -91,7 +91,7 @@ "x": 70, "y": 100 }, - "name": "ManualTask9", + "name": "Push", "taskType": "manual", "layoutOptions": { "paddingRight": 10, @@ -106,7 +106,7 @@ { "cssClasses": [], "type": "icon", - "id": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d_icon", + "id": "task_Push_icon", "position": { "x": 5, "y": 5 @@ -124,7 +124,7 @@ "x": 15.9609375, "y": 13 }, - "id": "task0", + "id": "task_Push_label", "text": "Push", "position": { "x": 31, @@ -143,7 +143,7 @@ "forkOrJoin" ], "type": "activityNode:fork", - "id": "16c7860a-c38b-4761-bb48-baa92113478b", + "id": "fork_1", "position": { "x": 170, "y": 90 @@ -159,18 +159,18 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "a8ffb72a-a632-4133-a55f-95e4df297875", - "sourceId": "3a083ea5-5d82-4942-8bb4-3c0c27e9a71d", - "targetId": "16c7860a-c38b-4761-bb48-baa92113478b", + "id": "edge_task_Push_fork_1", + "sourceId": "task_Push", + "targetId": "fork_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "d6fa4391-4139-4789-bd00-4c7509e5d4e1", - "sourceId": "16c7860a-c38b-4761-bb48-baa92113478b", - "targetId": "b300bdb8-52c3-4903-ba09-36dff9b281fd", + "id": "edge_fork1_task_ChkTp", + "sourceId": "fork_1", + "targetId": "task_ChkTp", "children": [] }, { @@ -179,7 +179,7 @@ "automated" ], "type": "task:automated", - "id": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", + "id": "task_ChkWt", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -191,7 +191,7 @@ "x": 220, "y": 50 }, - "name": "AutomatedTask10", + "name": "ChkWt", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -206,7 +206,7 @@ { "cssClasses": [], "type": "icon", - "id": "5496d85e-104c-47a8-bfe6-75211dbf5ae1_icon", + "id": "task_ChkWt_icon", "position": { "x": 5, "y": 5 @@ -224,7 +224,7 @@ "x": 21, "y": 13 }, - "id": "task0_automated", + "id": "task_ChkWt_label", "text": "ChkWt", "position": { "x": 31, @@ -243,7 +243,7 @@ "decision" ], "type": "activityNode:decision", - "id": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", + "id": "decision_1", "position": { "x": 330, "y": 50 @@ -266,7 +266,7 @@ "decision" ], "type": "activityNode:decision", - "id": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "id": "decision2", "position": { "x": 330, "y": 150 @@ -288,27 +288,27 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "ea94aeb9-c86f-42b2-94c6-4cc91d34a883", - "sourceId": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", - "targetId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", + "id": "edge_task_ChkWt_decision1", + "sourceId": "task_ChkWt", + "targetId": "decision_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "d7b69577-d35f-4e3b-b306-86e3f0b7008e", - "sourceId": "16c7860a-c38b-4761-bb48-baa92113478b", - "targetId": "5496d85e-104c-47a8-bfe6-75211dbf5ae1", + "id": "edge_fork1_task_ChkWt", + "sourceId": "fork_1", + "targetId": "task_ChkWt", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "5cb74a75-9676-4db4-a088-13d467234c19", - "sourceId": "b300bdb8-52c3-4903-ba09-36dff9b281fd", - "targetId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", + "id": "edge_task_ChkTp_decision2", + "sourceId": "task_ChkTp", + "targetId": "decision2", "children": [] }, { @@ -317,7 +317,7 @@ "automated" ], "type": "task:automated", - "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437", + "id": "task_PreHeat", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -329,7 +329,7 @@ "x": 390, "y": 180 }, - "name": "AutomatedTask11", + "name": "PreHeat", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -344,7 +344,7 @@ { "cssClasses": [], "type": "icon", - "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437_icon", + "id": "task_PreHeat_icon", "position": { "x": 5, "y": 5 @@ -362,7 +362,7 @@ "x": 25.6796875, "y": 13 }, - "id": "ab6e230a-33e6-4a0c-a08b-c0930767a437_classname", + "id": "task_PreHeat_label", "text": "PreHeat", "position": { "x": 31, @@ -382,7 +382,7 @@ "automated" ], "type": "task:automated", - "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd", + "id": "task_KeepTp", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -394,7 +394,7 @@ "x": 390, "y": 130 }, - "name": "AutomatedTask12", + "name": "KeepTp", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -409,7 +409,7 @@ { "cssClasses": [], "type": "icon", - "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd_icon", + "id": "task_KeepTp_icon", "position": { "x": 5, "y": 5 @@ -427,7 +427,7 @@ "x": 24.5234375, "y": 13 }, - "id": "8360c168-8f25-438a-a213-d5a84e9ae4cd_classname", + "id": "task_KeepTp_label", "text": "KeepTp", "position": { "x": 31, @@ -447,7 +447,7 @@ "manual" ], "type": "task:manual", - "id": "ef349859-2a89-46b0-b394-c339a79c10e3", + "id": "task_RflWt", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -457,9 +457,9 @@ }, "position": { "x": 390, - "y": 30 + "y": 20 }, - "name": "ManualTask13", + "name": "RflWt", "taskType": "manual", "layoutOptions": { "paddingRight": 10, @@ -474,7 +474,7 @@ { "cssClasses": [], "type": "icon", - "id": "ef349859-2a89-46b0-b394-c339a79c10e3_icon", + "id": "task_RflWt_icon", "position": { "x": 5, "y": 5 @@ -492,7 +492,7 @@ "x": 17.109375, "y": 13 }, - "id": "ef349859-2a89-46b0-b394-c339a79c10e3_classname", + "id": "task_RflWt_label", "text": "RflWt", "position": { "x": 31, @@ -512,7 +512,7 @@ "automated" ], "type": "task:automated", - "id": "da626312-7545-4b15-a502-766ab2da1e65", + "id": "task_WtOK", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -524,7 +524,7 @@ "x": 390, "y": 80 }, - "name": "AutomatedTask14", + "name": "WtOK", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -539,7 +539,7 @@ { "cssClasses": [], "type": "icon", - "id": "da626312-7545-4b15-a502-766ab2da1e65_icon", + "id": "task_WtOK_icon", "position": { "x": 5, "y": 5 @@ -557,7 +557,7 @@ "x": 18.671875, "y": 13 }, - "id": "da626312-7545-4b15-a502-766ab2da1e65_classname", + "id": "task_WtOK_label", "text": "WtOK", "position": { "x": 31, @@ -577,9 +577,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "b42e5b46-38c8-4a8b-a75c-34e7b503b123", - "sourceId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", - "targetId": "ef349859-2a89-46b0-b394-c339a79c10e3", + "id": "edge_decision1_task_RflWt", + "sourceId": "decision_1", + "targetId": "task_RflWt", "probability": "medium", "children": [] }, @@ -589,9 +589,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "254aa972-f692-4e71-ba47-69dd564146e6", - "sourceId": "304abdb7-a8b3-4035-b73e-45cc611dd4c3", - "targetId": "da626312-7545-4b15-a502-766ab2da1e65", + "id": "edge_decision_1_task_WtOK", + "sourceId": "decision_1", + "targetId": "task_WtOK", "probability": "medium", "children": [] }, @@ -601,9 +601,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "b65bb287-ba47-4c2c-9714-0fa61345e891", - "sourceId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", - "targetId": "8360c168-8f25-438a-a213-d5a84e9ae4cd", + "id": "edege_decision_2_task_KeepTp", + "sourceId": "decision2", + "targetId": "task_KeepTp", "probability": "medium", "children": [] }, @@ -613,9 +613,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "c3fd1ee7-d8df-4ba2-9741-95cca3d6f268", - "sourceId": "44cdab45-7480-4f67-850b-75d0bc2f22c4", - "targetId": "ab6e230a-33e6-4a0c-a08b-c0930767a437", + "id": "edege_decision2_task_PreHeat", + "sourceId": "decision2", + "targetId": "task_PreHeat", "probability": "medium", "children": [] }, @@ -624,7 +624,7 @@ "forkOrJoin" ], "type": "activityNode:join", - "id": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "id": "join_1", "position": { "x": 560, "y": 90 @@ -642,7 +642,7 @@ "automated" ], "type": "task:automated", - "id": "532ff124-9e72-4335-9e2b-423a544f1318", + "id": "task_Brew", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -654,7 +654,7 @@ "x": 600, "y": 100 }, - "name": "AutomatedTask15", + "name": "Brew", "taskType": "automated", "layoutOptions": { "paddingRight": 10, @@ -669,7 +669,7 @@ { "cssClasses": [], "type": "icon", - "id": "532ff124-9e72-4335-9e2b-423a544f1318_icon", + "id": "task_Brew_icon", "position": { "x": 5, "y": 5 @@ -687,7 +687,7 @@ "x": 15.953125, "y": 13 }, - "id": "532ff124-9e72-4335-9e2b-423a544f1318_classname", + "id": "task_Brew_label", "text": "Brew", "position": { "x": 31, @@ -706,7 +706,7 @@ "merge" ], "type": "activityNode:merge", - "id": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "id": "merge_1", "position": { "x": 500, "y": 50 @@ -729,7 +729,7 @@ "merge" ], "type": "activityNode:merge", - "id": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "id": "merge_2", "position": { "x": 500, "y": 150 @@ -751,63 +751,63 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "7b660e4a-c5cf-4746-91d6-5a7e732008de", - "sourceId": "ef349859-2a89-46b0-b394-c339a79c10e3", - "targetId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "id": "edge_task_RflWt_merge_1", + "sourceId": "task_RflWt", + "targetId": "merge_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "2a0ddf8c-fb98-4432-9844-e9189ba2fa6c", - "sourceId": "da626312-7545-4b15-a502-766ab2da1e65", - "targetId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", + "id": "edge_task_WTOK_merge_1", + "sourceId": "task_WtOK", + "targetId": "merge_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "58114c96-6558-4b46-9d91-a480e3f9c3cd", - "sourceId": "8360c168-8f25-438a-a213-d5a84e9ae4cd", - "targetId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "id": "edge_task_KeepTp_merge_2", + "sourceId": "task_KeepTp", + "targetId": "merge_2", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "7633af04-0cf0-4267-a681-646123270d54", - "sourceId": "ab6e230a-33e6-4a0c-a08b-c0930767a437", - "targetId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", + "id": "edege_task_PreHeat_merge_2", + "sourceId": "task_PreHeat", + "targetId": "merge_2", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "9ddef18e-ba9a-40a3-8961-eaee5ab0007f", - "sourceId": "55be52aa-fb5d-4f5c-8cdf-05c17cd9a1e3", - "targetId": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "id": "edge_merge_2_join_1", + "sourceId": "merge_2", + "targetId": "join_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "191fea84-e6a7-4ad6-94ee-a8e939a5058d", - "sourceId": "ba5ec10f-1aed-458f-a5a5-eb30b127f4b6", - "targetId": "8668b502-0a10-4f32-b751-97dbaa4f5372", + "id": "edge_merge_1_join_1", + "sourceId": "merge_1", + "targetId": "join_1", "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "c8fe1b1c-54fd-4bcd-b194-acc02299a0ed", - "sourceId": "8668b502-0a10-4f32-b751-97dbaa4f5372", - "targetId": "532ff124-9e72-4335-9e2b-423a544f1318", + "id": "edge_join_1_task_Brew", + "sourceId": "join_1", + "targetId": "task_Brew", "children": [] } ] diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index ef340fa..fbd26bf 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -14,24 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - GModelElement, - GRoutableElement, - KeyListener, - KeyTool, - SelectAction, - SwitchEditModeAction, - isSelectable -} from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, GRoutableElement, KeyListener, KeyTool, SelectAction, TYPES, isSelectable } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { toArray } from 'sprotty/lib/utils/iterable'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Tool } from '../../../base/tool-manager/tool'; -import { GResizeHandle } from '../../change-bounds/model'; +import { IToolManager } from '../../../base/tool-manager/tool-manager'; +import { SwitchRoutingModeAction } from '../../tools/edge-edit/edge-edit-tool-feedback'; /** - * Deselects the element if there is no interaction possible with element. + * Deselects currently selected elements on Escape if the default tools are currently active. */ @injectable() export class DeselectKeyTool implements Tool { @@ -42,6 +34,7 @@ export class DeselectKeyTool implements Tool { protected readonly deselectKeyListener = new DeselectKeyListener(); @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.IToolManager) protected toolManager: IToolManager; get id(): string { return DeselectKeyTool.ID; @@ -59,12 +52,6 @@ export class DeselectKeyTool implements Tool { export class DeselectKeyListener extends KeyListener { override keyDown(target: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesDeselectKeystroke(event)) { - const isResizeHandleActive = toArray(target.root.index.all().filter(el => el instanceof GResizeHandle)).length > 0; - - if (isResizeHandleActive) { - return []; - } - const deselect = toArray(target.root.index.all().filter(element => isSelectable(element) && element.selected)); const actions: Action[] = []; @@ -74,7 +61,7 @@ export class DeselectKeyListener extends KeyListener { const routableDeselect = deselect.filter(e => e instanceof GRoutableElement).map(e => e.id); if (routableDeselect.length > 0) { - actions.push(SwitchEditModeAction.create({ elementsToDeactivate: routableDeselect })); + actions.push(SwitchRoutingModeAction.create({ elementsToDeactivate: routableDeselect })); } return actions; From 6c0c1bf579e87306ab4305ca6d0344ed2c3cc488 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 15 Jul 2024 17:46:17 +0200 Subject: [PATCH 380/566] GLSP-1369 Fix wrong edge type hint (#93) - Ensure that the type hint for weighted edges if created with the correct `elementTypeId` Part of https://github.com/eclipse-glsp/glsp/issues/1369 --- .../src/common/workflow-diagram-configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow-server/src/common/workflow-diagram-configuration.ts b/examples/workflow-server/src/common/workflow-diagram-configuration.ts index d8bdd9c..3e4d290 100644 --- a/examples/workflow-server/src/common/workflow-diagram-configuration.ts +++ b/examples/workflow-server/src/common/workflow-diagram-configuration.ts @@ -63,7 +63,7 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration { return [ createDefaultEdgeTypeHint(DefaultTypes.EDGE), createDefaultEdgeTypeHint({ - elementTypeId: DefaultTypes.EDGE, + elementTypeId: types.WEIGHTED_EDGE, dynamic: true, sourceElementTypeIds: [types.ACTIVITY_NODE], targetElementTypeIds: [types.TASK, types.ACTIVITY_NODE] From bd2c13cfd6e599a5c3efa36ee868b52c4b9c7911 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 17 Jul 2024 23:55:11 +0200 Subject: [PATCH 381/566] GLSP-1371: Introduce event for focus changes (#380) - Add `onFocusChanged` event to focus tracker and also expose it via EditorContextService - Update documentation on `EditorContextService`. Also expose `onSelectionChanged` event via `EditorContextService` => `EditorContextService` becomes the central component to register for (most) GLSP event listeners Part of https://github.com/eclipse-glsp/glsp/issues/1371 --- .../client/src/base/editor-context-service.ts | 31 +++++++++++- .../client/src/base/focus/focus-tracker.ts | 50 +++++++++++++------ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index e8308f7..8b7cd26 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -37,8 +37,9 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { GLSPActionDispatcher } from './action-dispatcher'; +import { FocusChange, FocusTracker } from './focus/focus-tracker'; import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; -import { SelectionService } from './selection-service'; +import { SelectionChange, SelectionService } from './selection-service'; /** * A hook to listen for model root changes. Will be called after a server update @@ -91,24 +92,52 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra @inject(GLSPActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(FocusTracker) + protected focusTracker: FocusTracker; + protected _editMode: string; protected onEditModeChangedEmitter = new Emitter>(); + /** + * Event that is fired when the edit mode of the diagram changes i.e. after a {@link SetEditModeAction} has been handled. + */ get onEditModeChanged(): Event> { return this.onEditModeChangedEmitter.event; } protected _isDirty: boolean; protected onDirtyStateChangedEmitter = new Emitter(); + /** + * Event that is fired when the dirty state of the diagram changes i.e. after a {@link SetDirtyStateAction} has been handled. + */ get onDirtyStateChanged(): Event { return this.onDirtyStateChangedEmitter.event; } protected _modelRoot?: Readonly; + /** + * Event that is fired when the model root of the diagram changes i.e. after the `CommandStack` has processed a model update. + */ protected onModelRootChangedEmitter = new Emitter>(); get onModelRootChanged(): Event> { return this.onModelRootChangedEmitter.event; } + /** + * Event that is fired when the focus state of the diagram changes i.e. after a {@link FocusStateChangedAction} has been handled + * by the {@link FocusTracker}. + */ + get onFocusChanged(): Event { + return this.focusTracker.onFocusChanged; + } + + /** + * Event that is fired when the selection of the diagram changes i.e. a selection change has been handled + * by the {@link SelectionService}. + */ + get onSelectionChanged(): Event { + return this.selectionService.onSelectionChanged; + } + protected toDispose = new DisposableCollection(); @postConstruct() diff --git a/packages/client/src/base/focus/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts index 67806b8..fa2e2d1 100644 --- a/packages/client/src/base/focus/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -13,10 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, Emitter, Event, IActionHandler, ICommand, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Action, IActionHandler, ICommand, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { FocusStateChangedAction } from './focus-state-change-action'; +export interface FocusChange { + hasFocus: boolean; + focusElement: HTMLOrSVGElement | null; + diagramElement: HTMLElement | null; +} + +/** + * Tracks the focus state of the diagram by handling {@link FocusStateChangedAction}s. + * Emits a {@link FocusChange} event when the focus state changes. + * Allows querying of the current focus state and the focused root diagram element and the currently focused element within the diagram. + */ @injectable() export class FocusTracker implements IActionHandler { protected inActiveCssClass = 'inactive'; @@ -26,6 +37,14 @@ export class FocusTracker implements IActionHandler { @inject(TYPES.ViewerOptions) protected options: ViewerOptions; + protected onFocusChangedEmitter = new Emitter(); + /** + * Event that is fired when the focus state of the diagram changes i.e. after a {@link FocusStateChangedAction} has been handled. + */ + get onFocusChanged(): Event { + return this.onFocusChangedEmitter.event; + } + get hasFocus(): boolean { return this._hasFocus; } @@ -39,20 +58,23 @@ export class FocusTracker implements IActionHandler { } handle(action: Action): void | Action | ICommand { - if (FocusStateChangedAction.is(action)) { - this._hasFocus = action.hasFocus; - this._focusElement = document.activeElement as HTMLOrSVGElement | null; - this._diagramElement = document.getElementById(this.options.baseDiv); - if (!this._diagramElement) { - return; - } - if (this.hasFocus) { - if (this._diagramElement.classList.contains(this.inActiveCssClass)) { - this._diagramElement.classList.remove(this.inActiveCssClass); - } - } else { - this._diagramElement.classList.add(this.inActiveCssClass); + if (!FocusStateChangedAction.is(action)) { + return; + } + + this._hasFocus = action.hasFocus; + this._focusElement = document.activeElement as HTMLOrSVGElement | null; + this._diagramElement = document.getElementById(this.options.baseDiv); + if (!this._diagramElement) { + return; + } + if (this.hasFocus) { + if (this._diagramElement.classList.contains(this.inActiveCssClass)) { + this._diagramElement.classList.remove(this.inActiveCssClass); } + } else { + this._diagramElement.classList.add(this.inActiveCssClass); } + this.onFocusChangedEmitter.fire({ hasFocus: this.hasFocus, focusElement: this.focusElement, diagramElement: this.diagramElement }); } } From 72b595b71911f31254d354db927edbfe3bcf26ee Mon Sep 17 00:00:00 2001 From: Tobias Date: Thu, 18 Jul 2024 10:16:53 +0200 Subject: [PATCH 382/566] Update tsconfig to ES2019 Update tscoonfig to ES2019 to be in line with Theia's config --- dev-packages/ts-config/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index bea9bd6..a651d06 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -21,9 +21,9 @@ "resolveJsonModule": true, "module": "CommonJS", "moduleResolution": "Node", - "target": "ES2017", + "target": "ES2019", "jsx": "react", - "lib": ["ES2017", "dom"], + "lib": ["ES2019", "dom"], "sourceMap": true, "types": ["node", "reflect-metadata"] } From 8a21ddda9d3bd5565fe5bea54cb3f33615a0c261 Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 22 Jul 2024 00:08:11 +0200 Subject: [PATCH 383/566] v2.2.1 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 2050461..c84c888 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.2.0-next", + "version": "2.2.1", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.2.0-next", + "@eclipse-glsp/config": "2.2.1", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 2a3cd65..15cbf62 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.2.0-next", - "@eclipse-glsp/nyc-config": "2.2.0-next", + "@eclipse-glsp/mocha-config": "2.2.1", + "@eclipse-glsp/nyc-config": "2.2.1", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 035bb91..f3a752d 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.2.0-next", - "@eclipse-glsp/prettier-config": "2.2.0-next", - "@eclipse-glsp/ts-config": "2.2.0-next", + "@eclipse-glsp/eslint-config": "2.2.1", + "@eclipse-glsp/prettier-config": "2.2.1", + "@eclipse-glsp/ts-config": "2.2.1", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 8d1dcbc..9027648 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.2.0-next", + "version": "2.2.1", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.2.0-next", - "@eclipse-glsp/config": "2.2.0-next", - "@eclipse-glsp/config-test": "2.2.0-next" + "@eclipse-glsp/cli": "2.2.1", + "@eclipse-glsp/config": "2.2.1", + "@eclipse-glsp/config-test": "2.2.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index b2a2af3..8815a7c 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 426c423..10931a4 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 9a6c4f7..b83d304 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 3f40489..54d1ca6 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 9014dbf..fd3eb27 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From d07c8d20a2a7f6fa567b1242154aa5ee30ec824d Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 22 Jul 2024 00:14:49 +0200 Subject: [PATCH 384/566] Switch to 2.3.0 next versions --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index c84c888..fd8bbb2 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.2.1", + "version": "2.3.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.2.1", + "@eclipse-glsp/config": "2.3.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 15cbf62..bd78440 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.2.1", - "@eclipse-glsp/nyc-config": "2.2.1", + "@eclipse-glsp/mocha-config": "2.3.0-next", + "@eclipse-glsp/nyc-config": "2.3.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index f3a752d..a0da0aa 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.2.1", - "@eclipse-glsp/prettier-config": "2.2.1", - "@eclipse-glsp/ts-config": "2.2.1", + "@eclipse-glsp/eslint-config": "2.3.0-next", + "@eclipse-glsp/prettier-config": "2.3.0-next", + "@eclipse-glsp/ts-config": "2.3.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 9027648..ddb7166 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.2.1", + "version": "2.3.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.2.1", - "@eclipse-glsp/config": "2.2.1", - "@eclipse-glsp/config-test": "2.2.1" + "@eclipse-glsp/cli": "2.3.0-next", + "@eclipse-glsp/config": "2.3.0-next", + "@eclipse-glsp/config-test": "2.3.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 8815a7c..d254452 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 10931a4..d8598f7 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index b83d304..88ec87a 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 54d1ca6..e568a92 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index fd3eb27..8dde48c 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From 3b5a707861013f1d4b0bfe5c99efa5a35cf0fd84 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 00:31:39 +0200 Subject: [PATCH 385/566] Fix copyright header violations --- .../workflow-glsp/src/direct-task-editing/direct-task-editor.ts | 2 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- .../client/src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/feedback/css-feedback.ts | 2 +- packages/client/src/base/focus/focus-tracker.ts | 2 +- packages/client/src/base/model/model-registry.ts | 2 +- .../accessibility/element-navigation/local-element-navigator.ts | 2 +- .../accessibility/key-shortcut/accessible-key-shortcut.ts | 2 +- .../src/features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../accessibility/keyboard-pointer/keyboard-pointer-module.ts | 2 +- .../features/accessibility/keyboard-pointer/keyboard-pointer.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette-module.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../src/features/accessibility/move-zoom/move-zoom-module.ts | 2 +- .../features/accessibility/resize-key-tool/resize-key-module.ts | 2 +- .../src/features/accessibility/search/search-palette-module.ts | 2 +- packages/client/src/features/accessibility/toast/toast-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/freeform-layout.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../src/features/change-bounds/point-position-updater.spec.ts | 2 +- .../client/src/features/change-bounds/point-position-updater.ts | 2 +- packages/client/src/features/change-bounds/snap.ts | 2 +- .../client/src/features/context-menu/context-menu-module.ts | 2 +- packages/client/src/features/copy-paste/copy-paste-modules.ts | 2 +- .../client/src/features/copy-paste/copy-paste-standalone.ts | 2 +- packages/client/src/features/decoration/decoration-module.ts | 2 +- .../src/features/element-template/element-template-module.ts | 2 +- packages/client/src/features/export/export-modules.ts | 2 +- packages/client/src/features/export/glsp-svg-exporter.ts | 2 +- packages/client/src/features/grid/grid-snapper.spec.ts | 2 +- packages/client/src/features/helper-lines/helper-line-module.ts | 2 +- packages/client/src/features/helper-lines/model.ts | 2 +- packages/client/src/features/helper-lines/view.tsx | 2 +- packages/client/src/features/hints/type-hints-module.ts | 2 +- packages/client/src/features/hover/hover-module.ts | 2 +- packages/client/src/features/label-edit/label-edit-module.ts | 2 +- packages/client/src/features/layout/layout-elements-action.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- packages/client/src/features/navigation/navigation-module.ts | 2 +- packages/client/src/features/routing/routing-module.ts | 2 +- packages/client/src/features/save/save-module.ts | 2 +- packages/client/src/features/select/select-module.ts | 2 +- .../source-model-watcher/source-model-watcher-module.ts | 2 +- packages/client/src/features/status/status-module.ts | 2 +- packages/client/src/features/status/status-overlay.ts | 2 +- .../client/src/features/svg-metadata/svg-metadata-module.ts | 2 +- .../client/src/features/tool-palette/tool-palette-module.ts | 2 +- packages/client/src/features/tools/deletion/delete-tool.ts | 2 +- .../client/src/features/tools/deletion/deletion-tool-module.ts | 2 +- .../src/features/tools/edge-creation/edege-creation-module.ts | 2 +- packages/client/src/features/tools/edge-creation/view.tsx | 2 +- .../client/src/features/tools/edge-edit/edge-edit-module.ts | 2 +- .../features/tools/marquee-selection/marquee-behavior.spec.ts | 2 +- .../src/features/tools/marquee-selection/marquee-behavior.ts | 2 +- .../tools/marquee-selection/marquee-selection-module.ts | 2 +- packages/client/src/features/tools/marquee-selection/view.tsx | 2 +- packages/client/src/features/tools/tool-focus-loss-module.ts | 2 +- packages/client/src/features/validation/issue-marker.ts | 2 +- packages/client/src/features/validation/marker-navigator.ts | 2 +- packages/client/src/features/validation/validation-modules.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/geometry-util.ts | 2 +- packages/client/src/utils/layout-utils.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/gedge-view.tsx | 2 +- packages/client/src/views/issue-marker-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/protocol/src/action-protocol/types.ts | 2 +- .../jsonrpc/worker-connection-provider.ts | 2 +- packages/protocol/src/re-exports.ts | 2 +- packages/protocol/src/utils/array-util.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 80 files changed, 80 insertions(+), 80 deletions(-) diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 6155f92..d7cc8ff 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 036a065..bf027cb 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 08e9a93..f921b14 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/feedback/css-feedback.ts b/packages/client/src/base/feedback/css-feedback.ts index 713b44f..47bbe0e 100644 --- a/packages/client/src/base/feedback/css-feedback.ts +++ b/packages/client/src/base/feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/focus/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts index fa2e2d1..c76ba21 100644 --- a/packages/client/src/base/focus/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index 26e26da..fb8c4a5 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index efb5abd..2554844 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts index b1896a5..8d619e6 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 55010e6..183b031 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts index decbe60..ec4d0f5 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index b804bf8..5ed728d 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts index b9477a9..307a540 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index e8984cc..57c7a69 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts index d8b8eb8..204d282 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts index 7a29779..fb3e676 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index c2bf03a..46c3217 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 7fd8d10..4d8c212 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 85f616d..b7ceaff 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index d686ab8..3ad5880 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/freeform-layout.ts b/packages/client/src/features/bounds/freeform-layout.ts index 0cdfe78..650e9d0 100644 --- a/packages/client/src/features/bounds/freeform-layout.ts +++ b/packages/client/src/features/bounds/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index ac0b080..991df93 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 9f6641c..4e38c66 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.spec.ts b/packages/client/src/features/change-bounds/point-position-updater.spec.ts index b6e92bf..7e9d798 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.spec.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/point-position-updater.ts b/packages/client/src/features/change-bounds/point-position-updater.ts index d09991a..aefd167 100644 --- a/packages/client/src/features/change-bounds/point-position-updater.ts +++ b/packages/client/src/features/change-bounds/point-position-updater.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 5716c18..eb91a0b 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/context-menu-module.ts b/packages/client/src/features/context-menu/context-menu-module.ts index 46c6999..b7bca73 100644 --- a/packages/client/src/features/context-menu/context-menu-module.ts +++ b/packages/client/src/features/context-menu/context-menu-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 1bb8d4f..607a156 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index f3afae8..53e9913 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/decoration/decoration-module.ts b/packages/client/src/features/decoration/decoration-module.ts index ad0fd8e..8d5cf17 100644 --- a/packages/client/src/features/decoration/decoration-module.ts +++ b/packages/client/src/features/decoration/decoration-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/element-template/element-template-module.ts b/packages/client/src/features/element-template/element-template-module.ts index 7325b7f..a2ebcfb 100644 --- a/packages/client/src/features/element-template/element-template-module.ts +++ b/packages/client/src/features/element-template/element-template-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 4467372..820dfd5 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index d4877b1..86d61d6 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.spec.ts b/packages/client/src/features/grid/grid-snapper.spec.ts index 6e99eae..737cd3d 100644 --- a/packages/client/src/features/grid/grid-snapper.spec.ts +++ b/packages/client/src/features/grid/grid-snapper.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index 200dc16..d12b56f 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/model.ts b/packages/client/src/features/helper-lines/model.ts index 7b70cd6..cadd235 100644 --- a/packages/client/src/features/helper-lines/model.ts +++ b/packages/client/src/features/helper-lines/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/view.tsx b/packages/client/src/features/helper-lines/view.tsx index afde81c..2822e47 100644 --- a/packages/client/src/features/helper-lines/view.tsx +++ b/packages/client/src/features/helper-lines/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hints/type-hints-module.ts b/packages/client/src/features/hints/type-hints-module.ts index 970b2b7..c27df6f 100644 --- a/packages/client/src/features/hints/type-hints-module.ts +++ b/packages/client/src/features/hints/type-hints-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover-module.ts b/packages/client/src/features/hover/hover-module.ts index 360f4fa..5fd59c6 100644 --- a/packages/client/src/features/hover/hover-module.ts +++ b/packages/client/src/features/hover/hover-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/label-edit-module.ts b/packages/client/src/features/label-edit/label-edit-module.ts index 90460ab..b9a5786 100644 --- a/packages/client/src/features/label-edit/label-edit-module.ts +++ b/packages/client/src/features/label-edit/label-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.ts b/packages/client/src/features/layout/layout-elements-action.ts index 7996eb3..646a6d0 100644 --- a/packages/client/src/features/layout/layout-elements-action.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 8b7df50..5db6df3 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-module.ts b/packages/client/src/features/navigation/navigation-module.ts index 40fe7bf..1cad4ea 100644 --- a/packages/client/src/features/navigation/navigation-module.ts +++ b/packages/client/src/features/navigation/navigation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/routing/routing-module.ts b/packages/client/src/features/routing/routing-module.ts index 7985178..2b308f2 100644 --- a/packages/client/src/features/routing/routing-module.ts +++ b/packages/client/src/features/routing/routing-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/save/save-module.ts b/packages/client/src/features/save/save-module.ts index 5a97c2d..1094ac6 100644 --- a/packages/client/src/features/save/save-module.ts +++ b/packages/client/src/features/save/save-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/select/select-module.ts b/packages/client/src/features/select/select-module.ts index 6bf147d..b121e64 100644 --- a/packages/client/src/features/select/select-module.ts +++ b/packages/client/src/features/select/select-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts index 212cbf4..c699aa8 100644 --- a/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts +++ b/packages/client/src/features/source-model-watcher/source-model-watcher-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-module.ts b/packages/client/src/features/status/status-module.ts index 3ec9672..f9adc41 100644 --- a/packages/client/src/features/status/status-module.ts +++ b/packages/client/src/features/status/status-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index 06c19f7..f1972ef 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/svg-metadata/svg-metadata-module.ts b/packages/client/src/features/svg-metadata/svg-metadata-module.ts index cbab06a..194992e 100644 --- a/packages/client/src/features/svg-metadata/svg-metadata-module.ts +++ b/packages/client/src/features/svg-metadata/svg-metadata-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette-module.ts b/packages/client/src/features/tool-palette/tool-palette-module.ts index f7dbf86..572bb72 100644 --- a/packages/client/src/features/tool-palette/tool-palette-module.ts +++ b/packages/client/src/features/tool-palette/tool-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/delete-tool.ts b/packages/client/src/features/tools/deletion/delete-tool.ts index 0a58676..2505944 100644 --- a/packages/client/src/features/tools/deletion/delete-tool.ts +++ b/packages/client/src/features/tools/deletion/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/deletion/deletion-tool-module.ts b/packages/client/src/features/tools/deletion/deletion-tool-module.ts index 444a5cb..2ede9b6 100644 --- a/packages/client/src/features/tools/deletion/deletion-tool-module.ts +++ b/packages/client/src/features/tools/deletion/deletion-tool-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts index dd605a7..454d5c9 100644 --- a/packages/client/src/features/tools/edge-creation/edege-creation-module.ts +++ b/packages/client/src/features/tools/edge-creation/edege-creation-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-creation/view.tsx b/packages/client/src/features/tools/edge-creation/view.tsx index 496431b..5f85668 100644 --- a/packages/client/src/features/tools/edge-creation/view.tsx +++ b/packages/client/src/features/tools/edge-creation/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts index 42aa7a2..bcdf709 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-module.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts index ae6cf8e..783211c 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts index 1930802..e393c2f 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts index 27b3468..a165cf4 100644 --- a/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts +++ b/packages/client/src/features/tools/marquee-selection/marquee-selection-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/marquee-selection/view.tsx b/packages/client/src/features/tools/marquee-selection/view.tsx index 17e4067..1ec3e83 100644 --- a/packages/client/src/features/tools/marquee-selection/view.tsx +++ b/packages/client/src/features/tools/marquee-selection/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/tool-focus-loss-module.ts b/packages/client/src/features/tools/tool-focus-loss-module.ts index e83a781..bf8e3db 100644 --- a/packages/client/src/features/tools/tool-focus-loss-module.ts +++ b/packages/client/src/features/tools/tool-focus-loss-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/issue-marker.ts b/packages/client/src/features/validation/issue-marker.ts index 19f1c75..f001040 100644 --- a/packages/client/src/features/validation/issue-marker.ts +++ b/packages/client/src/features/validation/issue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 864cca3..91f2ce0 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/validation/validation-modules.ts b/packages/client/src/features/validation/validation-modules.ts index 6dacf57..6d46083 100644 --- a/packages/client/src/features/validation/validation-modules.ts +++ b/packages/client/src/features/validation/validation-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index e291c77..bef6b0b 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 7c86c7c..03378fc 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 2c2a467..ce2dfa3 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/geometry-util.ts b/packages/client/src/utils/geometry-util.ts index 47e6de7..28fc004 100644 --- a/packages/client/src/utils/geometry-util.ts +++ b/packages/client/src/utils/geometry-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index 52c5d4d..4d9e29d 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index bcdb5b3..9cab913 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 647fccc..326e1b1 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index c4002a7..e7e182c 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/gedge-view.tsx b/packages/client/src/views/gedge-view.tsx index e59c5b8..f79aafc 100644 --- a/packages/client/src/views/gedge-view.tsx +++ b/packages/client/src/views/gedge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 2059aa2..34e8b18 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index bee784d..68e02eb 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index 28d6650..d4e23bd 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts index f292443..a0b67ac 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/worker-connection-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/re-exports.ts b/packages/protocol/src/re-exports.ts index 7130bf1..1182b13 100644 --- a/packages/protocol/src/re-exports.ts +++ b/packages/protocol/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 7e8d23e..4a9f5e8 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 4f2bafb..5804d9d 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 9dab173..f157a5f 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 EclipseSource and others. + * Copyright (c) 2021-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From c1645e4c56f094fa0ddf3e29cbe68835af9a97c8 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 00:25:54 +0200 Subject: [PATCH 386/566] v2.2.1 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 6 +++--- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index cabe4e1..5b71ead 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.0-next", + "version": "2.2.1", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp/client": "2.2.1", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 3d8a93b..01c67c3 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.0-next", + "version": "2.2.1", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.0-next", - "@eclipse-glsp/client": "2.2.0-next", + "@eclipse-glsp-examples/workflow-glsp": "2.2.1", + "@eclipse-glsp/client": "2.2.1", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..42a14d5 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.2.1" } diff --git a/packages/client/package.json b/packages/client/package.json index 26e5612..9a2bae4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.0-next", + "version": "2.2.1", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,11 +46,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.0-next", + "@eclipse-glsp/sprotty": "2.2.1", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", - "snabbdom": "~3.5.1", "lodash": "4.17.21", + "snabbdom": "~3.5.1", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 3ce7ef5..864d7bf 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.0-next", + "@eclipse-glsp/protocol": "2.2.1", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 664d5eb..d47d8aa 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.0-next", + "version": "2.2.1", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 164408eeb5fbd7f0ef33390509df5a233edf2958 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 13:40:39 +0200 Subject: [PATCH 387/566] Update to 2.3.0-next version --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 5b71ead..8505f3f 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.2.1", + "version": "2.3.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.2.1", + "@eclipse-glsp/client": "2.3.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 01c67c3..111edd9 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.2.1", + "version": "2.3.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.2.1", - "@eclipse-glsp/client": "2.2.1", + "@eclipse-glsp-examples/workflow-glsp": "2.3.0-next", + "@eclipse-glsp/client": "2.3.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index 42a14d5..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.2.1" + "version": "next" } diff --git a/packages/client/package.json b/packages/client/package.json index 9a2bae4..aea7b0f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.2.1", + "version": "2.3.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.2.1", + "@eclipse-glsp/sprotty": "2.3.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 864d7bf..4d0a353 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.1", + "@eclipse-glsp/protocol": "2.3.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.2.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index d47d8aa..e8c6828 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.2.1", + "version": "2.3.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From a59583c7140f0447b63dac398e2cba215f0d0eb4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 00:44:17 +0200 Subject: [PATCH 388/566] GLSP-1374: Fix transformLable in elk engine Fixes https://github.com/eclipse-glsp/glsp/issues/1374 --- packages/layout-elk/src/glsp-elk-layout-engine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index cc39589..9a2f620 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -93,7 +93,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { } else if (model instanceof GEdge) { return this.transformEdge(model); } else if (model instanceof GLabel) { - return this.transformPort(model); + return this.transformLabel(model); } else if (model instanceof GPort) { return this.transformPort(model); } From 02caf2e17dbb9fc0b261b124ac4f9845539944ab Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 00:46:51 +0200 Subject: [PATCH 389/566] Fix copyright header violations --- .../provider/node-documentation-navigation-target-provider.ts | 2 +- examples/workflow-server/src/common/workflow-popup-factory.ts | 2 +- packages/graph/src/gedge-layoutable.ts | 2 +- packages/graph/src/gissue-marker.ts | 2 +- packages/layout-elk/src/glsp-elk-layout-engine.ts | 2 +- packages/server/src/common/actions/action-dispatcher.ts | 2 +- .../src/common/features/model/request-model-action-handler.ts | 2 +- packages/server/src/common/utils/client-options-util.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts index 32f8709..054bde4 100644 --- a/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts +++ b/examples/workflow-server/src/common/provider/node-documentation-navigation-target-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-server/src/common/workflow-popup-factory.ts b/examples/workflow-server/src/common/workflow-popup-factory.ts index d55432a..ea1f494 100644 --- a/examples/workflow-server/src/common/workflow-popup-factory.ts +++ b/examples/workflow-server/src/common/workflow-popup-factory.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gedge-layoutable.ts b/packages/graph/src/gedge-layoutable.ts index 72d571a..afbf09f 100644 --- a/packages/graph/src/gedge-layoutable.ts +++ b/packages/graph/src/gedge-layoutable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/graph/src/gissue-marker.ts b/packages/graph/src/gissue-marker.ts index 1250925..2399663 100644 --- a/packages/graph/src/gissue-marker.ts +++ b/packages/graph/src/gissue-marker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index 9a2f620..9373731 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2018-2023 TypeFox and others. + * Copyright (c) 2018-2024 TypeFox and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. diff --git a/packages/server/src/common/actions/action-dispatcher.ts b/packages/server/src/common/actions/action-dispatcher.ts index 3f7cc82..63fc208 100644 --- a/packages/server/src/common/actions/action-dispatcher.ts +++ b/packages/server/src/common/actions/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 093a10e..621d70a 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/client-options-util.ts b/packages/server/src/common/utils/client-options-util.ts index 80496f9..beb5d2e 100644 --- a/packages/server/src/common/utils/client-options-util.ts +++ b/packages/server/src/common/utils/client-options-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2023 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From d334a053c82da7bfc9dd18601ac34cf82fd30106 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 00:49:59 +0200 Subject: [PATCH 390/566] v2.2.1 --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 8 ++++---- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 6 +++--- packages/server/package.json | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index e291068..315e44a 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.2.0-next", + "version": "2.2.1", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 44c2389..6754f05 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.2.0-next", + "version": "2.2.1", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,9 +56,9 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.2.0-next", - "@eclipse-glsp/server": "2.2.0-next", - "inversify": "^6.0.1" + "@eclipse-glsp/layout-elk": "2.2.1", + "@eclipse-glsp/server": "2.2.1", + "inversify": "~6.0.2" }, "devDependencies": { "source-map-loader": "^4.0.1", diff --git a/packages/graph/package.json b/packages/graph/package.json index b8a11db..71bf308 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.2.0-next", + "version": "2.2.1", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.2.1" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 0f94953..9466fd8 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.2.0-next", + "version": "2.2.1", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,11 +49,11 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.2.0-next", + "@eclipse-glsp/server": "2.2.1", "elkjs": "^0.7.1" }, "peerDependencies": { - "inversify": "^6.0.0" + "inversify": "~6.0.2" }, "publishConfig": { "access": "public" diff --git a/packages/server/package.json b/packages/server/package.json index 8084843..cdd0b89 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.2.0-next", + "version": "2.2.1", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.2.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.2.1", + "@eclipse-glsp/protocol": "2.2.1", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", @@ -71,7 +71,7 @@ "@types/ws": "^8.5.4" }, "peerDependencies": { - "inversify": "^6.0.0" + "inversify": "~6.0.2" }, "publishConfig": { "access": "public" From 5fa08445b68ec67b4aa86f347c818e8fa2955914 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 22 Jul 2024 13:49:27 +0200 Subject: [PATCH 391/566] Switch back to next versions --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 315e44a..5c7c309 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.2.1", + "version": "2.3.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 6754f05..93dd0d0 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.2.1", + "version": "2.3.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.2.1", - "@eclipse-glsp/server": "2.2.1", + "@eclipse-glsp/layout-elk": "2.3.0-next", + "@eclipse-glsp/server": "2.3.0-next", "inversify": "~6.0.2" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 71bf308..ea7fc3c 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.2.1", + "version": "2.3.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.2.1" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 9466fd8..2036e58 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.2.1", + "version": "2.3.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.2.1", + "@eclipse-glsp/server": "2.3.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index cdd0b89..b313ba6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.2.1", + "version": "2.3.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.2.1", - "@eclipse-glsp/protocol": "2.2.1", + "@eclipse-glsp/graph": "2.3.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 544de1adcd12239667939bf0e305fcd4a587d76c Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 25 Jul 2024 15:08:39 +0000 Subject: [PATCH 392/566] Ensure no inversify-based code lands in the index (#384) - Ensure JsonrpcClientProxy is injectable in the glsp-sprotty context Contributed on behalf of Axon Ivy AG Part of https://github.com/eclipse-glsp/glsp/issues/1381 --- packages/glsp-sprotty/src/re-exports.ts | 54 ++++++++++--------- .../jsonrpc/base-jsonrpc-glsp-client.ts | 2 - 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index bd42b46..917e5f8 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -14,11 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { JsonrpcClientProxy } from '@eclipse-glsp/protocol'; +import { decorate, injectable } from 'inversify'; + /** * @eclipse-glsp/protocol */ export * from '@eclipse-glsp/protocol'; export * from '@eclipse-glsp/protocol/lib/di'; +decorate(injectable(), JsonrpcClientProxy); /* * sprotty @@ -31,8 +35,8 @@ export * from 'sprotty/lib/base/actions/action-dispatcher'; export { ActionHandlerRegistration, ActionHandlerRegistry, - IActionHandlerInitializer, configureActionHandler, + IActionHandlerInitializer, onAction } from 'sprotty/lib/base/actions/action-handler'; export * from 'sprotty/lib/base/actions/diagram-locker'; @@ -50,18 +54,19 @@ export * from 'sprotty/lib/base/features/initialize-canvas'; export * from 'sprotty/lib/base/features/set-model'; // Exclude SModelElementImpl as it as exported with augmentation module export { + createRandomId, FeatureSet, SChildElementImpl as GChildElement, SModelElementImpl as GModelElement, SModelRootImpl as GModelRoot, SParentElementImpl as GParentElement, IModelIndex, - ModelIndexImpl, - createRandomId, - isParent + isParent, + ModelIndexImpl } from 'sprotty/lib/base/model/smodel'; export { + createFeatureSet, CustomFeatures, EMPTY_ROOT, SModelElementConstructor as GModelElementConstructor, @@ -69,8 +74,7 @@ export { SModelFactory as GModelFactory, IModelFactory, // exported without alias we extend it in glsp-client to `GModelRegistry` - SModelRegistry, - createFeatureSet + SModelRegistry } from 'sprotty/lib/base/model/smodel-factory'; export * from 'sprotty/lib/base/model/smodel-utils'; @@ -99,30 +103,30 @@ export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; export * from 'sprotty/lib/features/bounds/layout'; export { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; export { - InternalBoundsAware as BoundsAware, - SShapeElementImpl as GShapeElement, - InternalLayoutContainer as LayoutContainer, - InternalLayoutableChild as LayoutableChild, - ModelLayoutOptions, alignFeature, + InternalBoundsAware as BoundsAware, boundsFeature, findChildrenAtPosition, getAbsoluteBounds, getAbsoluteClientBounds, + SShapeElementImpl as GShapeElement, isAlignable, isBoundsAware, - isLayoutContainer, isLayoutableChild, + isLayoutContainer, isSizeable, + InternalLayoutableChild as LayoutableChild, + layoutableChildFeature, + InternalLayoutContainer as LayoutContainer, layoutContainerFeature, - layoutableChildFeature + ModelLayoutOptions } from 'sprotty/lib/features/bounds/model'; // exclude stack layout as its not supported in GLSP // export * from 'sprotty/lib/features/bounds/stack-layout'; export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; -export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; +export { ButtonHandlerRegistry, configureButtonHandler, IButtonHandlerRegistration } from 'sprotty/lib/features/button/button-handler'; export { SButtonImpl as GButton } from 'sprotty/lib/features/button/model'; export { @@ -138,7 +142,7 @@ export * from 'sprotty/lib/features/context-menu/mouse-listener'; export * from 'sprotty/lib/features/edge-layout/di.config'; export * from 'sprotty/lib/features/edge-layout/edge-layout'; -export { DEFAULT_EDGE_PLACEMENT, checkEdgePlacement, edgeLayoutFeature, isEdgeLayoutable } from 'sprotty/lib/features/edge-layout/model'; +export { checkEdgePlacement, DEFAULT_EDGE_PLACEMENT, edgeLayoutFeature, isEdgeLayoutable } from 'sprotty/lib/features/edge-layout/model'; // Exclude client-side creation features (not supported in GLSP) // export * from 'sprotty/lib/features/edit/create'; // export * from 'sprotty/lib/features/edit/create-on-drag'; @@ -169,10 +173,10 @@ export * from 'sprotty/lib/features/hover/popup-position-updater'; export * from 'sprotty/lib/features/decoration/decoration-placer'; export { Decoration, - SDecoration as GDecoration, - SIssueMarkerImpl, decorationFeature, - isDecoration + SDecoration as GDecoration, + isDecoration, + SIssueMarkerImpl } from 'sprotty/lib/features/decoration/model'; export * from 'sprotty/lib/features/decoration/views'; @@ -190,7 +194,7 @@ export * from 'sprotty/lib/features/nameable/model'; export * from 'sprotty/lib/features/open/model'; export * from 'sprotty/lib/features/open/open'; -export { ViewProjection, getModelBounds, getProjectedBounds, getProjections, isProjectable } from 'sprotty/lib/features/projection/model'; +export { getModelBounds, getProjectedBounds, getProjections, isProjectable, ViewProjection } from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; export * from 'sprotty/lib/features/routing/abstract-edge-router'; @@ -203,17 +207,17 @@ export * from 'sprotty/lib/features/routing/manhattan-edge-router'; // Alias SModel types export { Connectable, - SConnectableElementImpl as GConnectableElement, - SDanglingAnchorImpl as GDanglingAnchor, - SRoutableElementImpl as GRoutableElement, - SRoutingHandleImpl as GRoutingHandle, - RoutingHandleKind, connectableFeature, edgeInProgressID, edgeInProgressTargetHandleID, + SConnectableElementImpl as GConnectableElement, + SDanglingAnchorImpl as GDanglingAnchor, getAbsoluteRouteBounds, getRouteBounds, - isConnectable + SRoutableElementImpl as GRoutableElement, + SRoutingHandleImpl as GRoutingHandle, + isConnectable, + RoutingHandleKind } from 'sprotty/lib/features/routing/model'; export * from 'sprotty/lib/features/routing/polyline-anchors'; export * from 'sprotty/lib/features/routing/polyline-edge-router'; diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index be58166..6577a95 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; import { ActionMessage } from '../../action-protocol/base-protocol'; import { Emitter, Event } from '../../utils/event'; @@ -175,7 +174,6 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { /** * Default {@link GLSPClientProxy} implementation for jsonrpc-based client-server communication with typescript based servers. */ -@injectable() export class JsonrpcClientProxy implements GLSPClientProxy { protected clientConnection?: MessageConnection; protected enableLogging: boolean; From b7757b2857eef9ef4c30d7ef07e774fbc8408349 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 25 Jul 2024 14:38:08 +0200 Subject: [PATCH 393/566] Ensure JsonrpcClientProxy is injectable in the Node Server context Contributed on behalf of Axon Ivy AG Part of https://github.com/eclipse-glsp/glsp/issues/1381 --- packages/server/src/common/reexport.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/server/src/common/reexport.ts b/packages/server/src/common/reexport.ts index 662a16b..39b09c3 100644 --- a/packages/server/src/common/reexport.ts +++ b/packages/server/src/common/reexport.ts @@ -14,6 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { JsonrpcClientProxy } from '@eclipse-glsp/protocol'; +import { decorate, injectable } from 'inversify'; + export * from '@eclipse-glsp/graph'; export * from '@eclipse-glsp/protocol'; export * from '@eclipse-glsp/protocol/lib/di'; +decorate(injectable(), JsonrpcClientProxy); From dc8d329aaff2238953c0946e842ba9e49508e660 Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Wed, 21 Aug 2024 16:37:07 +0200 Subject: [PATCH 394/566] GLSP-1388 Enhance extensibility of `ContainerManager` (#385) * GLSP-1388 Enhance extensibility of `ContainerManager` - Added IContainerManager interface for easier customization - Consume IContainerManager for dependency injection - Extract isCreationAllowed submethod for element insertion checks Resolves https://github.com/eclipse-glsp/glsp/issues/1388 * Address review comments - Ensure backward compatibility for ContainerManager binding by using bindAsService. - Make isCreationAllowed public and reuse in KeyboardPointerPosition (accessibility tools) --- .../keyboard-pointer-position.ts | 18 ++++++++--------- .../keyboard-pointer/keyboard-pointer.ts | 8 ++++++-- .../tools/node-creation/container-manager.ts | 20 +++++++++++++++++-- .../node-creation/node-creation-module.ts | 2 +- .../tools/node-creation/node-creation-tool.ts | 6 +++--- packages/glsp-sprotty/src/types.ts | 1 + 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts index 315be5f..01c296b 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-position.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,11 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { findChildrenAtPosition, findParentByFeature, GModelElement, Point } from '@eclipse-glsp/sprotty'; -import { Containable, isContainable } from '../../hints/model'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; +import { ContainerElement, isContainable } from '../../hints/model'; import { KeyboardPointerMetadata } from './constants'; import { KeyboardPointer } from './keyboard-pointer'; -import { CursorCSS } from '../../../base/feedback/css-feedback'; export class KeyboardPointerPosition { public renderPosition: Point = { x: 20, y: 20 }; @@ -46,7 +46,7 @@ export class KeyboardPointerPosition { } containableParentAtDiagramPosition(elementTypeId: string): { - container: (GModelElement & Containable) | undefined; + container: ContainerElement | undefined; status: CursorCSS; } { const children = this.childrenAtDiagramPosition(); @@ -64,15 +64,13 @@ export class KeyboardPointerPosition { private containableParentOf( target: GModelElement, elementTypeId: string - ): { container: (GModelElement & Containable) | undefined; status: CursorCSS } { + ): { container: ContainerElement | undefined; status: CursorCSS } { const container = findParentByFeature(target, isContainable); return { container, - status: this.isCreationAllowed(container, elementTypeId) ? CursorCSS.NODE_CREATION : CursorCSS.OPERATION_NOT_ALLOWED + status: this.keyboardPointer.containerManager.isCreationAllowed(container, elementTypeId) + ? CursorCSS.NODE_CREATION + : CursorCSS.OPERATION_NOT_ALLOWED }; } - - private isCreationAllowed(container: (GModelElement & Containable) | undefined, elementTypeId: string): boolean | undefined { - return container && container.isContainableElement(elementTypeId); - } } diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts index 5ed728d..2f011a1 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer.ts @@ -18,6 +18,7 @@ import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../../base/editor-context-service'; import { CursorCSS } from '../../../base/feedback/css-feedback'; import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; +import { ContainerManager } from '../../tools/node-creation/container-manager'; import { KeyboardGridCellSelectedAction } from '../keyboard-grid/action'; import { SetKeyboardPointerRenderPositionAction } from './actions'; import { KeyboardPointerMetadata } from './constants'; @@ -31,10 +32,13 @@ export class KeyboardPointer extends GLSPAbstractUIExtension implements IActionH kind: 'triggerNodeCreation' }; - protected position: KeyboardPointerPosition = new KeyboardPointerPosition(this); - protected keyListener: KeyboardPointerKeyboardListener; @inject(EditorContextService) public editorContextService: EditorContextService; + @inject(TYPES.IContainerManager) + public containerManager: ContainerManager; + + protected position: KeyboardPointerPosition = new KeyboardPointerPosition(this); + protected keyListener: KeyboardPointerKeyboardListener; constructor(@inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher) { super(); diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index 397126c..5696133 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -44,14 +44,26 @@ export interface TrackedInsert { options: InsertOptions; } +export interface IContainerManager { + insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial): TrackedInsert; + isCreationAllowed(container: ContainerElement | undefined, elementTypeId: string, opts?: Partial): boolean; + findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined; + addInsertFeedback(feedback: FeedbackEmitter, trackedInsert: TrackedInsert, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter; +} + +/** + * The default {@link IContainerManager} implementation. + * This class class manages the insertion of elements into containers by validating their positions and types, + * providing feedback on the insertion process, and determining the appropriate container based on the location and context. + */ @injectable() -export class ContainerManager { +export class ContainerManager implements IContainerManager { @inject(ChangeBoundsManager) protected readonly changeBoundsManager: ChangeBoundsManager; insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial): TrackedInsert { const options = { ...DEFAULT_INSERT_OPTIONS, ...opts }; const container = this.findContainer(location, proxy, opts?.evt); - let valid = !container || container.isContainableElement(elementTypeId); + let valid = this.isCreationAllowed(container, elementTypeId, opts); if (valid && (!container || options.validateLocationInContainer)) { // we need to check whether the location is valid either because we do not have a container or the option is set valid = opts?.validLocationOverwrite ?? this.changeBoundsManager.hasValidPosition(proxy, location); @@ -59,6 +71,10 @@ export class ContainerManager { return { elementTypeId, container, location, valid, options }; } + isCreationAllowed(container: ContainerElement | undefined, elementTypeId: string, opts?: Partial): boolean { + return !container || container.isContainableElement(elementTypeId); + } + findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined { // reverse order of children to find the innermost, top-rendered containers first return findChildrenAtPosition(ctx.root, location) diff --git a/packages/client/src/features/tools/node-creation/node-creation-module.ts b/packages/client/src/features/tools/node-creation/node-creation-module.ts index d26e945..f591560 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-module.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-module.ts @@ -30,7 +30,7 @@ import { InsertIndicatorView } from './node-creation-views'; export const nodeCreationToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - bind(ContainerManager).toSelf().inSingletonScope(); + bindAsService(context, TYPES.IContainerManager, ContainerManager); bindAsService(context, TYPES.ITool, NodeCreationTool); configureActionHandler(context, TriggerNodeCreationAction.KIND, NodeCreationTool); configureModelElement(context, InsertIndicator.TYPE, InsertIndicator, InsertIndicatorView); diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index e66e93d..a3c24c1 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -45,7 +45,7 @@ import { RemoveTemplateElementsAction } from '../../element-template/remove-temp import { BaseCreationTool } from '../base-tools'; import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; import { TrackedMove } from '../change-bounds/change-bounds-tracker'; -import { ContainerManager, TrackedInsert } from './container-manager'; +import { IContainerManager, TrackedInsert } from './container-manager'; import { InsertIndicator } from './insert-indicator'; @injectable() @@ -55,7 +55,7 @@ export class NodeCreationTool extends BaseCreationTool Date: Thu, 22 Aug 2024 08:19:44 +0000 Subject: [PATCH 395/566] Ensure anyone who imports the di functionality gets decorated classes (#387) --- packages/glsp-sprotty/src/re-exports.ts | 4 ---- packages/protocol/src/di/index.ts | 1 + packages/protocol/src/di/re-decorate.ts | 22 ++++++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 packages/protocol/src/di/re-decorate.ts diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 917e5f8..ca76bd5 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -14,15 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { JsonrpcClientProxy } from '@eclipse-glsp/protocol'; -import { decorate, injectable } from 'inversify'; - /** * @eclipse-glsp/protocol */ export * from '@eclipse-glsp/protocol'; export * from '@eclipse-glsp/protocol/lib/di'; -decorate(injectable(), JsonrpcClientProxy); /* * sprotty diff --git a/packages/protocol/src/di/index.ts b/packages/protocol/src/di/index.ts index dadc469..33a8b6c 100644 --- a/packages/protocol/src/di/index.ts +++ b/packages/protocol/src/di/index.ts @@ -17,3 +17,4 @@ export * from './container-configuration'; export * from './feature-module'; export * from './inversify-util'; export * from './lazy-injector'; +export * from './re-decorate'; diff --git a/packages/protocol/src/di/re-decorate.ts b/packages/protocol/src/di/re-decorate.ts new file mode 100644 index 0000000..c71def1 --- /dev/null +++ b/packages/protocol/src/di/re-decorate.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { decorate, injectable } from 'inversify'; +import { JsonrpcClientProxy } from '../client-server-protocol/jsonrpc/base-jsonrpc-glsp-client'; + +// Decorate `JsonrpcClientProxy` as injectable for anyone who imports the di functionality, such as the client package, +// the Theia integration package and the server package. +decorate(injectable(), JsonrpcClientProxy); From 89272f86fdf73741e0f4a476321de6ff8fb5ead8 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 22 Aug 2024 08:35:33 +0000 Subject: [PATCH 396/566] Remove unnecessary re-decoration, part of protocol DI import/export (#96) Part of https://github.com/eclipse-glsp/glsp/issues/1381 --- packages/server/src/common/reexport.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/server/src/common/reexport.ts b/packages/server/src/common/reexport.ts index 39b09c3..662a16b 100644 --- a/packages/server/src/common/reexport.ts +++ b/packages/server/src/common/reexport.ts @@ -14,10 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { JsonrpcClientProxy } from '@eclipse-glsp/protocol'; -import { decorate, injectable } from 'inversify'; - export * from '@eclipse-glsp/graph'; export * from '@eclipse-glsp/protocol'; export * from '@eclipse-glsp/protocol/lib/di'; -decorate(injectable(), JsonrpcClientProxy); From 86d7bbc5451fbfd324bdb12bafb9f06517db7963 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 28 Aug 2024 11:40:31 +0200 Subject: [PATCH 397/566] GLSP-1398: Fix default type mappings Ensure that the correct type for `foreign-object` is configure and add missing configuration for GShaprePreRenderedElements Fixes https://github.com/eclipse-glsp/glsp/issues/1398 --- packages/graph/src/default-types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts index 0b6f628..60bdad8 100644 --- a/packages/graph/src/default-types.ts +++ b/packages/graph/src/default-types.ts @@ -17,6 +17,7 @@ import { DefaultTypes as types } from '@eclipse-glsp/protocol'; import { GButton } from './gbutton'; import { GCompartment } from './gcompartment'; import { GEdge } from './gedge'; +import { GForeignObjectElement } from './gforeign-object-element'; import { GGraph } from './ggraph'; import { GHtmlRoot } from './ghtml-root'; import { GIssueMarker } from './gissue-marker'; @@ -43,6 +44,7 @@ export function getDefaultMapping(): Map { mapping.set(types.HTML, GHtmlRoot); mapping.set(types.PRE_RENDERED, GPreRenderedElement); - mapping.set(types.FOREIGN_OBJECT, GShapedPreRenderedElement); + mapping.set(types.SHAPE_PRE_RENDERED, GShapedPreRenderedElement); + mapping.set(types.FOREIGN_OBJECT, GForeignObjectElement); return mapping; } From 403e7430f1c90383b07a5fdf31e853b633e6afcd Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 5 Sep 2024 12:12:12 +0200 Subject: [PATCH 398/566] GLSP-1401: Fix weighted edge creation (#98) Fixes https://github.com/eclipse-glsp/glsp/issues/1401 --- .../src/common/workflow-edge-creation-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts index da45ef4..6addee0 100644 --- a/examples/workflow-server/src/common/workflow-edge-creation-checker.ts +++ b/examples/workflow-server/src/common/workflow-edge-creation-checker.ts @@ -21,7 +21,7 @@ import { ModelTypes } from './util/model-types'; @injectable() export class WorkflowEdgeCreationChecker implements EdgeCreationChecker { isValidSource(edgeType: string, sourceElement: GModelElement): boolean { - return edgeType !== ModelTypes.WEIGHTED_EDGE && sourceElement.type === ModelTypes.DECISION_NODE; + return edgeType === ModelTypes.WEIGHTED_EDGE && sourceElement.type === ModelTypes.DECISION_NODE; } isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean { return ( From 046e69d0427422de25cf6b7a19c3413137087fdc Mon Sep 17 00:00:00 2001 From: Nina Doschek Date: Mon, 9 Sep 2024 13:50:53 +0200 Subject: [PATCH 399/566] GLSP-1393 Align Interface usage across *Manager classes (#388) - Align interface + symbol usage for *Manager classes - `ChangeBoundsManager`, `GridManager`, `DebugManager` - Use bindAsService to ensure backward compatibility - Update injects to use the interface instead of the actual manager class Resolves https://github.com/eclipse-glsp/glsp/issues/1393 --- .../workflow-glsp/src/workflow-startup.ts | 6 +- .../view-key-tools/movement-key-tool.ts | 4 +- .../features/debug/debug-bounds-decorator.tsx | 6 +- .../src/features/debug/debug-manager.ts | 15 +- .../client/src/features/debug/debug-module.ts | 2 +- ...ouse-tracking-element-position-listener.ts | 4 +- .../client/src/features/grid/grid-manager.ts | 17 ++- .../client/src/features/grid/grid-module.ts | 4 +- .../features/select/select-mouse-listener.ts | 6 +- .../src/features/tool-palette/tool-palette.ts | 13 +- .../change-bounds/change-bounds-manager.ts | 132 +++++++++++++++++- .../change-bounds-tool-feedback.ts | 4 +- .../change-bounds-tool-module.ts | 2 +- .../tools/change-bounds/change-bounds-tool.ts | 4 +- .../edge-edit/edge-edit-tool-feedback.ts | 4 +- .../tools/edge-edit/edge-edit-tool.ts | 5 +- .../tools/node-creation/container-manager.ts | 6 +- .../tools/node-creation/node-creation-tool.ts | 4 +- packages/client/src/views/ggraph-view.tsx | 8 +- .../client/src/views/glsp-projection-view.tsx | 7 +- packages/glsp-sprotty/src/types.ts | 3 + 21 files changed, 208 insertions(+), 48 deletions(-) diff --git a/examples/workflow-glsp/src/workflow-startup.ts b/examples/workflow-glsp/src/workflow-startup.ts index c0cd9d3..c42df90 100644 --- a/examples/workflow-glsp/src/workflow-startup.ts +++ b/examples/workflow-glsp/src/workflow-startup.ts @@ -14,15 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GridManager, IDiagramStartup } from '@eclipse-glsp/client'; -import { MaybePromise } from '@eclipse-glsp/sprotty'; +import { IDiagramStartup, IGridManager } from '@eclipse-glsp/client'; +import { MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; @injectable() export class WorkflowStartup implements IDiagramStartup { rank = -1; - @inject(GridManager) @optional() protected gridManager?: GridManager; + @inject(TYPES.IGridManager) @optional() protected gridManager?: IGridManager; preRequestModel(): MaybePromise { this.gridManager?.setGridVisible(true); diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index 1eebf5b..dee57de 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -21,7 +21,7 @@ import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; import { Grid } from '../../grid/grid'; -import { ChangeBoundsManager } from '../../tools/change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../../tools/change-bounds/change-bounds-manager'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; @@ -41,7 +41,7 @@ export class MovementKeyTool implements Tool { @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; @inject(TYPES.Grid) @optional() protected grid: Grid; - @inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; get id(): string { return MovementKeyTool.ID; diff --git a/packages/client/src/features/debug/debug-bounds-decorator.tsx b/packages/client/src/features/debug/debug-bounds-decorator.tsx index c8647ec..4c847ad 100644 --- a/packages/client/src/features/debug/debug-bounds-decorator.tsx +++ b/packages/client/src/features/debug/debug-bounds-decorator.tsx @@ -15,18 +15,18 @@ ********************************************************************************/ /* eslint-disable max-len */ /** @jsx svg */ -import { Bounds, GModelElement, IVNodePostprocessor, Point, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; +import { Bounds, GModelElement, IVNodePostprocessor, Point, TYPES, isDecoration, isSizeable, setClass, svg } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode } from 'snabbdom'; import { GGraph } from '../../model'; import { BoundsAwareModelElement } from '../../utils/gmodel-util'; -import { DebugManager } from './debug-manager'; +import { IDebugManager } from './debug-manager'; export const CSS_DEBUG_BOUNDS = 'debug-bounds'; @injectable() export class DebugBoundsDecorator implements IVNodePostprocessor { - @inject(DebugManager) @optional() protected debugManager?: DebugManager; + @inject(TYPES.IDebugManager) @optional() protected debugManager?: IDebugManager; decorate(vnode: VNode, element: GModelElement): VNode { if (!this.debugManager?.isDebugEnabled) { diff --git a/packages/client/src/features/debug/debug-manager.ts b/packages/client/src/features/debug/debug-manager.ts index aa68734..34941bb 100644 --- a/packages/client/src/features/debug/debug-manager.ts +++ b/packages/client/src/features/debug/debug-manager.ts @@ -20,8 +20,21 @@ import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-d import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { EnableDebugModeAction } from './debug-model'; +export interface IDebugManager { + /** Flag to indicate whether the debug mode is enabled. */ + readonly isDebugEnabled: boolean; + /** Sets the debug enabled state. */ + setDebugEnabled(enabled: boolean): void; + /** Toggles the debug enabled state. */ + toggleDebugEnabled(): void; +} + +/** + * The default {@link IDebugManager} implementation. + * This class manages the debug mode and provides functionality to enable or disable it. + */ @injectable() -export class DebugManager implements IActionHandler { +export class DebugManager implements IActionHandler, IDebugManager { protected _debugEnabled: boolean = false; protected debugFeedback: FeedbackEmitter; diff --git a/packages/client/src/features/debug/debug-module.ts b/packages/client/src/features/debug/debug-module.ts index 5b2bdde..6b22c75 100644 --- a/packages/client/src/features/debug/debug-module.ts +++ b/packages/client/src/features/debug/debug-module.ts @@ -26,7 +26,7 @@ export const debugModule = new FeatureModule( configureCommand(context, EnableDebugModeCommand); - bind(DebugManager).toSelf().inSingletonScope(); + bindAsService(context, TYPES.IDebugManager, DebugManager); configureActionHandler(context, EnableDebugModeAction.KIND, DebugManager); bindAsService(context, TYPES.IVNodePostprocessor, DebugBoundsDecorator); diff --git a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts index e6c0e07..32909f3 100644 --- a/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts +++ b/packages/client/src/features/element-template/mouse-tracking-element-position-listener.ts @@ -32,12 +32,12 @@ import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { MoveableElement } from '../../utils/gmodel-util'; import { getAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackAwareTool } from '../tools/base-tools'; -import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; import { MoveFinishedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { ChangeBoundsTracker, TrackedMove } from '../tools/change-bounds/change-bounds-tracker'; export interface PositioningTool extends FeedbackAwareTool { - readonly changeBoundsManager: ChangeBoundsManager; + readonly changeBoundsManager: IChangeBoundsManager; } export class MouseTrackingElementPositionListener extends DragAwareMouseListener { diff --git a/packages/client/src/features/grid/grid-manager.ts b/packages/client/src/features/grid/grid-manager.ts index 5d2aacb..032eef9 100644 --- a/packages/client/src/features/grid/grid-manager.ts +++ b/packages/client/src/features/grid/grid-manager.ts @@ -23,8 +23,23 @@ import { ShowGridAction } from './grid-model'; export type GridStyle = Record & Partial>; +export interface IGridManager { + /** The grid to manage. */ + readonly grid: Grid; + /** Flag to indicate whether the grid is visible. */ + readonly isGridVisible: boolean; + /** Sets the visibility of the grid. */ + setGridVisible(visible: boolean): void; + /** Toggles the visibility of the grid. */ + toggleGridVisible(): void; +} + +/** + * The default {@link IGridManager} implementation. + * This class manages the visibility and behavior of a grid in the application. + */ @injectable() -export class GridManager implements IActionHandler { +export class GridManager implements IActionHandler, IGridManager { protected _gridVisible: boolean = false; protected gridFeedback: FeedbackEmitter; diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index 1b20830..3e6c264 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, TYPES, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; +import { FeatureModule, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import '../../../css/grid.css'; import { GridManager } from './grid-manager'; import { ShowGridAction, ShowGridCommand } from './grid-model'; @@ -28,7 +28,7 @@ export const gridModule = new FeatureModule( configureCommand(context, ShowGridCommand); - bind(GridManager).toSelf().inSingletonScope(); + bindAsService(context, TYPES.IGridManager, GridManager); configureActionHandler(context, ShowGridAction.KIND, GridManager); bind(TYPES.ISnapper).to(GridSnapper); diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 9671c17..87a10ba 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener } from '@eclipse-glsp/sprotty'; +import { Action, BringToFrontAction, GModelElement, SelectAction, SelectMouseListener, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { Ranked } from '../../base/ranked'; import { SelectableElement } from '../../utils/gmodel-util'; import { GResizeHandle } from '../change-bounds/model'; -import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; /** * Ranked select mouse listener that is executed before default mouse listeners when using the RankedMouseTool. @@ -28,7 +28,7 @@ import { ChangeBoundsManager } from '../tools/change-bounds/change-bounds-manage export class RankedSelectMouseListener extends SelectMouseListener implements Ranked { rank: number = Ranked.DEFAULT_RANK - 100; /* we want to be executed before all default mouse listeners */ - @inject(ChangeBoundsManager) @optional() readonly changeBoundsManager?: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) @optional() readonly changeBoundsManager?: IChangeBoundsManager; protected override handleSelectTarget( selectableTarget: SelectableElement, diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index cbd4159..437c539 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -25,6 +25,7 @@ import { SetContextActions, SetModelAction, SetUIExtensionVisibilityAction, + TYPES, TriggerNodeCreationAction, UpdateModelAction, codiconCSSClasses, @@ -37,8 +38,8 @@ import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; -import { DebugManager } from '../debug/debug-manager'; -import { GridManager } from '../grid/grid-manager'; +import { IDebugManager } from '../debug/debug-manager'; +import { IGridManager } from '../grid/grid-manager'; import { MouseDeleteTool } from '../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-selection/marquee-mouse-tool'; import { OriginViewportAction } from '../viewport/origin-viewport'; @@ -77,13 +78,13 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl @inject(FocusTracker) protected focusTracker: FocusTracker; - @inject(GridManager) + @inject(TYPES.IGridManager) @optional() - protected gridManager?: GridManager; + protected gridManager?: IGridManager; - @inject(DebugManager) + @inject(TYPES.IDebugManager) @optional() - protected debugManager?: DebugManager; + protected debugManager?: IDebugManager; protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts index 28bb579..3909b31 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-manager.ts @@ -46,7 +46,7 @@ import { movementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor'; -import { GridManager } from '../../grid/grid-manager'; +import { IGridManager } from '../../grid/grid-manager'; import { IHelperLineManager } from '../../helper-lines/helper-line-manager'; import { InsertIndicator } from '../node-creation/insert-indicator'; import { ChangeBoundsTracker, TrackedElementMove, TrackedElementResize, TrackedMove, TrackedResize } from './change-bounds-tracker'; @@ -55,14 +55,140 @@ export const CSS_RESIZE_MODE = 'resize-mode'; export const CSS_RESTRICTED_RESIZE = 'resize-not-allowed'; export const CSS_ACTIVE_HANDLE = 'active'; +export interface IChangeBoundsManager { + /** + * Unsnap the modifier used for changing bounds. + * @returns The unsnapped keyboard modifier, or undefined if no modifier was snapped. + */ + unsnapModifier(): KeyboardModifier | undefined; + + /** + * Determine whether to use position snap for changing bounds. + * @param arg - The event argument. + * @returns True if position snap should be used, false otherwise. + */ + usePositionSnap(arg: MouseEvent | KeyboardEvent | any): boolean; + + /** + * Snap the position of an element. + * @param element - The element to snap. + * @param position - The position to snap. + * @returns The snapped position. + */ + snapPosition(element: GModelElement, position: Point): Point; + + /** + * Check if an element is valid for changing bounds. + * @param element - The element to check. + * @returns True if the element is valid, false otherwise. + */ + isValid(element: GModelElement): boolean; + + /** + * Check if an element has a valid position for changing bounds. + * @param element - The element to check. + * @param position - The position to check. + * @returns True if the element has a valid position, false otherwise. + */ + hasValidPosition(element: GModelElement, position?: Point): boolean; + + /** + * Check if an element has a valid size for changing bounds. + * @param element - The element to check. + * @param size - The size to check. + * @returns True if the element has a valid size, false otherwise. + */ + hasValidSize(element: GModelElement, size?: Dimension): boolean; + + /** + * Get the minimum size of an element for changing bounds. + * @param element - The element to get the minimum size for. + * @returns The minimum size of the element. + */ + getMinimumSize(element: GModelElement): Dimension; + + /** + * Determine whether to use movement restriction for changing bounds. + * @param arg - The event argument. + * @returns True if movement restriction should be used, false otherwise. + */ + useMovementRestriction(arg: MouseEvent | KeyboardEvent | any): boolean; + + /** + * Restrict the movement of an element. + * @param element - The element to restrict movement for. + * @param movement - The movement to restrict. + * @returns The restricted movement. + */ + restrictMovement(element: GModelElement, movement: Movement): Movement; + + /** + * Add move feedback for changing bounds. + * @param feedback - The feedback emitter. + * @param trackedMove - The tracked move. + * @param ctx - The context element. (optional) + * @param event - The mouse event. (optional) + * @returns The feedback emitter. + */ + addMoveFeedback(feedback: FeedbackEmitter, trackedMove: TrackedMove, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter; + + /** + * Add resize feedback for changing bounds. + * @param feedback - The feedback emitter. + * @param resize - The tracked resize. + * @param ctx - The context element. (optional) + * @param event - The mouse event. (optional) + * @returns The feedback emitter. + */ + addResizeFeedback(feedback: FeedbackEmitter, resize: TrackedResize, ctx?: GModelElement, event?: MouseEvent): FeedbackEmitter; + + /** + * Add move restriction feedback for changing bounds. + * @param feedback - The feedback emitter. + * @param change - The tracked element resize or move. + * @param ctx - The context element. (optional) + * @param event - The mouse event. (optional) + * @returns The feedback emitter. + */ + addMoveRestrictionFeedback( + feedback: FeedbackEmitter, + change: TrackedElementResize | TrackedElementMove, + ctx?: GModelElement, + event?: MouseEvent + ): FeedbackEmitter; + + /** + * Get the default resize locations for changing bounds. + * @returns The default resize handle locations. + */ + defaultResizeLocations(): ResizeHandleLocation[]; + + /** + * Determine whether to use symmetric resize for changing bounds. + * @param arg - The event argument. + * @returns True if symmetric resize should be used, false otherwise. + */ + useSymmetricResize(arg: MouseEvent | KeyboardEvent | any): boolean; + + /** + * Create a tracker for changing bounds. + * @returns The change bounds tracker. + */ + createTracker(): ChangeBoundsTracker; +} + +/** + * The default {@link IChangeBoundsManager} implementation. It is responsible for managing + * the change of bounds for {@link GModelElement}s. + */ @injectable() -export class ChangeBoundsManager { +export class ChangeBoundsManager implements IChangeBoundsManager { constructor( @inject(MousePositionTracker) readonly positionTracker: MousePositionTracker, @optional() @inject(TYPES.IMovementRestrictor) readonly movementRestrictor?: IMovementRestrictor, @optional() @inject(TYPES.ISnapper) readonly snapper?: ISnapper, @optional() @inject(TYPES.IHelperLineManager) readonly helperLineManager?: IHelperLineManager, - @optional() @inject(GridManager) protected gridManager?: GridManager + @optional() @inject(TYPES.IGridManager) protected gridManager?: IGridManager ) {} unsnapModifier(): KeyboardModifier | undefined { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts index dcbee52..39ac6e2 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-feedback.ts @@ -20,7 +20,7 @@ import { FeedbackCommand } from '../../../base/feedback/feedback-command'; import { OptionalAction } from '../../../base/model/glsp-model-source'; import { forEachElement } from '../../../utils/gmodel-util'; import { ResizeHandleLocation, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model'; -import { ChangeBoundsManager } from './change-bounds-manager'; +import { IChangeBoundsManager } from './change-bounds-manager'; export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; @@ -71,7 +71,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { static readonly KIND = ShowChangeBoundsToolResizeFeedbackAction.KIND; @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; - @inject(ChangeBoundsManager) protected changeBoundsManager: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) protected changeBoundsManager: IChangeBoundsManager; execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts index c35ae26..ce6d94b 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-module.ts @@ -24,7 +24,7 @@ import { GResizeHandleView } from './view'; export const changeBoundsToolModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - bind(ChangeBoundsManager).toSelf().inSingletonScope(); + bindAsService(context, TYPES.IChangeBoundsManager, ChangeBoundsManager); bindAsService(context, TYPES.IDefaultTool, ChangeBoundsTool); configureCommand(context, ShowChangeBoundsToolResizeFeedbackCommand); configureCommand(context, HideChangeBoundsToolResizeFeedbackCommand); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index a7f23fe..4237e66 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -54,7 +54,7 @@ import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-comman import { GResizeHandle, isResizable } from '../../change-bounds/model'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { BaseEditTool } from '../base-tools'; -import { CSS_ACTIVE_HANDLE, ChangeBoundsManager } from './change-bounds-manager'; +import { CSS_ACTIVE_HANDLE, IChangeBoundsManager } from './change-bounds-manager'; import { HideChangeBoundsToolResizeFeedbackAction, MoveFinishedEventAction, @@ -83,7 +83,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; - @inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; get id(): string { return ChangeBoundsTool.ID; diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts index d525a21..e8f74af 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool-feedback.ts @@ -48,7 +48,7 @@ import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { forEachElement, getMatchingElements, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../../reconnect/model'; -import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../change-bounds/change-bounds-manager'; import { ChangeBoundsTracker, MoveableRoutingHandle } from '../change-bounds/change-bounds-tracker'; import { FeedbackEdgeEnd, feedbackEdgeEndId, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { FeedbackEdgeEndMovingMouseListener } from '../edge-creation/edge-creation-tool-feedback'; @@ -259,7 +259,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends DragAwareMouseListener protected tracker: ChangeBoundsTracker; constructor( - protected changeBoundsManager: ChangeBoundsManager, + protected changeBoundsManager: IChangeBoundsManager, protected edgeRouterRegistry?: EdgeRouterRegistry ) { super(); diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index 5e35560..b2a10ea 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -24,6 +24,7 @@ import { GRoutableElement, GRoutingHandle, ReconnectEdgeOperation, + TYPES, canEditRouting, findParentByFeature, isConnectable, @@ -37,7 +38,7 @@ import { ISelectionListener, SelectionService } from '../../../base/selection-se import { calcElementAndRoutingPoints, isRoutable, isRoutingHandle } from '../../../utils/gmodel-util'; import { GReconnectHandle, isReconnectHandle, isReconnectable, isSourceRoutingHandle, isTargetRoutingHandle } from '../../reconnect/model'; import { BaseEditTool } from '../base-tools'; -import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../change-bounds/change-bounds-manager'; import { DrawFeedbackEdgeAction, RemoveFeedbackEdgeAction, feedbackEdgeId } from '../edge-creation/dangling-edge-feedback'; import { DrawFeedbackEdgeSourceAction, @@ -56,7 +57,7 @@ export class EdgeEditTool extends BaseEditTool { @inject(SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; - @inject(ChangeBoundsManager) readonly changeBoundsManager: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index 5696133..f21e5cc 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -14,12 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, Point, findChildrenAtPosition } from '@eclipse-glsp/sprotty'; +import { GModelElement, Point, TYPES, findChildrenAtPosition } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { CSS_GHOST_ELEMENT, CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { ContainerElement, isContainable } from '../../hints/model'; -import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../change-bounds/change-bounds-manager'; export interface InsertOptions extends Record { /** Flag to indicate whether the location within a container needs to be valid. Default: false */ @@ -58,7 +58,7 @@ export interface IContainerManager { */ @injectable() export class ContainerManager implements IContainerManager { - @inject(ChangeBoundsManager) protected readonly changeBoundsManager: ChangeBoundsManager; + @inject(TYPES.IChangeBoundsManager) protected readonly changeBoundsManager: IChangeBoundsManager; insert(proxy: GModelElement, location: Point, elementTypeId: string, opts?: Partial): TrackedInsert { const options = { ...DEFAULT_INSERT_OPTIONS, ...opts }; diff --git a/packages/client/src/features/tools/node-creation/node-creation-tool.ts b/packages/client/src/features/tools/node-creation/node-creation-tool.ts index a3c24c1..43dd3aa 100644 --- a/packages/client/src/features/tools/node-creation/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation/node-creation-tool.ts @@ -43,7 +43,7 @@ import { AddTemplateElementsAction, getTemplateElementId } from '../../element-t import { MouseTrackingElementPositionListener, PositioningTool } from '../../element-template/mouse-tracking-element-position-listener'; import { RemoveTemplateElementsAction } from '../../element-template/remove-template-element'; import { BaseCreationTool } from '../base-tools'; -import { ChangeBoundsManager } from '../change-bounds/change-bounds-manager'; +import { IChangeBoundsManager } from '../change-bounds/change-bounds-manager'; import { TrackedMove } from '../change-bounds/change-bounds-tracker'; import { IContainerManager, TrackedInsert } from './container-manager'; import { InsertIndicator } from './insert-indicator'; @@ -54,7 +54,7 @@ export class NodeCreationTool extends BaseCreationTool, context: RenderingContext): VNode { const graph = super.render(model, context); @@ -45,7 +45,7 @@ export class GGraphView extends SGraphView { }; } - protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: GridManager): Writable { + protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: IGridManager): Writable { const position = Point.multiplyScalar(Point.subtract(gridManager.grid, viewport.scroll), viewport.zoom); const size = Dimension.fromPoint(Point.multiplyScalar(gridManager.grid, viewport.zoom)); return { ...position, ...size }; diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 0322409..febddbb 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -25,6 +25,7 @@ import { ProjectionParams, RenderingContext, SGraphImpl, + TYPES, ViewProjection, Writable, html, @@ -33,7 +34,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode, VNodeStyle, h } from 'snabbdom'; -import { GridManager, GridStyle } from '../features/grid/grid-manager'; +import { GridStyle, IGridManager } from '../features/grid/grid-manager'; import { GridProperty } from '../features/grid/grid-style'; /** @@ -42,7 +43,7 @@ import { GridProperty } from '../features/grid/grid-style'; @injectable() export class GLSPProjectionView extends ProjectedViewportView { @inject(EdgeRouterRegistry) edgeRouterRegistry: EdgeRouterRegistry; - @inject(GridManager) @optional() protected gridManager?: GridManager; + @inject(TYPES.IGridManager) @optional() protected gridManager?: IGridManager; override render(model: Readonly, context: RenderingContext, args?: IViewArgs): VNode { const rootNode: VNode = ( @@ -83,7 +84,7 @@ export class GLSPProjectionView extends ProjectedViewportView { }; } - protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: GridManager): Writable { + protected getBackgroundBounds(viewport: Readonly, context: RenderingContext, gridManager: IGridManager): Writable { const position = Point.multiplyScalar(Point.subtract(gridManager.grid, viewport.scroll), viewport.zoom); const size = Dimension.fromPoint(Point.multiplyScalar(gridManager.grid, viewport.zoom)); return { ...position, ...size }; diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index a9e7623..2054a8a 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -49,5 +49,8 @@ export const TYPES = { IDiagramStartup: Symbol('IDiagramStartup'), IToolManager: Symbol('IToolManager'), IContainerManager: Symbol('IContainerManager'), + IChangeBoundsManager: Symbol('IChangeBoundsManager'), + IGridManager: Symbol('IGridManager'), + IDebugManager: Symbol('IDebugManager'), Grid: Symbol('Grid') }; From 23e14f8edaf4f1f1f40c2ce781821b543885df2a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Sep 2024 12:56:11 +0200 Subject: [PATCH 400/566] GLSP-1399: Fix MousePositionTracker (#391) Ensure that the `GLSPMousePositionTracker` correctly calculates the diagram local mouse position --- .../client/src/base/mouse-position-tracker.ts | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/client/src/base/mouse-position-tracker.ts b/packages/client/src/base/mouse-position-tracker.ts index 1041a4c..8edc612 100644 --- a/packages/client/src/base/mouse-position-tracker.ts +++ b/packages/client/src/base/mouse-position-tracker.ts @@ -14,17 +14,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - DOMHelper, - GModelElement, - MousePositionTracker, - Point, - TYPES, - ViewerOptions, - getAbsoluteClientBounds -} from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { Action, GModelElement, MousePositionTracker, Point } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; import { Ranked } from './ranked'; @injectable() @@ -32,15 +23,12 @@ export class GLSPMousePositionTracker extends MousePositionTracker implements Ra /* we want to be executed before all default mouse listeners since we are just tracking the position and others may need it */ rank = Ranked.DEFAULT_RANK - 200; - @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; - @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; - override mouseMove(target: GModelElement, event: MouseEvent): (Action | Promise)[] { // we cannot simply use the offsetX and offsetY properties of the event since they also consider nested HTML elements // such as foreignObjects or the projection bars divs. Instead, we manually translate the client coordinates to the diagram - const clientToRoot = getAbsoluteClientBounds(target.root, this.domHelper, this.viewerOptions); + const globalOrigin = target.root.canvasBounds; const clientToPosition = { x: event.clientX, y: event.clientY }; - const rootToPosition = Point.subtract(clientToPosition, clientToRoot); + const rootToPosition = Point.subtract(clientToPosition, globalOrigin); const positionOnDiagram = target.root.parentToLocal(rootToPosition); this.lastPosition = positionOnDiagram; return []; From e8515e0b2cb38307db238cc2ca306df1b744db10 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 12 Nov 2024 10:46:42 +0100 Subject: [PATCH 401/566] GLSP-1416: Align ActionDispatcher API (#394) - Rename `action-override` module of @eclipse-glsp/sprotty to `api-override` which a better fit. - Add additional API methods of the `GLSPActionDispatcher` directly to the `IActionDispatcher` interface and update related code for consistent usage of the interface. Export new `IActionDispatcher` interface via api-override - Also add API override for `IVNodePostProcessor` Also: - Adjust directs imports of `matchesKeystroke` and `toArray` from sprotty. Import from @eclipse/glsp-sprotty instead Fixes https://github.com/eclipse-glsp/glsp/issues/1416 --- .../direct-task-editing/direct-task-editor.ts | 4 +- packages/client/src/base/action-dispatcher.ts | 13 +++- .../base-autocomplete-palette.ts | 5 +- .../client/src/base/editor-context-service.ts | 6 +- .../client/src/base/model/diagram-loader.ts | 6 +- .../edge-autocomplete-palette.ts | 20 ++--- .../edge-autocomplete-tool.ts | 3 +- .../diagram-navigation-tool.ts | 12 +-- .../left-right-top-bottom-navigator.ts | 5 +- .../local-element-navigator.ts | 5 +- .../element-navigation/position-navigator.ts | 18 ++++- .../focus-tracker/focus-tracker-tool.ts | 5 +- .../accessibility/global-keylistener-tool.ts | 12 ++- .../keyboard-grid/keyboard-grid.ts | 7 +- .../keyboard-grid/keyboard-node-grid.ts | 9 +-- .../keyboard-pointer-listener.ts | 7 +- .../keyboard-tool-palette.ts | 9 ++- .../resize-key-tool/resize-key-tool.ts | 6 +- .../accessibility/search/search-palette.ts | 4 +- .../accessibility/search/search-tool.ts | 15 +++- .../accessibility/toast/toast-tool.ts | 5 +- .../view-key-tools/deselect-key-tool.ts | 15 +++- .../view-key-tools/movement-key-tool.ts | 6 +- .../view-key-tools/zoom-key-tool.ts | 24 +++--- .../src/features/bounds/local-bounds.ts | 4 +- .../bounds/set-bounds-feedback-command.ts | 4 +- .../server-command-palette-provider.ts | 8 +- .../glsp-context-menu-mouse-listener.ts | 6 +- .../server-context-menu-provider.ts | 8 +- .../features/copy-paste/copy-paste-handler.ts | 8 +- .../element-template/add-template-element.ts | 4 +- .../features/hints/type-hint-provider.spec.ts | 4 +- .../src/features/hints/type-hint-provider.ts | 6 +- .../label-edit/edit-label-validator.ts | 8 +- .../layout/layout-elements-action.spec.ts | 12 +++ .../src/features/status/status-overlay.ts | 7 +- .../src/features/tool-palette/tool-palette.ts | 6 +- .../client/src/features/tools/base-tools.ts | 5 +- .../tools/edge-creation/edge-creation-tool.ts | 4 +- .../features/validation/marker-navigator.ts | 4 +- .../{action-override.ts => api-override.ts} | 77 ++++++++++++++++++- packages/glsp-sprotty/src/index.ts | 2 +- packages/glsp-sprotty/src/re-exports.ts | 56 +++++++------- 43 files changed, 280 insertions(+), 174 deletions(-) rename packages/glsp-sprotty/src/{action-override.ts => api-override.ts} (54%) diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index d7cc8ff..e64875f 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -19,8 +19,8 @@ import { DOMHelper, EditorContextService, GLSPAbstractUIExtension, - GLSPActionDispatcher, GModelRoot, + IActionDispatcher, ILogger, LabeledAction, ModelIndexImpl, @@ -95,7 +95,7 @@ export class TaskEditor extends GLSPAbstractUIExtension { }; @inject(TYPES.IActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + protected actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContextService: EditorContextService; diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index d45cd3f..bdd3635 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,7 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ActionDispatcher, EMPTY_ROOT, GModelRoot, RequestAction, ResponseAction, SetModelAction } from '@eclipse-glsp/sprotty'; +import { + Action, + ActionDispatcher, + EMPTY_ROOT, + GModelRoot, + IActionDispatcher, + RequestAction, + ResponseAction, + SetModelAction +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { IGModelRootListener } from './editor-context-service'; @@ -21,7 +30,7 @@ import { OptionalAction } from './model/glsp-model-source'; import { ModelInitializationConstraint } from './model/model-initialization-constraint'; @injectable() -export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRootListener { +export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRootListener, IActionDispatcher { protected readonly timeouts: Map = new Map(); protected initializedConstraint = false; diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index f921b14..b80f9d6 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -14,10 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelRoot, LabeledAction, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, GModelRoot, IActionDispatcher, LabeledAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject } from 'inversify'; import '../../../css/autocomplete-palette.css'; -import { GLSPActionDispatcher } from '../action-dispatcher'; import { GLSPAbstractUIExtension } from '../ui-extension/ui-extension'; import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete-widget'; @@ -38,7 +37,7 @@ export abstract class BaseAutocompletePalette extends GLSPAbstractUIExtension { protected autocompleteWidget: AutoCompleteWidget; @inject(TYPES.IActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + protected actionDispatcher: IActionDispatcher; containerClass(): string { return 'autocomplete-palette'; diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 8b7cd26..69854ef 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -26,6 +26,7 @@ import { Event, GModelElement, GModelRoot, + IActionDispatcher, IActionHandler, LazyInjector, MaybePromise, @@ -36,7 +37,6 @@ import { ValueChange } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; -import { GLSPActionDispatcher } from './action-dispatcher'; import { FocusChange, FocusTracker } from './focus/focus-tracker'; import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; import { SelectionChange, SelectionService } from './selection-service'; @@ -89,8 +89,8 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra @inject(TYPES.IDiagramOptions) protected diagramOptions: IDiagramOptions; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; @inject(FocusTracker) protected focusTracker: FocusTracker; diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 473fc67..2725cf8 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -20,6 +20,7 @@ import { Args, EMPTY_ROOT, GLSPClient, + IActionDispatcher, InitializeParameters, LazyInjector, MaybePromise, @@ -30,7 +31,6 @@ import { hasNumberProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../action-dispatcher'; import { Ranked } from '../ranked'; import { GLSPModelSource } from './glsp-model-source'; import { ModelInitializationConstraint } from './model-initialization-constraint'; @@ -148,8 +148,8 @@ export class DiagramLoader { @inject(TYPES.IDiagramOptions) protected options: IDiagramOptions; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; @inject(GLSPModelSource) protected modelSource: GLSPModelSource; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts index 066c57c..3f58688 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts @@ -13,28 +13,28 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { + Action, codiconCSSString, - isConnectable, + CreateEdgeOperation, GModelElement, GModelRoot, + IActionHandler, + isConnectable, LabeledAction, name, SetUIExtensionVisibilityAction, - IActionHandler, - Action, - CreateEdgeOperation, + toArray, TriggerEdgeCreationAction } from '@eclipse-glsp/sprotty'; -import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { EdgeAutocompleteContext } from './edge-autocomplete-context'; -import { SearchAutocompletePalette } from '../search/search-palette'; -import { SetEdgeTargetSelectionAction } from './action'; +import { injectable } from 'inversify'; import { CloseReason, toActionArray } from '../../../base/auto-complete/auto-complete-widget'; import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; +import { SearchAutocompletePalette } from '../search/search-palette'; +import { SetEdgeTargetSelectionAction } from './action'; +import { EdgeAutocompleteContext } from './edge-autocomplete-context'; export namespace EdgeAutocompletePaletteMetadata { export const ID = 'edge-autocomplete-palette'; diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts index 0aca80e..63a6729 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelElement, KeyListener, KeyTool, matchesKeystroke, SetUIExtensionVisibilityAction } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { KeyTool, KeyListener, SetUIExtensionVisibilityAction, GModelElement, Action } from '@eclipse-glsp/sprotty'; import { Tool } from '../../../base/tool-manager/tool'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { EdgeAutocompletePaletteMetadata } from './edge-autocomplete-palette'; @injectable() diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 312083a..8a0016b 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -18,6 +18,7 @@ import { Action, GModelElement, GModelRoot, + IActionDispatcher, KeyListener, KeyTool, SelectAction, @@ -26,13 +27,13 @@ import { findParentByFeature, isBoundsAware, isSelectable, - isSelected + isSelected, + matchesKeystroke, + toArray } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; +import { GEdge } from '../../../model'; import { SelectableBoundsAware } from '../../../utils/gmodel-util'; import { RepositionAction } from '../../viewport/reposition'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; @@ -41,7 +42,6 @@ import { SearchAutocompletePaletteTool } from '../search/search-tool'; import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigator } from './element-navigator'; -import { GEdge } from '../../../model'; @injectable() export class ElementNavigatorTool implements Tool { @@ -53,7 +53,7 @@ export class ElementNavigatorTool implements Tool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IElementNavigator) readonly elementNavigator: ElementNavigator; @inject(TYPES.ILocalElementNavigator) readonly localElementNavigator: ElementNavigator; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; get id(): string { return ElementNavigatorTool.ID; diff --git a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts index a022625..f92ccf7 100644 --- a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts @@ -14,12 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { EdgeRouterRegistry, findParentByFeature, GModelElement, GModelRoot, Point } from '@eclipse-glsp/sprotty'; +import { EdgeRouterRegistry, findParentByFeature, GModelElement, GModelRoot, Point, toArray } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; +import { GEdge } from '../../../model'; import { calcElementAndRoute, isRoutable, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/gmodel-util'; import { ElementNavigator } from './element-navigator'; -import { GEdge } from '../../../model'; @injectable() export class LeftToRightTopToBottomElementNavigator implements ElementNavigator { diff --git a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts index 2554844..bd6768b 100644 --- a/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/local-element-navigator.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { EdgeRouterRegistry, GConnectableElement, GModelElement, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; +import { EdgeRouterRegistry, GConnectableElement, GModelElement, GModelRoot, IActionDispatcher, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; import { GEdge } from '../../../model'; import { BoundsAwareModelElement, SelectableBoundsAware } from '../../../utils/gmodel-util'; @@ -25,7 +24,7 @@ import { ElementNavigator } from './element-navigator'; export class LocalElementNavigator implements ElementNavigator { navigableElementCSS = 'navigable-element'; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; - @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; previous( root: Readonly, diff --git a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts index 4866aed..f529ade 100644 --- a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts @@ -14,16 +14,26 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + Bounds, + GChildElement, + GModelElement, + GModelRoot, + GNode, + IActionDispatcher, + Point, + TYPES, + isBoundsAware, + isSelectable, + toArray +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { Bounds, GChildElement, Point, GModelElement, GModelRoot, GNode, TYPES, isBoundsAware, isSelectable } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectableBoundsAware } from '../../../utils/gmodel-util'; import { ElementNavigator } from './element-navigator'; @injectable() export class PositionNavigator implements ElementNavigator { - @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; previous( root: Readonly, diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts index c5b4ec6..7af8dd2 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { IActionDispatcher, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { Tool } from '../../../base/tool-manager/tool'; import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; @@ -27,7 +26,7 @@ export class FocusTrackerTool implements Tool { isEditTool = false; @inject(TYPES.IActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + protected actionDispatcher: IActionDispatcher; @inject(TYPES.ViewerOptions) protected readonly viewerOptions: ViewerOptions; diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index d79df24..a398693 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -13,16 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, IActionDispatcher, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { SetUIExtensionVisibilityAction, Action } from '@eclipse-glsp/sprotty'; import { Tool } from '../../base/tool-manager/tool'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; import { ToolPalette } from '../tool-palette/tool-palette'; import { FocusDomAction } from './actions'; -import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; -import { KeyboardPointerMetadata } from './keyboard-pointer/constants'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from './key-shortcut/accessible-key-shortcut'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { KeyboardPointerMetadata } from './keyboard-pointer/constants'; import { KeyboardToolPalette } from './keyboard-tool-palette/keyboard-tool-palette'; @injectable() @@ -32,8 +30,8 @@ export class GlobalKeyListenerTool implements Tool, AccessibleKeyShortcutProvide isEditTool = false; protected alreadyRegistered = false; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; get id(): string { return GlobalKeyListenerTool.ID; diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 183b031..eeb631b 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -17,23 +17,24 @@ import '../../../../css/keyboard.css'; import { Action, - ActionDispatcher, GModelRoot, + IActionDispatcher, IActionHandler, ICommand, + KeyCode, + matchesKeystroke, Point, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from './action'; import { KeyboardGridMetadata } from './constants'; @injectable() export class KeyboardGrid extends GLSPAbstractUIExtension implements IActionHandler { - @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: ActionDispatcher; + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; protected triggerActions: Action[] = []; protected originId: string; diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts index 24dd3c8..54d3b14 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts @@ -15,14 +15,13 @@ ********************************************************************************/ import '../../../../css/keyboard.css'; +import { Action, ICommand, matchesKeystroke, SetUIExtensionVisibilityAction } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { ICommand, SetUIExtensionVisibilityAction, Action } from '@eclipse-glsp/sprotty'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { KeyboardPointer } from '../keyboard-pointer/keyboard-pointer'; import { KeyboardPointerMetadata } from '../keyboard-pointer/constants'; -import { GridSearchPaletteMetadata } from './keyboard-grid-search-palette'; -import { KeyboardGrid } from './keyboard-grid'; +import { KeyboardPointer } from '../keyboard-pointer/keyboard-pointer'; import { KeyboardNodeGridMetadata } from './constants'; +import { KeyboardGrid } from './keyboard-grid'; +import { GridSearchPaletteMetadata } from './keyboard-grid-search-palette'; @injectable() export class KeyboardNodeGrid extends KeyboardGrid { diff --git a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts index e46cc11..6c83f1d 100644 --- a/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts +++ b/packages/client/src/features/accessibility/keyboard-pointer/keyboard-pointer-listener.ts @@ -14,14 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionDispatcher, SetUIExtensionVisibilityAction, CreateNodeOperation } from '@eclipse-glsp/sprotty'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; +import { CreateNodeOperation, IActionDispatcher, matchesKeystroke, SetUIExtensionVisibilityAction } from '@eclipse-glsp/sprotty'; +import { CursorCSS } from '../../../base/feedback/css-feedback'; +import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; import { KeyboardPointerMetadata } from './constants'; import { KeyboardPointer } from './keyboard-pointer'; import { KeyboardPointerPosition } from './keyboard-pointer-position'; -import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool'; -import { CursorCSS } from '../../../base/feedback/css-feedback'; /** * Keyboard listener for the pointer with the necessary logic to handle keyboard events diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index 57c7a69..3edcbc1 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -16,6 +16,8 @@ import { Action, ICommand, + KeyCode, + matchesKeystroke, PaletteItem, RequestContextActions, RequestMarkersAction, @@ -24,15 +26,14 @@ import { TriggerNodeCreationAction } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { KeyCode, matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; import { - EnableToolPaletteAction, - ToolPalette, changeCodiconClass, compare, createIcon, - createToolGroup + createToolGroup, + EnableToolPaletteAction, + ToolPalette } from '../../tool-palette/tool-palette'; import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts index 6e62c54..b8e40fe 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts @@ -14,10 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, GModelElement, IActionDispatcher, ISnapper, KeyListener, KeyTool, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, ISnapper, KeyListener, KeyTool, GModelElement, TYPES } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; @@ -35,7 +33,7 @@ export class ResizeKeyTool implements Tool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; @inject(SelectionService) readonly selectionService: SelectionService; protected resizeKeyListener: ResizeKeyListener = new ResizeKeyListener(this); diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index 330c0b7..3d8bcc9 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -25,11 +25,11 @@ import { SelectAllAction, codiconCSSString, isNameable, - name + name, + toArray } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { isEqual } from 'lodash'; -import { toArray } from 'sprotty/lib/utils/iterable'; import { BaseAutocompletePalette } from '../../../base/auto-complete/base-autocomplete-palette'; import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index 389c9d8..c970d9c 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -14,10 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { + Action, + GModelElement, + IActionDispatcher, + KeyListener, + KeyTool, + matchesKeystroke, + SetUIExtensionVisibilityAction, + TYPES +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { Action, KeyListener, KeyTool, GModelElement, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { Tool } from '../../../base/tool-manager/tool'; import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { SearchAutocompletePalette } from './search-palette'; @@ -26,7 +33,7 @@ export class SearchAutocompletePaletteTool implements Tool { static readonly ID = 'glsp.search-autocomplete-palette-tool'; protected readonly keyListener = new SearchAutocompletePaletteKeyListener(this); - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; @inject(KeyTool) protected keyTool: KeyTool; get id(): string { diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 4d8c212..25d2be2 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, IActionDispatcher, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { EditorContextService } from '../../../base/editor-context-service'; import { IDiagramStartup } from '../../../base/model/diagram-loader'; import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; @@ -30,7 +29,7 @@ export class Toast extends GLSPAbstractUIExtension implements IActionHandler, ID static readonly ID = 'toast'; protected messages: { [key: symbol]: ToastOptions } = {}; - @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; diff --git a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts index fbd26bf..41f541f 100644 --- a/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/deselect-key-tool.ts @@ -14,10 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelElement, GRoutableElement, KeyListener, KeyTool, SelectAction, TYPES, isSelectable } from '@eclipse-glsp/sprotty'; +import { + Action, + GModelElement, + GRoutableElement, + KeyListener, + KeyTool, + SelectAction, + TYPES, + isSelectable, + matchesKeystroke, + toArray +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { toArray } from 'sprotty/lib/utils/iterable'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Tool } from '../../../base/tool-manager/tool'; import { IToolManager } from '../../../base/tool-manager/tool-manager'; import { SwitchRoutingModeAction } from '../../tools/edge-edit/edge-edit-tool-feedback'; diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts index dee57de..bd2e61f 100644 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts @@ -14,10 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelElement, ISnapper, KeyListener, KeyTool, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, IActionDispatcher, ISnapper, KeyListener, KeyTool, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; import { Grid } from '../../grid/grid'; @@ -39,7 +37,7 @@ export class MovementKeyTool implements Tool { @inject(KeyTool) protected readonly keytool: KeyTool; @inject(SelectionService) selectionService: SelectionService; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; @inject(TYPES.Grid) @optional() protected grid: Grid; @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts index ba8006c..011294e 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts @@ -14,32 +14,32 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { Action, CenterAction, + GModelElement, + GModelRoot, + IActionDispatcher, KeyListener, KeyTool, Point, - GModelElement, - GModelRoot, SetViewportAction, TYPES, Viewport, - isViewport + isViewport, + matchesKeystroke } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; +import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../../../base/editor-context-service'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; -import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; -import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; -import { EditorContextService } from '../../../base/editor-context-service'; -import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; +import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; +import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; +import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; import * as messages from '../toast/messages.json'; +import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; /** * Zoom viewport and elements when its focused and arrow keys are hit. @@ -53,7 +53,7 @@ export class ZoomKeyTool implements Tool { protected readonly zoomKeyListener = new ZoomKeyListener(this); @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; @inject(SelectionService) selectionService: SelectionService; @inject(EditorContextService) protected editorContextService: EditorContextService; diff --git a/packages/client/src/features/bounds/local-bounds.ts b/packages/client/src/features/bounds/local-bounds.ts index df21698..271ea40 100644 --- a/packages/client/src/features/bounds/local-bounds.ts +++ b/packages/client/src/features/bounds/local-bounds.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { Action, - ActionDispatcher, Command, CommandExecutionContext, CommandResult, @@ -24,6 +23,7 @@ import { ComputedBoundsApplicator, GModelRoot, GModelRootSchema, + IActionDispatcher, RequestBoundsAction, TYPES, ViewerOptions, @@ -53,7 +53,7 @@ export namespace LocalRequestBoundsAction { export function fromCommand( { root }: CommandExecutionContext, - actionDispatcher: ActionDispatcher, + actionDispatcher: IActionDispatcher, cause?: Action, elementIDs?: string[] ): CommandResult { diff --git a/packages/client/src/features/bounds/set-bounds-feedback-command.ts b/packages/client/src/features/bounds/set-bounds-feedback-command.ts index 99d992b..7dec934 100644 --- a/packages/client/src/features/bounds/set-bounds-feedback-command.ts +++ b/packages/client/src/features/bounds/set-bounds-feedback-command.ts @@ -18,13 +18,13 @@ import { CommandExecutionContext, CommandReturn, ElementAndBounds, + IActionDispatcher, SetBoundsAction, SetBoundsCommand, TYPES, isLayoutContainer } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { Ranked } from '../../base/ranked'; import { LocalRequestBoundsAction } from './local-bounds'; @@ -51,7 +51,7 @@ export class SetBoundsFeedbackCommand extends SetBoundsCommand implements Feedba readonly rank: number = Ranked.DEFAULT_RANK; - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; override execute(context: CommandExecutionContext): CommandReturn { super.execute(context); diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index d288d45..24a5178 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, + GModelElement, + IActionDispatcher, ICommandPaletteActionProvider, LabeledAction, Point, RequestContextActions, - GModelElement, SetContextActions, TYPES } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; export namespace ServerCommandPalette { @@ -35,7 +35,7 @@ export namespace ServerCommandPalette { @injectable() export class ServerCommandPaletteActionProvider implements ICommandPaletteActionProvider { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; async getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { diff --git a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts index 0f392f6..6edffaa 100644 --- a/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/glsp-context-menu-mouse-listener.ts @@ -17,6 +17,7 @@ import { Action, ContextMenuProviderRegistry, GModelElement, + IActionDispatcher, IContextMenuService, IContextMenuServiceProvider, MouseListener, @@ -26,7 +27,6 @@ import { isSelectable } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; @injectable() @@ -39,8 +39,8 @@ export class GLSPContextMenuMouseListener extends MouseListener { @optional() protected menuProvider?: ContextMenuProviderRegistry; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; protected menuService?: IContextMenuService; diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 6bcd3eb..62e221d 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -13,19 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, + GModelElement, + IActionDispatcher, IContextMenuItemProvider, LabeledAction, Point, RequestContextActions, - GModelElement, SetContextActions, TYPES, isSelected } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; export namespace ServerContextMenu { @@ -34,7 +34,7 @@ export namespace ServerContextMenu { @injectable() export class ServerContextMenuItemProvider implements IContextMenuItemProvider { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; async getItems(root: Readonly, _lastMousePosition?: Point): Promise { diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 50aceb8..0a4a365 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { v4 as uuid } from 'uuid'; import { ClipboardData, CutOperation, + IActionDispatcher, PasteOperation, RequestClipboardDataAction, SetClipboardDataAction, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { inject, injectable } from 'inversify'; +import { v4 as uuid } from 'uuid'; import { EditorContextService } from '../../base/editor-context-service'; export interface ICopyPasteHandler { @@ -100,7 +100,7 @@ const CLIPBOARD_DATA_FORMAT = 'text/plain'; @injectable() export class ServerCopyPasteHandler implements ICopyPasteHandler { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; @inject(TYPES.IAsyncClipboardService) protected clipboardService: IAsyncClipboardService; @inject(EditorContextService) protected editorContext: EditorContextService; diff --git a/packages/client/src/features/element-template/add-template-element.ts b/packages/client/src/features/element-template/add-template-element.ts index 6cbf20c..71506ed 100644 --- a/packages/client/src/features/element-template/add-template-element.ts +++ b/packages/client/src/features/element-template/add-template-element.ts @@ -21,12 +21,12 @@ import { ElementTemplate, GChildElement, GModelElementSchema, + IActionDispatcher, TYPES, distinctAdd, remove } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { isNotUndefined } from '../../utils/gmodel-util'; import { LocalRequestBoundsAction } from '../bounds/local-bounds'; @@ -57,7 +57,7 @@ export function getTemplateElementId(template: ElementTemplate): string { export class AddTemplateElementsFeedbackCommand extends FeedbackCommand { static readonly KIND = AddTemplateElementsAction.KIND; - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; constructor(@inject(TYPES.Action) readonly action: AddTemplateElementsAction) { super(); diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index 330df15..228c33b 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -37,15 +37,16 @@ import { Container } from 'inversify'; import * as sinon from 'sinon'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher-default'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { GEdge } from '../../model'; import { isResizable } from '../change-bounds/model'; import { isReconnectable } from '../reconnect/model'; import { Containable, isContainable, isReparentable } from './model'; import { ApplyTypeHintsAction, ApplyTypeHintsCommand, ITypeHintProvider, TypeHintProvider } from './type-hint-provider'; -import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; describe('TypeHintProvider', () => { const container = new Container(); container.bind(GLSPActionDispatcher).toConstantValue(sinon.createStubInstance(GLSPActionDispatcher)); + container.bind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); const stub = sinon.createStubInstance(FeedbackActionDispatcher); stub.createEmitter.returns(new FeedbackEmitter(stub)); container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(stub); @@ -163,6 +164,7 @@ describe('ApplyTypeHintCommand', () => { getShapeTypeHint: () => undefined }); container.bind(GLSPActionDispatcher).toConstantValue(sandbox.createStubInstance(GLSPActionDispatcher)); + container.bind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); container.bind(TYPES.IFeedbackActionDispatcher).toConstantValue(sandbox.createStubInstance(FeedbackActionDispatcher)); container.bind(TYPES.ITypeHintProvider).toConstantValue(typeHintProviderMock); bindOrRebind(container, TYPES.Action).toConstantValue(ApplyTypeHintsAction.create()); diff --git a/packages/client/src/features/hints/type-hint-provider.ts b/packages/client/src/features/hints/type-hint-provider.ts index 6b7bfd8..d2145d2 100644 --- a/packages/client/src/features/hints/type-hint-provider.ts +++ b/packages/client/src/features/hints/type-hint-provider.ts @@ -23,6 +23,7 @@ import { GModelRoot, GRoutableElement, GShapeElement, + IActionDispatcher, IActionHandler, RequestTypeHintsAction, SetTypeHintsAction, @@ -37,7 +38,6 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; @@ -202,8 +202,8 @@ export class TypeHintProvider implements IActionHandler, ITypeHintProvider, IDia @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; protected typeHintsFeedback: FeedbackEmitter; protected shapeHints: Map = new Map(); diff --git a/packages/client/src/features/label-edit/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts index a208857..7320750 100644 --- a/packages/client/src/features/label-edit/edit-label-validator.ts +++ b/packages/client/src/features/label-edit/edit-label-validator.ts @@ -13,21 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { Action, EditLabelValidationResult, EditableLabel, + GModelElement, + IActionDispatcher, IEditLabelValidationDecorator, IEditLabelValidator, RequestEditValidationAction, - GModelElement, SetEditValidationResultAction, Severity, TYPES, ValidationStatus } from '@eclipse-glsp/sprotty'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; +import { inject, injectable } from 'inversify'; export namespace LabelEditValidation { export const CONTEXT_ID = 'label-edit'; @@ -50,7 +50,7 @@ export namespace LabelEditValidation { @injectable() export class ServerEditLabelValidator implements IEditLabelValidator { - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; async validate(value: string, label: EditableLabel & GModelElement): Promise { const action = LabelEditValidation.createValidationRequestAction(value, label.id); diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index b8cf71a..3780654 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -57,6 +57,18 @@ import { class MockActionDispatcher implements IActionDispatcher { constructor(public dispatchedActions: Action[] = []) {} + requestUntil( + action: RequestAction, + timeoutMs?: number | undefined, + rejectOnTimeout?: boolean | undefined + ): Promise { + throw new Error('Method not implemented.'); + } + dispatchOnceModelInitialized(...actions: Action[]): void {} + onceModelInitialized(): Promise { + return Promise.resolve(); + } + dispatchAfterNextUpdate(...actions: Action[]): void {} dispatch(action: Action): Promise { this.dispatchedActions.push(action); return Promise.resolve(); diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index f1972ef..e49d91d 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionHandler, StatusAction, codiconCSSClasses } from '@eclipse-glsp/sprotty'; +import { IActionDispatcher, IActionHandler, StatusAction, TYPES, codiconCSSClasses } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; @@ -27,8 +26,8 @@ import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; export class StatusOverlay extends GLSPAbstractUIExtension implements IActionHandler, IDiagramStartup { static readonly ID = 'glsp.server.status.overlay'; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 437c539..d9200a7 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -16,6 +16,7 @@ import { Action, GModelRoot, + IActionDispatcher, IActionHandler, ICommand, MarkersReason, @@ -32,7 +33,6 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; @@ -69,8 +69,8 @@ export namespace EnableToolPaletteAction { export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandler, IEditModeListener, IDiagramStartup { static readonly ID = 'tool-palette'; - @inject(GLSPActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; @inject(EditorContextService) protected editorContext: EditorContextService; diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index c062bd5..4b3fcb1 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, DisposableCollection, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, Disposable, DisposableCollection, IActionDispatcher, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; @@ -59,7 +58,7 @@ export interface FeedbackAwareTool extends Tool { @injectable() export abstract class BaseEditTool implements FeedbackAwareTool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; - @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; @inject(GLSPKeyTool) protected keyTool: GLSPKeyTool; @inject(EditorContextService) protected readonly editorContext: EditorContextService; diff --git a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts index 3ac3122..79e765d 100644 --- a/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation/edge-creation-tool.ts @@ -19,6 +19,7 @@ import { Args, CreateEdgeOperation, GModelElement, + IActionDispatcher, RequestCheckEdgeAction, TYPES, TriggerEdgeCreationAction, @@ -27,7 +28,6 @@ import { isCtrlOrCmd } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; -import { GLSPActionDispatcher } from '../../../base/action-dispatcher'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; @@ -98,7 +98,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { constructor( protected triggerAction: TriggerEdgeCreationAction, - protected actionDispatcher: GLSPActionDispatcher, + protected actionDispatcher: IActionDispatcher, protected typeHintProvider: ITypeHintProvider, protected tool: EdgeCreationTool, protected dragSensitivity?: number diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 91f2ce0..0a808ee 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -19,6 +19,7 @@ import { GIssueSeverity, GModelElement, GModelRoot, + IActionDispatcher, IActionHandler, IContextMenuItemProvider, KeyListener, @@ -34,7 +35,6 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { SelectionService } from '../../base/selection-service'; import { BoundsAwareModelElement, SelectableElement, getElements, isSelectableAndBoundsAware } from '../../utils/gmodel-util'; import { MarkerPredicates, collectIssueMarkers } from '../../utils/marker'; @@ -166,7 +166,7 @@ export class NavigateToMarkerActionHandler implements IActionHandler { protected selectionService: SelectionService; @inject(TYPES.IActionDispatcher) - protected actionDispatcher: GLSPActionDispatcher; + protected actionDispatcher: IActionDispatcher; handle(action: NavigateToMarkerAction): void { const selected = this.getSelectedElements(action); diff --git a/packages/glsp-sprotty/src/action-override.ts b/packages/glsp-sprotty/src/api-override.ts similarity index 54% rename from packages/glsp-sprotty/src/action-override.ts rename to packages/glsp-sprotty/src/api-override.ts index 8f4bf07..81dc464 100644 --- a/packages/glsp-sprotty/src/action-override.ts +++ b/packages/glsp-sprotty/src/api-override.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { Action, LabeledAction, Point, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { @@ -21,10 +21,12 @@ import { SModelElementImpl as GModelElement, SModelRootImpl as GModelRoot, ICommand, + IActionDispatcher as SIActionDispatcher, IActionHandler as SIActionHandler, IButtonHandler as SIButtonHandler, ICommandPaletteActionProvider as SICommandPaletteActionProvider, IContextMenuItemProvider as SIContextMenuItemProvider, + IVNodePostprocessor as SIVNodePostprocessor, KeyListener as SKeyListener, MouseListener as SMouseListener } from 'sprotty'; @@ -32,9 +34,11 @@ import { /* * The GLSP-protocol comes with its own type definition for `Action`. However, sprotty * also has a separate `Action` definition that is heavily integrated into core API concepts. - * While these two definitions are fully compatible it messes up IDE support for auto import - * when implementing/overriding these API Concepts. To bypass this issue we create sub types - * of the sprotty API concepts that use the Action definition from GLSP and export them instead. + * While these two definitions are fully compatible it messes up IDE support for auto import. + * The same problem also applies to references of sprotty's internal SModel in the API. + * To bypass this issue we create sub types of the sprotty API concepts that use the Action/GModel definition from GLSP + * and export them instead. + * */ /** @@ -122,3 +126,68 @@ export class MouseListener extends SMouseListener { return vnode; } } +/** + * Manipulates a created VNode after it has been created. + * Used to register listeners and add animations. + */ +export interface IVNodePostprocessor extends SIVNodePostprocessor { + decorate(vnode: VNode, element: GModelElement): VNode; +} + +export interface IActionDispatcher extends SIActionDispatcher { + /** + * Dispatch an action by querying all handlers that are registered for its kind. + * The returned promise is resolved when all handler results (commands or actions) + * have been processed. + */ + dispatch(action: Action): Promise; + /** + * Calls `dispatch` on every action in the given array. The returned promise + * is resolved when the promises of all `dispatch` calls have been resolved. + */ + dispatchAll(actions: Action[]): Promise; + /** + * Dispatch a request. The returned promise is resolved when a response with matching + * identifier is dispatched. That response is _not_ passed to the registered action + * handlers. Instead, it is the responsibility of the caller of this method to handle + * the response properly. For example, it can be sent to the registered handlers by + * passing it to the `dispatch` method. + * + * If no explicit `requestId` has been set on the action, a generated id will be set before dispatching the action. + */ + request(action: RequestAction): Promise; + // GLSP-specific API additions + /** + * Dispatch a request and waits for a response until the timeout given in `timeoutMs` (default 2000) has + * been reached. The returned promise is resolved when a response with matching identifier + * is dispatched or when the timeout has been reached. That response is _not_ passed to the + * registered action handlers. Instead, it is the responsibility of the caller of this method + * to handle the response properly. For example, it can be sent to the registered handlers by + * passing it again to the `dispatch` method. + * If `rejectOnTimeout` is set to false (default) the returned promise will be resolved with + * no value, otherwise it will be rejected. + */ + requestUntil( + action: RequestAction, + timeoutMs?: number, + rejectOnTimeout?: boolean + ): Promise; + /** + * Processes all given actions, by dispatching them to the corresponding handlers, after the model initialization is completed. + */ + dispatchOnceModelInitialized(...actions: Action[]): void; + + /** + * Returns a promise that resolves once the model initialization is completed. + */ + onceModelInitialized(): Promise; + + /** + * Processes all given actions, by dispatching them to the corresponding handlers, after the next model update. + * The given actions are queued until the next model update cycle has been completed i.e. + * the `EditorContextService.onModelRootChanged` event is triggered. + */ + dispatchAfterNextUpdate(...actions: Action[]): void; +} + +export type IActionDispatcherProvider = () => Promise; diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 9fc7ab5..95916b0 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from './action-override'; +export * from './api-override'; export * from './feature-modules'; export * from './re-exports'; export * from './svg-views-override'; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index ca76bd5..39cf5f3 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -27,12 +27,13 @@ export * from '@eclipse-glsp/protocol/lib/di'; // Exclude definition for labeled actions // export * from 'sprotty/lib/base/actions/action'; -export * from 'sprotty/lib/base/actions/action-dispatcher'; +// Exclude IActionDispatcher and IActionDispatcherProvider. Exported via api-override module instead +export { ActionDispatcher, PostponedAction } from 'sprotty/lib/base/actions/action-dispatcher'; export { ActionHandlerRegistration, ActionHandlerRegistry, - configureActionHandler, IActionHandlerInitializer, + configureActionHandler, onAction } from 'sprotty/lib/base/actions/action-handler'; export * from 'sprotty/lib/base/actions/diagram-locker'; @@ -50,19 +51,18 @@ export * from 'sprotty/lib/base/features/initialize-canvas'; export * from 'sprotty/lib/base/features/set-model'; // Exclude SModelElementImpl as it as exported with augmentation module export { - createRandomId, FeatureSet, SChildElementImpl as GChildElement, SModelElementImpl as GModelElement, SModelRootImpl as GModelRoot, SParentElementImpl as GParentElement, IModelIndex, - isParent, - ModelIndexImpl + ModelIndexImpl, + createRandomId, + isParent } from 'sprotty/lib/base/model/smodel'; export { - createFeatureSet, CustomFeatures, EMPTY_ROOT, SModelElementConstructor as GModelElementConstructor, @@ -70,7 +70,8 @@ export { SModelFactory as GModelFactory, IModelFactory, // exported without alias we extend it in glsp-client to `GModelRegistry` - SModelRegistry + SModelRegistry, + createFeatureSet } from 'sprotty/lib/base/model/smodel-factory'; export * from 'sprotty/lib/base/model/smodel-utils'; @@ -85,7 +86,8 @@ export * from 'sprotty/lib/base/views/view'; export * from 'sprotty/lib/base/views/viewer'; export * from 'sprotty/lib/base/views/viewer-cache'; export * from 'sprotty/lib/base/views/viewer-options'; -export * from 'sprotty/lib/base/views/vnode-postprocessor'; +// Exclude IVnodePostprocessor. Exported via api-override module instead +export { FocusFixPostprocessor } from 'sprotty/lib/base/views/vnode-postprocessor'; export * from 'sprotty/lib/base/views/vnode-utils'; // Exclude sprotty types and export augmented GLSP types instead @@ -99,30 +101,30 @@ export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; export * from 'sprotty/lib/features/bounds/layout'; export { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; export { - alignFeature, InternalBoundsAware as BoundsAware, + SShapeElementImpl as GShapeElement, + InternalLayoutContainer as LayoutContainer, + InternalLayoutableChild as LayoutableChild, + ModelLayoutOptions, + alignFeature, boundsFeature, findChildrenAtPosition, getAbsoluteBounds, getAbsoluteClientBounds, - SShapeElementImpl as GShapeElement, isAlignable, isBoundsAware, - isLayoutableChild, isLayoutContainer, + isLayoutableChild, isSizeable, - InternalLayoutableChild as LayoutableChild, - layoutableChildFeature, - InternalLayoutContainer as LayoutContainer, layoutContainerFeature, - ModelLayoutOptions + layoutableChildFeature } from 'sprotty/lib/features/bounds/model'; // exclude stack layout as its not supported in GLSP // export * from 'sprotty/lib/features/bounds/stack-layout'; export * from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; -export { ButtonHandlerRegistry, configureButtonHandler, IButtonHandlerRegistration } from 'sprotty/lib/features/button/button-handler'; +export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; export { SButtonImpl as GButton } from 'sprotty/lib/features/button/model'; export { @@ -138,7 +140,7 @@ export * from 'sprotty/lib/features/context-menu/mouse-listener'; export * from 'sprotty/lib/features/edge-layout/di.config'; export * from 'sprotty/lib/features/edge-layout/edge-layout'; -export { checkEdgePlacement, DEFAULT_EDGE_PLACEMENT, edgeLayoutFeature, isEdgeLayoutable } from 'sprotty/lib/features/edge-layout/model'; +export { DEFAULT_EDGE_PLACEMENT, checkEdgePlacement, edgeLayoutFeature, isEdgeLayoutable } from 'sprotty/lib/features/edge-layout/model'; // Exclude client-side creation features (not supported in GLSP) // export * from 'sprotty/lib/features/edit/create'; // export * from 'sprotty/lib/features/edit/create-on-drag'; @@ -169,10 +171,10 @@ export * from 'sprotty/lib/features/hover/popup-position-updater'; export * from 'sprotty/lib/features/decoration/decoration-placer'; export { Decoration, - decorationFeature, SDecoration as GDecoration, - isDecoration, - SIssueMarkerImpl + SIssueMarkerImpl, + decorationFeature, + isDecoration } from 'sprotty/lib/features/decoration/model'; export * from 'sprotty/lib/features/decoration/views'; @@ -190,7 +192,7 @@ export * from 'sprotty/lib/features/nameable/model'; export * from 'sprotty/lib/features/open/model'; export * from 'sprotty/lib/features/open/open'; -export { getModelBounds, getProjectedBounds, getProjections, isProjectable, ViewProjection } from 'sprotty/lib/features/projection/model'; +export { ViewProjection, getModelBounds, getProjectedBounds, getProjections, isProjectable } from 'sprotty/lib/features/projection/model'; export * from 'sprotty/lib/features/projection/views'; export * from 'sprotty/lib/features/routing/abstract-edge-router'; @@ -203,17 +205,17 @@ export * from 'sprotty/lib/features/routing/manhattan-edge-router'; // Alias SModel types export { Connectable, + SConnectableElementImpl as GConnectableElement, + SDanglingAnchorImpl as GDanglingAnchor, + SRoutableElementImpl as GRoutableElement, + SRoutingHandleImpl as GRoutingHandle, + RoutingHandleKind, connectableFeature, edgeInProgressID, edgeInProgressTargetHandleID, - SConnectableElementImpl as GConnectableElement, - SDanglingAnchorImpl as GDanglingAnchor, getAbsoluteRouteBounds, getRouteBounds, - SRoutableElementImpl as GRoutableElement, - SRoutingHandleImpl as GRoutingHandle, - isConnectable, - RoutingHandleKind + isConnectable } from 'sprotty/lib/features/routing/model'; export * from 'sprotty/lib/features/routing/polyline-anchors'; export * from 'sprotty/lib/features/routing/polyline-edge-router'; From 2571bc9ddbbcc09c315f4e237306b3c3479a3707 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 14 Nov 2024 10:33:13 +0100 Subject: [PATCH 402/566] GLSP-1421: Properly scope standalone copy-paste listeners (#395) Ensure that the cut/copy/paste listers for the standalonCopyPasteModule are only invoked if the diagram is currently active (i.e. the active dom element is the diagram svg) Fixes https://github.com/eclipse-glsp/glsp/issues/1421 --- .../features/copy-paste/copy-paste-modules.ts | 1 + .../copy-paste/copy-paste-standalone.ts | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/client/src/features/copy-paste/copy-paste-modules.ts b/packages/client/src/features/copy-paste/copy-paste-modules.ts index 607a156..f56f52e 100644 --- a/packages/client/src/features/copy-paste/copy-paste-modules.ts +++ b/packages/client/src/features/copy-paste/copy-paste-modules.ts @@ -33,6 +33,7 @@ export const copyPasteModule = new FeatureModule( export const standaloneCopyPasteModule = new FeatureModule( (bind, _unbind, isBound) => { bindAsService(bind, TYPES.IDiagramStartup, CopyPasteStartup); + bind(TYPES.IGModelRootListener).toService(CopyPasteStartup); }, { featureId: Symbol('standaloneCopyPaste'), diff --git a/packages/client/src/features/copy-paste/copy-paste-standalone.ts b/packages/client/src/features/copy-paste/copy-paste-standalone.ts index 53e9913..de32ecf 100644 --- a/packages/client/src/features/copy-paste/copy-paste-standalone.ts +++ b/packages/client/src/features/copy-paste/copy-paste-standalone.ts @@ -14,33 +14,50 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Disposable, DisposableCollection, TYPES } from '@eclipse-glsp/sprotty'; +import { DOMHelper, Disposable, DisposableCollection, EMPTY_ROOT, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, preDestroy } from 'inversify'; +import { IGModelRootListener } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { ICopyPasteHandler } from './copy-paste-handler'; /** * Startup service to hook up the copy&paste event handler */ @injectable() -export class CopyPasteStartup implements IDiagramStartup, Disposable { +export class CopyPasteStartup implements IDiagramStartup, Disposable, IGModelRootListener { @inject(TYPES.ICopyPasteHandler) @optional() protected copyPasteHandler?: ICopyPasteHandler; + @inject(TYPES.DOMHelper) + protected domHelper: DOMHelper; + protected toDispose = new DisposableCollection(); + protected graphElementId?: string; + + modelRootChanged(root: Readonly): void { + if (root.id !== EMPTY_ROOT.id) { + this.graphElementId = this.domHelper.createUniqueDOMElementId(root); + } + } postModelInitialization(): void { if (!this.copyPasteHandler) { return; } const copyListener = (e: ClipboardEvent): void => { - this.copyPasteHandler?.handleCopy(e); + if (this.graphElementId && document.activeElement?.id === this.graphElementId) { + this.copyPasteHandler?.handleCopy(e); + } }; const cutListener = (e: ClipboardEvent): void => { - this.copyPasteHandler?.handleCut(e); + if (this.graphElementId && document.activeElement?.id === this.graphElementId) { + this.copyPasteHandler?.handleCut(e); + } }; const pasteListener = (e: ClipboardEvent): void => { - this.copyPasteHandler?.handlePaste(e); + if (this.graphElementId && document.activeElement?.id === this.graphElementId) { + this.copyPasteHandler?.handlePaste(e); + } }; window.addEventListener('copy', copyListener); window.addEventListener('cut', cutListener); From e3532802cc3b4368231bf92d63d996bb51952c61 Mon Sep 17 00:00:00 2001 From: Lukas Lieb <42733123+ivy-lli@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:05:02 +0100 Subject: [PATCH 403/566] GLSP-1423 Add MovementOptions to control invalid multi element movements (#397) --- .../change-bounds-tool-move-feedback.ts | 16 ++++++++++++---- .../tools/change-bounds/change-bounds-tool.ts | 12 +++++++++++- packages/glsp-sprotty/src/types.ts | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index c946741..6843359 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -185,10 +185,18 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { if (!this.tracker.isTracking()) { return []; } - // only reset the move of invalid elements, the others will be handled by the change bounds tool itself - this.getElementsToMove(target) - .filter(element => this.tool.changeBoundsManager.isValid(element)) - .forEach(element => this.elementId2startPos.delete(element.id)); + const elementsToMove = this.getElementsToMove(target); + if (!this.tool.movementOptions.allElementsNeedToBeValid) { + // only reset the move of invalid elements, the others will be handled by the change bounds tool itself + elementsToMove + .filter(element => this.tool.changeBoundsManager.isValid(element)) + .forEach(element => this.elementId2startPos.delete(element.id)); + } else { + if (elementsToMove.every(element => this.tool.changeBoundsManager.isValid(element))) { + // do not reset any element as all are valid + this.elementId2startPos.clear(); + } + } this.dispose(); return []; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 4237e66..cafebcb 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -63,6 +63,11 @@ import { import { FeedbackMoveMouseListener } from './change-bounds-tool-move-feedback'; import { ChangeBoundsTracker, TrackedElementResize, TrackedResize } from './change-bounds-tracker'; +export interface IMovementOptions { + /** If set to true, a move with multiple elements is only performed if each individual move is valid. */ + readonly allElementsNeedToBeValid: boolean; +} + /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. * In contrast to Sprotty's implementation this tool only sends a `ChangeBoundsOperationAction` when an operation has finished and does not @@ -84,6 +89,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; + @inject(TYPES.IMovementOptions) @optional() readonly movementOptions: IMovementOptions = { allElementsNeedToBeValid: true }; get id(): string { return ChangeBoundsTool.ID; @@ -260,7 +266,11 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel protected getElementsToMove(target: GModelElement): SelectableBoundsAware[] { const selectedElements = getMatchingElements(target.index, isNonRoutableSelectedMovableBoundsAware); const selectionSet: Set = new Set(selectedElements); - return selectedElements.filter(element => this.isValidMove(element, selectionSet)); + const elementsToMove = selectedElements.filter(element => this.isValidMove(element, selectionSet)); + if (this.tool.movementOptions.allElementsNeedToBeValid && elementsToMove.length !== selectionSet.size) { + return []; + } + return elementsToMove; } protected handleMoveElementsOnServer(elementsToMove: SelectableBoundsAware[]): Operation[] { diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 2054a8a..27ac94c 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -30,6 +30,7 @@ export const TYPES = { IToolFactory: Symbol('Factory'), ITypeHintProvider: Symbol('ITypeHintProvider'), IMovementRestrictor: Symbol('IMovementRestrictor'), + IMovementOptions: Symbol('IMovementOptions'), ISelectionListener: Symbol('ISelectionListener'), /** @deprecated Use {@link TYPES.IGModelRootListener} instead */ // eslint-disable-next-line deprecation/deprecation From 52bddde6696819bb9cd6f0b8dcf88262367e1b8e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 22 Nov 2024 09:47:50 +0100 Subject: [PATCH 404/566] GLSP-1427: Improve diagram loading and commmand handling (#398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Diagram Loader - Avoid unnecessary dispatch of empty `SetModelAction`. Await the initialize method of the action dispatcher instead (which already dispatches an empty model internally - Fix behavior of `postRequestModel` hook to actually work as described in the model. Instead of requesting and awaiting the dispatch of the response `SetModelAction` we now simply dispatch the `RequestModelAction` and continue with the`postRequestModelHook` - Use promise instead of listener for the model initalization constraint. This way we can await the `postModelInitialization` hook . I.e. the DiagramLoader.load methods actually completes once its completely finished (all hooks have been invoked and resolved) ## UiExtensions - Fix action of startup UIExtensions. Instead of directly calling their show-Method, a corresponding `SetUIExtensionVisibilityAction` is dispatched. This ensures that the extension only get activated after the initial diagram (empty-model) is available - Make `preInitialize´ hook of StatusOverlay async. The status overlay is used to print messages during the `loading` phase. By making it async, the loader waits until the overlay is actually visible => now messages get lost. - Move tool palette initialization into from `preRequest`to`postRequest` hook. This ensures that the server request for retrieving the items is dispatched after the model request => does not block diagram loading - Fix unnecessary reload of the palette body if in read-only mode (palette not visible) ## CommandStack - Override methods that populate/manage the internal undo/redo stack to no-ops Commandstack undo/redo is not supported in GLSP. This means managing the stack is just unnecessary overhead Contributed on behalf of AxonIvy AG Fixes https://github.com/eclipse-glsp/glsp/issues/1427 --- packages/client/src/base/command-stack.ts | 32 +++++++++++++-- .../feedback-action-dispatcher-default.ts | 23 ++++++++--- .../client/src/base/model/diagram-loader.ts | 39 +++++++++++-------- .../accessibility/toast/toast-tool.ts | 4 +- .../src/features/status/status-overlay.ts | 17 ++++---- .../src/features/tool-palette/tool-palette.ts | 16 +++++--- packages/glsp-sprotty/src/api-override.ts | 7 ++++ 7 files changed, 97 insertions(+), 41 deletions(-) diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index ddb0ef3..a740fec 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { + CommandExecutionContext, CommandStack, Disposable, DisposableCollection, @@ -51,6 +52,10 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.editorContext.onModelRootChanged; } + /** + * Client-side undo/redo is not supported in GLSP. The server is responsible for handling undo/redo requests. + * If this method get called it's probably a mistake and a warning is logged + */ override undo(): Promise { this.logger.warn( this, @@ -59,6 +64,10 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.currentModel; } + /** + * Client-side undo/redo is not supported in GLSP. The server is responsible for handling undo/redo requests. + * If this method get called it's probably a mistake and a warning is logged + */ override redo(): Promise { this.logger.warn( this, @@ -67,14 +76,29 @@ export class GLSPCommandStack extends CommandStack implements Disposable { return this.currentModel; } + /** + * Client-side undo/redo is not supported in GLSP. + * To avoid unnecessary infraction with the command stack (pushing/merging/popping commands) + * related methods are overridden to no-ops. + */ + protected override pushToUndoStack(command: ICommand): void { + // no-op + } + + /** + * Client-side undo/redo is not supported in GLSP. + * To avoid unnecessary infraction with the command stack (pushing/merging/popping commands) + * related methods are overridden to no-ops. + */ + protected override mergeOrPush(command: ICommand, context: CommandExecutionContext): void { + // no-op + } override async execute(command: ICommand): Promise { + const result = await super.execute(command); if (command instanceof SetModelCommand || command instanceof UpdateModelCommand) { - const result = await super.execute(command); this.notifyListeners(result); - return result; } - - return super.execute(command); + return result; } protected notifyListeners(root: Readonly): void { diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts b/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts index 2bc703c..f81a36f 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts @@ -27,20 +27,23 @@ import { TYPES, toTypeGuard } from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, preDestroy } from 'inversify'; +import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; import { getFeedbackRank } from './feedback-command'; import { FeedbackEmitter } from './feedback-emitter'; -import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; @injectable() -export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { +export class FeedbackActionDispatcher implements IFeedbackActionDispatcher, Disposable { protected registeredFeedback: Map = new Map(); - @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; + @inject(TYPES.ILogger) protected logger: ILogger; @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; + protected isDisposed = false; + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { if (feedbackEmitter instanceof GModelElement) { this.logger.log( @@ -109,11 +112,19 @@ export class FeedbackActionDispatcher implements IFeedbackActionDispatcher { protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { try { - const actionDispatcher = await this.actionDispatcher(); - await actionDispatcher.dispatchAll(actions); + if (this.isDisposed) { + return; + } + await this.actionDispatcher.dispatchAll(actions); this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); } catch (reason) { this.logger.error(this, 'Failed to dispatch feedback actions', reason); } } + + @preDestroy() + dispose(): void { + this.registeredFeedback.clear(); + this.isDisposed = true; + } } diff --git a/packages/client/src/base/model/diagram-loader.ts b/packages/client/src/base/model/diagram-loader.ts index 2725cf8..a5b4b88 100644 --- a/packages/client/src/base/model/diagram-loader.ts +++ b/packages/client/src/base/model/diagram-loader.ts @@ -18,14 +18,13 @@ import { AnyObject, ApplicationIdProvider, Args, - EMPTY_ROOT, GLSPClient, IActionDispatcher, InitializeParameters, LazyInjector, MaybePromise, + RequestAction, RequestModelAction, - SetModelAction, StatusAction, TYPES, hasNumberProp @@ -44,19 +43,23 @@ export interface IDiagramOptions { * corresponding client session. */ clientId: string; + /** * The diagram type i.e. diagram language this diagram is associated with. */ diagramType: string; + /** * The provider function to retrieve the GLSP client used by this diagram to communicate with the server. - * Multiple invocations of the provder function should always return the same `GLSPClient` instance. + * Multiple invocations of the provider function should always return the same {@link GLSPClient} instance. */ glspClientProvider: () => Promise; + /** * The file source URI associated with this diagram. */ sourceUri?: string; + /** * The initial edit mode of diagram. Defaults to `editable`. */ @@ -74,24 +77,28 @@ export interface IDiagramStartup extends Partial { * first hook that is invoked directly after {@link DiagramLoader.load} is called. */ preLoadDiagram?(): MaybePromise; + /** - * Hook for services that want to execute code before the underlying GLSP client is configured and the server is initialized. + * Hook for services that want to execute code before the underlying {@link GLSPClient} is configured and the server is initialized. */ preInitialize?(): MaybePromise; + /** - * Hook for services that want to execute code before the initial model loading request (i.e. `RequestModelAction`) but + * Hook for services that want to execute code before the initial model loading request (i.e. {@link RequestModelAction}) but * after the underlying GLSP client has been configured and the server is initialized. */ preRequestModel?(): MaybePromise; + /** - * Hook for services that want to execute code after the initial model loading request (i.e. `RequestModelAction`). - * Note that this hook is invoked directly after the `RequestModelAction` has been dispatched. It does not necessarily wait + * Hook for services that want to execute code after the initial model loading request (i.e. {@link RequestModelAction}). + * Note that this hook is invoked directly after the {@link RequestModelAction} has been dispatched. It does not necessarily wait * until the client-server update roundtrip is completed. If you need to wait until the diagram is fully initialized use the * {@link postModelInitialization} hook. */ + postRequestModel?(): MaybePromise; - /* Hook for services that want to execute code after the diagram model is fully initialized - * (i.e. `ModelInitializationConstraint` is completed). + /** Hook for services that want to execute code after the diagram model is fully initialized + * (i.e. {@link ModelInitializationConstraint} is completed). */ postModelInitialization?(): MaybePromise; } @@ -119,7 +126,7 @@ export interface DiagramLoadingOptions { requestModelOptions?: Args; /** - * Optional partial {@link InitializeParameters} that should be used for `initializeServer` request if the underlying + * Optional partial {@link InitializeParameters} that should be used for {@link GLSPClient.initializeServer} request if the underlying * {@link GLSPClient} has not been initialized yet. */ initializeParameters?: Partial; @@ -180,14 +187,15 @@ export class DiagramLoader { }, enableNotifications: options.enableNotifications ?? true }; - // Set empty place holder model until actual model from server is available - await this.actionDispatcher.dispatch(SetModelAction.create(EMPTY_ROOT)); + // Ensure that the action dispatcher is initialized before starting the diagram loading process + await this.actionDispatcher.initialize?.(); await this.invokeStartupHook('preInitialize'); await this.initialize(resolvedOptions); await this.invokeStartupHook('preRequestModel'); await this.requestModel(resolvedOptions); await this.invokeStartupHook('postRequestModel'); - this.modelInitializationConstraint.onInitialized(() => this.invokeStartupHook('postModelInitialization')); + await this.modelInitializationConstraint.onInitialized(); + await this.invokeStartupHook('postModelInitialization'); } protected async invokeStartupHook(hook: keyof Omit): Promise { @@ -201,10 +209,9 @@ export class DiagramLoader { } protected async requestModel(options: ResolvedDiagramLoadingOptions): Promise { - const response = await this.actionDispatcher.request( - RequestModelAction.create({ options: options.requestModelOptions }) + await this.actionDispatcher.dispatch( + RequestModelAction.create({ options: options.requestModelOptions, requestId: RequestAction.generateRequestId() }) ); - return this.actionDispatcher.dispatch(response); } protected async initialize(options: ResolvedDiagramLoadingOptions): Promise { diff --git a/packages/client/src/features/accessibility/toast/toast-tool.ts b/packages/client/src/features/accessibility/toast/toast-tool.ts index 25d2be2..5a13979 100644 --- a/packages/client/src/features/accessibility/toast/toast-tool.ts +++ b/packages/client/src/features/accessibility/toast/toast-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, IActionDispatcher, IActionHandler, ICommand, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, IActionDispatcher, IActionHandler, ICommand, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../../base/editor-context-service'; import { IDiagramStartup } from '../../../base/model/diagram-loader'; @@ -99,7 +99,7 @@ export class Toast extends GLSPAbstractUIExtension implements IActionHandler, ID } preInitialize(): void { - this.show(this.editorContext.modelRoot); + this.actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: Toast.ID, visible: true })); } values(obj: { [key: symbol]: ToastOptions }): ToastOptions[] { diff --git a/packages/client/src/features/status/status-overlay.ts b/packages/client/src/features/status/status-overlay.ts index e49d91d..c226e40 100644 --- a/packages/client/src/features/status/status-overlay.ts +++ b/packages/client/src/features/status/status-overlay.ts @@ -13,9 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { IActionDispatcher, IActionHandler, StatusAction, TYPES, codiconCSSClasses } from '@eclipse-glsp/sprotty'; +import { + IActionDispatcher, + IActionHandler, + SetUIExtensionVisibilityAction, + StatusAction, + TYPES, + codiconCSSClasses +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EditorContextService } from '../../base/editor-context-service'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; @@ -29,9 +35,6 @@ export class StatusOverlay extends GLSPAbstractUIExtension implements IActionHan @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - @inject(EditorContextService) - protected editorContext: EditorContextService; - protected statusIconDiv?: HTMLDivElement; protected statusMessageDiv?: HTMLDivElement; protected pendingTimeout?: number; @@ -115,7 +118,7 @@ export class StatusOverlay extends GLSPAbstractUIExtension implements IActionHan } } - preInitialize(): void { - this.show(this.editorContext.modelRoot); + preInitialize(): Promise { + return this.actionDispatcher.dispatch(SetUIExtensionVisibilityAction.create({ extensionId: this.id(), visible: true })); } } diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index d9200a7..e74f75e 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -474,17 +474,21 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl this.createBody(); } - async preRequestModel(): Promise { + /** + * @deprecated This hook is no longer used by the ToolPalette. + * It is kept for compatibility reasons and will be removed in the future. + * Move initialization logic to the `postRequestModel` method. + * This ensures that tool palette initialization does not block the diagram loading process. + */ + async preRequestModel(): Promise {} + + async postRequestModel(): Promise { await this.setPaletteItems(); if (!this.editorContext.isReadonly) { this.show(this.editorContext.modelRoot); } } - async postRequestModel(): Promise { - this.reloadPaletteBody(); - } - protected async setPaletteItems(): Promise { const requestAction = RequestContextActions.create({ contextId: ToolPalette.ID, @@ -506,7 +510,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl } protected async reloadPaletteBody(): Promise { - if (this.dynamic) { + if (!this.editorContext.isReadonly && this.dynamic) { await this.setPaletteItems(); this.paletteItemsCopy = []; this.requestFilterUpdate(this.searchField.value); diff --git a/packages/glsp-sprotty/src/api-override.ts b/packages/glsp-sprotty/src/api-override.ts index 81dc464..06b75fd 100644 --- a/packages/glsp-sprotty/src/api-override.ts +++ b/packages/glsp-sprotty/src/api-override.ts @@ -135,6 +135,13 @@ export interface IVNodePostprocessor extends SIVNodePostprocessor { } export interface IActionDispatcher extends SIActionDispatcher { + /** + * Optional method to initialize the action dispatcher. + * Implementation can use this as a hook to perform any initialization tasks, + * like registering action handlers or setting up the initial diagram. + * Called by the `DiagramLoader` when starting the loading process. + */ + initialize?(): Promise; /** * Dispatch an action by querying all handlers that are registered for its kind. * The returned promise is resolved when all handler results (commands or actions) From 9706c2e5d12d13486f5e76b45aaa33a9e77a03cd Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 25 Nov 2024 11:48:49 +0000 Subject: [PATCH 405/566] Properly handle move feedback if mouse up happens outside window (#399) - Handle mouse down if we should be dragging as mouse up - Reset feedback if selection has changed -- Ensure element does not have to be selected to revert move -- Add method for listeners to selection service Fixes https://github.com/eclipse-glsp/glsp/issues/1426 --- packages/client/src/base/selection-service.ts | 22 ++++++++++----- .../helper-line-manager-default.ts | 4 +-- .../change-bounds-tool-move-feedback.ts | 27 ++++++++++++++----- .../tools/change-bounds/change-bounds-tool.ts | 16 ++++++----- .../tools/edge-edit/edge-edit-tool.ts | 2 +- packages/client/src/utils/gmodel-util.ts | 16 ++++++++++- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/client/src/base/selection-service.ts b/packages/client/src/base/selection-service.ts index c371f5e..69ee09b 100644 --- a/packages/client/src/base/selection-service.ts +++ b/packages/client/src/base/selection-service.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { Action, + AnyObject, Command, CommandExecutionContext, Disposable, @@ -32,6 +33,7 @@ import { SprottySelectCommand, TYPES, hasArrayProp, + hasFunctionProp, isSelectable, pluck } from '@eclipse-glsp/sprotty'; @@ -45,6 +47,12 @@ export interface ISelectionListener { selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void; } +export namespace ISelectionListener { + export function is(object: unknown): object is ISelectionListener { + return AnyObject.is(object) && hasFunctionProp(object, 'selectionChanged'); + } +} + export interface SelectionChange { root: Readonly; selectedElements: string[]; @@ -73,13 +81,7 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr } preLoadDiagram(): void { - this.lazyInjector - .getAll(TYPES.ISelectionListener) - .forEach(listener => - this.onSelectionChanged(change => - listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements) - ) - ); + this.lazyInjector.getAll(TYPES.ISelectionListener).forEach(listener => this.addListener(listener)); } @preDestroy() @@ -92,6 +94,12 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr return this.onSelectionChangedEmitter.event; } + addListener(listener: ISelectionListener): Disposable { + return this.onSelectionChanged(change => + listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements) + ); + } + modelRootChanged(root: Readonly): void { this.updateSelection(root, [], []); } diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts index 0d9c45f..4a30d87 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager-default.ts @@ -112,9 +112,7 @@ export class HelperLineManager implements IActionHandler, ISelectionListener, IH dynamicOptions.minimumMoveDelta = Point.multiplyScalar(this.grid, 2); } this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...dynamicOptions, ...this.userOptions }; - this.selectionService.onSelectionChanged(change => - this.selectionChanged(change.root, change.selectedElements, change.deselectedElements) - ); + this.selectionService.addListener(this); } handle(action: Action): void { diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index 6843359..31c8c8f 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -13,16 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ElementMove, GModelElement, GModelRoot, MoveAction, Point, findParentByFeature } from '@eclipse-glsp/sprotty'; +import { Action, ElementMove, GModelElement, GModelRoot, MoveAction, Point, TypeGuard, findParentByFeature } from '@eclipse-glsp/sprotty'; import { DebouncedFunc, debounce } from 'lodash'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; +import { ISelectionListener } from '../../../base/selection-service'; import { MoveableElement, filter, getElements, + isNonRoutableMovableBoundsAware, isNonRoutableSelectedMovableBoundsAware, removeDescendants } from '../../../utils/gmodel-util'; @@ -38,7 +40,7 @@ import { ChangeBoundsTracker, TrackedMove } from './change-bounds-tracker'; * the visual feedback but also the basis for sending the change to the server * (see also `tools/MoveTool`). */ -export class FeedbackMoveMouseListener extends DragAwareMouseListener { +export class FeedbackMoveMouseListener extends DragAwareMouseListener implements ISelectionListener { protected rootElement?: GModelRoot; protected tracker: ChangeBoundsTracker; protected elementId2startPos = new Map(); @@ -58,6 +60,11 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { override mouseDown(target: GModelElement, event: MouseEvent): Action[] { super.mouseDown(target, event); if (event.button === 0) { + if (this.tracker.isTracking()) { + // we have a move in progress that was not resolved yet (e.g., user may have triggered a mouse up outside the window) + this.draggingMouseUp(target, event); + return []; + } this.initializeMove(target, event); return []; } @@ -105,6 +112,10 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { return !!element && isNonRoutableSelectedMovableBoundsAware(element) && !(element instanceof GResizeHandle); } + protected isValidRevertable(element?: GModelElement): element is MoveableElement { + return !!element && isNonRoutableMovableBoundsAware(element) && !(element instanceof GResizeHandle); + } + override nonDraggingMouseUp(element: GModelElement, event: MouseEvent): Action[] { // should reset everything that may have happend on mouse down this.moveInitializedFeedback.dispose(); @@ -161,8 +172,8 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { return Array.from(topLevelElements); } - protected getElementsToMove(context: GModelElement): MoveableElement[] { - return getElements(context.root.index, Array.from(this.elementId2startPos.keys()), this.isValidMoveable); + protected getElementsToMove(context: GModelElement, moveable: TypeGuard = this.isValidMoveable): MoveableElement[] { + return getElements(context.root.index, Array.from(this.elementId2startPos.keys()), moveable); } protected resetElementPositions(context: GModelElement): MoveAction | undefined { @@ -173,7 +184,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { protected revertElementMoves(context?: GModelElement): ElementMove[] { const elementMoves: ElementMove[] = []; if (context?.root?.index) { - const movableElements = this.getElementsToMove(context); + const movableElements = this.getElementsToMove(context, this.isValidRevertable); movableElements.forEach(element => elementMoves.push({ elementId: element.id, toPosition: this.elementId2startPos.get(element.id)! }) ); @@ -192,7 +203,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { .filter(element => this.tool.changeBoundsManager.isValid(element)) .forEach(element => this.elementId2startPos.delete(element.id)); } else { - if (elementsToMove.every(element => this.tool.changeBoundsManager.isValid(element))) { + if (elementsToMove.length > 0 && elementsToMove.every(element => this.tool.changeBoundsManager.isValid(element))) { // do not reset any element as all are valid this.elementId2startPos.clear(); } @@ -201,6 +212,10 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { return []; } + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[]): void { + this.dispose(); + } + override dispose(): void { this.pendingMoveInitialized?.cancel(); this.moveInitializedFeedback.dispose(); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index cafebcb..9f008ae 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -98,21 +98,23 @@ export class ChangeBoundsTool extends BaseEditTool { enable(): void { // install feedback move mouse listener for client-side move updates const feedbackMoveMouseListener = this.createMoveMouseListener(); + this.toDisposeOnDisable.push(this.mouseTool.registerListener(feedbackMoveMouseListener)); if (Disposable.is(feedbackMoveMouseListener)) { this.toDisposeOnDisable.push(feedbackMoveMouseListener); } + if (ISelectionListener.is(feedbackMoveMouseListener)) { + this.toDisposeOnDisable.push(this.selectionService.addListener(feedbackMoveMouseListener)); + } // install change bounds listener for client-side resize updates and server-side updates const changeBoundsListener = this.createChangeBoundsListener(); + this.toDisposeOnDisable.push(this.mouseTool.registerListener(changeBoundsListener)); if (Disposable.is(changeBoundsListener)) { this.toDisposeOnDisable.push(changeBoundsListener); } - - this.toDisposeOnDisable.push( - this.mouseTool.registerListener(feedbackMoveMouseListener), - this.mouseTool.registerListener(changeBoundsListener), - this.selectionService.onSelectionChanged(change => changeBoundsListener.selectionChanged(change.root, change.selectedElements)) - ); + if (ISelectionListener.is(changeBoundsListener)) { + this.toDisposeOnDisable.push(this.selectionService.addListener(changeBoundsListener)); + } } createChangeBoundsTracker(): ChangeBoundsTracker { @@ -123,7 +125,7 @@ export class ChangeBoundsTool extends BaseEditTool { return new FeedbackMoveMouseListener(this); } - protected createChangeBoundsListener(): MouseListener & ISelectionListener { + protected createChangeBoundsListener(): MouseListener { return new ChangeBoundsListener(this); } } diff --git a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts index b2a10ea..7f7083a 100644 --- a/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit/edge-edit-tool.ts @@ -82,7 +82,7 @@ export class EdgeEditTool extends BaseEditTool { this.feedbackEdgeSourceMovingListener, this.feedbackEdgeTargetMovingListener, this.feedbackMovingListener, - this.selectionService.onSelectionChanged(change => this.edgeEditListener.selectionChanged(change.root, change.selectedElements)) + this.selectionService.addListener(this.edgeEditListener) ); } diff --git a/packages/client/src/utils/gmodel-util.ts b/packages/client/src/utils/gmodel-util.ts index 2261e5f..796a185 100644 --- a/packages/client/src/utils/gmodel-util.ts +++ b/packages/client/src/utils/gmodel-util.ts @@ -196,6 +196,10 @@ export function isNonRoutableSelectedMovableBoundsAware(element: GModelElement): return isNonRoutableSelectedBoundsAware(element) && isMoveable(element); } +export function isNonRoutableMovableBoundsAware(element: GModelElement): element is BoundsAwareModelElement { + return isNonRoutableBoundsAware(element) && isMoveable(element); +} + /** * A typeguard function to check wether a given {@link GModelElement} implements the {@link BoundsAware} model feature, * the {@link Selectable} model feature and is actually selected. In addition, the element must not be a {@link GRoutableElement}. @@ -203,7 +207,17 @@ export function isNonRoutableSelectedMovableBoundsAware(element: GModelElement): * @returns A type predicate indicating wether the element is of type {@link SelectableBoundsAware}. */ export function isNonRoutableSelectedBoundsAware(element: GModelElement): element is SelectableBoundsAware { - return isBoundsAware(element) && isSelected(element) && !isRoutable(element); + return isNonRoutableBoundsAware(element) && isSelected(element); +} + +/** + * A typeguard function to check wether a given {@link GModelElement} implements the {@link BoundsAware} model feature. + * In addition, the element must not be a {@link GRoutableElement}. + * @param element The element to check. + * @returns A type predicate indicating wether the element is of type {@link BoundsAwareModelElement}. + */ +export function isNonRoutableBoundsAware(element: GModelElement): element is BoundsAwareModelElement { + return isBoundsAware(element) && !isRoutable(element); } /** From bbecc48eeff9e771e05e46b23f92e719363b1e38 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 25 Nov 2024 14:10:01 +0100 Subject: [PATCH 406/566] GLSP-1429: Update to sprotty 1.3.0 (#400) Update to latest sprotty release Fixes https://github.com/eclipse-glsp/glsp/issues/1429 --- .../src/features/export/glsp-svg-exporter.ts | 24 +++++++++++++------ packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- .../src/action-protocol/model-saving.ts | 19 +++++++++++---- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 86d61d6..849bbb1 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -13,31 +13,41 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ExportSvgAction, GModelRoot, RequestAction, SvgExporter } from '@eclipse-glsp/sprotty'; +import { + Action, + ExportSvgAction, + ExportSvgOptions, + GModelRoot, + RequestAction, + RequestExportSvgAction, + SvgExporter +} from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { v4 as uuid } from 'uuid'; @injectable() export class GLSPSvgExporter extends SvgExporter { - override export(root: GModelRoot, request?: RequestAction): void { + override export(root: GModelRoot, request?: RequestExportSvgAction): void { if (typeof document !== 'undefined') { let svgElement = this.findSvgElement(); if (svgElement) { svgElement = this.prepareSvgElement(svgElement, root, request); - const serializedSvg = this.createSvg(svgElement, root); + const serializedSvg = this.createSvg(svgElement, root, request?.options ?? {}, request); const svgExport = this.getSvgExport(serializedSvg, svgElement, root, request); // do not give request/response id here as otherwise the action is treated as an unrequested response - this.actionDispatcher.dispatch(ExportSvgAction.create(svgExport)); + this.actionDispatcher.dispatch( + ExportSvgAction.create(svgExport, { responseId: request?.requestId, options: request?.options }) + ); } } } - protected override createSvg(svgElement: SVGSVGElement, root: GModelRoot): string { + protected override createSvg(svgElement: SVGSVGElement, root: GModelRoot, options?: ExportSvgOptions, cause?: Action): string { // createSvg requires the svg to have a non-empty id, so we generate one if necessary const originalId = svgElement.id; try { svgElement.id = originalId || uuid(); - return super.createSvg(svgElement, root); + return super.createSvg(svgElement, root, options, cause); } finally { svgElement.id = originalId; } @@ -109,7 +119,7 @@ export class GLSPSvgExporter extends SvgExporter { protected getSvgExportStyle(svgElement: SVGElement, root: GModelRoot, request?: RequestAction): string | undefined { // provide generated svg code with respective sizing for proper viewing in browser and remove undesired border - const bounds = this.getBounds(root); + const bounds = this.getBounds(root, document); return ( `width: ${bounds.width}px !important;` + `height: ${bounds.height}px !important;` + diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 4d0a353..8d2d22c 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -36,8 +36,8 @@ "@eclipse-glsp/protocol": "2.3.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", - "sprotty": "1.2.0", - "sprotty-protocol": "1.2.0", + "sprotty": "1.3.0", + "sprotty-protocol": "1.3.0", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index e8c6828..c7a87e7 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -45,7 +45,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "1.2.0", + "sprotty-protocol": "1.3.0", "uuid": "~10.0.0", "vscode-jsonrpc": "8.2.0" }, diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index 6d225fa..cb5ae63 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as sprotty from 'sprotty-protocol/lib/actions'; import { hasBooleanProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; @@ -87,8 +88,9 @@ export namespace SetDirtyStateAction { * The handler of this action is expected to retrieve the diagram SVG and should send a {@link ExportSvgAction} as response. * Typically the {@link ExportSvgAction} is handled directly on client side. */ -export interface RequestExportSvgAction extends RequestAction { +export interface RequestExportSvgAction extends RequestAction, sprotty.RequestExportSvgAction { kind: typeof RequestExportSvgAction.KIND; + options?: ExportSvgOptions; } export namespace RequestExportSvgAction { export const KIND = 'requestExportSvg'; @@ -97,7 +99,7 @@ export namespace RequestExportSvgAction { return RequestAction.hasKind(object, KIND); } - export function create(options: { requestId?: string } = {}): RequestExportSvgAction { + export function create(options: { options?: ExportSvgOptions; requestId?: string } = {}): RequestExportSvgAction { return { kind: KIND, requestId: '', @@ -106,6 +108,12 @@ export namespace RequestExportSvgAction { } } +/** Configuration options for the {@link RequestExportSvgAction */ +export interface ExportSvgOptions extends sprotty.ExportSvgOptions { + // If set to false applied diagram styles will not be copied to the exported SVG + skipCopyStyles?: boolean; +} + /** * The client sends an `ExportSvgAction` to indicate that the diagram, which represents the current model state, * should be exported in SVG format. The action only provides the diagram SVG as plain string. The expected result of executing @@ -113,11 +121,12 @@ export namespace RequestExportSvgAction { * concrete file name, file extension etc. are not specified in the protocol. So it is the responsibility of the action handler to * process this information accordingly and export the result to the underlying filesystem. */ -export interface ExportSvgAction extends ResponseAction { +export interface ExportSvgAction extends ResponseAction, sprotty.ExportSvgAction { kind: typeof ExportSvgAction.KIND; svg: string; - responseId: string; + options?: ExportSvgOptions; } + export namespace ExportSvgAction { export const KIND = 'exportSvg'; @@ -125,7 +134,7 @@ export namespace ExportSvgAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'svg'); } - export function create(svg: string, options: { responseId?: string } = {}): ExportSvgAction { + export function create(svg: string, options: { options?: ExportSvgOptions; responseId?: string } = {}): ExportSvgAction { return { kind: KIND, svg, From 4a832bcddd5577841f9bdb533346b36e8bef5440 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 6 Dec 2024 11:07:15 +0100 Subject: [PATCH 407/566] Various minor improvements (#401) - Merge *-default.ts implementation files of `FeedbackActionDispatcher` and `HelperLineManager` with the the interface definition files. The split is no longer necessary since we resolved potential circular dependency issues - Ensure that we consistently use the `LazyInjector` over dedicated async providers and deprecate existing provider injection - Move MaybeActions utility type from feedback-action-dispatcher into the protocol package to enable reuse + add testcases --- packages/client/src/base/default.module.ts | 4 +- .../feedback-action-dispatcher-default.ts | 130 --------- .../feedback/feedback-action-dispatcher.ts | 125 ++++++++- .../src/base/feedback/feedback-emitter.ts | 4 +- .../delete-element-context-menu.ts | 18 +- .../helper-line-manager-default.ts | 250 ------------------ .../helper-lines/helper-line-manager.ts | 236 ++++++++++++++++- .../helper-lines/helper-line-module.ts | 2 +- .../features/hints/type-hint-provider.spec.ts | 2 +- .../navigation/navigation-action-handler.ts | 14 +- .../client/src/features/tools/base-tools.ts | 4 +- packages/client/src/index.ts | 2 - packages/glsp-sprotty/src/types.ts | 6 +- packages/protocol/src/utils/type-util.spec.ts | 33 ++- packages/protocol/src/utils/type-util.ts | 12 + 15 files changed, 425 insertions(+), 417 deletions(-) delete mode 100644 packages/client/src/base/feedback/feedback-action-dispatcher-default.ts delete mode 100644 packages/client/src/features/helper-lines/helper-line-manager-default.ts diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 0ad81f0..2349923 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -39,7 +39,7 @@ import { GLSPActionHandlerRegistry } from './action-handler-registry'; import { GLSPCommandStack } from './command-stack'; import { EditorContextService } from './editor-context-service'; import { ModifyCssFeedbackCommand } from './feedback/css-feedback'; -import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher-default'; +import { FeedbackActionDispatcher } from './feedback/feedback-action-dispatcher'; import { FeedbackAwareSetModelCommand } from './feedback/set-model-command'; import { FeedbackAwareUpdateModelCommand } from './feedback/update-model-command'; import { FocusStateChangedAction } from './focus/focus-state-change-action'; @@ -72,6 +72,7 @@ export const defaultModule = new FeatureModule( bind(EditorContextService).toSelf().inSingletonScope(); bind(TYPES.IDiagramStartup).toService(EditorContextService); + // eslint-disable-next-line deprecation/deprecation bind(TYPES.IEditorContextServiceProvider).toProvider( ctx => async () => ctx.container.get(EditorContextService) ); @@ -80,6 +81,7 @@ export const defaultModule = new FeatureModule( configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); bind(FocusTracker).toSelf().inSingletonScope(); + bind(TYPES.IDiagramStartup).toService(FocusTracker); configureActionHandler(context, FocusStateChangedAction.KIND, FocusTracker); // Model update initialization ------------------------------------ diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts b/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts deleted file mode 100644 index f81a36f..0000000 --- a/packages/client/src/base/feedback/feedback-action-dispatcher-default.ts +++ /dev/null @@ -1,130 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { - Action, - ActionHandlerRegistry, - Command, - CommandActionHandler, - CommandExecutionContext, - Disposable, - GModelElement, - IActionDispatcher, - ICommand, - ILogger, - TYPES, - toTypeGuard -} from '@eclipse-glsp/sprotty'; -import { inject, injectable, preDestroy } from 'inversify'; -import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; -import { getFeedbackRank } from './feedback-command'; -import { FeedbackEmitter } from './feedback-emitter'; - -@injectable() -export class FeedbackActionDispatcher implements IFeedbackActionDispatcher, Disposable { - protected registeredFeedback: Map = new Map(); - - @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - - @inject(TYPES.ILogger) protected logger: ILogger; - - @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; - - protected isDisposed = false; - - registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { - if (feedbackEmitter instanceof GModelElement) { - this.logger.log( - this, - // eslint-disable-next-line max-len - 'GModelElements as feedback emitters are discouraged, as they usually change between model updates and are considered unstable.' - ); - } - if (feedbackActions.length > 0) { - this.registeredFeedback.set(feedbackEmitter, feedbackActions); - this.dispatchFeedback(feedbackActions, feedbackEmitter); - } - return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); - } - - deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void { - this.registeredFeedback.delete(feedbackEmitter); - const actions = MaybeActions.asArray(cleanupActions); - if (actions.length > 0) { - this.dispatchFeedback(actions, feedbackEmitter); - } - } - - getRegisteredFeedback(): Action[] { - const result: Action[] = []; - this.registeredFeedback.forEach(actions => result.push(...actions)); - return result; - } - - getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { - const result: IFeedbackEmitter[] = []; - this.registeredFeedback.forEach((actions, emitter) => { - if (actions.includes(action)) { - result.push(emitter); - } - }); - return result; - } - - getFeedbackCommands(): Command[] { - return this.getRegisteredFeedback() - .flatMap(action => this.actionToCommands(action)) - .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); - } - - async applyFeedbackCommands(context: CommandExecutionContext): Promise { - const feedbackCommands = this.getFeedbackCommands() ?? []; - if (feedbackCommands?.length > 0) { - const results = feedbackCommands.map(command => command.execute(context)); - await Promise.all(results); - } - } - - protected actionToCommands(action: Action): ICommand[] { - return ( - this.actionHandlerRegistry - .get(action.kind) - .filter(toTypeGuard(CommandActionHandler)) - .map(handler => handler.handle(action)) ?? [] - ); - } - - createEmitter(): FeedbackEmitter { - return new FeedbackEmitter(this); - } - - protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { - try { - if (this.isDisposed) { - return; - } - await this.actionDispatcher.dispatchAll(actions); - this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); - } catch (reason) { - this.logger.error(this, 'Failed to dispatch feedback actions', reason); - } - } - - @preDestroy() - dispose(): void { - this.registeredFeedback.clear(); - this.isDisposed = true; - } -} diff --git a/packages/client/src/base/feedback/feedback-action-dispatcher.ts b/packages/client/src/base/feedback/feedback-action-dispatcher.ts index e3e658a..d8a1562 100644 --- a/packages/client/src/base/feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/base/feedback/feedback-action-dispatcher.ts @@ -13,23 +13,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Command, CommandExecutionContext, Disposable, MaybeFunction, call, asArray as toArray } from '@eclipse-glsp/sprotty'; +import { + Action, + ActionHandlerRegistry, + Command, + CommandActionHandler, + CommandExecutionContext, + Disposable, + GModelElement, + IActionDispatcher, + ICommand, + ILogger, + MaybeActions, + TYPES, + toTypeGuard +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, preDestroy } from 'inversify'; +import { getFeedbackRank } from './feedback-command'; import { FeedbackEmitter } from './feedback-emitter'; export interface IFeedbackEmitter {} export const feedbackFeature = Symbol('feedbackFeature'); -export type MaybeActions = MaybeFunction; - -export namespace MaybeActions { - // eslint-disable-next-line @typescript-eslint/no-shadow - export function asArray(actions?: MaybeActions): Action[] { - const cleanup = actions ? call(actions) : []; - return cleanup ? toArray(cleanup) : []; - } -} - /** * Dispatcher for actions that are meant to show visual feedback on * the diagram that is not part of the diagram sent from the server @@ -86,3 +92,100 @@ export interface IFeedbackActionDispatcher { */ createEmitter(): FeedbackEmitter; } + +@injectable() +export class FeedbackActionDispatcher implements IFeedbackActionDispatcher, Disposable { + protected registeredFeedback: Map = new Map(); + + @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; + + @inject(TYPES.ILogger) protected logger: ILogger; + + @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; + + protected isDisposed = false; + + registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: MaybeActions): Disposable { + if (feedbackEmitter instanceof GModelElement) { + this.logger.log( + this, + // eslint-disable-next-line max-len + 'GModelElements as feedback emitters are discouraged, as they usually change between model updates and are considered unstable.' + ); + } + if (feedbackActions.length > 0) { + this.registeredFeedback.set(feedbackEmitter, feedbackActions); + this.dispatchFeedback(feedbackActions, feedbackEmitter); + } + return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions)); + } + + deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: MaybeActions): void { + this.registeredFeedback.delete(feedbackEmitter); + const actions = MaybeActions.asArray(cleanupActions); + if (actions.length > 0) { + this.dispatchFeedback(actions, feedbackEmitter); + } + } + + getRegisteredFeedback(): Action[] { + const result: Action[] = []; + this.registeredFeedback.forEach(actions => result.push(...actions)); + return result; + } + + getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] { + const result: IFeedbackEmitter[] = []; + this.registeredFeedback.forEach((actions, emitter) => { + if (actions.includes(action)) { + result.push(emitter); + } + }); + return result; + } + + getFeedbackCommands(): Command[] { + return this.getRegisteredFeedback() + .flatMap(action => this.actionToCommands(action)) + .sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right)); + } + + async applyFeedbackCommands(context: CommandExecutionContext): Promise { + const feedbackCommands = this.getFeedbackCommands() ?? []; + if (feedbackCommands?.length > 0) { + const results = feedbackCommands.map(command => command.execute(context)); + await Promise.all(results); + } + } + + protected actionToCommands(action: Action): ICommand[] { + return ( + this.actionHandlerRegistry + .get(action.kind) + .filter(toTypeGuard(CommandActionHandler)) + .map(handler => handler.handle(action)) ?? [] + ); + } + + createEmitter(): FeedbackEmitter { + return new FeedbackEmitter(this); + } + + protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise { + try { + if (this.isDisposed) { + return; + } + await this.actionDispatcher.dispatchAll(actions); + this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`); + } catch (reason) { + this.logger.error(this, 'Failed to dispatch feedback actions', reason); + } + } + + @preDestroy() + dispose(): void { + this.registeredFeedback.clear(); + this.isDisposed = true; + } +} diff --git a/packages/client/src/base/feedback/feedback-emitter.ts b/packages/client/src/base/feedback/feedback-emitter.ts index f39a8bf..eeed779 100644 --- a/packages/client/src/base/feedback/feedback-emitter.ts +++ b/packages/client/src/base/feedback/feedback-emitter.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, arrayOf } from '@eclipse-glsp/sprotty'; -import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from './feedback-action-dispatcher'; +import { Action, Disposable, MaybeActions, arrayOf } from '@eclipse-glsp/sprotty'; +import type { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback-action-dispatcher'; // counter for internal id, mainly useful for debugging let idCounter = 0; diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index 6470961..c3dfbec 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,27 +13,31 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { DeleteElementOperation, GModelRoot, IContextMenuItemProvider, MenuItem, Point, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { DeleteElementOperation, IContextMenuItemProvider, MenuItem, Point, GModelRoot, TYPES } from '@eclipse-glsp/sprotty'; import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; @injectable() export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { + /** @deprecated No longer used. The {@link EditorContextService} is now directly injected.*/ + // eslint-disable-next-line deprecation/deprecation @inject(TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; + @inject(EditorContextService) + protected editorContext: EditorContextService; + async getItems(_root: Readonly, _lastMousePosition?: Point): Promise { - const editorContextService = await this.editorContextServiceProvider(); - return [this.createDeleteMenuItem(editorContextService)]; + return [this.createDeleteMenuItem()]; } - protected createDeleteMenuItem(editorContextService: EditorContextService): MenuItem { + protected createDeleteMenuItem(): MenuItem { return { id: 'delete', label: 'Delete', sortString: 'd', group: 'edit', - actions: [DeleteElementOperation.create(editorContextService.selectedElements.map(e => e.id))], - isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 + actions: [DeleteElementOperation.create(this.editorContext.selectedElements.map(e => e.id))], + isEnabled: () => !this.editorContext.isReadonly && this.editorContext.selectedElements.length > 0 }; } } diff --git a/packages/client/src/features/helper-lines/helper-line-manager-default.ts b/packages/client/src/features/helper-lines/helper-line-manager-default.ts deleted file mode 100644 index 4a30d87..0000000 --- a/packages/client/src/features/helper-lines/helper-line-manager-default.ts +++ /dev/null @@ -1,250 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { - Action, - GModelElement, - GModelRoot, - IActionHandler, - MoveAction, - Point, - SetBoundsAction, - TYPES, - Vector, - Writable -} from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional, postConstruct } from 'inversify'; -import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; -import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; -import { ISelectionListener, SelectionService } from '../../base/selection-service'; -import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; -import { GResizeHandle, ResizeHandleLocation } from '../change-bounds/model'; -import { Grid } from '../grid/grid'; -import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; -import { - AlignmentElementFilter, - DEFAULT_ALIGNABLE_ELEMENT_FILTER, - DEFAULT_DEBUG, - DEFAULT_ELEMENT_LINES, - DEFAULT_EPSILON, - DEFAULT_VIEWPORT_LINES, - DrawHelperLinesFeedbackAction, - RemoveHelperLinesFeedbackAction, - ViewportLineType -} from './helper-line-feedback'; -import { IHelperLineManager } from './helper-line-manager'; -import { Direction, HelperLine, HelperLineType, isHelperLine } from './model'; - -export interface IHelperLineOptions { - /** - * A list of helper line types that should be rendered when elements are aligned. - * Defaults to all possible alignments. - */ - elementLines?: HelperLineType[]; - /** - * A list of helper line types that should be rendered when an element is aligned with the viewport. - * Defaults to middle and center alignment. - */ - viewportLines?: ViewportLineType[]; - /** - * The minimum difference between two coordinates - * Defaults to 1 or zero (perfect match) if the optional grid module is loaded. - */ - alignmentEpsilon?: number; - /** - * A filter that is applied to determine on which elements the alignment calculation is performed. - * By default all top-level bounds-aware, non-routable elements that are visible on the canvas are considered. - */ - alignmentElementFilter?: AlignmentElementFilter; - /** - * The minimum move delta that is necessary for an element to break through a helper line. - * Defaults to { x: 1, y: 1 } whereas the x represents the horizontal distance and y represents the vertical distance. - * If the optional grid module is loaded, defaults to twice the grid size, i.e., two grid moves to break through a helper line. - */ - minimumMoveDelta?: Point; - - /** - * Produces debug output. - * Defaults to false. - */ - debug?: boolean; -} - -export const DEFAULT_MOVE_DELTA = { x: 1, y: 1 }; - -export const DEFAULT_HELPER_LINE_OPTIONS: Required = { - elementLines: DEFAULT_ELEMENT_LINES, - viewportLines: DEFAULT_VIEWPORT_LINES, - alignmentEpsilon: DEFAULT_EPSILON, - alignmentElementFilter: DEFAULT_ALIGNABLE_ELEMENT_FILTER, - minimumMoveDelta: DEFAULT_MOVE_DELTA, - debug: DEFAULT_DEBUG -}; - -@injectable() -export class HelperLineManager implements IActionHandler, ISelectionListener, IHelperLineManager { - @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; - @inject(SelectionService) protected selectionService: SelectionService; - @optional() @inject(TYPES.IHelperLineOptions) protected userOptions?: IHelperLineOptions; - @optional() @inject(TYPES.Grid) protected grid?: Grid; - - protected options: Required; - protected feedback: FeedbackEmitter; - - @postConstruct() - protected init(): void { - this.feedback = this.feedbackDispatcher.createEmitter(); - const dynamicOptions: IHelperLineOptions = {}; - if (this.grid) { - dynamicOptions.alignmentEpsilon = 0; - dynamicOptions.minimumMoveDelta = Point.multiplyScalar(this.grid, 2); - } - this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...dynamicOptions, ...this.userOptions }; - this.selectionService.addListener(this); - } - - handle(action: Action): void { - if (MoveInitializedEventAction.is(action)) { - this.handleMoveInitializedAction(action); - } else if (MoveAction.is(action)) { - this.handleMoveAction(action); - } else if (MoveFinishedEventAction.is(action)) { - this.handleMoveFinishedAction(action); - } else if (SetBoundsAction.is(action) || SetBoundsFeedbackAction.is(action)) { - this.handleSetBoundsAction(action); - } - } - - protected handleMoveInitializedAction(_action: MoveInitializedEventAction): void { - this.submitHelperLineFeedback(); - } - - protected handleMoveFinishedAction(_action: MoveFinishedEventAction): void { - this.feedback.dispose(); - } - - protected handleMoveAction(action: MoveAction): void { - if (!action.finished) { - this.submitHelperLineFeedback(action.moves.map(move => move.elementId)); - } else { - this.feedback.dispose(); - } - } - - protected submitHelperLineFeedback(elementIds: string[] = this.selectionService.getSelectedElementIDs()): void { - const feedback = this.createHelperLineFeedback(elementIds); - this.feedback.add(feedback, [RemoveHelperLinesFeedbackAction.create()]).submit(); - } - - protected createHelperLineFeedback(elementIds: string[]): DrawHelperLinesFeedbackAction { - return DrawHelperLinesFeedbackAction.create({ elementIds, ...this.options }); - } - - protected handleSetBoundsAction(action: SetBoundsAction | SetBoundsFeedbackAction): void { - this.submitHelperLineFeedback(action.bounds.map(bound => bound.elementId)); - } - - selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[] | undefined): void { - this.feedback.dispose(); - } - - getMinimumMoveDelta(element: GModelElement, isSnap: boolean, direction: Direction): number { - if (!isSnap) { - return 0; - } - const minimumMoveDelta = this.options.minimumMoveDelta; - return direction === Direction.Left || direction === Direction.Right ? minimumMoveDelta.x : minimumMoveDelta.y; - } - - getMinimumMoveVector(element: GModelElement, isSnap: boolean, move: Direction[]): Vector | undefined { - if (!isSnap) { - return undefined; - } - - const state = this.getHelperLineState(element); - if (state.helperLines.length === 0) { - return undefined; - } - - const minimum: Writable = { ...Vector.ZERO }; - const resize = - element instanceof GResizeHandle - ? ResizeHandleLocation.direction(element.location) - : [Direction.Left, Direction.Right, Direction.Up, Direction.Down]; - - if ((state.types.left || state.types.center) && move.includes(Direction.Left) && resize.includes(Direction.Left)) { - minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Left); - } else if ((state.types.right || state.types.center) && move.includes(Direction.Right) && resize.includes(Direction.Right)) { - minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Right); - } - if ((state.types.top || state.types.middle) && move.includes(Direction.Up) && resize.includes(Direction.Up)) { - minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Up); - } else if ((state.types.bottom || state.types.middle) && move.includes(Direction.Down) && resize.includes(Direction.Down)) { - minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Down); - } - return Vector.isZero(minimum) ? undefined : minimum; - } - - protected getHelperLineState(element: GModelElement): HelperLineState { - const helperLines = element.root.children.filter(isHelperLine) || []; - const types = { - left: false, - right: false, - top: false, - bottom: false, - center: false, - middle: false - }; - for (const line of helperLines) { - switch (line.lineType) { - case HelperLineType.Left: - case HelperLineType.LeftRight: - types.left = true; - break; - case HelperLineType.Right: - case HelperLineType.RightLeft: - types.right = true; - break; - case HelperLineType.Top: - case HelperLineType.TopBottom: - types.top = true; - break; - case HelperLineType.Bottom: - case HelperLineType.BottomTop: - types.bottom = true; - break; - case HelperLineType.Center: - types.center = true; - break; - case HelperLineType.Middle: - types.middle = true; - break; - } - } - return { helperLines, types }; - } -} - -export interface HelperLineState { - helperLines: HelperLine[]; - types: { - left: boolean; - right: boolean; - top: boolean; - bottom: boolean; - center: boolean; - middle: boolean; - }; -} diff --git a/packages/client/src/features/helper-lines/helper-line-manager.ts b/packages/client/src/features/helper-lines/helper-line-manager.ts index 5c00c88..e939113 100644 --- a/packages/client/src/features/helper-lines/helper-line-manager.ts +++ b/packages/client/src/features/helper-lines/helper-line-manager.ts @@ -13,8 +13,38 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, Vector } from '@eclipse-glsp/sprotty'; -import { Direction } from './model'; +import { + Action, + GModelElement, + GModelRoot, + IActionHandler, + MoveAction, + Point, + SetBoundsAction, + TYPES, + Vector, + Writable +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional, postConstruct } from 'inversify'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; +import { ISelectionListener, SelectionService } from '../../base/selection-service'; +import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; +import { GResizeHandle, ResizeHandleLocation } from '../change-bounds/model'; +import { Grid } from '../grid/grid'; +import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; +import { + AlignmentElementFilter, + DEFAULT_ALIGNABLE_ELEMENT_FILTER, + DEFAULT_DEBUG, + DEFAULT_ELEMENT_LINES, + DEFAULT_EPSILON, + DEFAULT_VIEWPORT_LINES, + DrawHelperLinesFeedbackAction, + RemoveHelperLinesFeedbackAction, + ViewportLineType +} from './helper-line-feedback'; +import { Direction, HelperLine, HelperLineType, isHelperLine } from './model'; export interface IHelperLineManager { /** @@ -35,3 +65,205 @@ export interface IHelperLineManager { */ getMinimumMoveVector(element: GModelElement, isSnap: boolean, directions: Direction[]): Vector | undefined; } + +export interface IHelperLineOptions { + /** + * A list of helper line types that should be rendered when elements are aligned. + * Defaults to all possible alignments. + */ + elementLines?: HelperLineType[]; + /** + * A list of helper line types that should be rendered when an element is aligned with the viewport. + * Defaults to middle and center alignment. + */ + viewportLines?: ViewportLineType[]; + /** + * The minimum difference between two coordinates + * Defaults to 1 or zero (perfect match) if the optional grid module is loaded. + */ + alignmentEpsilon?: number; + /** + * A filter that is applied to determine on which elements the alignment calculation is performed. + * By default all top-level bounds-aware, non-routable elements that are visible on the canvas are considered. + */ + alignmentElementFilter?: AlignmentElementFilter; + /** + * The minimum move delta that is necessary for an element to break through a helper line. + * Defaults to { x: 1, y: 1 } whereas the x represents the horizontal distance and y represents the vertical distance. + * If the optional grid module is loaded, defaults to twice the grid size, i.e., two grid moves to break through a helper line. + */ + minimumMoveDelta?: Point; + + /** + * Produces debug output. + * Defaults to false. + */ + debug?: boolean; +} + +export const DEFAULT_MOVE_DELTA = { x: 1, y: 1 }; + +export const DEFAULT_HELPER_LINE_OPTIONS: Required = { + elementLines: DEFAULT_ELEMENT_LINES, + viewportLines: DEFAULT_VIEWPORT_LINES, + alignmentEpsilon: DEFAULT_EPSILON, + alignmentElementFilter: DEFAULT_ALIGNABLE_ELEMENT_FILTER, + minimumMoveDelta: DEFAULT_MOVE_DELTA, + debug: DEFAULT_DEBUG +}; + +@injectable() +export class HelperLineManager implements IActionHandler, ISelectionListener, IHelperLineManager { + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(SelectionService) protected selectionService: SelectionService; + @optional() @inject(TYPES.IHelperLineOptions) protected userOptions?: IHelperLineOptions; + @optional() @inject(TYPES.Grid) protected grid?: Grid; + + protected options: Required; + protected feedback: FeedbackEmitter; + + @postConstruct() + protected init(): void { + this.feedback = this.feedbackDispatcher.createEmitter(); + const dynamicOptions: IHelperLineOptions = {}; + if (this.grid) { + dynamicOptions.alignmentEpsilon = 0; + dynamicOptions.minimumMoveDelta = Point.multiplyScalar(this.grid, 2); + } + this.options = { ...DEFAULT_HELPER_LINE_OPTIONS, ...dynamicOptions, ...this.userOptions }; + this.selectionService.addListener(this); + } + + handle(action: Action): void { + if (MoveInitializedEventAction.is(action)) { + this.handleMoveInitializedAction(action); + } else if (MoveAction.is(action)) { + this.handleMoveAction(action); + } else if (MoveFinishedEventAction.is(action)) { + this.handleMoveFinishedAction(action); + } else if (SetBoundsAction.is(action) || SetBoundsFeedbackAction.is(action)) { + this.handleSetBoundsAction(action); + } + } + + protected handleMoveInitializedAction(_action: MoveInitializedEventAction): void { + this.submitHelperLineFeedback(); + } + + protected handleMoveFinishedAction(_action: MoveFinishedEventAction): void { + this.feedback.dispose(); + } + + protected handleMoveAction(action: MoveAction): void { + if (!action.finished) { + this.submitHelperLineFeedback(action.moves.map(move => move.elementId)); + } else { + this.feedback.dispose(); + } + } + + protected submitHelperLineFeedback(elementIds: string[] = this.selectionService.getSelectedElementIDs()): void { + const feedback = this.createHelperLineFeedback(elementIds); + this.feedback.add(feedback, [RemoveHelperLinesFeedbackAction.create()]).submit(); + } + + protected createHelperLineFeedback(elementIds: string[]): DrawHelperLinesFeedbackAction { + return DrawHelperLinesFeedbackAction.create({ elementIds, ...this.options }); + } + + protected handleSetBoundsAction(action: SetBoundsAction | SetBoundsFeedbackAction): void { + this.submitHelperLineFeedback(action.bounds.map(bound => bound.elementId)); + } + + selectionChanged(root: Readonly, selectedElements: string[], deselectedElements?: string[] | undefined): void { + this.feedback.dispose(); + } + + getMinimumMoveDelta(element: GModelElement, isSnap: boolean, direction: Direction): number { + if (!isSnap) { + return 0; + } + const minimumMoveDelta = this.options.minimumMoveDelta; + return direction === Direction.Left || direction === Direction.Right ? minimumMoveDelta.x : minimumMoveDelta.y; + } + + getMinimumMoveVector(element: GModelElement, isSnap: boolean, move: Direction[]): Vector | undefined { + if (!isSnap) { + return undefined; + } + + const state = this.getHelperLineState(element); + if (state.helperLines.length === 0) { + return undefined; + } + + const minimum: Writable = { ...Vector.ZERO }; + const resize = + element instanceof GResizeHandle + ? ResizeHandleLocation.direction(element.location) + : [Direction.Left, Direction.Right, Direction.Up, Direction.Down]; + + if ((state.types.left || state.types.center) && move.includes(Direction.Left) && resize.includes(Direction.Left)) { + minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Left); + } else if ((state.types.right || state.types.center) && move.includes(Direction.Right) && resize.includes(Direction.Right)) { + minimum.x = this.getMinimumMoveDelta(element, isSnap, Direction.Right); + } + if ((state.types.top || state.types.middle) && move.includes(Direction.Up) && resize.includes(Direction.Up)) { + minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Up); + } else if ((state.types.bottom || state.types.middle) && move.includes(Direction.Down) && resize.includes(Direction.Down)) { + minimum.y = this.getMinimumMoveDelta(element, isSnap, Direction.Down); + } + return Vector.isZero(minimum) ? undefined : minimum; + } + + protected getHelperLineState(element: GModelElement): HelperLineState { + const helperLines = element.root.children.filter(isHelperLine) || []; + const types = { + left: false, + right: false, + top: false, + bottom: false, + center: false, + middle: false + }; + for (const line of helperLines) { + switch (line.lineType) { + case HelperLineType.Left: + case HelperLineType.LeftRight: + types.left = true; + break; + case HelperLineType.Right: + case HelperLineType.RightLeft: + types.right = true; + break; + case HelperLineType.Top: + case HelperLineType.TopBottom: + types.top = true; + break; + case HelperLineType.Bottom: + case HelperLineType.BottomTop: + types.bottom = true; + break; + case HelperLineType.Center: + types.center = true; + break; + case HelperLineType.Middle: + types.middle = true; + break; + } + } + return { helperLines, types }; + } +} + +export interface HelperLineState { + helperLines: HelperLine[]; + types: { + left: boolean; + right: boolean; + top: boolean; + bottom: boolean; + center: boolean; + middle: boolean; + }; +} diff --git a/packages/client/src/features/helper-lines/helper-line-module.ts b/packages/client/src/features/helper-lines/helper-line-module.ts index d12b56f..0b02bb8 100644 --- a/packages/client/src/features/helper-lines/helper-line-module.ts +++ b/packages/client/src/features/helper-lines/helper-line-module.ts @@ -27,7 +27,7 @@ import { import { SetBoundsFeedbackAction } from '../bounds/set-bounds-feedback-command'; import { MoveFinishedEventAction, MoveInitializedEventAction } from '../tools/change-bounds/change-bounds-tool-feedback'; import { DrawHelperLinesFeedbackCommand, RemoveHelperLinesFeedbackCommand } from './helper-line-feedback'; -import { HelperLineManager } from './helper-line-manager-default'; +import { HelperLineManager } from './helper-line-manager'; import { HELPER_LINE, HelperLine, SELECTION_BOUNDS, SelectionBounds } from './model'; import { HelperLineView, SelectionBoundsView } from './view'; diff --git a/packages/client/src/features/hints/type-hint-provider.spec.ts b/packages/client/src/features/hints/type-hint-provider.spec.ts index 228c33b..38d2549 100644 --- a/packages/client/src/features/hints/type-hint-provider.spec.ts +++ b/packages/client/src/features/hints/type-hint-provider.spec.ts @@ -36,7 +36,7 @@ import { expect } from 'chai'; import { Container } from 'inversify'; import * as sinon from 'sinon'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; -import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher-default'; +import { FeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { GEdge } from '../../model'; import { isResizable } from '../change-bounds/model'; diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index b255d33..5dd308e 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -38,7 +38,7 @@ import { hasStringProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EditorContextServiceProvider } from '../../base/editor-context-service'; +import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; import { NavigationTargetResolver } from './navigation-target-resolver'; /** @@ -140,9 +140,15 @@ export class NavigationActionHandler implements IActionHandler { @inject(TYPES.ILogger) protected logger: ILogger; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + /** @deprecated No longer in used. The {@link ActionHandlerRegistry} is now directly injected */ + // eslint-disable-next-line deprecation/deprecation @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; + /** @deprecated No longer in used. The {@link EditorContextService} is now directly injected */ + // eslint-disable-next-line deprecation/deprecation @inject(TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; + @inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry; @inject(NavigationTargetResolver) protected resolver: NavigationTargetResolver; + @inject(EditorContextService) protected editorContext: EditorContextService; handle(action: Action): ICommand | Action | void { if (NavigateAction.is(action)) { @@ -158,8 +164,7 @@ export class NavigationActionHandler implements IActionHandler { protected async handleNavigateAction(action: NavigateAction): Promise { try { - const editorContextService = await this.editorContextService(); - const editorContext = editorContextService.get(action.args); + const editorContext = this.editorContext.get(action.args); const response = await this.dispatcher.request( RequestNavigationTargetsAction.create({ targetTypeId: action.targetTypeId, editorContext }) ); @@ -250,8 +255,7 @@ export class NavigationActionHandler implements IActionHandler { } protected async handleNavigateToExternalTarget(action: NavigateToExternalTargetAction): Promise { - const registry = await this.actionHandlerRegistryProvider(); - const handlers = registry.get(NavigateToExternalTargetAction.KIND); + const handlers = this.actionHandlerRegistry.get(NavigateToExternalTargetAction.KIND); if (handlers.length === 1) { // we are the only handler so we know nobody took care of it this.warnAboutFailedNavigation('Could not resolve or navigate to target', action.target); diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 4b3fcb1..164f4f9 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Disposable, DisposableCollection, IActionDispatcher, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, Disposable, DisposableCollection, IActionDispatcher, IActionHandler, MaybeActions, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; -import { IFeedbackActionDispatcher, IFeedbackEmitter, MaybeActions } from '../../base/feedback/feedback-action-dispatcher'; +import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../../base/feedback/feedback-action-dispatcher'; import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; import { EnableToolsAction, Tool } from '../../base/tool-manager/tool'; import { GLSPKeyTool } from '../../base/view/key-tool'; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 49d62a4..bc980f3 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -27,7 +27,6 @@ export * from './base/drag-aware-mouse-listener'; export * from './base/editor-context-service'; export * from './base/feedback/css-feedback'; export * from './base/feedback/feedback-action-dispatcher'; -export * from './base/feedback/feedback-action-dispatcher-default'; export * from './base/feedback/feedback-command'; export * from './base/feedback/feedback-emitter'; export * from './base/feedback/set-model-command'; @@ -145,7 +144,6 @@ export * from './features/grid/grid-snapper'; export * from './features/grid/grid-style'; export * from './features/helper-lines/helper-line-feedback'; export * from './features/helper-lines/helper-line-manager'; -export * from './features/helper-lines/helper-line-manager-default'; export * from './features/helper-lines/helper-line-module'; export * from './features/helper-lines/model'; export * from './features/helper-lines/view'; diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 27ac94c..95b4381 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable deprecation/deprecation */ import { TYPES as SprottyTYPES } from 'sprotty'; /** @@ -21,10 +22,12 @@ import { TYPES as SprottyTYPES } from 'sprotty'; */ const IGModelRootListener = Symbol('IGModelRootListener'); - export const TYPES = { ...SprottyTYPES, + /** @deprecated Using async providers for container service retrieval is discouraged. Use the `LazyInjector` instead */ + ActionHandlerRegistryProvider: SprottyTYPES.ActionHandlerRegistryProvider, IAsyncClipboardService: Symbol('IAsyncClipboardService'), + /** @deprecated Async provider is no longer necessary. Either directly inject or use `LazyInjector`*/ IEditorContextServiceProvider: Symbol('IEditorContextProvider'), IFeedbackActionDispatcher: Symbol('IFeedbackActionDispatcher'), IToolFactory: Symbol('Factory'), @@ -33,7 +36,6 @@ export const TYPES = { IMovementOptions: Symbol('IMovementOptions'), ISelectionListener: Symbol('ISelectionListener'), /** @deprecated Use {@link TYPES.IGModelRootListener} instead */ - // eslint-disable-next-line deprecation/deprecation ISModelRootListener: IGModelRootListener, IGModelRootListener: IGModelRootListener, IContextMenuProvider: Symbol('IContextMenuProvider'), diff --git a/packages/protocol/src/utils/type-util.spec.ts b/packages/protocol/src/utils/type-util.spec.ts index 8922a75..fbc34a4 100644 --- a/packages/protocol/src/utils/type-util.spec.ts +++ b/packages/protocol/src/utils/type-util.spec.ts @@ -14,7 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { expect } from 'chai'; -import { AnyObject, hasArrayProp, hasBooleanProp, hasFunctionProp, hasNumberProp, hasObjectProp, hasStringProp } from './type-util'; +import { Action } from '../action-protocol/base-protocol'; +import { + AnyObject, + hasArrayProp, + hasBooleanProp, + hasFunctionProp, + hasNumberProp, + hasObjectProp, + hasStringProp, + MaybeActions +} from './type-util'; describe('TypeUtil', () => { describe('AnyObject', () => { @@ -192,4 +202,25 @@ describe('TypeUtil', () => { expect(hasArrayProp({ someProp: ['some', 'prop'] }, 'someProp', true)).to.be.true; }); }); + + describe('MaybeActions', () => { + describe('asArray', () => { + it('should return an empty array if undefined is provided', () => { + expect(MaybeActions.asArray(undefined)).to.deep.equal([]); + }); + it('should return an array with a single action if a single action is provided', () => { + const action = { kind: 'someAction' }; + expect(MaybeActions.asArray(action)).to.deep.equal([action]); + }); + it('should return the same array if an array of actions is provided', () => { + const actions = [{ kind: 'action1' }, { kind: 'action2' }]; + expect(MaybeActions.asArray(actions)).to.deep.equal(actions); + }); + it('should return the result of the function if a function returning actions is provided', () => { + const actions = [{ kind: 'action1' }, { kind: 'action2' }]; + const actionFunction = (): Action[] => actions; + expect(MaybeActions.asArray(actionFunction)).to.deep.equal(actions); + }); + }); + }); }); diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index f157a5f..78890ac 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -14,6 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '../action-protocol/base-protocol'; +import { asArray as toArray } from '../utils/array-util'; + /** Helper type to describe any defined object*/ export type AnyObject = object; @@ -101,6 +104,15 @@ export function call(maybeFun: MaybeFunction, ...args: any[]): T { return typeof maybeFun === 'function' ? (maybeFun as SafeFunction)(...args) : maybeFun; } +export type MaybeActions = MaybeFunction; + +export namespace MaybeActions { + export function asArray(actions?: MaybeActions): Action[] { + const cleanup = actions ? call(actions) : []; + return cleanup ? toArray(cleanup) : []; + } +} + /** * Validates whether the given object has a property of type `string` with the given key. * @param object The object that should be validated From 01e32b191b26ba259d0dd8d768af6af8176e448c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 6 Dec 2024 11:19:48 +0100 Subject: [PATCH 408/566] GLSP-1438: Improve GLSPClient default implementations (#402) - Introduce `onCurrentStateChanged` event for `GLSPClient` - Ensure that `start` and `initializeServer` behave as expected if they are called again while the previous promise is still pending. - Restructure members of the default implementations so that get/setters and their _property are grouped together - Introduce a dedicated namespace for the `Event` interface with utility functions to - listen on a certain event exactly once - waitUntil a certain event is fired the next time - Adapt test cases to verify new behavior Also: - Update `GLSPActionDispatcher` to use direct injection for registry instead of async provider --- packages/client/src/base/action-dispatcher.ts | 16 ++- .../base-glsp-client.spec.ts | 54 +++++++--- .../base-glsp-client.ts | 66 +++++++++---- .../src/client-server-protocol/glsp-client.ts | 5 + .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 89 +++++++++++++++-- .../jsonrpc/base-jsonrpc-glsp-client.ts | 99 ++++++++++++------- packages/protocol/src/utils/event.spec.ts | 70 +++++++++++++ packages/protocol/src/utils/event.ts | 43 ++++++++ 8 files changed, 366 insertions(+), 76 deletions(-) create mode 100644 packages/protocol/src/utils/event.spec.ts diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index bdd3635..d6c7aa8 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -16,12 +16,14 @@ import { Action, ActionDispatcher, + ActionHandlerRegistry, EMPTY_ROOT, GModelRoot, IActionDispatcher, RequestAction, ResponseAction, - SetModelAction + SetModelAction, + TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { GLSPActionHandlerRegistry } from './action-handler-registry'; @@ -37,6 +39,12 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo @inject(ModelInitializationConstraint) protected initializationConstraint: ModelInitializationConstraint; + @inject(ActionHandlerRegistry) + protected override actionHandlerRegistry: ActionHandlerRegistry; + + /** @deprecated No longer in used. The {@link ActionHandlerRegistry} is now directly injected */ + // eslint-disable-next-line deprecation/deprecation + @inject(TYPES.ActionHandlerRegistryProvider) protected override actionHandlerRegistryProvider: () => Promise; protected postUpdateQueue: Action[] = []; override initialize(): Promise { @@ -47,10 +55,8 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo } protected async doInitialize(): Promise { - const registry = await this.actionHandlerRegistryProvider(); - this.actionHandlerRegistry = registry; - if (registry instanceof GLSPActionHandlerRegistry) { - registry.initialize(); + if (this.actionHandlerRegistry instanceof GLSPActionHandlerRegistry) { + this.actionHandlerRegistry.initialize(); } this.handleAction(SetModelAction.create(EMPTY_ROOT)).catch(() => { /* Logged in handleAction method */ diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index d8f1683..d24ac73 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -70,31 +70,48 @@ describe('Node GLSP Client', () => { describe('start', () => { it('should fail if no server is configured', async () => { resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); client.setStartupTimeout(5); await expectToThrowAsync(() => client.start()); expect(client.currentState).to.be.equal(ClientState.StartFailed); + expect(stateChangeHandler.calledWith(ClientState.StartFailed)).to.be.true; }); it('Should resolve when server is configured', async () => { resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); + const started = client.start(); + expect(client.currentState).to.be.equal(ClientState.Starting); + expect(stateChangeHandler.calledWith(ClientState.Starting)).to.be.true; client.configureServer(server); - const result = await client.start(); - expect(result).to.be.undefined; + await started; expect(client.currentState).to.be.equal(ClientState.Running); + expect(stateChangeHandler.calledWith(ClientState.Running)).to.be.true; }); }); describe('stop & onStop', () => { - beforeEach(() => resetClient()); it('onStop should not resolve if stop has not been called', () => { + resetClient(); expect(util.inspect(client.onStop())).to.include('pending'); }); it('should be in stopped state and onStop should resolve', async () => { + resetClient(); expect(client.currentState).to.be.not.equal(ClientState.Stopped); - const stopResult = await client.stop(); - expect(stopResult).to.be.undefined; + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); + await client.stop(); + expect(client.currentState).to.be.equal(ClientState.Stopped); + expect(stateChangeHandler.calledWith(ClientState.Stopping)).to.be.true; + expect(stateChangeHandler.calledWith(ClientState.Stopped)).to.be.true; + expect(server.shutdown.calledOnce).to.be.true; + }); + it('should only stop a running client once, if stop is called multiple times ', async () => { + resetClient(); + client.stop(); + await client.stop(); expect(client.currentState).to.be.equal(ClientState.Stopped); - const onStopResult = await client.onStop(); - expect(onStopResult).to.be.undefined; expect(server.shutdown.calledOnce).to.be.true; }); }); @@ -104,12 +121,14 @@ describe('Node GLSP Client', () => { resetClient(false); await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); expect(server.initialize.called).to.be.false; + expect(client.initializeResult).to.be.undefined; }); it('should fail if client is not running', async () => { resetClient(false); client.configureServer(server); await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); expect(server.initialize.called).to.be.false; + expect(client.initializeResult).to.be.undefined; }); it('should invoke the corresponding server method', async () => { resetClient(); @@ -122,17 +141,17 @@ describe('Node GLSP Client', () => { expect(client.initializeResult).to.be.equal(result); }); it('should return cached result on consecutive invocation', async () => { - await resetClient(); + resetClient(); const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; const params = { applicationId: 'id', protocolVersion: '1.0.0' }; server.initialize.returns(Promise.resolve(expectedResult)); - client['_initializeResult'] = expectedResult; + client.initializeServer(params); const result = await client.initializeServer(params); expect(result).to.be.deep.equal(client.initializeResult); - expect(server.initialize.called).to.be.false; + expect(server.initialize.calledOnce).to.be.true; }); it('should fire event on first invocation', async () => { - await resetClient(); + resetClient(); const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; const params = { applicationId: 'id', protocolVersion: '1.0.0' }; server.initialize.returns(Promise.resolve(expectedResult)); @@ -144,6 +163,19 @@ describe('Node GLSP Client', () => { await client.initializeServer(params); expect(eventHandlerSpy.calledOnceWith(expectedResult)).to.be.true; }); + it('should not use cached result on consecutive invocation if previous invocation errored', async () => { + resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + server.initialize.throws(new Error('error')); + expectToThrowAsync(() => client.initializeServer(params)); + expect(client.initializeResult).to.be.undefined; + server.initialize.returns(Promise.resolve(expectedResult)); + const result = await client.initializeServer(params); + expect(result).to.be.deep.equal(expectedResult); + expect(server.initialize.calledTwice).to.be.true; + expect(client.initializeResult).to.be.equal(result); + }); }); describe('initializeClientSession', () => { diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts index d8cc78c..084b271 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -30,21 +30,49 @@ export const GLOBAL_HANDLER_ID = '*'; * directly communicates with a given {@link GLSPServer} instance. */ export class BaseGLSPClient implements GLSPClient { - protected state: ClientState; - protected _server?: GLSPServer; protected serverDeferred = new Deferred(); protected onStartDeferred = new Deferred(); protected onStopDeferred = new Deferred(); readonly proxy: GLSPClientProxy; protected startupTimeout = 1500; protected actionMessageHandlers: Map = new Map([[GLOBAL_HANDLER_ID, []]]); - protected _initializeResult: InitializeResult; + protected pendingServerInitialize?: Promise; protected onServerInitializedEmitter = new Emitter(); get onServerInitialized(): Event { return this.onServerInitializedEmitter.event; } + protected onCurrentStateChangedEmitter = new Emitter(); + get onCurrentStateChanged(): Event { + return this.onCurrentStateChangedEmitter.event; + } + + protected _state: ClientState; + protected set state(state: ClientState) { + if (this._state !== state) { + this._state = state; + this.onCurrentStateChangedEmitter.fire(state); + } + } + protected get state(): ClientState { + return this._state; + } + + protected _server?: GLSPServer; + protected get checkedServer(): GLSPServer { + this.checkState(); + if (!this._server) { + throw new Error(`No server is configured for GLSPClient with id '${this.id}'`); + } + return this._server; + } + + protected _initializeResult?: InitializeResult; + get initializeResult(): InitializeResult | undefined { + return this._initializeResult; + } + constructor(protected options: GLSPClient.Options) { this.state = ClientState.Initial; this.proxy = this.createProxy(); @@ -71,7 +99,7 @@ export class BaseGLSPClient implements GLSPClient { } start(): Promise { - if (this.state === ClientState.Running) { + if (this.state === ClientState.Running || this.state === ClientState.Starting) { return this.onStartDeferred.promise; } @@ -96,15 +124,25 @@ export class BaseGLSPClient implements GLSPClient { } async initializeServer(params: InitializeParameters): Promise { - if (!this._initializeResult) { + if (this.initializeResult) { + return this.initializeResult; + } else if (this.pendingServerInitialize) { + return this.pendingServerInitialize; + } + + const initializeDeferred = new Deferred(); + try { + this.pendingServerInitialize = initializeDeferred.promise; this._initializeResult = await this.checkedServer.initialize(params); this.onServerInitializedEmitter.fire(this._initializeResult); + initializeDeferred.resolve(this._initializeResult); + this.pendingServerInitialize = undefined; + } catch (error) { + initializeDeferred.reject(error); + this._initializeResult = undefined; + this.pendingServerInitialize = undefined; } - return this._initializeResult; - } - - get initializeResult(): InitializeResult | undefined { - return this._initializeResult; + return initializeDeferred.promise; } initializeClientSession(params: InitializeClientSessionParameters): Promise { @@ -174,14 +212,6 @@ export class BaseGLSPClient implements GLSPClient { } } - protected get checkedServer(): GLSPServer { - this.checkState(); - if (!this._server) { - throw new Error(`No server is configured for GLSPClient with id '${this.id}'`); - } - return this._server; - } - setStartupTimeout(ms: number): void { this.startupTimeout = ms; } diff --git a/packages/protocol/src/client-server-protocol/glsp-client.ts b/packages/protocol/src/client-server-protocol/glsp-client.ts index 177380d..c7dc062 100644 --- a/packages/protocol/src/client-server-protocol/glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/glsp-client.ts @@ -74,6 +74,10 @@ export interface GLSPClient { * Current client state. */ readonly currentState: ClientState; + /** + * Event that is fired whenever the client state changes. + */ + readonly onCurrentStateChanged: Event; /** * Initializes the client and the server connection. During the start procedure the client is in the @@ -134,6 +138,7 @@ export interface GLSPClient { /** * Stops the client and disposes unknown resources. During the stop procedure the client is in the `Stopping` state and will * transition to either `Stopped` or `ServerError`. + * Calling the method if client is already stopped has no effect. * * @returns A promise that resolves after the server was stopped and disposed. */ diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 5538f76..6d2290f 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -94,39 +94,78 @@ describe('Base JSON-RPC GLSP Client', () => { describe('start', () => { it('should successfully start & activate the connection', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); expect(client.currentState).to.be.equal(ClientState.Initial); - client.start(); + const startCompleted = client.start(); expect(client.currentState).to.be.equal(ClientState.Starting); - const result = await client.start(); - expect(result).to.be.undefined; + expect(stateChangeHandler.calledWith(ClientState.Starting)).to.be.true; + await startCompleted; expect(client.currentState).to.be.equal(ClientState.Running); expect(client.isConnectionActive()).to.be.true; + expect(stateChangeHandler.calledWith(ClientState.Running)).to.be.true; + }); + it('should fail to start if connecting to the server fails', async () => { + await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); + expect(client.currentState).to.be.equal(ClientState.Initial); + connection.listen.throws(new Error('Connection failed')); + await client.start(); + expect(client.currentState).to.be.equal(ClientState.StartFailed); + expect(stateChangeHandler.calledWith(ClientState.StartFailed)).to.be.true; + }); + it('should not start another connection if another start is already in progress', async () => { + await resetClient(false); + client.start(); + await client.start(); + expect(client.currentState).to.be.equal(ClientState.Running); + expect(client.isConnectionActive()).to.be.true; + expect(connection.listen.calledOnce).to.be.true; }); }); describe('stop', () => { it('should successfully stop if the client was not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); expect(client.currentState).to.be.equal(ClientState.Initial); - const stopResult = await client.stop(); - expect(stopResult).to.be.undefined; + await client.stop(); expect(client.currentState).to.be.equal(ClientState.Stopped); + expect(stateChangeHandler.calledWith(ClientState.Stopped)).to.be.true; + expect(connection.dispose.called).to.be.false; }); it('should successfully stop if the client was running', async () => { + await resetClient(); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); + const stopped = client.stop(); + expect(client.currentState).to.be.equal(ClientState.Stopping); + expect(stateChangeHandler.calledWith(ClientState.Stopping)).to.be.true; + await stopped; + expect(client.currentState).to.be.equal(ClientState.Stopped); + expect(stateChangeHandler.calledWith(ClientState.Stopped)).to.be.true; + expect(connection.dispose.called).to.be.true; + }); + it('should only stop a running client once, if stop is called multiple times', async () => { await resetClient(); client.stop(); expect(client.currentState).to.be.equal(ClientState.Stopping); - const stopResult = await client.stop(); - expect(stopResult).to.be.undefined; + await client.stop(); expect(client.currentState).to.be.equal(ClientState.Stopped); + expect(connection.dispose.calledOnce).to.be.true; }); }); describe('initialize', () => { it('should fail if client is not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); await expectToThrowAsync(() => client.initializeServer({ applicationId: '', protocolVersion: '' })); expect(connection.sendRequest.called).to.be.false; + expect(stateChangeHandler.called).to.be.false; }); it('should forward the corresponding initialize request and cache result', async () => { await resetClient(); @@ -146,10 +185,10 @@ describe('Base JSON-RPC GLSP Client', () => { const params = { applicationId: 'id', protocolVersion: '1.0.0' }; const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); initializeMock.returns(expectedResult); - client['_initializeResult'] = expectedResult; + client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); const result = await client.initializeServer({ applicationId: 'id', protocolVersion: '1.0.0' }); expect(result).to.be.deep.equal(client.initializeResult); - expect(initializeMock.called).to.be.false; + expect(initializeMock.calledOnce).to.be.true; }); it('should fire event on first invocation', async () => { await resetClient(); @@ -164,13 +203,30 @@ describe('Base JSON-RPC GLSP Client', () => { await client.initializeServer(params); expect(eventHandlerSpy.calledOnceWith(expectedResult)).to.be.true; }); + it('should not use cached result on consecutive invocation if previous invocation errored', async () => { + await resetClient(); + const expectedResult = { protocolVersion: '1.0.0', serverActions: {} }; + const params = { applicationId: 'id', protocolVersion: '1.0.0' }; + const initializeMock = connection.sendRequest.withArgs(JsonrpcGLSPClient.InitializeRequest, params); + initializeMock.throws(new Error('SomeError')); + expectToThrowAsync(() => client.initializeServer(params)); + expect(client.initializeResult).to.be.undefined; + initializeMock.returns(expectedResult); + const result = await client.initializeServer(params); + expect(result).to.be.deep.equal(expectedResult); + expect(initializeMock.calledTwice).to.be.true; + expect(client.initializeResult).to.be.equal(result); + }); }); describe('initializeClientSession', () => { it('should fail if client is not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); await expectToThrowAsync(() => client.initializeClientSession({ clientSessionId: '', diagramType: '', clientActionKinds: [] })); expect(connection.sendRequest.called).to.be.false; + expect(stateChangeHandler.called).to.be.false; }); it('should invoke the corresponding server method', async () => { await resetClient(); @@ -185,8 +241,11 @@ describe('Base JSON-RPC GLSP Client', () => { describe('disposeClientSession', () => { it('should fail if client is not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); await expectToThrowAsync(() => client.disposeClientSession({ clientSessionId: '' })); expect(connection.sendRequest.called).to.be.false; + expect(stateChangeHandler.called).to.be.false; }); it('should invoke the corresponding server method', async () => { await resetClient(); @@ -201,8 +260,11 @@ describe('Base JSON-RPC GLSP Client', () => { describe('shutdownServer', () => { it('should fail if client is not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); expect(() => client.shutdownServer()).to.throw(); expect(connection.sendNotification.called).to.be.false; + expect(stateChangeHandler.called).to.be.false; }); it('should invoke the corresponding server method', async () => { await resetClient(); @@ -216,8 +278,11 @@ describe('Base JSON-RPC GLSP Client', () => { describe('sendActionMessage', () => { it('should fail if client is not running', async () => { await resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); expect(() => client.sendActionMessage({ action: { kind: '' }, clientId: '' })).to.throw(); expect(connection.sendNotification.called).to.be.false; + expect(stateChangeHandler.called).to.be.false; }); it('should invoke the corresponding server method', async () => { await resetClient(); @@ -257,6 +322,8 @@ describe('Base JSON-RPC GLSP Client', () => { it('Should be in error state after connection error', async () => { // mock setup resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); const listeners: ((e: unknown) => unknown)[] = []; connection.onError.callsFake(listener => { listeners.push(listener); @@ -266,10 +333,13 @@ describe('Base JSON-RPC GLSP Client', () => { await client.start(); listeners.forEach(listener => listener(new Error('SomeError'))); expect(client.currentState).to.be.equal(ClientState.ServerError); + expect(stateChangeHandler.calledWith(ClientState.ServerError)).to.be.true; }); it('Should be in error state after connection close while running', async () => { // mock setup resetClient(false); + const stateChangeHandler = sinon.spy(); + client.onCurrentStateChanged(stateChangeHandler); const listeners: ((e: unknown) => unknown)[] = []; connection.onClose.callsFake(listener => { listeners.push(listener); @@ -279,6 +349,7 @@ describe('Base JSON-RPC GLSP Client', () => { await client.start(); listeners.forEach(listener => listener(undefined)); expect(client.currentState).to.be.equal(ClientState.ServerError); + expect(stateChangeHandler.calledWith(ClientState.ServerError)).to.be.true; }); }); }); diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts index 6577a95..40e3d96 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Deferred } from 'sprotty-protocol'; import { Disposable, Message, MessageConnection } from 'vscode-jsonrpc'; import { ActionMessage } from '../../action-protocol/base-protocol'; import { Emitter, Event } from '../../utils/event'; @@ -26,9 +27,8 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { protected readonly connectionProvider: ConnectionProvider; protected connectionPromise?: Promise; protected resolvedConnection?: MessageConnection; - protected state: ClientState; protected onStop?: Promise; - protected _initializeResult: InitializeResult | undefined; + protected pendingServerInitialize?: Promise; protected onServerInitializedEmitter = new Emitter(); get onServerInitialized(): Event { @@ -40,25 +40,69 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { return this.onActionMessageNotificationEmitter.event; } + protected onCurrentStateChangedEmitter = new Emitter(); + get onCurrentStateChanged(): Event { + return this.onCurrentStateChangedEmitter.event; + } + + protected _state: ClientState; + protected set state(state: ClientState) { + if (this._state !== state) { + this._state = state; + this.onCurrentStateChangedEmitter.fire(state); + } + } + protected get state(): ClientState { + return this._state; + } + + protected _initializeResult?: InitializeResult; + get initializeResult(): InitializeResult | undefined { + return this._initializeResult; + } constructor(options: JsonrpcGLSPClient.Options) { - Object.assign(this, options); + this.connectionProvider = options.connectionProvider; this.state = ClientState.Initial; } - - shutdownServer(): void { - this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification); + async start(): Promise { + if (this.state === ClientState.Running || this.state === ClientState.StartFailed) { + return; + } else if (this.state === ClientState.Starting) { + await Event.waitUntil(this.onCurrentStateChanged, state => state === ClientState.Running || state === ClientState.StartFailed); + return; + } + try { + this.state = ClientState.Starting; + const connection = await this.resolveConnection(); + connection.listen(); + this.resolvedConnection = connection; + this.state = ClientState.Running; + } catch (error) { + JsonrpcGLSPClient.error('Failed to start connection to server', error); + this.state = ClientState.StartFailed; + } } async initializeServer(params: InitializeParameters): Promise { - if (!this._initializeResult) { + if (this.initializeResult) { + return this.initializeResult; + } else if (this.pendingServerInitialize) { + return this.pendingServerInitialize; + } + + const initializeDeferred = new Deferred(); + try { + this.pendingServerInitialize = initializeDeferred.promise; this._initializeResult = await this.checkedConnection.sendRequest(JsonrpcGLSPClient.InitializeRequest, params); this.onServerInitializedEmitter.fire(this._initializeResult); + initializeDeferred.resolve(this._initializeResult); + this.pendingServerInitialize = undefined; + } catch (error) { + initializeDeferred.reject(error); + this._initializeResult = undefined; + this.pendingServerInitialize = undefined; } - return this._initializeResult; - } - - get initializeResult(): InitializeResult | undefined { - return this._initializeResult; + return initializeDeferred.promise; } initializeClientSession(params: InitializeClientSessionParameters): Promise { @@ -81,27 +125,8 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { this.checkedConnection.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); } - protected get checkedConnection(): MessageConnection { - if (!this.isConnectionActive()) { - throw new Error(JsonrpcGLSPClient.ClientNotReadyMsg); - } - return this.resolvedConnection!; - } - - async start(): Promise { - if (this.state === ClientState.Running) { - return; - } - try { - this.state = ClientState.Starting; - const connection = await this.resolveConnection(); - connection.listen(); - this.resolvedConnection = connection; - this.state = ClientState.Running; - } catch (error) { - JsonrpcGLSPClient.error('Failed to start connection to server', error); - this.state = ClientState.StartFailed; - } + shutdownServer(): void { + this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification); } stop(): Promise { @@ -118,11 +143,19 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { this.state = ClientState.Stopped; this.onStop = undefined; this.onActionMessageNotificationEmitter.dispose(); + this.onCurrentStateChangedEmitter.dispose(); this.connectionPromise = undefined; this.resolvedConnection = undefined; })); } + protected get checkedConnection(): MessageConnection { + if (!this.isConnectionActive()) { + throw new Error(JsonrpcGLSPClient.ClientNotReadyMsg); + } + return this.resolvedConnection!; + } + protected resolveConnection(): Promise { if (!this.connectionPromise) { this.connectionPromise = this.doCreateConnection(); diff --git a/packages/protocol/src/utils/event.spec.ts b/packages/protocol/src/utils/event.spec.ts new file mode 100644 index 0000000..6d114fd --- /dev/null +++ b/packages/protocol/src/utils/event.spec.ts @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { Emitter, Event } from './event'; + +describe('Event', () => { + let emitter: Emitter; + + beforeEach(() => { + emitter = new Emitter(); + }); + + describe('once', () => { + it('should invoke the listener when the event is fired', () => { + const listener = sinon.spy((e: string) => {}); + Event.once(emitter.event, listener); + emitter.fire('test'); + expect(listener.calledOnce).to.be.true; + expect(listener.calledWith('test')).to.be.true; + }); + it('should invoke the listener only once when the event is fired multiple times', () => { + const listener = sinon.spy((e: string) => {}); + Event.once(emitter.event, listener); + emitter.fire('test'); + emitter.fire('test1'); + expect(listener.calledOnce).to.be.true; + expect(listener.calledWith('test')).to.be.true; + }); + it('should not invoke the listener when its disposed before the event fired', () => { + const listener = sinon.spy((e: string) => {}); + const disposable = Event.once(emitter.event, listener); + disposable.dispose(); + emitter.fire('test'); + expect(listener.called).to.be.false; + }); + }); + + describe('waitUntil', () => { + it('should resolve the promise when the event is fired', async () => { + const promise = Event.waitUntil(emitter.event); + emitter.fire('test'); + const result = await promise; + expect(result).to.equal('test'); + }); + + it('should resolve the promise only when the predicate matches', async () => { + const predicate = (e: string): boolean => e === 'match'; + const promise = Event.waitUntil(emitter.event, predicate); + emitter.fire('no-match'); + emitter.fire('match'); + const result = await promise; + expect(result).to.equal('match'); + }); + }); +}); diff --git a/packages/protocol/src/utils/event.ts b/packages/protocol/src/utils/event.ts index 3505f4c..ef61fa8 100644 --- a/packages/protocol/src/utils/event.ts +++ b/packages/protocol/src/utils/event.ts @@ -46,6 +46,49 @@ export interface Event extends jsonrpc.Event { (listener: (e: T) => unknown, thisArgs?: unknown, disposables?: Disposable[]): Disposable; } +export namespace Event { + /** + * Utility function to register a one-time listener for an event. The listener will be disposed + * automatically after the next event is fired. + * @param event The event to listen to + * @param listener The listener function that will be called when the event happens. + * @param thisArgs The 'this' which will be used when calling the event listener. + * @param disposables An array to which the {@link Disposable} for removing the listener will be added. + * @returns a {@link Disposable} to remove the listener again. + */ + export function once(event: Event, listener: (e: T) => unknown, thisArgs?: unknown, disposables?: Disposable[]): Disposable { + const toDispose = event( + e => { + listener(e); + toDispose.dispose(); + }, + thisArgs, + disposables + ); + return toDispose; + } + + /** + * Utility function to wait for an event to happen. The function will return a promise that will be resolved + * when the event is fired. Optionally a predicate can be provided that will be used to filter the event. + * If a predicate is provided, the promise will only be resolved when the predicate returns true. + * The underlying listener will be disposed automatically when the promise is resolved. + * @param event The event to listen to + * @param predicate An optional predicate that will be used to filter the event + * @returns a promise that will be resolved when the event is fired (and the optional predicate matches) + */ + export function waitUntil(event: Event, predicate?: (e: T) => boolean): Promise { + return new Promise(resolve => { + const toDispose = event(e => { + if (!predicate || predicate(e)) { + resolve(e); + toDispose.dispose(); + } + }); + }); + } +} + /** * Optional options that can be passed to the constructor * of an {@link Emitter}. From 84f0df5390e12cb7941f23d82f53d6b27442e2d4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 6 Dec 2024 12:32:14 +0100 Subject: [PATCH 409/566] Remove workaround in GLPSManhattanEdgeRouter (#404) Remove the override of `applyInnerHandleMoves` in `GLSPManhattanEdgeRouter`. This workaround was introduced as a fix for https://github.com/eclipse-glsp/glsp-client/pull/200 but has since then also been fixed in base sprotty: https://github.com/eclipse-sprotty/sprotty/issues/310 therefore overriding is no longer necessary. --- .../src/features/routing/edge-router.ts | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/packages/client/src/features/routing/edge-router.ts b/packages/client/src/features/routing/edge-router.ts index c2ebdc5..9e14001 100644 --- a/packages/client/src/features/routing/edge-router.ts +++ b/packages/client/src/features/routing/edge-router.ts @@ -22,9 +22,7 @@ import { GRoutableElement, ManhattanEdgeRouter, Point, - PolylineEdgeRouter, - ResolvedHandleMove, - almostEquals + PolylineEdgeRouter } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; @@ -86,46 +84,6 @@ export class GLSPManhattanEdgeRouter extends ManhattanEdgeRouter { return Point.isValid(anchor) ? anchor : refPoint; } - protected override applyInnerHandleMoves(edge: GRoutableElement, moves: ResolvedHandleMove[]): void { - const route = this.route(edge); - const routingPoints = edge.routingPoints; - const minimalPointDistance = this.getOptions(edge).minimalPointDistance; - moves.forEach(move => { - const handle = move.handle; - const index = handle.pointIndex; - const correctedX = this.correctX(routingPoints, index, move.toPosition.x, minimalPointDistance); - const correctedY = this.correctY(routingPoints, index, move.toPosition.y, minimalPointDistance); - switch (handle.kind) { - case 'manhattan-50%': - if (index < 0) { - if (routingPoints.length === 0) { - routingPoints.push({ x: correctedX, y: correctedY }); - move.handle.pointIndex = 0; - } else if (almostEquals(route[0].x, route[1].x)) { - this.alignX(routingPoints, 0, correctedX); - } else { - this.alignY(routingPoints, 0, correctedY); - } - } else if (index < routingPoints.length - 1) { - if (almostEquals(routingPoints[index].x, routingPoints[index + 1].x)) { - this.alignX(routingPoints, index, correctedX); - this.alignX(routingPoints, index + 1, correctedX); - } else { - this.alignY(routingPoints, index, correctedY); - this.alignY(routingPoints, index + 1, correctedY); - } - } else { - if (almostEquals(route[route.length - 2].x, route[route.length - 1].x)) { - this.alignX(routingPoints, routingPoints.length - 1, correctedX); - } else { - this.alignY(routingPoints, routingPoints.length - 1, correctedY); - } - } - break; - } - }); - } - override cleanupRoutingPoints(edge: GRoutableElement, routingPoints: Point[], updateHandles: boolean, addRoutingPoints: boolean): void { // sometimes it might happen that the source or target has the bounds not properly set when using the feedback edge if (ensureBounds(edge.source) && ensureBounds(edge.target)) { From c4258d76298afac79a9e25a8c60a526ccbae212c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 6 Dec 2024 12:32:24 +0100 Subject: [PATCH 410/566] GLSP-1441: Fix NavigationTargetResolver uri handling (#403) Normalize source and target uris by striping a potential file:// prefix before comparing Part of: https://github.com/eclipse-glsp/glsp/issues/1441 --- .../features/navigation/navigation-target-resolver.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 8d7cdc4..a297f30 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; import { IActionDispatcher, ILogger, @@ -23,6 +22,7 @@ import { SetResolvedNavigationTargetAction, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { IDiagramOptions } from '../../base/model/diagram-loader'; /** @@ -51,9 +51,12 @@ export class NavigationTargetResolver { target: NavigationTarget ): Promise { const targetUri = decodeURIComponent(target.uri); - if (sourceUri && sourceUri !== targetUri && `file://${sourceUri}` !== targetUri) { + const normalizedSourceUri = sourceUri?.replace(/^file:\/\//, ''); + const normalizedTargetUri = targetUri.replace(/^file:\/\//, ''); + + if (normalizedSourceUri && normalizedSourceUri !== normalizedTargetUri) { // different URI, so we can't resolve it locally - this.logger.info("Source and Target URI are different. Can't resolve locally.", sourceUri, targetUri); + this.logger.info("Source and Target URI are different. Can't resolve locally.", normalizedSourceUri, normalizedTargetUri); return undefined; } if (NavigationTarget.getElementIds(target).length > 0) { From 3aaa74c35c0ca8fc89104e393d60b7c2ca149fed Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 12 Dec 2024 09:34:14 +0000 Subject: [PATCH 411/566] Align Node server validation with Java server (async live validation) (#99) Fixes https://github.com/eclipse-glsp/glsp/issues/1408 --- packages/server/package.json | 2 ++ .../model/model-submission-handler.ts | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index b313ba6..29b7001 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -63,11 +63,13 @@ "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", + "lodash": "4.17.21", "vscode-jsonrpc": "8.2.0", "winston": "^3.3.3", "ws": "^8.12.1" }, "devDependencies": { + "@types/lodash": "4.14.191", "@types/ws": "^8.5.4" }, "peerDependencies": { diff --git a/packages/server/src/common/features/model/model-submission-handler.ts b/packages/server/src/common/features/model/model-submission-handler.ts index f491db2..78b11c1 100644 --- a/packages/server/src/common/features/model/model-submission-handler.ts +++ b/packages/server/src/common/features/model/model-submission-handler.ts @@ -24,10 +24,13 @@ import { SetDirtyStateAction, SetMarkersAction, SetModelAction, + StatusAction, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { DebouncedFunc, debounce } from 'lodash'; import { inject, injectable, optional } from 'inversify'; +import { ActionDispatcher } from '../../actions/action-dispatcher'; import { CommandStack } from '../../command/command-stack'; import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; import { LayoutEngine } from '../layout/layout-engine'; @@ -70,8 +73,14 @@ export class ModelSubmissionHandler { @optional() protected validator?: ModelValidator; + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + protected requestModelAction?: RequestModelAction; + liveValidationDelay = 100; + protected debouncedLiveValidation?: DebouncedFunc<(validator: ModelValidator) => void>; + /** * Returns a list of actions to submit the initial revision of the client-side model, based on the injected * {@link ModelState}. Typically this method is invoked by the {@link RequestModelActionHandler} when the diagram @@ -149,12 +158,33 @@ export class ModelSubmissionHandler { result.push(SetDirtyStateAction.create(this.commandStack.isDirty, { reason })); } if (this.validator) { - const markers = await this.validator.validate([this.modelState.root], MarkersReason.LIVE); - result.push(SetMarkersAction.create(markers, { reason: MarkersReason.LIVE })); + const validationActions = await this.validateModel(this.validator); + result.push(...validationActions); } return result; } + protected validateModel(validator: ModelValidator): MaybePromise { + this.scheduleLiveValidation(validator); + // we are using async, debounced live validation so there are no actions to return for the model submission + return []; + } + + protected scheduleLiveValidation(validator: ModelValidator): void { + this.debouncedLiveValidation?.cancel(); + this.debouncedLiveValidation = debounce(validator => this.performLiveValidation(validator), this.liveValidationDelay); + this.debouncedLiveValidation(validator); + } + + protected async performLiveValidation(validator: ModelValidator): Promise { + this.actionDispatcher.dispatch(StatusAction.create('Validate Model...')); + const markers = await validator.validate([this.modelState.root], MarkersReason.LIVE); + return this.actionDispatcher.dispatchAll( + SetMarkersAction.create(markers, { reason: MarkersReason.LIVE }), + StatusAction.create('', { severity: 'NONE' }) + ); + } + protected createSetModeAction(newRoot: GModelRootSchema): SetModelAction { const responseId = this.requestModelAction?.requestId ?? ''; const response = SetModelAction.create(newRoot, { responseId }); From 2d41d5462592cc926ea28416fce56799b345a3dd Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 16 Dec 2024 09:43:46 +0100 Subject: [PATCH 412/566] Use deferred for action dispatcher initialization (#405) --- packages/client/src/base/action-dispatcher.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index d6c7aa8..9686a29 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -17,6 +17,7 @@ import { Action, ActionDispatcher, ActionHandlerRegistry, + Deferred, EMPTY_ROOT, GModelRoot, IActionDispatcher, @@ -47,21 +48,29 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo @inject(TYPES.ActionHandlerRegistryProvider) protected override actionHandlerRegistryProvider: () => Promise; protected postUpdateQueue: Action[] = []; + protected initializeDeferred = new Deferred(); + override initialize(): Promise { if (!this.initialized) { - this.initialized = this.doInitialize(); + this.initialized = this.initializeDeferred.promise; + this.doInitialize(); } return this.initialized; } protected async doInitialize(): Promise { - if (this.actionHandlerRegistry instanceof GLSPActionHandlerRegistry) { - this.actionHandlerRegistry.initialize(); + try { + if (this.actionHandlerRegistry instanceof GLSPActionHandlerRegistry) { + this.actionHandlerRegistry.initialize(); + } + this.handleAction(SetModelAction.create(EMPTY_ROOT)).catch(() => { + /* Logged in handleAction method */ + }); + this.startModelInitialization(); + this.initializeDeferred.resolve(); + } catch (error) { + this.initializeDeferred.reject(error); } - this.handleAction(SetModelAction.create(EMPTY_ROOT)).catch(() => { - /* Logged in handleAction method */ - }); - this.startModelInitialization(); } protected startModelInitialization(): void { From b2d04483b00969f2e38e4d894eaf32ddbeee6315 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 16 Dec 2024 10:53:30 +0100 Subject: [PATCH 413/566] GLSP-1449: Update dependencies (#1450) * GLSP-1449: Update dependencies - Align reflect-metadata version to be in line with latest inversify versions - Update prettier - Update eslint (not latest 8.x version) - Update mocha * Update jenkins file --- dev-packages/config-test/package.json | 16 ++++++++-------- dev-packages/config/package.json | 6 +++--- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 4 ++-- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index bd78440..248b418 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -28,17 +28,17 @@ "@eclipse-glsp/mocha-config": "2.3.0-next", "@eclipse-glsp/nyc-config": "2.3.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@types/chai": "^4.3.7", - "@types/mocha": "^10.0.2", + "@types/chai": "^5.0.1", + "@types/mocha": "10.0.10", "@types/sinon": "^10.0.19", - "chai": "^4.3.10", + "chai": "^5.1.2", "ignore-styles": "^5.0.1", - "mocha": "^10.2.0", + "mocha": "^11.0.1", "mocha-jenkins-reporter": "^0.4.8", - "nyc": "^15.1.0", - "reflect-metadata": "^0.1.13", - "sinon": "^15.1.0", - "ts-node": "^10.9.1" + "nyc": "^17.1.0", + "reflect-metadata": "^0.2.2", + "sinon": "^19.0.2", + "ts-node": "^10.9.2" }, "publishConfig": { "access": "public" diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index a0da0aa..46d4158 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -29,15 +29,15 @@ "@eclipse-glsp/ts-config": "2.3.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.51.0", + "eslint": "^8.53.1", "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.28.1", "eslint-plugin-no-null": "^1.0.2", - "prettier": "^3.0.3", - "reflect-metadata": "^0.1.13", + "prettier": "^3.4.2", + "reflect-metadata": "^0.2.2", "rimraf": "^5.0.5" }, "publishConfig": { diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index d254452..a8c43a4 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -28,7 +28,7 @@ "peerDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.51.0", + "eslint": "^8.53.1", "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", "eslint-plugin-deprecation": "^2.0.0", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index d8598f7..0745c17 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -31,8 +31,8 @@ ], "peerDependencies": { "ignore-styles": "^5.0.1", - "mocha": "^10.2.0", - "reflect-metadata": "^0.1.13" + "mocha": "^11.0.1", + "reflect-metadata": "^0.2.2" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 88ec87a..ad71451 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -27,7 +27,7 @@ "main": "index.json", "peerDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", - "nyc": "15.1.0" + "nyc": "17.1.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index e568a92..b0445c4 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "~2.4.6" + "prettier-plugin-packagejson": "^2.5.6" }, "publishConfig": { "access": "public" From b6ac43d3ebe3928ecfed9d26b46938b084159671 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 16 Dec 2024 13:47:15 +0100 Subject: [PATCH 414/566] Revert "GLSP-1449: Update dependencies (#1450)" (#1452) This reverts commit b2d04483b00969f2e38e4d894eaf32ddbeee6315. --- dev-packages/config-test/package.json | 16 ++++++++-------- dev-packages/config/package.json | 6 +++--- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 4 ++-- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 248b418..bd78440 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -28,17 +28,17 @@ "@eclipse-glsp/mocha-config": "2.3.0-next", "@eclipse-glsp/nyc-config": "2.3.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@types/chai": "^5.0.1", - "@types/mocha": "10.0.10", + "@types/chai": "^4.3.7", + "@types/mocha": "^10.0.2", "@types/sinon": "^10.0.19", - "chai": "^5.1.2", + "chai": "^4.3.10", "ignore-styles": "^5.0.1", - "mocha": "^11.0.1", + "mocha": "^10.2.0", "mocha-jenkins-reporter": "^0.4.8", - "nyc": "^17.1.0", - "reflect-metadata": "^0.2.2", - "sinon": "^19.0.2", - "ts-node": "^10.9.2" + "nyc": "^15.1.0", + "reflect-metadata": "^0.1.13", + "sinon": "^15.1.0", + "ts-node": "^10.9.1" }, "publishConfig": { "access": "public" diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 46d4158..a0da0aa 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -29,15 +29,15 @@ "@eclipse-glsp/ts-config": "2.3.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.53.1", + "eslint": "^8.51.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.28.1", "eslint-plugin-no-null": "^1.0.2", - "prettier": "^3.4.2", - "reflect-metadata": "^0.2.2", + "prettier": "^3.0.3", + "reflect-metadata": "^0.1.13", "rimraf": "^5.0.5" }, "publishConfig": { diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index a8c43a4..d254452 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -28,7 +28,7 @@ "peerDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.53.1", + "eslint": "^8.51.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-chai-friendly": "^0.7.2", "eslint-plugin-deprecation": "^2.0.0", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 0745c17..d8598f7 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -31,8 +31,8 @@ ], "peerDependencies": { "ignore-styles": "^5.0.1", - "mocha": "^11.0.1", - "reflect-metadata": "^0.2.2" + "mocha": "^10.2.0", + "reflect-metadata": "^0.1.13" }, "publishConfig": { "access": "public" diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index ad71451..88ec87a 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -27,7 +27,7 @@ "main": "index.json", "peerDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", - "nyc": "17.1.0" + "nyc": "15.1.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index b0445c4..e568a92 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -26,7 +26,7 @@ ], "main": "index.json", "dependencies": { - "prettier-plugin-packagejson": "^2.5.6" + "prettier-plugin-packagejson": "~2.4.6" }, "publishConfig": { "access": "public" From 9bc49cbf17727a4bde7a7a691e4dac36343b2f57 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 16 Dec 2024 14:21:59 +0100 Subject: [PATCH 415/566] GLSP-1449: Ensure that codebase is compatible with inversify >= 6.1.3 (#407) - Remove usage of not-exported constant in lazy injector - Fix constructor injection of classes that are overwritten to replace constructor injection with lazy injection. To pass the stricter type checks of newer inversify version we need to inject an empty array here. --- .../src/base/action-handler-registry.ts | 7 +++++-- packages/client/src/base/default.module.ts | 2 ++ .../ui-extension/ui-extension-registry.ts | 4 ++-- packages/client/src/base/view/key-tool.ts | 4 ++-- packages/client/src/base/view/mouse-tool.ts | 4 ++-- packages/glsp-sprotty/src/types.ts | 4 ++++ packages/protocol/src/di/lazy-injector.ts | 19 +------------------ 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/client/src/base/action-handler-registry.ts b/packages/client/src/base/action-handler-registry.ts index da7ee5d..d432e02 100644 --- a/packages/client/src/base/action-handler-registry.ts +++ b/packages/client/src/base/action-handler-registry.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { ActionHandlerRegistration, ActionHandlerRegistry, IActionHandlerInitializer, LazyInjector, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; @injectable() export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { @@ -24,7 +24,10 @@ export class GLSPActionHandlerRegistry extends ActionHandlerRegistry { protected initialized = false; - constructor(@optional() registrations: ActionHandlerRegistration[] = [], @optional() initializers: IActionHandlerInitializer[] = []) { + constructor( + @inject(TYPES.EmptyArray) registrations: ActionHandlerRegistration[], + @inject(TYPES.EmptyArray) initializers: IActionHandlerInitializer[] + ) { super(registrations, initializers); } /** diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 2349923..3a83a5d 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -140,6 +140,8 @@ export const defaultModule = new FeatureModule( bind(GLSPUIExtensionRegistry).toSelf().inSingletonScope(); bindOrRebind(context, TYPES.UIExtensionRegistry).toService(GLSPUIExtensionRegistry); bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); + + bind(TYPES.EmptyArray).toDynamicValue(() => []); }, { featureId: Symbol('default') diff --git a/packages/client/src/base/ui-extension/ui-extension-registry.ts b/packages/client/src/base/ui-extension/ui-extension-registry.ts index 9e26880..68279dc 100644 --- a/packages/client/src/base/ui-extension/ui-extension-registry.ts +++ b/packages/client/src/base/ui-extension/ui-extension-registry.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { IUIExtension, LazyInjector, MaybePromise, TYPES, UIExtensionRegistry } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import { IDiagramStartup } from '../model/diagram-loader'; @injectable() @@ -23,7 +23,7 @@ export class GLSPUIExtensionRegistry extends UIExtensionRegistry implements IDia @inject(LazyInjector) protected lazyInjector: LazyInjector; - constructor(@optional() extensions: IUIExtension[] = []) { + constructor(@inject(TYPES.EmptyArray) extensions: IUIExtension[]) { super(extensions); } diff --git a/packages/client/src/base/view/key-tool.ts b/packages/client/src/base/view/key-tool.ts index b6492ee..24fee18 100644 --- a/packages/client/src/base/view/key-tool.ts +++ b/packages/client/src/base/view/key-tool.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Disposable, KeyListener, KeyTool, LazyInjector, MaybePromise, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import { IDiagramStartup } from '../model/diagram-loader'; @injectable() @@ -22,7 +22,7 @@ export class GLSPKeyTool extends KeyTool implements IDiagramStartup { @inject(LazyInjector) protected lazyInjector: LazyInjector; - constructor(@optional() keyListeners: KeyListener[] = []) { + constructor(@inject(TYPES.EmptyArray) keyListeners: KeyListener[]) { super(keyListeners); } diff --git a/packages/client/src/base/view/mouse-tool.ts b/packages/client/src/base/view/mouse-tool.ts index 1e9ed16..cc0ef67 100644 --- a/packages/client/src/base/view/mouse-tool.ts +++ b/packages/client/src/base/view/mouse-tool.ts @@ -24,7 +24,7 @@ import { MouseTool, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import { IDiagramStartup } from '../model/diagram-loader'; import { Ranked } from '../ranked'; @@ -41,7 +41,7 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup { protected rankedMouseListeners: Map; - constructor(@optional() mouseListeners: MouseListener[] = []) { + constructor(@inject(TYPES.EmptyArray) mouseListeners: MouseListener[]) { super(mouseListeners); } diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 95b4381..2f9dcf1 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -24,6 +24,10 @@ import { TYPES as SprottyTYPES } from 'sprotty'; const IGModelRootListener = Symbol('IGModelRootListener'); export const TYPES = { ...SprottyTYPES, + // GLSP extends certain sprotty base classes and replaces constructor injection with lazy injection to avoid circular dependencies + // To pass inversify base class checks an empty array has to be injected. + // This is the purpose of this service identifier. Typically adopters should not have to use this identifier. + EmptyArray: Symbol('EmptyArray'), /** @deprecated Using async providers for container service retrieval is discouraged. Use the `LazyInjector` instead */ ActionHandlerRegistryProvider: SprottyTYPES.ActionHandlerRegistryProvider, IAsyncClipboardService: Symbol('IAsyncClipboardService'), diff --git a/packages/protocol/src/di/lazy-injector.ts b/packages/protocol/src/di/lazy-injector.ts index 814d56e..962e360 100644 --- a/packages/protocol/src/di/lazy-injector.ts +++ b/packages/protocol/src/di/lazy-injector.ts @@ -14,24 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - import { getServiceIdentifierAsString, interfaces } from 'inversify'; -import { NOT_REGISTERED } from 'inversify/lib/constants/error_msgs'; import { MaybeArray } from '../utils/array-util'; import { AnyObject } from '../utils/type-util'; import { BindingContext } from './inversify-util'; @@ -101,7 +84,7 @@ export class DefaultLazyInjector implements LazyInjector { get(serviceIdentifier: interfaces.ServiceIdentifier): T { const service = this.getOptional(serviceIdentifier); if (service === undefined) { - throw new Error(NOT_REGISTERED + getServiceIdentifierAsString(serviceIdentifier)); + throw new Error('No matching bindings found for serviceIdentifier:' + getServiceIdentifierAsString(serviceIdentifier)); } return service; } From a534da3d4b57d328135e234c6c5ffb5346651bf4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 18 Dec 2024 13:47:16 +0100 Subject: [PATCH 416/566] GLSP-1449: Update inversify (#408) * GLSP-1449: Update inversify - Update to inversify ^6.1.3 - Update to sprotty 1.4.0 Part of: https://github.com/eclipse-glsp/glsp/issues/1449 - Drop support for node 16 Part of: https://github.com/eclipse-glsp/glsp/issues/1456 - Update changelog * Fix wrong type in ws-connection-provider --- examples/workflow-standalone/package.json | 2 +- .../workflow-standalone/scripts/start-example-server.ts | 2 +- packages/client/package.json | 2 +- packages/glsp-sprotty/package.json | 6 +++--- packages/protocol/package.json | 4 ++-- .../jsonrpc/ws-connection-provider.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 111edd9..f08265a 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -41,7 +41,7 @@ "@types/tar": "6.1.5", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", - "inversify": "^6.0.1", + "inversify": "^6.1.3", "path-browserify": "^1.0.1", "process": "^0.11.10", "source-map-loader": "^4.0.0", diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts index 11fa427..52c4c81 100644 --- a/examples/workflow-standalone/scripts/start-example-server.ts +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -52,7 +52,7 @@ async function downloadIfNecessary(): Promise { .exec(`npm pack @eclipse-glsp-examples/workflow-server-bundled@${config.version} --json`, { silent: true }) .stdout.trim(); const version = JSON.parse(packResultJson)[0].version; - const tarBall = fs.readdirSync(serverDirPath).find(file => file.endsWith('.tgz' || '.tar.gz'))!; + const tarBall = fs.readdirSync(serverDirPath).find(file => file.endsWith('.tar.gz') || file.endsWith('.tgz'))!; console.log('Extract downloaded server tarball'); await extract({ file: tarBall, diff --git a/packages/client/package.json b/packages/client/package.json index aea7b0f..19e6b16 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -58,7 +58,7 @@ "@types/lodash": "4.14.191" }, "peerDependencies": { - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "publishConfig": { "access": "public" diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 8d2d22c..53be90b 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -36,8 +36,8 @@ "@eclipse-glsp/protocol": "2.3.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", - "sprotty": "1.3.0", - "sprotty-protocol": "1.3.0", + "sprotty": "1.4.0", + "sprotty-protocol": "1.4.0", "vscode-jsonrpc": "8.2.0" }, "devDependencies": { @@ -46,7 +46,7 @@ "snabbdom-to-html": "^7.1.0" }, "peerDependencies": { - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/package.json b/packages/protocol/package.json index c7a87e7..06ef339 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -45,7 +45,7 @@ "watch": "tsc -w" }, "dependencies": { - "sprotty-protocol": "1.3.0", + "sprotty-protocol": "1.4.0", "uuid": "~10.0.0", "vscode-jsonrpc": "8.2.0" }, @@ -53,7 +53,7 @@ "@types/uuid": "~9.0.8" }, "peerDependencies": { - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "publishConfig": { "access": "public" diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts index 73ad42d..c797daf 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/ws-connection-provider.ts @@ -45,7 +45,7 @@ export interface GLSPConnectionHandler { export class GLSPWebSocketProvider { protected webSocket: WebSocket; - protected reconnectTimer: NodeJS.Timer; + protected reconnectTimer: NodeJS.Timeout; protected reconnectAttempts = 0; protected options: GLSPWebSocketOptions = { From 8f60dd5f8be56b441284b5fa5b66e8dfb1cfcb3e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 18 Dec 2024 16:14:29 +0100 Subject: [PATCH 417/566] GLSP-1449: Update inversify (#100) * GLSP-1449: Update inversify - Update to inversify ^6.1.3 - Update to latest glsp protocol version Part of: https://github.com/eclipse-glsp/glsp/issues/1449 Drop support for node 16 https://github.com/eclipse-glsp/glsp/issues/1456 - Update changelog * Update jenkins file --- examples/workflow-server/package.json | 2 +- packages/layout-elk/package.json | 2 +- packages/server/package.json | 2 +- .../navigation/request-navigation-targets-action-handler.ts | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 93dd0d0..53bc9ac 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -58,7 +58,7 @@ "dependencies": { "@eclipse-glsp/layout-elk": "2.3.0-next", "@eclipse-glsp/server": "2.3.0-next", - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "devDependencies": { "source-map-loader": "^4.0.1", diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 2036e58..f449bf9 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -53,7 +53,7 @@ "elkjs": "^0.7.1" }, "peerDependencies": { - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "publishConfig": { "access": "public" diff --git a/packages/server/package.json b/packages/server/package.json index 29b7001..9f3e1e3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -73,7 +73,7 @@ "@types/ws": "^8.5.4" }, "peerDependencies": { - "inversify": "~6.0.2" + "inversify": "^6.1.3" }, "publishConfig": { "access": "public" diff --git a/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts b/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts index 77cbc09..82a47a1 100644 --- a/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts +++ b/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Action, MaybePromise, NavigationTarget, RequestNavigationTargetsAction, SetNavigationTargetsAction } from '@eclipse-glsp/protocol'; -import { injectable } from 'inversify'; -import { inject } from 'inversify/lib/annotation/inject'; +import { inject, injectable } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; import { NavigationTargetProviderRegistry } from './navigation-target-provider-registry'; From eece7977741985234f3ea8cd1bf2071c5c69de2e Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 23 Dec 2024 09:53:46 +0100 Subject: [PATCH 418/566] v2.3.0 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index fd8bbb2..65a20c2 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.3.0-next", + "version": "2.3.0", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.3.0-next", + "@eclipse-glsp/config": "2.3.0", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index bd78440..8d883f5 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.3.0-next", - "@eclipse-glsp/nyc-config": "2.3.0-next", + "@eclipse-glsp/mocha-config": "2.3.0", + "@eclipse-glsp/nyc-config": "2.3.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index a0da0aa..675c6fb 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.3.0-next", - "@eclipse-glsp/prettier-config": "2.3.0-next", - "@eclipse-glsp/ts-config": "2.3.0-next", + "@eclipse-glsp/eslint-config": "2.3.0", + "@eclipse-glsp/prettier-config": "2.3.0", + "@eclipse-glsp/ts-config": "2.3.0", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index ddb7166..c9fb1bd 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.3.0-next", + "version": "2.3.0", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.3.0-next", - "@eclipse-glsp/config": "2.3.0-next", - "@eclipse-glsp/config-test": "2.3.0-next" + "@eclipse-glsp/cli": "2.3.0", + "@eclipse-glsp/config": "2.3.0", + "@eclipse-glsp/config-test": "2.3.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index d254452..e965ed0 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index d8598f7..fa7dcfa 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 88ec87a..401aede 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index e568a92..ddc5463 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 8dde48c..4d8e683 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From 6019d8dc1c77471ad8c45a571f28c138de4657d0 Mon Sep 17 00:00:00 2001 From: Henk van de Berg <11218586+holkerveen@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:41:15 +0100 Subject: [PATCH 419/566] Add host and port configuration to start-example server and the standalone client (#409) --- .../scripts/start-example-server.ts | 32 ++++++++++++++++++- examples/workflow-standalone/src/app.ts | 5 +-- examples/workflow-standalone/src/globals.d.ts | 2 ++ .../workflow-standalone/webpack.config.js | 6 +++- 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 examples/workflow-standalone/src/globals.d.ts diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts index 52c4c81..0e6c097 100644 --- a/examples/workflow-standalone/scripts/start-example-server.ts +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -23,8 +23,12 @@ const serverDirPath = path.resolve(__dirname, '..', 'server'); async function run() { const serverFile = await downloadIfNecessary(); console.log(); + + const port = parsePortArg(); + const host = parseHostArg(); + sh.cd(serverDirPath); - sh.exec(`node ${serverFile} -w -p 8081`); + sh.exec(`node ${serverFile} -w --port ${port} --host ${host}`); } async function downloadIfNecessary(): Promise { @@ -72,4 +76,30 @@ async function downloadIfNecessary(): Promise { return `${config.fileName}-${version}.js`; } +function parsePortArg(): number { + let port = 8081; + const portIndex = process.argv.indexOf('--port'); + if (portIndex >= 0) { + port = parseInt(process.argv[portIndex + 1]); + } + if(isNaN(port)) { + console.error('Invalid port number'); + process.exit(1); + } + return port; +} + +function parseHostArg(): string { + let host = 'localhost'; + const hostIndex = process.argv.indexOf('--host'); + if(hostIndex >= 0) { + host = process.argv[hostIndex + 1]; + } + if(typeof host !== 'string') { + console.error('Invalid host'); + process.exit(1); + } + return host; +} + run(); diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index aeea634..94dc720 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -28,7 +28,8 @@ import { Container } from 'inversify'; import { join, resolve } from 'path'; import { MessageConnection } from 'vscode-jsonrpc'; import createContainer from './di.config'; -const port = 8081; +const host = GLSP_SERVER_HOST; +const port = GLSP_SERVER_PORT; const id = 'workflow'; const diagramType = 'workflow-diagram'; @@ -37,7 +38,7 @@ const currentDir = loc.substring(0, loc.lastIndexOf('/')); const examplePath = resolve(join(currentDir, '../app/example1.wf')); const clientId = 'sprotty'; -const webSocketUrl = `ws://localhost:${port}/${id}`; +const webSocketUrl = `ws://${host}:${port}/${id}`; let glspClient: GLSPClient; let container: Container; diff --git a/examples/workflow-standalone/src/globals.d.ts b/examples/workflow-standalone/src/globals.d.ts new file mode 100644 index 0000000..6ea9616 --- /dev/null +++ b/examples/workflow-standalone/src/globals.d.ts @@ -0,0 +1,2 @@ +declare const GLSP_SERVER_HOST: string; +declare const GLSP_SERVER_PORT: string; diff --git a/examples/workflow-standalone/webpack.config.js b/examples/workflow-standalone/webpack.config.js index 306821e..ec590c5 100644 --- a/examples/workflow-standalone/webpack.config.js +++ b/examples/workflow-standalone/webpack.config.js @@ -70,6 +70,10 @@ module.exports = { new webpack.ProvidePlugin({ process: 'process/browser' }), - new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }) + new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }), + new webpack.DefinePlugin({ + GLSP_SERVER_HOST: JSON.stringify(process.env.GLSP_SERVER_HOST || 'localhost'), + GLSP_SERVER_PORT: JSON.stringify(process.env.GLSP_SERVER_PORT || '8081'), + }) ] }; From f9ddcf879427e5c3d8bbb50d5b6e78b57cb159b3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 23 Dec 2024 10:31:55 +0100 Subject: [PATCH 420/566] v2.3.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 8505f3f..a9ccf8c 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.3.0-next", + "version": "2.3.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.3.0-next", + "@eclipse-glsp/client": "^2.3.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index f08265a..d301a73 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.3.0-next", + "version": "2.3.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.3.0-next", - "@eclipse-glsp/client": "2.3.0-next", + "@eclipse-glsp-examples/workflow-glsp": "^2.3.0", + "@eclipse-glsp/client": "^2.3.0", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index eae82b0..70a1f4a 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "next" + "version": "2.3.0" } diff --git a/packages/client/package.json b/packages/client/package.json index 19e6b16..70e9f5a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.3.0-next", + "version": "2.3.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.3.0-next", + "@eclipse-glsp/sprotty": "^2.3.0", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 53be90b..79f65ad 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.3.0-next", + "@eclipse-glsp/protocol": "^2.3.0", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 06ef339..fd4d526 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.3.0-next", + "version": "2.3.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 11a963dcdfe84b8cd665c358df9094309471eab6 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 23 Dec 2024 13:30:29 +0100 Subject: [PATCH 421/566] v2.3.0 --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 5c7c309..fb126a4 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.3.0-next", + "version": "2.3.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 53bc9ac..a2e662b 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.3.0-next", + "version": "2.3.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.3.0-next", - "@eclipse-glsp/server": "2.3.0-next", + "@eclipse-glsp/layout-elk": "2.3.0", + "@eclipse-glsp/server": "2.3.0", "inversify": "^6.1.3" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index ea7fc3c..05ed3cd 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.3.0-next", + "version": "2.3.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.3.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index f449bf9..90c962a 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.3.0-next", + "version": "2.3.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.3.0-next", + "@eclipse-glsp/server": "2.3.0", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 9f3e1e3..ca2d30a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.3.0-next", + "version": "2.3.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.3.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.3.0", + "@eclipse-glsp/protocol": "2.3.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 8435cf84f9688c5efe852c32cd9cdcff99f07ad3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 14 Jan 2025 11:50:34 +0100 Subject: [PATCH 422/566] Switch to 2.4.0-next version (#1465) --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 65a20c2..e7d3370 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.3.0", + "version": "2.4.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.3.0", + "@eclipse-glsp/config": "2.4.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 8d883f5..109f988 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.3.0", - "@eclipse-glsp/nyc-config": "2.3.0", + "@eclipse-glsp/mocha-config": "2.4.0-next", + "@eclipse-glsp/nyc-config": "2.4.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 675c6fb..d573b77 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.3.0", - "@eclipse-glsp/prettier-config": "2.3.0", - "@eclipse-glsp/ts-config": "2.3.0", + "@eclipse-glsp/eslint-config": "2.4.0-next", + "@eclipse-glsp/prettier-config": "2.4.0-next", + "@eclipse-glsp/ts-config": "2.4.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index c9fb1bd..5079198 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.3.0", + "version": "2.4.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.3.0", - "@eclipse-glsp/config": "2.3.0", - "@eclipse-glsp/config-test": "2.3.0" + "@eclipse-glsp/cli": "2.4.0-next", + "@eclipse-glsp/config": "2.4.0-next", + "@eclipse-glsp/config-test": "2.4.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index e965ed0..4973228 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index fa7dcfa..6036036 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 401aede..d5066d1 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index ddc5463..e1847c4 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 4d8e683..d74e382 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From 8b86746e6dedd44606f8f93b20dc83203a7ac9d9 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 14 Jan 2025 17:31:45 +0100 Subject: [PATCH 423/566] Switch to 3.4.0-next (#411) --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index a9ccf8c..e5b51d6 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.3.0", + "version": "2.4.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "^2.3.0", + "@eclipse-glsp/client": "2.4.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index d301a73..2368569 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.3.0", + "version": "2.4.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "^2.3.0", - "@eclipse-glsp/client": "^2.3.0", + "@eclipse-glsp-examples/workflow-glsp": "2.4.0-next", + "@eclipse-glsp/client": "2.4.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/packages/client/package.json b/packages/client/package.json index 70e9f5a..4a2c37b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.3.0", + "version": "2.4.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "^2.3.0", + "@eclipse-glsp/sprotty": "2.4.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 79f65ad..966a159 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "^2.3.0", + "@eclipse-glsp/protocol": "2.4.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index fd4d526..0b311d6 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.3.0", + "version": "2.4.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From ba65b8564ba38ded60f83c9cd686e33229f94e84 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 16 Jan 2025 14:16:42 +0100 Subject: [PATCH 424/566] Update to 2.4.0 next (#102) --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index fb126a4..998fc2f 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.3.0", + "version": "2.4.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index a2e662b..30513ac 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.3.0", + "version": "2.4.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.3.0", - "@eclipse-glsp/server": "2.3.0", + "@eclipse-glsp/layout-elk": "2.4.0-next", + "@eclipse-glsp/server": "2.4.0-next", "inversify": "^6.1.3" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 05ed3cd..28fb41f 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.3.0", + "version": "2.4.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.3.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 90c962a..c51bf9d 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.3.0", + "version": "2.4.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.3.0", + "@eclipse-glsp/server": "2.4.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index ca2d30a..cc578e4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.3.0", + "version": "2.4.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.3.0", - "@eclipse-glsp/protocol": "2.3.0", + "@eclipse-glsp/graph": "2.4.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From a3ea340a5bf38b16e2316fc8fdf7c9602353a18e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 18 Feb 2025 04:08:40 -0800 Subject: [PATCH 425/566] Fix route application (#103) Fixes https://github.com/eclipse-glsp/glsp/issues/1483 --- .../workflow-server/workspace/example1.wf | 975 ------------------ .../server/src/common/utils/layout-util.ts | 5 +- 2 files changed, 4 insertions(+), 976 deletions(-) delete mode 100644 examples/workflow-server/workspace/example1.wf diff --git a/examples/workflow-server/workspace/example1.wf b/examples/workflow-server/workspace/example1.wf deleted file mode 100644 index e63f57b..0000000 --- a/examples/workflow-server/workspace/example1.wf +++ /dev/null @@ -1,975 +0,0 @@ -{ - "id": "sprotty", - "children": [ - { - "name": "Push", - "taskType": "manual", - "id": "task0", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task0_header", - "children": [ - { - "id": "task0_icon", - "children": [ - { - "id": "task0_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task0_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 48.90625, - "height": 22.0 - }, - "text": "Push", - "alignment": { - "x": 24.453125, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 91.90625, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 50.0, - "y": 130.0 - }, - "size": { - "width": 101.90625, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ManualTask1", - "taskType": "manual", - "id": "task1", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task1_header", - "children": [ - { - "id": "task1_icon", - "children": [ - { - "id": "task1_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task1_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 52.5625, - "height": 22.0 - }, - "text": "RflWt", - "alignment": { - "x": 26.109375, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 95.5625, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 510.0, - "y": 30.0 - }, - "size": { - "width": 105.5625, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ManualTask2", - "taskType": "manual", - "id": "task2", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task2_header", - "children": [ - { - "id": "task2_icon", - "children": [ - { - "id": "task2_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task2_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 62.21875, - "height": 22.0 - }, - "text": "ChkTp", - "alignment": { - "x": 31.109375, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 105.21875, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 250.0, - "y": 200.0 - }, - "size": { - "width": 115.21875, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ManualTask3", - "taskType": "manual", - "id": "task3", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task3_header", - "children": [ - { - "id": "task3_icon", - "children": [ - { - "id": "task3_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task3_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 75.953125, - "height": 22.0 - }, - "text": "PreHeat", - "alignment": { - "x": 37.8125, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 118.953125, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 510.0, - "y": 240.0 - }, - "size": { - "width": 128.953125, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ManualTask4", - "taskType": "manual", - "id": "task4", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task4_header", - "children": [ - { - "id": "task4_icon", - "children": [ - { - "id": "task4_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task4_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 49.359375, - "height": 22.0 - }, - "text": "Brew", - "alignment": { - "x": 24.453125, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 92.359375, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 840.0, - "y": 130.0 - }, - "size": { - "width": 102.359375, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "ManualTask6", - "taskType": "manual", - "id": "task6", - "cssClasses": [ - "task", - "manual" - ], - "children": [ - { - "id": "task6_header", - "children": [ - { - "id": "task6_icon", - "children": [ - { - "id": "task6_ticon", - "type": "label:icon", - "position": { - "x": 7.65625, - "y": 5.0 - }, - "size": { - "width": 16.6875, - "height": 22.0 - }, - "text": "M", - "alignment": { - "x": 8.34375, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task6_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 73.34375, - "height": 22.0 - }, - "text": "KeepTp", - "alignment": { - "x": 36.671875, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 116.34375, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:manual", - "position": { - "x": 510.0, - "y": 170.0 - }, - "size": { - "width": 126.34375, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "AutomatedTask8", - "taskType": "automated", - "id": "task8", - "cssClasses": [ - "task", - "automated" - ], - "children": [ - { - "id": "task8_header", - "children": [ - { - "id": "task8_icon", - "children": [ - { - "id": "task8_ticon", - "type": "label:icon", - "position": { - "x": 8.4921875, - "y": 5.0 - }, - "size": { - "width": 15.015625, - "height": 22.0 - }, - "text": "A", - "alignment": { - "x": 7.671875, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task8_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 63.671875, - "height": 22.0 - }, - "text": "ChkWt", - "alignment": { - "x": 31.671875, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 106.671875, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:automated", - "position": { - "x": 250.0, - "y": 60.0 - }, - "size": { - "width": 116.671875, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "name": "AutomatedTask9", - "taskType": "automated", - "id": "task9", - "cssClasses": [ - "task", - "automated" - ], - "children": [ - { - "id": "task9_header", - "children": [ - { - "id": "task9_icon", - "children": [ - { - "id": "task9_ticon", - "type": "label:icon", - "position": { - "x": 8.4921875, - "y": 5.0 - }, - "size": { - "width": 15.015625, - "height": 22.0 - }, - "text": "A", - "alignment": { - "x": 7.671875, - "y": 18.0 - } - } - ], - "type": "icon", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layout": "stack", - "layoutOptions": { - "resizeContainer": false, - "hAlign": "center" - } - }, - { - "id": "task9_classname", - "type": "label:heading", - "position": { - "x": 38.0, - "y": 10.0 - }, - "size": { - "width": 56.109375, - "height": 22.0 - }, - "text": "WtOK", - "alignment": { - "x": 27.78125, - "y": 18.0 - } - } - ], - "type": "comp:header", - "position": { - "x": 5.0, - "y": 5.0 - }, - "size": { - "width": 99.109375, - "height": 42.0 - }, - "layout": "hbox" - } - ], - "type": "task:automated", - "position": { - "x": 510.0, - "y": 100.0 - }, - "size": { - "width": 109.109375, - "height": 52.0 - }, - "layout": "vbox", - "args": { - "radiusBottomLeft": 5.0, - "radiusTopLeft": 5.0, - "radiusTopRight": 5.0, - "radiusBottomRight": 5.0 - } - }, - { - "nodeType": "mergeNode", - "id": "activityNode1", - "type": "activityNode:merge", - "position": { - "x": 680.0, - "y": 70.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } - }, - { - "id": "edge4", - "type": "edge", - "sourceId": "task1", - "targetId": "activityNode1" - }, - { - "id": "edge5", - "type": "edge", - "sourceId": "task9", - "targetId": "activityNode1" - }, - { - "nodeType": "decisionNode", - "id": "activityNode2", - "type": "activityNode:decision", - "position": { - "x": 420.0, - "y": 210.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "id": "edge7", - "type": "edge", - "sourceId": "task2", - "targetId": "activityNode2" - }, - { - "nodeType": "mergeNode", - "id": "activityNode4", - "type": "activityNode:merge", - "position": { - "x": 690.0, - "y": 210.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minHeight": 32.0, - "resizeContainer": false - } - }, - { - "id": "edge11", - "type": "edge", - "sourceId": "task6", - "targetId": "activityNode4" - }, - { - "id": "edge12", - "type": "edge", - "sourceId": "task3", - "targetId": "activityNode4" - }, - { - "nodeType": "decisionNode", - "id": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "type": "activityNode:decision", - "position": { - "x": 410.0, - "y": 70.0 - }, - "size": { - "width": 32.0, - "height": 32.0 - }, - "layoutOptions": { - "minWidth": 32.0, - "minHeight": 32.0 - } - }, - { - "id": "69d7d698-c5ce-4e85-be06-d9bd9357322d", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "task1" - }, - { - "nodeType": "forkNode", - "id": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "cssClasses": [ - "forkOrJoin" - ], - "type": "activityNode:fork", - "position": { - "x": 170.0, - "y": 130.0 - }, - "size": { - "width": 10.0, - "height": 50.0 - } - }, - { - "id": "d34c37e0-e45e-4cfe-a76f-0e9274ed8e60", - "type": "edge", - "sourceId": "task0", - "targetId": "bb2709f5-0ff0-4438-8853-b7e934b506d7" - }, - { - "id": "5eeaa218-ad56-4052-8b28-bbc08d82ed34", - "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task2" - }, - { - "nodeType": "joinNode", - "id": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "cssClasses": [ - "forkOrJoin" - ], - "type": "activityNode:join", - "position": { - "x": 800.0, - "y": 130.0 - }, - "size": { - "width": 10.0, - "height": 50.0 - } - }, - { - "id": "2f57af4e-2960-496e-992d-03c8b021d81a", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "activityNode2", - "targetId": "task6" - }, - { - "id": "2c7427b0-9743-4bfb-b04c-078ba98f8d7e", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "activityNode2", - "targetId": "task3" - }, - { - "id": "7296b4a8-55c6-4c61-bddb-deacae77efa6", - "type": "edge", - "sourceId": "activityNode1", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" - }, - { - "id": "0471cae4-c754-4e89-8337-96ed1546dd02", - "type": "edge", - "sourceId": "activityNode4", - "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" - }, - { - "id": "2c3ec34b-291c-43ef-bdf9-035afc4764e6", - "type": "edge", - "sourceId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b", - "targetId": "task4" - }, - { - "id": "e9aa796a-aa5b-4e6d-8860-4b9c5a5b8d89", - "cssClasses": [ - "medium" - ], - "type": "edge:weighted", - "sourceId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4", - "targetId": "task9" - }, - { - "id": "f49a588c-618c-4d9f-96fc-feee776a0ead", - "type": "edge", - "sourceId": "task8", - "targetId": "b00fc494-0fa4-4448-8bf9-162c2c0865e4" - }, - { - "id": "568d91e3-d5bf-464d-923e-9769a8a651f3", - "type": "edge", - "sourceId": "bb2709f5-0ff0-4438-8853-b7e934b506d7", - "targetId": "task8" - } - ], - "type": "graph", - "revision": 1 -} \ No newline at end of file diff --git a/packages/server/src/common/utils/layout-util.ts b/packages/server/src/common/utils/layout-util.ts index d66ed8b..6407f3d 100644 --- a/packages/server/src/common/utils/layout-util.ts +++ b/packages/server/src/common/utils/layout-util.ts @@ -18,7 +18,7 @@ import { GAlignable, GBoundsAware, GEdge, GGraph, GModelElement, GModelRoot, isG import { ElementAndAlignment, ElementAndBounds, ElementAndRoutingPoints, Point } from '@eclipse-glsp/protocol'; import { GModelIndex } from '../features/model/gmodel-index'; import { ArgsUtil } from './args-util'; -import { getOrThrow, GLSPServerError } from './glsp-server-error'; +import { GLSPServerError, getOrThrow } from './glsp-server-error'; /** * Applies the new bounds to the model. @@ -78,6 +78,9 @@ export function applyRoute(route: ElementAndRoutingPoints, index: GModelIndex): args[ArgsUtil.KEY_EDGE_SOURCE_POINT_Y] = source.y; args[ArgsUtil.KEY_EDGE_TARGET_POINT_X] = target.x; args[ArgsUtil.KEY_EDGE_TARGET_POINT_Y] = target.y; + if (!edge.args) { + edge.args = args; + } return edge; } From a35d0ff7369dde1a4ddb34264004bc135bf2579c Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 19 Feb 2025 03:08:47 -0800 Subject: [PATCH 426/566] GLSP-1483: Stop progress on model loading error (#104) Update `RequestModelActionHandler` to ensure that pending progresses are properly cancelled/ended if an error occured during loading In addition, make progress monitor arg in `reportModelLoadingFinished` optional. This covers the case where an adopter overrides the `reportModelLoading` method and opts for no progress reporting Fixes: eclipse-glsp/glsp#1485 --- .../model/request-model-action-handler.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index 621d70a..af0f0ab 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -54,11 +54,16 @@ export class RequestModelActionHandler implements ActionHandler { const progress = this.reportModelLoading('Model loading in progress'); - if (isReconnecting) { - await this.handleReconnect(action); - } else { - await this.sourceModelStorage.loadSourceModel(action); + try { + if (isReconnecting) { + await this.handleReconnect(action); + } else { + await this.sourceModelStorage.loadSourceModel(action); + } + } catch (err) { + this.handleModelLoadingError(err, progress); } + this.reportModelLoadingFinished(progress); return this.submissionHandler.submitInitialModel(action); @@ -75,13 +80,20 @@ export class RequestModelActionHandler implements ActionHandler { } } - protected reportModelLoading(message: string): ProgressMonitor { + protected reportModelLoading(message: string): ProgressMonitor | undefined { this.actionDispatcher.dispatch(StatusAction.create(message, { severity: 'INFO' })); return this.progressService.start(message); } - protected reportModelLoadingFinished(monitor: ProgressMonitor): void { + protected reportModelLoadingFinished(monitor?: ProgressMonitor): void { this.actionDispatcher.dispatch(StatusAction.create('', { severity: 'NONE' })); - monitor.end(); + monitor?.end(); + } + + protected handleModelLoadingError(error: unknown, monitor?: ProgressMonitor): void { + // In case of an error we end progress reporting and rethrow the error so + // that it can be handled by the default error handler + monitor?.end(); + throw error; } } From 745f6ceb708a0cb46170675a7d4d734f32195145 Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Wed, 19 Feb 2025 13:04:31 +0100 Subject: [PATCH 427/566] Remove important from accessibility css (#412) --- examples/workflow-glsp/css/diagram.css | 24 +++++++-------- examples/workflow-standalone/css/diagram.css | 2 +- packages/client/css/glsp-sprotty.css | 15 ---------- packages/client/css/keyboard.css | 12 +++++++- packages/client/css/navigation.css | 8 +++-- packages/client/css/search.css | 29 +++++++++++++++++++ .../keyboard-grid/keyboard-grid.ts | 8 ++--- .../search/search-palette-module.ts | 1 + 8 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 packages/client/css/search.css diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index e09b088..bd3f819 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -72,33 +72,33 @@ stroke-width: 1; } -.sprotty-edge.weighted.low:not(.selected), -.sprotty-edge.weighted.low:not(.selected) .arrow { +.sprotty-edge.weighted.low:not(.selected, .navigable-element), +.sprotty-edge.weighted.low:not(.selected, .navigable-element) .arrow { stroke: rgb(128, 90, 233); } -.sprotty-edge.weighted.low:not(.selected) .arrow { +.sprotty-edge.weighted.low:not(.selected, .navigable-element) .arrow { fill: rgb(128, 90, 233); } -.sprotty-edge.weighted:not(.selected), -.sprotty-edge.weighted:not(.selected) .arrow, -.sprotty-edge.weighted.medium:not(.selected), -.sprotty-edge.weighted.medium:not(.selected) .arrow { +.sprotty-edge.weighted:not(.selected, .navigable-element), +.sprotty-edge.weighted:not(.selected, .navigable-element) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element), +.sprotty-edge.weighted.medium:not(.selected, .navigable-element) .arrow { stroke: rgb(98, 60, 193); } -.sprotty-edge.weighted:not(.selected) .arrow, -.sprotty-edge.weighted.medium:not(.selected) .arrow { +.sprotty-edge.weighted:not(.selected, .navigable-element) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element) .arrow { fill: rgb(98, 60, 193); } -.sprotty-edge.weighted.high:not(.selected), -.sprotty-edge.weighted.high:not(.selected) .arrow { +.sprotty-edge.weighted.high:not(.selected, .navigable-element), +.sprotty-edge.weighted.high:not(.selected, .navigable-element) .arrow { stroke: rgb(68, 30, 163); } -.sprotty-edge.weighted.high:not(.selected) .arrow { +.sprotty-edge.weighted.high:not(.selected, .navigable-element) .arrow { fill: rgb(68, 30, 163); } diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 782f1d5..2f6276c 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -27,7 +27,7 @@ stroke: rgb(0, 0, 0); } -.sprotty-edge { +.sprotty-edge:not(.navigable-element) { stroke: black; } diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 26c7677..e10e152 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -318,21 +318,6 @@ fill: transparent; } -.search-hidden { - opacity: 0.4; -} - -.search-highlighted .sprotty-node, -.search-highlighted .sprotty-edge { - stroke-width: 1.5px; - stroke-width: 2px; - stroke-dashoffset: 5; - stroke-dasharray: 5, 5; - stroke-linecap: round; - stroke: rgb(218, 89, 15) !important; - stroke: #130bf7 !important; -} - .ui-extension.hidden { display: none; opacity: 0; diff --git a/packages/client/css/keyboard.css b/packages/client/css/keyboard.css index 49360a5..8c66805 100644 --- a/packages/client/css/keyboard.css +++ b/packages/client/css/keyboard.css @@ -24,8 +24,18 @@ grid-template-columns: auto auto auto; } +.grid-container.grid-visible { + opacity: 0.7; + visibility: visible; +} + +.grid-container.grid-hidden { + opacity: 0; + visibility: hidden; +} + .grid-container:focus { - opacity: 1 !important; + opacity: 1; } .grid-item { diff --git a/packages/client/css/navigation.css b/packages/client/css/navigation.css index e826d22..9ef7193 100644 --- a/packages/client/css/navigation.css +++ b/packages/client/css/navigation.css @@ -17,10 +17,12 @@ :root { --glsp-navigation-highlight: rgba(136, 68, 68, 0.2); } + .navigable-element { - stroke: var(--glsp-navigation-highlight) !important; + stroke: var(--glsp-navigation-highlight); } + .navigable-element .sprotty-edge.arrow { - fill: var(--glsp-navigation-highlight) !important; - stroke: var(--glsp-navigation-highlight) !important; + fill: var(--glsp-navigation-highlight); + stroke: var(--glsp-navigation-highlight); } diff --git a/packages/client/css/search.css b/packages/client/css/search.css new file mode 100644 index 0000000..c12f9f7 --- /dev/null +++ b/packages/client/css/search.css @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.search-hidden { + opacity: 0.4; +} + +.search-highlighted .sprotty-node, +.search-highlighted .sprotty-edge { + stroke-width: 1.5px; + stroke-width: 2px; + stroke-dashoffset: 5; + stroke-dasharray: 5, 5; + stroke-linecap: round; + stroke: #130bf7; +} diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index eeb631b..6711be9 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -97,11 +97,11 @@ export class KeyboardGrid extends GLSPAbstractUIExtension implements IActionHand protected override setContainerVisible(visible: boolean): void { if (this.containerElement) { if (visible) { - this.containerElement.style.visibility = 'visible'; - this.containerElement.style.opacity = '0.7'; + this.containerElement.classList.remove('grid-hidden'); + this.containerElement.classList.add('grid-visible'); } else { - this.containerElement.style.visibility = 'hidden'; - this.containerElement.style.opacity = '0'; + this.containerElement.classList.remove('grid-visible'); + this.containerElement.classList.add('grid-hidden'); } } } diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 46c3217..0aeb5e9 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { bindAsService, BindingContext, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import '../../../../css/search.css'; import { SearchAutocompletePalette } from './search-palette'; import { SearchAutocompletePaletteTool } from './search-tool'; From 37465215c048439639fe18cefa2ceb360991bd9b Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Wed, 19 Feb 2025 15:59:56 +0100 Subject: [PATCH 428/566] Remove important from sprotty css (#413) --- packages/client/css/glsp-sprotty.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index e10e152..f0128a5 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -222,25 +222,25 @@ } .sprotty g .movement-not-allowed > .sprotty-node { - stroke: var(--glsp-error-foreground) !important; + stroke: var(--glsp-error-foreground); stroke-width: 1.5px; } .sprotty-resize-handle.movement-not-allowed { - stroke: var(--glsp-error-foreground) !important; - fill: var(--glsp-error-foreground) !important; + stroke: var(--glsp-error-foreground); + fill: var(--glsp-error-foreground); } .sprotty .error > .sprotty-node { - filter: drop-shadow(0px 0px 2px var(--glsp-error-foreground)) !important; + filter: drop-shadow(0px 0px 2px var(--glsp-error-foreground)); } .sprotty .warning > .sprotty-node { - filter: drop-shadow(0px 0px 2px var(--glsp-warning-foreground)) !important; + filter: drop-shadow(0px 0px 2px var(--glsp-warning-foreground)); } .sprotty .info > .sprotty-node { - filter: drop-shadow(0px 0px 2px var(--glsp-info-foreground)) !important; + filter: drop-shadow(0px 0px 2px var(--glsp-info-foreground)); } .sprotty svg { From 02ff167e0ad540093c627f861ea0378b5eb332d8 Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Thu, 20 Feb 2025 10:01:25 +0100 Subject: [PATCH 429/566] Increase css specifity for movement restriction (#414) --- packages/client/css/glsp-sprotty.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index f0128a5..2c5efeb 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -226,7 +226,7 @@ stroke-width: 1.5px; } -.sprotty-resize-handle.movement-not-allowed { +.sprotty .sprotty-resize-handle.movement-not-allowed { stroke: var(--glsp-error-foreground); fill: var(--glsp-error-foreground); } From 53c355265c6ccf6de1068d1106504e7cff5af653 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 21 Feb 2025 05:14:09 -0800 Subject: [PATCH 430/566] Update default styling for labels (#415) Set `user-select` to none to avoid blinking cursors and/or other indication for text selection when clicking on labels. (Default document text selection of svg text elements is not working anyways) --- examples/workflow-glsp/css/diagram.css | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/workflow-glsp/css/diagram.css b/examples/workflow-glsp/css/diagram.css index bd3f819..86cc739 100644 --- a/examples/workflow-glsp/css/diagram.css +++ b/examples/workflow-glsp/css/diagram.css @@ -36,6 +36,7 @@ font-weight: inherit; text-align: inherit; font-size: 100%; + user-select: none; } .sprotty-label.heading { From 2c05cd77da83936016962e7d0a7c5abafc5be538 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Wed, 26 Feb 2025 13:15:50 +0000 Subject: [PATCH 431/566] Avoid executing unnecessary operations (#105) - Filter empty change bounds and change routing points operations - Filter empty compound commands - Ensure we only submit a new model if actual changes were applied Relates to https://github.com/eclipse-glsp/glsp/issues/1477 --- .../gmodel/change-bounds-operation-handler.ts | 22 +++++++++++++--- ...change-routing-points-operation-handler.ts | 25 ++++++++++++++----- .../operations/compound-operation-handler.ts | 2 +- .../operations/operation-action-handler.ts | 3 ++- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/server/src/common/gmodel/change-bounds-operation-handler.ts b/packages/server/src/common/gmodel/change-bounds-operation-handler.ts index 5225475..681f60b 100644 --- a/packages/server/src/common/gmodel/change-bounds-operation-handler.ts +++ b/packages/server/src/common/gmodel/change-bounds-operation-handler.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelRoot, GNode } from '@eclipse-glsp/graph'; -import { ChangeBoundsOperation, Dimension, MaybePromise, Point } from '@eclipse-glsp/protocol'; +import { GModelRoot, GNode, isGBoundsAware } from '@eclipse-glsp/graph'; +import { ChangeBoundsOperation, Dimension, ElementAndBounds, MaybePromise, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { Command } from '../command/command'; import { GModelOperationHandler } from './gmodel-operation-handler'; @@ -27,7 +27,23 @@ export class GModelChangeBoundsOperationHandler extends GModelOperationHandler { operationType = ChangeBoundsOperation.KIND; createCommand(operation: ChangeBoundsOperation): MaybePromise { - return this.commandOf(() => this.executeChangeBounds(operation)); + const newBounds = operation.newBounds.filter(element => this.hasChanged(element)); + if (newBounds.length === 0) { + return undefined; + } + return this.commandOf(() => this.executeChangeBounds({ ...operation, newBounds })); + } + + protected hasChanged(element: ElementAndBounds): boolean { + const knownElement = this.modelState.index.find(element.elementId); + if (!knownElement || !isGBoundsAware(knownElement)) { + return true; + } + const sizeChanged = knownElement.size ? !Dimension.equals(knownElement.size, element.newSize) : true; + if (sizeChanged) { + return true; + } + return knownElement.position && element.newPosition ? !Point.equals(knownElement.position, element.newPosition) : true; } protected executeChangeBounds(operation: ChangeBoundsOperation): void { diff --git a/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts b/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts index b9de135..0e3024d 100644 --- a/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts +++ b/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts @@ -14,10 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ChangeRoutingPointsOperation, MaybePromise } from '@eclipse-glsp/protocol'; +import { GEdge } from '@eclipse-glsp/graph'; +import { ChangeRoutingPointsOperation, ElementAndRoutingPoints, MaybePromise, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { Command } from '../command/command'; -import { GLSPServerError } from '../utils/glsp-server-error'; import { applyRoutingPoints } from '../utils/layout-util'; import { GModelOperationHandler } from './gmodel-operation-handler'; @@ -26,14 +26,27 @@ export class GModelChangeRoutingPointsOperationHandler extends GModelOperationHa operationType = ChangeRoutingPointsOperation.KIND; createCommand(operation: ChangeRoutingPointsOperation): MaybePromise { - return this.commandOf(() => this.executeChangeRoutingPoints(operation)); + const newRoutingPoints = operation.newRoutingPoints.filter(newRoutingPoints => this.hasChanged(newRoutingPoints)); + if (newRoutingPoints.length === 0) { + return undefined; + } + return this.commandOf(() => this.executeChangeRoutingPoints({ ...operation, newRoutingPoints })); } - executeChangeRoutingPoints(operation: ChangeRoutingPointsOperation): MaybePromise { - if (!operation.newRoutingPoints) { - throw new GLSPServerError('Incomplete change routingPoints action'); + protected hasChanged(element: ElementAndRoutingPoints): boolean { + const knownElement = this.modelState.index.findByClass(element.elementId, GEdge); + if (!knownElement || knownElement.routingPoints.length !== element.newRoutingPoints?.length) { + return true; + } + for (let i = 0; i < knownElement.routingPoints.length; i++) { + if (!Point.equals(knownElement.routingPoints[i], element.newRoutingPoints[i])) { + return true; + } } + return false; + } + executeChangeRoutingPoints(operation: ChangeRoutingPointsOperation): MaybePromise { const index = this.modelState.index; operation.newRoutingPoints.forEach(routingPoints => applyRoutingPoints(routingPoints, index)); } diff --git a/packages/server/src/common/operations/compound-operation-handler.ts b/packages/server/src/common/operations/compound-operation-handler.ts index 20aae35..f3ea4dd 100644 --- a/packages/server/src/common/operations/compound-operation-handler.ts +++ b/packages/server/src/common/operations/compound-operation-handler.ts @@ -40,6 +40,6 @@ export class CompoundOperationHandler extends OperationHandler { commands.push(command); } } - return new CompoundCommand(commands); + return commands.length > 0 ? new CompoundCommand(commands) : undefined; } } diff --git a/packages/server/src/common/operations/operation-action-handler.ts b/packages/server/src/common/operations/operation-action-handler.ts index 3412557..21e3962 100644 --- a/packages/server/src/common/operations/operation-action-handler.ts +++ b/packages/server/src/common/operations/operation-action-handler.ts @@ -67,8 +67,9 @@ export class OperationActionHandler implements ActionHandler { const command = await handler.execute(operation); if (command) { await this.executeCommand(command); + return this.submitModel(); } - return this.modelSubmissionHandler.submitModel('operation'); + return []; } protected async executeCommand(command: Command): Promise { From 6191ef82cb22423e311415bbb5d3b82e1e7795d6 Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Mon, 17 Mar 2025 16:11:39 +0100 Subject: [PATCH 432/566] Prevent rendering of sprotty reconnect handle --- packages/client/src/views/base-view-module.ts | 2 +- .../src/views/routing-point-handle-view.tsx | 32 +++++++++++++++++++ packages/glsp-sprotty/src/re-exports.ts | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/views/routing-point-handle-view.tsx diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 326e1b1..c6937ce 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -34,7 +34,6 @@ import { GPort, GPreRenderedElement, GRoutingHandle, - GRoutingHandleView, GShapedPreRenderedElement, GViewportRootElement, HtmlRootView, @@ -52,6 +51,7 @@ import { GEdgeView } from './gedge-view'; import { GGraphView } from './ggraph-view'; import { GIssueMarkerView } from './issue-marker-view'; import { RoundedCornerNodeView } from './rounded-corner-view'; +import { GRoutingHandleView } from './routing-point-handle-view'; export const baseViewModule = new FeatureModule( (bind, unbind, isBound, rebind) => { diff --git a/packages/client/src/views/routing-point-handle-view.tsx b/packages/client/src/views/routing-point-handle-view.tsx new file mode 100644 index 0000000..853ab40 --- /dev/null +++ b/packages/client/src/views/routing-point-handle-view.tsx @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { SRoutingHandleView, type GRoutingHandle, type RenderingContext, type RoutedPoint } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import type { VNode } from 'snabbdom'; +import { isReconnectHandle } from '../features/reconnect/model'; + +@injectable() +export class GRoutingHandleView extends SRoutingHandleView { + override render(handle: Readonly, context: RenderingContext, args?: { route?: RoutedPoint[] }): VNode { + // We have our own handle view for the reconnect handles + if (isReconnectHandle(handle)) { + return undefined as any; + } + + return super.render(handle, context, args); + } +} diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 39cf5f3..80d9b29 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -268,7 +268,7 @@ export { SBezierCreateHandleView as GBezierCreateHandleView, SCompartmentView as GCompartmentView, SLabelView as GLabelView, - SRoutingHandleView as GRoutingHandleView, + SRoutingHandleView, JumpingPolylineEdgeView, PolylineEdgeView, PolylineEdgeViewWithGapsOnIntersections, From 40e42c2f1f0177975af97a4a83a19a7a279465cc Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Mon, 17 Mar 2025 15:39:23 +0100 Subject: [PATCH 433/566] Include root as findable container for the container manager --- .../src/features/tools/node-creation/container-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index f21e5cc..d99cf6a 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -77,7 +77,7 @@ export class ContainerManager implements IContainerManager { findContainer(location: Point, ctx: GModelElement, evt?: MouseEvent): ContainerElement | undefined { // reverse order of children to find the innermost, top-rendered containers first - return findChildrenAtPosition(ctx.root, location) + return [ctx.root, ...findChildrenAtPosition(ctx.root, location)] .reverse() .find(element => isContainable(element) && !element.cssClasses?.includes(CSS_GHOST_ELEMENT)) as ContainerElement | undefined; } From 5a3ba1e59665e0ad53919ac6a46c34784073b13a Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Tue, 19 Nov 2024 14:30:57 +0100 Subject: [PATCH 434/566] GH-1377: Extract move and zoom features from accessibility to own module --- .../accessibility/accessibility-module.ts | 2 - .../move-zoom/move-zoom-module.ts | 44 ----- .../accessibility/move-zoom/zoom-handler.ts | 182 ------------------ ...zoom-key-tool.ts => grid-zoom-key-tool.ts} | 68 ++----- .../view-key-tools/movement-key-tool.ts | 135 ------------- .../view-key-tools/view-key-tools-module.ts | 10 +- .../src/features/bounds/bounds-module.ts | 6 + .../move-element-handler.ts} | 125 ++---------- .../move-element-key-listener.ts | 70 +++++++ packages/client/src/features/grid/grid.ts | 3 + .../tools/change-bounds/change-bounds-tool.ts | 18 +- .../client/src/features/viewport/utils.ts | 22 +++ .../src/features/viewport/viewport-handler.ts | 139 ++++++++++++- .../viewport/viewport-key-listener.ts | 156 +++++++++++++++ .../src/features/viewport/viewport-modules.ts | 20 +- packages/client/src/index.ts | 9 +- .../src/action-protocol/element-move.spec.ts | 59 ++++++ .../src/action-protocol/element-move.ts | 55 ++++++ .../src/action-protocol/element-zoom.spec.ts | 55 ++++++ .../src/action-protocol/element-zoom.ts | 46 +++++ .../src/action-protocol/viewport.spec.ts | 62 +++++- .../protocol/src/action-protocol/viewport.ts | 58 +++++- packages/protocol/src/index.ts | 2 + 23 files changed, 801 insertions(+), 545 deletions(-) delete mode 100644 packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts delete mode 100644 packages/client/src/features/accessibility/move-zoom/zoom-handler.ts rename packages/client/src/features/accessibility/view-key-tools/{zoom-key-tool.ts => grid-zoom-key-tool.ts} (64%) delete mode 100644 packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts rename packages/client/src/features/{accessibility/move-zoom/move-handler.ts => change-bounds/move-element-handler.ts} (59%) create mode 100644 packages/client/src/features/change-bounds/move-element-key-listener.ts create mode 100644 packages/client/src/features/viewport/utils.ts create mode 100644 packages/client/src/features/viewport/viewport-key-listener.ts create mode 100644 packages/protocol/src/action-protocol/element-move.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-move.ts create mode 100644 packages/protocol/src/action-protocol/element-zoom.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-zoom.ts diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 3f962ed..ae8b2a3 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -20,7 +20,6 @@ import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module' import { configureShortcutHelpTool } from './key-shortcut/shortcut-help-module'; import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; -import { configureMoveZoom } from './move-zoom/move-zoom-module'; import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; import { configureToastTool } from './toast/toast-module'; @@ -34,7 +33,6 @@ export const accessibilityModule = new FeatureModule( const context = { bind, unbind, isBound, rebind }; configureResizeTools(context); configureViewKeyTools(context); - configureMoveZoom(context); configureSearchPaletteModule(context); configureShortcutHelpTool(context); configureKeyboardToolPaletteTool(context); diff --git a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts b/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts deleted file mode 100644 index 204d282..0000000 --- a/packages/client/src/features/accessibility/move-zoom/move-zoom-module.ts +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { BindingContext, FeatureModule, configureActionHandler } from '@eclipse-glsp/sprotty'; -import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler'; -import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler'; - -/** - * Handles move and zoom actions. - */ -export const moveZoomModule = new FeatureModule( - (bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureMoveZoom(context); - }, - { featureId: Symbol('moveZoom') } -); - -export function configureMoveZoom(context: BindingContext): void { - context.bind(MoveViewportHandler).toSelf().inSingletonScope(); - context.bind(MoveElementHandler).toSelf().inSingletonScope(); - - context.bind(ZoomViewportHandler).toSelf().inSingletonScope(); - context.bind(ZoomElementHandler).toSelf().inSingletonScope(); - - configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler); - configureActionHandler(context, MoveElementAction.KIND, MoveElementHandler); - - configureActionHandler(context, ZoomViewportAction.KIND, ZoomViewportHandler); - configureActionHandler(context, ZoomElementAction.KIND, ZoomElementHandler); -} diff --git a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts b/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts deleted file mode 100644 index f1bccc3..0000000 --- a/packages/client/src/features/accessibility/move-zoom/zoom-handler.ts +++ /dev/null @@ -1,182 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { inject, injectable } from 'inversify'; -import { throttle } from 'lodash'; -import { - Action, - Bounds, - GChildElement, - IActionDispatcher, - IActionHandler, - ICommand, - Point, - GModelElement, - GModelRoot, - SetViewportAction, - TYPES, - Viewport, - findParentByFeature, - isViewport -} from '@eclipse-glsp/sprotty'; -import { EditorContextService } from '../../../base/editor-context-service'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; - -/** - * Action for triggering zooming of the viewport. - */ -export interface ZoomViewportAction extends Action { - kind: typeof ZoomViewportAction.KIND; - /** - * used to specify the amount by which the viewport should be zoomed - */ - zoomFactor: number; -} - -export namespace ZoomViewportAction { - export const KIND = 'zoomViewportAction'; - - export function is(object: any): object is ZoomViewportAction { - return Action.hasKind(object, KIND); - } - - export function create(zoomFactor: number): ZoomViewportAction { - return { kind: KIND, zoomFactor }; - } -} - -/** - * Action for triggering zooming of the elements.. - */ -export interface ZoomElementAction extends Action { - kind: typeof ZoomElementAction.KIND; - /** - * used to specify the elements to be zoomed in/out - */ - elementIds: string[]; - /** - * used to specify the amount by which the viewport should be zoomed - */ - zoomFactor: number; -} - -export namespace ZoomElementAction { - export const KIND = 'zoomElementAction'; - - export function is(object: any): object is ZoomElementAction { - return Action.hasKind(object, KIND); - } - - export function create(elementIds: string[], zoomFactor: number): ZoomElementAction { - return { kind: KIND, elementIds, zoomFactor }; - } -} - -/* The ZoomViewportHandler class is an implementation of the IActionHandler interface that handles -zooming in and out of a viewport. */ -@injectable() -export class ZoomViewportHandler implements IActionHandler { - @inject(EditorContextService) - protected editorContextService: EditorContextService; - - static readonly defaultZoomInFactor = 1.1; - static readonly defaultZoomOutFactor = 0.9; - - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - protected readonly throttledHandleViewportZoom = throttle((action: ZoomViewportAction) => this.handleZoomViewport(action), 150); - - handle(action: Action): void { - if (ZoomViewportAction.is(action)) { - this.throttledHandleViewportZoom(action); - } - } - - handleZoomViewport(action: ZoomViewportAction): void { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); - if (!viewport) { - return; - } - this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor)); - } - - protected setNewZoomFactor(viewport: GModelRoot & Viewport, zoomFactor: number): SetViewportAction { - const newZoom = viewport.zoom * zoomFactor; - - const newViewport = { - scroll: viewport.scroll, - zoom: newZoom - }; - - return SetViewportAction.create(viewport.id, newViewport, { animate: true }); - } -} - -/* The ZoomElementHandler class is an implementation of the IActionHandler interface that handles -zooming in and out of elements. */ -@injectable() -export class ZoomElementHandler implements IActionHandler { - @inject(EditorContextService) - protected editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - protected readonly throttledHandleElementZoom = throttle((action: ZoomElementAction) => this.handleZoomElement(action), 150); - - handle(action: Action): void | Action | ICommand { - if (ZoomElementAction.is(action)) { - this.throttledHandleElementZoom(action); - } - } - - handleZoomElement(action: ZoomElementAction): void { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); - if (!viewport) { - return; - } - - const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); - const center = this.getCenter(viewport, elements); - this.dispatcher.dispatch(this.setNewZoomFactor(viewport, action.zoomFactor, center)); - } - - protected getCenter(viewport: GModelRoot & Viewport, selectedElements: SelectableBoundsAware[]): Point { - // Get bounds of elements based on the viewport - const allBounds = selectedElements.map(e => this.boundsInViewport(viewport, e, e.bounds)); - const mergedBounds = allBounds.reduce((b0, b1) => Bounds.combine(b0, b1)); - return Bounds.center(mergedBounds); - } - - // copy from center-fit.ts, translates the children bounds to the viewport bounds - protected boundsInViewport(viewport: GModelRoot & Viewport, element: GModelElement, bounds: Bounds): Bounds { - if (element instanceof GChildElement && element.parent !== viewport) { - return this.boundsInViewport(viewport, element.parent, element.parent.localToParent(bounds) as Bounds); - } else { - return bounds; - } - } - - protected setNewZoomFactor(viewport: GModelRoot & Viewport, zoomFactor: number, point: Point): SetViewportAction { - const newZoom = viewport.zoom * zoomFactor; - - const newViewport = { - scroll: { - x: point.x - (0.5 * viewport.canvasBounds.width) / newZoom, - y: point.y - (0.5 * viewport.canvasBounds.height) / newZoom - }, - zoom: newZoom - }; - - return SetViewportAction.create(viewport.id, newViewport, { animate: true }); - } -} diff --git a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts similarity index 64% rename from packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts rename to packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts index 011294e..f7b07bc 100644 --- a/packages/client/src/features/accessibility/view-key-tools/zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts @@ -34,10 +34,10 @@ import { EditorContextService } from '../../../base/editor-context-service'; import { SelectionService } from '../../../base/selection-service'; import { Tool } from '../../../base/tool-manager/tool'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; +import { ZoomFactor } from '../../viewport/utils'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; -import { ZoomElementAction, ZoomViewportAction } from '../move-zoom/zoom-handler'; import * as messages from '../toast/messages.json'; import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; @@ -45,12 +45,12 @@ import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler' * Zoom viewport and elements when its focused and arrow keys are hit. */ @injectable() -export class ZoomKeyTool implements Tool { - static ID = 'glsp.zoom-key-tool'; +export class GridZoomTool implements Tool { + static ID = 'glsp.accessibility-grid-zoom-tool'; isEditTool = false; - protected readonly zoomKeyListener = new ZoomKeyListener(this); + protected readonly zoomKeyListener = new GridZoomKeyListener(this); @inject(KeyTool) protected readonly keytool: KeyTool; @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; @@ -59,7 +59,7 @@ export class ZoomKeyTool implements Tool { protected editorContextService: EditorContextService; get id(): string { - return ZoomKeyTool.ID; + return GridZoomTool.ID; } enable(): void { @@ -75,18 +75,15 @@ export class ZoomKeyTool implements Tool { if (isViewport(this.editorContextService.modelRoot)) { let viewportAction: Action | undefined = undefined; - if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === ZoomKeyTool.ID) { + if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === GridZoomTool.ID) { viewportAction = this.zoomKeyListener.setNewZoomFactor( this.editorContextService.modelRoot, - ZoomKeyListener.defaultZoomInFactor, + ZoomFactor.Default.IN, getAbsolutePositionByPoint(this.editorContextService.modelRoot, action.options.centerCellPosition) ); - } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === ZoomKeyTool.ID) { + } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === GridZoomTool.ID) { if (matchesKeystroke(action.options.event, 'Minus')) { - viewportAction = this.zoomKeyListener.setNewZoomFactor( - this.editorContextService.modelRoot, - ZoomKeyListener.defaultZoomOutFactor - ); + viewportAction = this.zoomKeyListener.setNewZoomFactor(this.editorContextService.modelRoot, ZoomFactor.Default.OUT); } else if (matchesKeystroke(action.options.event, 'Digit0', 'ctrl')) { viewportAction = CenterAction.create([]); } @@ -102,12 +99,10 @@ export class ZoomKeyTool implements Tool { } } -export class ZoomKeyListener extends KeyListener { - static readonly defaultZoomInFactor = 1.1; - static readonly defaultZoomOutFactor = 0.9; - protected readonly token = ZoomKeyListener.name; +export class GridZoomKeyListener extends KeyListener { + protected readonly token = GridZoomKeyListener.name; - constructor(protected tool: ZoomKeyTool) { + constructor(protected tool: GridZoomTool) { super(); } @@ -115,12 +110,7 @@ export class ZoomKeyListener extends KeyListener { this.tool.actionDispatcher.dispatchOnceModelInitialized( SetAccessibleKeyShortcutAction.create({ token: this.token, - keys: [ - { shortcuts: ['+'], description: 'Zoom in to element or viewport', group: 'Zoom', position: 0 }, - { shortcuts: ['-'], description: 'Zoom out to element or viewport', group: 'Zoom', position: 1 }, - { shortcuts: ['CTRL', '0'], description: 'Reset zoom to default', group: 'Zoom', position: 2 }, - { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 3 } - ] + keys: [{ shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 0 }] }) ); } @@ -143,16 +133,14 @@ export class ZoomKeyListener extends KeyListener { zoom: newZoom }; } - return SetViewportAction.create(viewport.id, newViewport, { animate: true }); + return SetViewportAction.create(viewport.id, newViewport, { animate: false }); } override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { - const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); - if (this.matchesZoomViaGrid(event)) { return [ EnableKeyboardGridAction.create({ - originId: ZoomKeyTool.ID, + originId: GridZoomTool.ID, triggerActions: [] }), @@ -161,37 +149,11 @@ export class ZoomKeyListener extends KeyListener { message: messages.grid.zoom_in_grid }) ]; - } else if (this.matchesZoomOutKeystroke(event)) { - if (selectedElementIds.length > 0) { - return [ZoomElementAction.create(selectedElementIds, ZoomKeyListener.defaultZoomOutFactor)]; - } else { - return [ZoomViewportAction.create(ZoomKeyListener.defaultZoomOutFactor)]; - } - } else if (this.matchesZoomInKeystroke(event)) { - if (selectedElementIds.length > 0) { - return [ZoomElementAction.create(selectedElementIds, ZoomKeyListener.defaultZoomInFactor)]; - } else { - return [ZoomViewportAction.create(ZoomKeyListener.defaultZoomInFactor)]; - } - } else if (this.matchesMinZoomLevelKeystroke(event)) { - return [CenterAction.create(selectedElementIds)]; } return []; } - protected matchesZoomInKeystroke(event: KeyboardEvent): boolean { - /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ - return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); - } - protected matchesZoomViaGrid(event: KeyboardEvent): boolean { return event.key === '+' && event.ctrlKey; } - protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); - } - - protected matchesZoomOutKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract'); - } } diff --git a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts deleted file mode 100644 index bd2e61f..0000000 --- a/packages/client/src/features/accessibility/view-key-tools/movement-key-tool.ts +++ /dev/null @@ -1,135 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Action, GModelElement, IActionDispatcher, ISnapper, KeyListener, KeyTool, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; -import { SelectionService } from '../../../base/selection-service'; -import { Tool } from '../../../base/tool-manager/tool'; -import { Grid } from '../../grid/grid'; -import { IChangeBoundsManager } from '../../tools/change-bounds/change-bounds-manager'; -import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import { MoveElementAction, MoveViewportAction } from '../move-zoom/move-handler'; - -/** - * Moves viewport and elements when its focused and arrow keys are hit. - */ -@injectable() -export class MovementKeyTool implements Tool { - static ID = 'glsp.movement-key-tool'; - - isEditTool = true; - - protected movementKeyListener: MoveKeyListener; - - @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(SelectionService) selectionService: SelectionService; - @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; - @inject(TYPES.Grid) @optional() protected grid: Grid; - @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; - - get id(): string { - return MovementKeyTool.ID; - } - - enable(): void { - if (!this.movementKeyListener) { - this.movementKeyListener = new MoveKeyListener(this, this.grid); - } - this.keytool.register(this.movementKeyListener); - this.movementKeyListener.registerShortcutKey(); - } - - disable(): void { - this.keytool.deregister(this.movementKeyListener); - } -} - -export class MoveKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { - // Default x distance used if grid is not provided - static readonly defaultMoveX = 20; - - // Default y distance used if grid is not provided - static readonly defaultMoveY = 20; - - protected readonly token = MoveKeyListener.name; - - constructor( - protected readonly tool: MovementKeyTool, - protected grid: Grid = { x: MoveKeyListener.defaultMoveX, y: MoveKeyListener.defaultMoveY } - ) { - super(); - } - - registerShortcutKey(): void { - this.tool.actionDispatcher.dispatchOnceModelInitialized( - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [{ shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element or viewport', group: 'Move', position: 0 }] - }) - ); - } - - override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { - const selectedElementIds = this.tool.selectionService.getSelectedElementIDs(); - const snap = this.tool.changeBoundsManager.usePositionSnap(event); - const offsetX = snap ? this.grid.x : 1; - const offsetY = snap ? this.grid.y : 1; - - if (selectedElementIds.length > 0) { - if (this.matchesMoveUpKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, 0, -offsetY, snap)]; - } else if (this.matchesMoveDownKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, 0, offsetY, snap)]; - } else if (this.matchesMoveRightKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, offsetX, 0, snap)]; - } else if (this.matchesMoveLeftKeystroke(event)) { - return [MoveElementAction.create(selectedElementIds, -offsetX, 0, snap)]; - } - } else { - if (this.matchesMoveUpKeystroke(event)) { - return [MoveViewportAction.create(0, -offsetY)]; - } else if (this.matchesMoveDownKeystroke(event)) { - return [MoveViewportAction.create(0, offsetY)]; - } else if (this.matchesMoveRightKeystroke(event)) { - return [MoveViewportAction.create(offsetX, 0)]; - } else if (this.matchesMoveLeftKeystroke(event)) { - return [MoveViewportAction.create(-offsetX, 0)]; - } - } - return []; - } - - protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { - const unsnap = this.tool.changeBoundsManager.unsnapModifier(); - return matchesKeystroke(event, 'ArrowUp') || (!!unsnap && matchesKeystroke(event, 'ArrowUp', unsnap)); - } - - protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { - const unsnap = this.tool.changeBoundsManager.unsnapModifier(); - return matchesKeystroke(event, 'ArrowDown') || (!!unsnap && matchesKeystroke(event, 'ArrowDown', unsnap)); - } - - protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { - const unsnap = this.tool.changeBoundsManager.unsnapModifier(); - return matchesKeystroke(event, 'ArrowRight') || (!!unsnap && matchesKeystroke(event, 'ArrowRight', unsnap)); - } - - protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { - const unsnap = this.tool.changeBoundsManager.unsnapModifier(); - return matchesKeystroke(event, 'ArrowLeft') || (!!unsnap && matchesKeystroke(event, 'ArrowLeft', unsnap)); - } -} diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index b7ceaff..23110ff 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -17,8 +17,7 @@ import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import { KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; import { DeselectKeyTool } from './deselect-key-tool'; -import { MovementKeyTool } from './movement-key-tool'; -import { ZoomKeyTool } from './zoom-key-tool'; +import { GridZoomTool } from './grid-zoom-key-tool'; export const viewKeyToolsModule = new FeatureModule( (bind, _unbind, isBound, rebind) => { @@ -31,9 +30,8 @@ export const viewKeyToolsModule = new FeatureModule( ); export function configureViewKeyTools(context: Pick): void { - bindAsService(context, TYPES.IDefaultTool, MovementKeyTool); - bindAsService(context, TYPES.IDefaultTool, ZoomKeyTool); - configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, ZoomKeyTool); - configureActionHandler(context, KeyboardGridKeyboardEventAction.KIND, ZoomKeyTool); + bindAsService(context, TYPES.IDefaultTool, GridZoomTool); + configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, GridZoomTool); + configureActionHandler(context, KeyboardGridKeyboardEventAction.KIND, GridZoomTool); bindAsService(context, TYPES.IDefaultTool, DeselectKeyTool); } diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 3ad5880..36584de 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -18,14 +18,17 @@ import { HBoxLayouter, HiddenBoundsUpdater, LayoutRegistry, + MoveElementRelativeAction, RequestBoundsCommand, SetBoundsCommand, TYPES, VBoxLayouter, bindAsService, + configureActionHandler, configureCommand, configureLayout } from '@eclipse-glsp/sprotty'; +import { MoveElementHandler } from '../change-bounds/move-element-handler'; import { PositionSnapper } from '../change-bounds/position-snapper'; import { FreeFormLayouter } from './freeform-layout'; import { GLSPHiddenBoundsUpdater } from './glsp-hidden-bounds-updater'; @@ -53,6 +56,9 @@ export const boundsModule = new FeatureModule( configureLayout(context, HBoxLayouter.KIND, HBoxLayouterExt); configureLayout(context, FreeFormLayouter.KIND, FreeFormLayouter); + bind(MoveElementHandler).toSelf().inSingletonScope(); + configureActionHandler(context, MoveElementRelativeAction.KIND, MoveElementHandler); + // backwards compatibility // eslint-disable-next-line deprecation/deprecation bind(PositionSnapper).toSelf(); diff --git a/packages/client/src/features/accessibility/move-zoom/move-handler.ts b/packages/client/src/features/change-bounds/move-element-handler.ts similarity index 59% rename from packages/client/src/features/accessibility/move-zoom/move-handler.ts rename to packages/client/src/features/change-bounds/move-element-handler.ts index c0f34e3..b9cb2c8 100644 --- a/packages/client/src/features/accessibility/move-zoom/move-handler.ts +++ b/packages/client/src/features/change-bounds/move-element-handler.ts @@ -19,127 +19,28 @@ import { ChangeBoundsOperation, ElementAndBounds, ElementMove, - GModelRoot, IActionDispatcher, IActionHandler, ICommand, ISnapper, MoveAction, + MoveElementRelativeAction, + MoveViewportAction, Point, - SetViewportAction, TYPES, - Viewport, findParentByFeature, isBoundsAware, isViewport } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; -import { EditorContextService } from '../../../base/editor-context-service'; -import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher'; -import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; -import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../../utils/gmodel-util'; -import { isValidMove } from '../../../utils/layout-utils'; -import { outsideOfViewport } from '../../../utils/viewpoint-util'; -import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; - -/** - * Action for triggering moving of the viewport. - */ -export interface MoveViewportAction extends Action { - kind: typeof MoveViewportAction.KIND; - /** - * used to specify the amount to be moved in the x-axis - */ - moveX: number; - /** - * used to specify the amount to be moved in the y-axis - */ - moveY: number; -} - -export namespace MoveViewportAction { - export const KIND = 'moveViewportAction'; - - export function is(object: any): object is MoveViewportAction { - return Action.hasKind(object, KIND); - } - - export function create(moveX: number, moveY: number): MoveViewportAction { - return { kind: KIND, moveX, moveY }; - } -} - -/** - * Action for triggering moving of elements. - */ -export interface MoveElementAction extends Action { - kind: typeof MoveElementAction.KIND; - /** - * used to specify the elements to be zoomed in/out - */ - elementIds: string[]; - /** - * used to specify the amount to be moved in the x-axis - */ - moveX: number; - /** - * used to specify the amount to be moved in the y-axis - */ - moveY: number; - /** - * used to specify whether we should snap to the grid - */ - snap: boolean; -} - -export namespace MoveElementAction { - export const KIND = 'moveElementAction'; - - export function is(object: any): object is MoveElementAction { - return Action.hasKind(object, KIND); - } - - export function create(elementIds: string[], moveX: number, moveY: number, snap: boolean = true): MoveElementAction { - return { kind: KIND, elementIds, moveX, moveY, snap }; - } -} - -/** - * Action handler for moving of the viewport. - */ -@injectable() -export class MoveViewportHandler implements IActionHandler { - @inject(EditorContextService) - protected editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) - protected dispatcher: IActionDispatcher; - - handle(action: Action): void | Action | ICommand { - if (MoveViewportAction.is(action)) { - this.handleMoveViewport(action); - } - } - - handleMoveViewport(action: MoveViewportAction): void { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); - if (!viewport) { - return; - } - this.dispatcher.dispatch(this.moveViewport(viewport, action.moveX, action.moveY)); - } - - protected moveViewport(viewport: GModelRoot & Viewport, offsetX: number, offSetY: number): SetViewportAction { - const newViewport: Viewport = { - scroll: { - x: viewport.scroll.x + offsetX, - y: viewport.scroll.y + offSetY - }, - zoom: viewport.zoom - }; - return SetViewportAction.create(viewport.id, newViewport, { animate: false }); - } -} +import { EditorContextService } from '../../base/editor-context-service'; +import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher'; +import { FeedbackEmitter } from '../../base/feedback/feedback-emitter'; +import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from '../../utils/gmodel-util'; +import { isValidMove } from '../../utils/layout-utils'; +import { outsideOfViewport } from '../../utils/viewpoint-util'; +import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; /** * Action handler for moving elements. @@ -172,12 +73,12 @@ export class MoveElementHandler implements IActionHandler { } handle(action: Action): void | Action | ICommand { - if (MoveElementAction.is(action)) { + if (MoveElementRelativeAction.is(action)) { this.handleMoveElement(action); } } - handleMoveElement(action: MoveElementAction): void { + protected handleMoveElement(action: MoveElementRelativeAction): void { const modelRoot = this.editorContextService.modelRoot; const viewport = findParentByFeature(modelRoot, isViewport); if (!viewport) { @@ -198,7 +99,7 @@ export class MoveElementHandler implements IActionHandler { toPosition: newPosition }); if (outsideOfViewport(newPosition, viewport)) { - viewportActions.push(MoveViewportAction.create(action.moveX, action.moveY)); + viewportActions.push(MoveViewportAction.create({ moveX: action.moveX, moveY: action.moveY })); } } @@ -209,7 +110,7 @@ export class MoveElementHandler implements IActionHandler { this.scheduleChangeBounds(this.toElementAndBounds(elementMoves)); } - protected getTargetBounds(element: SelectableBoundsAware, action: MoveElementAction): Point { + protected getTargetBounds(element: SelectableBoundsAware, action: MoveElementRelativeAction): Point { let position = Point.add(element.bounds, { x: action.moveX, y: action.moveY }); if (this.snapper && action.snap) { position = this.snapper.snap(position, element); diff --git a/packages/client/src/features/change-bounds/move-element-key-listener.ts b/packages/client/src/features/change-bounds/move-element-key-listener.ts new file mode 100644 index 0000000..16bd9bf --- /dev/null +++ b/packages/client/src/features/change-bounds/move-element-key-listener.ts @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { KeyListener, matchesKeystroke, MoveElementRelativeAction, type Action, type GModelElement } from '@eclipse-glsp/sprotty'; +import type { SelectionService } from '../../base/selection-service'; +import { Grid } from '../grid/grid'; +import type { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; + +export class MoveElementKeyListener extends KeyListener { + constructor( + protected readonly selectionService: SelectionService, + protected readonly changeBoundsManager: IChangeBoundsManager, + protected readonly grid: Grid = Grid.DEFAULT + ) { + super(); + } + + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.selectionService.getSelectedElementIDs(); + const snap = this.changeBoundsManager.usePositionSnap(event); + const offsetX = snap ? this.grid.x : 1; + const offsetY = snap ? this.grid.y : 1; + + if (selectedElementIds.length > 0) { + if (this.matchesMoveUpKeystroke(event)) { + return [MoveElementRelativeAction.create({ elementIds: selectedElementIds, moveX: 0, moveY: -offsetY, snap })]; + } else if (this.matchesMoveDownKeystroke(event)) { + return [MoveElementRelativeAction.create({ elementIds: selectedElementIds, moveX: 0, moveY: offsetY, snap })]; + } else if (this.matchesMoveRightKeystroke(event)) { + return [MoveElementRelativeAction.create({ elementIds: selectedElementIds, moveX: offsetX, moveY: 0, snap })]; + } else if (this.matchesMoveLeftKeystroke(event)) { + return [MoveElementRelativeAction.create({ elementIds: selectedElementIds, moveX: -offsetX, moveY: 0, snap })]; + } + } + return []; + } + + protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { + const unsnap = this.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowUp') || (!!unsnap && matchesKeystroke(event, 'ArrowUp', unsnap)); + } + + protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { + const unsnap = this.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowDown') || (!!unsnap && matchesKeystroke(event, 'ArrowDown', unsnap)); + } + + protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { + const unsnap = this.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowRight') || (!!unsnap && matchesKeystroke(event, 'ArrowRight', unsnap)); + } + + protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { + const unsnap = this.changeBoundsManager.unsnapModifier(); + return matchesKeystroke(event, 'ArrowLeft') || (!!unsnap && matchesKeystroke(event, 'ArrowLeft', unsnap)); + } +} diff --git a/packages/client/src/features/grid/grid.ts b/packages/client/src/features/grid/grid.ts index 2ef94be..6e5eba3 100644 --- a/packages/client/src/features/grid/grid.ts +++ b/packages/client/src/features/grid/grid.ts @@ -16,3 +16,6 @@ import { Point } from '@eclipse-glsp/sprotty'; export type Grid = Point; +export namespace Grid { + export const DEFAULT: Grid = { x: 20, y: 20 }; +} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 9f008ae..b759d2a 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -31,6 +31,7 @@ import { GModelElement, GModelRoot, GParentElement, + KeyListener, MouseListener, Operation, Point, @@ -52,7 +53,9 @@ import { import { LocalRequestBoundsAction } from '../../bounds/local-bounds'; import { SetBoundsFeedbackAction } from '../../bounds/set-bounds-feedback-command'; import { GResizeHandle, isResizable } from '../../change-bounds/model'; +import { MoveElementKeyListener } from '../../change-bounds/move-element-key-listener'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; +import { Grid } from '../../grid/grid'; import { BaseEditTool } from '../base-tools'; import { CSS_ACTIVE_HANDLE, IChangeBoundsManager } from './change-bounds-manager'; import { @@ -90,6 +93,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; @inject(TYPES.IMovementOptions) @optional() readonly movementOptions: IMovementOptions = { allElementsNeedToBeValid: true }; + @inject(TYPES.Grid) @optional() readonly grid?: Grid; get id(): string { return ChangeBoundsTool.ID; @@ -106,12 +110,20 @@ export class ChangeBoundsTool extends BaseEditTool { this.toDisposeOnDisable.push(this.selectionService.addListener(feedbackMoveMouseListener)); } + // install move key listener for client-side move updates + const createMoveKeyListener = this.createMoveKeyListener(); + this.toDisposeOnDisable.push(this.keyTool.registerListener(createMoveKeyListener)); + if (Disposable.is(createMoveKeyListener)) { + this.toDisposeOnDisable.push(createMoveKeyListener); + } + // install change bounds listener for client-side resize updates and server-side updates const changeBoundsListener = this.createChangeBoundsListener(); this.toDisposeOnDisable.push(this.mouseTool.registerListener(changeBoundsListener)); if (Disposable.is(changeBoundsListener)) { this.toDisposeOnDisable.push(changeBoundsListener); } + if (ISelectionListener.is(changeBoundsListener)) { this.toDisposeOnDisable.push(this.selectionService.addListener(changeBoundsListener)); } @@ -125,7 +137,11 @@ export class ChangeBoundsTool extends BaseEditTool { return new FeedbackMoveMouseListener(this); } - protected createChangeBoundsListener(): MouseListener { + protected createMoveKeyListener(): KeyListener { + return new MoveElementKeyListener(this.selectionService, this.changeBoundsManager, this.grid); + } + + protected createChangeBoundsListener(): MouseListener & ISelectionListener { return new ChangeBoundsListener(this); } } diff --git a/packages/client/src/features/viewport/utils.ts b/packages/client/src/features/viewport/utils.ts new file mode 100644 index 0000000..4bd2878 --- /dev/null +++ b/packages/client/src/features/viewport/utils.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export namespace ZoomFactor { + export namespace Default { + export const IN = 1.2; + export const OUT = 0.8; + } +} diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index aa8a49c..3fc5f7b 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -13,12 +13,32 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, DOMHelper, IActionHandler, TYPES } from '@eclipse-glsp/sprotty'; +import { + Action, + Bounds, + DOMHelper, + findParentByFeature, + GChildElement, + GModelElement, + GModelRoot, + IActionDispatcher, + IActionHandler, + ICommand, + isViewport, + MoveViewportAction, + Point, + SetViewportAction, + TYPES, + Viewport, + ZoomElementAction, + ZoomViewportAction +} from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; +import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../utils/gmodel-util'; import { FocusDomAction } from '../accessibility/actions'; /** @@ -78,3 +98,120 @@ export class RestoreViewportHandler implements IActionHandler, IDiagramStartup { }); } } + +/** + * Handles moving the viewport. + */ +@injectable() +export class MoveViewportHandler implements IActionHandler { + @inject(EditorContextService) + protected readonly editorContextService: EditorContextService; + + handle(action: Action): void | Action | ICommand { + if (MoveViewportAction.is(action)) { + return this.handleMoveViewport(action); + } + } + + protected handleMoveViewport(action: MoveViewportAction): Action | undefined { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + const newViewport: Viewport = { + scroll: { + x: viewport.scroll.x + action.moveX, + y: viewport.scroll.y + action.moveY + }, + zoom: viewport.zoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: false }); + } +} + +/* + * Handles zooming in and out of the viewport. + */ +@injectable() +export class ZoomViewportHandler implements IActionHandler { + @inject(EditorContextService) + protected readonly editorContextService: EditorContextService; + @inject(TYPES.IActionDispatcher) + protected readonly actionDispatcher: IActionDispatcher; + + handle(action: Action): Action | void { + if (ZoomViewportAction.is(action)) { + return this.handleZoomViewport(action); + } + } + + protected handleZoomViewport(action: ZoomViewportAction): Action | undefined { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + + const newZoom = viewport.zoom * action.zoomFactor; + + const newViewport = { + scroll: viewport.scroll, + zoom: newZoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: false }); + } +} + +/* + * Handles zooming in and out of the viewport in the direction of the elements. + */ +@injectable() +export class ZoomElementHandler implements IActionHandler { + @inject(EditorContextService) + protected readonly editorContextService: EditorContextService; + @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; + + handle(action: Action): void | Action | ICommand { + if (ZoomElementAction.is(action)) { + return this.handleZoomElement(action); + } + } + + protected handleZoomElement(action: ZoomElementAction): Action | undefined { + const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + if (!viewport) { + return; + } + + const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + const center = this.getCenter(viewport, elements); + + const newZoom = viewport.zoom * action.zoomFactor; + + const newViewport = { + scroll: { + x: center.x - (0.5 * viewport.canvasBounds.width) / newZoom, + y: center.y - (0.5 * viewport.canvasBounds.height) / newZoom + }, + zoom: newZoom + }; + + return SetViewportAction.create(viewport.id, newViewport, { animate: false }); + } + + protected getCenter(viewport: GModelRoot & Viewport, selectedElements: SelectableBoundsAware[]): Point { + // Get bounds of elements based on the viewport + const allBounds = selectedElements.map(e => this.boundsInViewport(viewport, e, e.bounds)); + const mergedBounds = allBounds.reduce((b0, b1) => Bounds.combine(b0, b1)); + return Bounds.center(mergedBounds); + } + + protected boundsInViewport(viewport: GModelRoot & Viewport, element: GModelElement, bounds: Bounds): Bounds { + if (element instanceof GChildElement && element.parent !== viewport) { + return this.boundsInViewport(viewport, element.parent, element.parent.localToParent(bounds) as Bounds); + } else { + return bounds; + } + } +} diff --git a/packages/client/src/features/viewport/viewport-key-listener.ts b/packages/client/src/features/viewport/viewport-key-listener.ts new file mode 100644 index 0000000..5adb2d1 --- /dev/null +++ b/packages/client/src/features/viewport/viewport-key-listener.ts @@ -0,0 +1,156 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + CenterAction, + GModelElement, + KeyListener, + matchesKeystroke, + MoveViewportAction, + TYPES, + ZoomElementAction, + ZoomViewportAction +} from '@eclipse-glsp/sprotty'; +import { inject, injectable, optional } from 'inversify'; +import { SelectionService } from '../../base/selection-service'; +import { Grid } from '../grid/grid'; +import { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; +import { ZoomFactor } from './utils'; + +@injectable() +export class MoveViewportKeyListener extends KeyListener { + @inject(SelectionService) + protected readonly selectionService: SelectionService; + @inject(TYPES.IChangeBoundsManager) + protected readonly changeBoundsManager: IChangeBoundsManager; + @inject(TYPES.Grid) + @optional() + protected readonly grid = Grid.DEFAULT; + + override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.selectionService.getSelectedElementIDs(); + const snap = this.changeBoundsManager.usePositionSnap(event); + const offsetX = snap ? this.grid.x : 1; + const offsetY = snap ? this.grid.y : 1; + + if (selectedElementIds.length === 0) { + if (this.matchesMoveUpKeystroke(event)) { + return [MoveViewportAction.create({ moveX: 0, moveY: -offsetY })]; + } else if (this.matchesMoveDownKeystroke(event)) { + return [MoveViewportAction.create({ moveX: 0, moveY: offsetY })]; + } else if (this.matchesMoveRightKeystroke(event)) { + return [MoveViewportAction.create({ moveX: offsetX, moveY: 0 })]; + } else if (this.matchesMoveLeftKeystroke(event)) { + return [MoveViewportAction.create({ moveX: -offsetX, moveY: 0 })]; + } + } + return []; + } + + protected matchesMoveUpKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowUp'); + } + + protected matchesMoveDownKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowDown'); + } + + protected matchesMoveRightKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowRight'); + } + + protected matchesMoveLeftKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'ArrowLeft'); + } +} + +@injectable() +export class ZoomViewportKeyListener extends KeyListener { + @inject(SelectionService) + protected readonly selectionService: SelectionService; + + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.selectionService.getSelectedElementIDs(); + + if (selectedElementIds.length === 0) { + if (this.matchesZoomOutKeystroke(event)) { + return [ZoomViewportAction.create({ zoomFactor: ZoomFactor.Default.OUT })]; + } else if (this.matchesZoomInKeystroke(event)) { + return [ZoomViewportAction.create({ zoomFactor: ZoomFactor.Default.IN })]; + } else if (this.matchesMinZoomLevelKeystroke(event)) { + return [CenterAction.create(selectedElementIds)]; + } + } + + return []; + } + + protected matchesZoomInKeystroke(event: KeyboardEvent): boolean { + /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ + return !event.ctrlKey && (event.key === '+' || matchesKeystroke(event, 'NumpadAdd')); + } + + protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); + } + + protected matchesZoomOutKeystroke(event: KeyboardEvent): boolean { + return !event.ctrlKey && (matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract')); + } +} + +export class ZoomElementKeyListener extends KeyListener { + @inject(SelectionService) + protected readonly selectionService: SelectionService; + + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + const selectedElementIds = this.selectionService.getSelectedElementIDs(); + + if (selectedElementIds.length > 0) { + if (this.matchesZoomOutKeystroke(event)) { + if (selectedElementIds.length > 0) { + return [ + ZoomElementAction.create({ + elementIds: selectedElementIds, + zoomFactor: ZoomFactor.Default.OUT + }) + ]; + } + } else if (this.matchesZoomInKeystroke(event)) { + if (selectedElementIds.length > 0) { + return [ZoomElementAction.create({ elementIds: selectedElementIds, zoomFactor: ZoomFactor.Default.IN })]; + } + } else if (this.matchesMinZoomLevelKeystroke(event)) { + return [CenterAction.create(selectedElementIds)]; + } + } + return []; + } + + protected matchesZoomInKeystroke(event: KeyboardEvent): boolean { + /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ + return !event.ctrlKey && (event.key === '+' || matchesKeystroke(event, 'NumpadAdd')); + } + + protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); + } + + protected matchesZoomOutKeystroke(event: KeyboardEvent): boolean { + return !event.ctrlKey && (matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract')); + } +} diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 03378fc..f620649 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -22,16 +22,20 @@ import { FeatureModule, FitToScreenCommand, GetViewportCommand, + MoveViewportAction, SetViewportCommand, TYPES, - ZoomMouseListener + ZoomElementAction, + ZoomMouseListener, + ZoomViewportAction } from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { FocusDomAction } from '../accessibility/actions'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; import { OriginViewportCommand } from './origin-viewport'; import { RepositionCommand } from './reposition'; -import { RestoreViewportHandler } from './viewport-handler'; +import { MoveViewportHandler, RestoreViewportHandler, ZoomElementHandler, ZoomViewportHandler } from './viewport-handler'; +import { MoveViewportKeyListener, ZoomElementKeyListener, ZoomViewportKeyListener } from './viewport-key-listener'; export const viewportModule = new FeatureModule( (bind, _unbind, isBound) => { @@ -52,6 +56,18 @@ export const viewportModule = new FeatureModule( bindAsService(context, TYPES.IDiagramStartup, RestoreViewportHandler); configureActionHandler(context, EnableDefaultToolsAction.KIND, RestoreViewportHandler); configureActionHandler(context, FocusDomAction.KIND, RestoreViewportHandler); + + bind(MoveViewportHandler).toSelf().inSingletonScope(); + bindAsService(context, TYPES.KeyListener, MoveViewportKeyListener); + configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler); + + bind(ZoomViewportHandler).toSelf().inSingletonScope(); + bindAsService(context, TYPES.KeyListener, ZoomViewportKeyListener); + configureActionHandler(context, ZoomViewportAction.KIND, ZoomViewportHandler); + + bind(ZoomElementHandler).toSelf().inSingletonScope(); + bindAsService(context, TYPES.KeyListener, ZoomElementKeyListener); + configureActionHandler(context, ZoomElementAction.KIND, ZoomElementHandler); }, { featureId: Symbol('viewport') } ); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index bc980f3..03d6a91 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -80,9 +80,6 @@ export * from './features/accessibility/keyboard-pointer/keyboard-pointer-module export * from './features/accessibility/keyboard-pointer/keyboard-pointer-position'; export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette'; export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module'; -export * from './features/accessibility/move-zoom/move-handler'; -export * from './features/accessibility/move-zoom/move-zoom-module'; -export * from './features/accessibility/move-zoom/zoom-handler'; export * from './features/accessibility/resize-key-tool/resize-key-handler'; export * from './features/accessibility/resize-key-tool/resize-key-module'; export * from './features/accessibility/resize-key-tool/resize-key-tool'; @@ -93,9 +90,8 @@ export * from './features/accessibility/toast/toast-handler'; export * from './features/accessibility/toast/toast-module'; export * from './features/accessibility/toast/toast-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; -export * from './features/accessibility/view-key-tools/movement-key-tool'; +export * from './features/accessibility/view-key-tools/grid-zoom-key-tool'; export * from './features/accessibility/view-key-tools/view-key-tools-module'; -export * from './features/accessibility/view-key-tools/zoom-key-tool'; export * from './features/bounds/bounds-module'; export * from './features/bounds/freeform-layout'; export * from './features/bounds/glsp-hidden-bounds-updater'; @@ -106,6 +102,7 @@ export * from './features/bounds/local-bounds'; export * from './features/bounds/set-bounds-feedback-command'; export * from './features/bounds/vbox-layout'; export * from './features/change-bounds/model'; +export * from './features/change-bounds/move-element-handler'; export * from './features/change-bounds/movement-restrictor'; export * from './features/change-bounds/point-position-updater'; export * from './features/change-bounds/position-snapper'; @@ -218,7 +215,9 @@ export * from './features/validation/validation-modules'; export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './features/viewport/origin-viewport'; export * from './features/viewport/reposition'; +export * from './features/viewport/utils'; export * from './features/viewport/viewport-handler'; +export * from './features/viewport/viewport-key-listener'; export * from './features/viewport/viewport-modules'; export * from './model'; export * from './re-exports'; diff --git a/packages/protocol/src/action-protocol/element-move.spec.ts b/packages/protocol/src/action-protocol/element-move.spec.ts new file mode 100644 index 0000000..676b557 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-move.spec.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { MoveElementAction } from './element-move'; + +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Element move actions', () => { + describe('MoveElementAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: MoveElementAction = { + kind: 'moveElement', + elementIds: ['someId'], + moveX: 0, + moveY: 0, + snap: true + }; + expect(MoveElementAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(MoveElementAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(MoveElementAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: MoveElementAction = { + kind: 'moveElement', + elementIds: ['someId'], + moveX: 0, + moveY: 0, + snap: true + }; + const { elementIds, moveX, moveY } = expected; + expect(MoveElementAction.create({ elementIds, moveX, moveY })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-move.ts b/packages/protocol/src/action-protocol/element-move.ts new file mode 100644 index 0000000..8cba69f --- /dev/null +++ b/packages/protocol/src/action-protocol/element-move.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from './base-protocol'; + +/** + * Action for triggering moving of elements. It is similar to `MoveAction` but is used for moving elements relative + * to their current position. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `MoveElementRelativeAction`. + */ +export interface MoveElementRelativeAction extends Action { + kind: typeof MoveElementRelativeAction.KIND; + /** + * Specifies the elements to be moved in/out + */ + elementIds: string[]; + /** + * Specifies the amount to be moved in the x-axis + */ + moveX: number; + /** + * Specifies the amount to be moved in the y-axis + */ + moveY: number; + /** + * Specifies whether we should snap to the grid + */ + snap: boolean; +} + +export namespace MoveElementRelativeAction { + export const KIND = 'moveElementRelative'; + + export function is(object: any): object is MoveElementRelativeAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { elementIds: string[]; moveX: number; moveY: number; snap?: boolean }): MoveElementRelativeAction { + return { kind: KIND, snap: true, ...options }; + } +} diff --git a/packages/protocol/src/action-protocol/element-zoom.spec.ts b/packages/protocol/src/action-protocol/element-zoom.spec.ts new file mode 100644 index 0000000..8944273 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-zoom.spec.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { ZoomElementAction } from '../../lib'; + +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Element zoom actions', () => { + describe('ZoomElementAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ZoomElementAction = { + kind: 'zoomElement', + elementIds: ['someId'], + zoomFactor: 1 + }; + expect(ZoomElementAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ZoomElementAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ZoomElementAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ZoomElementAction = { + kind: 'zoomElement', + elementIds: ['someId'], + zoomFactor: 1 + }; + const { elementIds, zoomFactor } = expected; + expect(ZoomElementAction.create({ elementIds, zoomFactor })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-zoom.ts b/packages/protocol/src/action-protocol/element-zoom.ts new file mode 100644 index 0000000..db4d2cf --- /dev/null +++ b/packages/protocol/src/action-protocol/element-zoom.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2024 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from './base-protocol'; + +/** + * Zooms to given elements. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ZoomElementAction`. + */ +export interface ZoomElementAction extends Action { + kind: typeof ZoomElementAction.KIND; + /** + * Specifies the elements to be zoomed in/out + */ + elementIds: string[]; + /** + * Specifies the amount by which the viewport should be zoomed + */ + zoomFactor: number; +} + +export namespace ZoomElementAction { + export const KIND = 'zoomElement'; + + export function is(object: any): object is ZoomElementAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { elementIds: string[]; zoomFactor: number }): ZoomElementAction { + return { kind: KIND, ...options }; + } +} diff --git a/packages/protocol/src/action-protocol/viewport.spec.ts b/packages/protocol/src/action-protocol/viewport.spec.ts index f5afb2b..bd07613 100644 --- a/packages/protocol/src/action-protocol/viewport.spec.ts +++ b/packages/protocol/src/action-protocol/viewport.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { CenterAction, FitToScreenAction } from './viewport'; +import { CenterAction, FitToScreenAction, MoveViewportAction, ZoomViewportAction } from './viewport'; describe('Viewport Actions', () => { describe('CenterAction', () => { @@ -102,4 +102,64 @@ describe('Viewport Actions', () => { }); }); }); + + describe('MoveViewportAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: MoveViewportAction = { + kind: 'moveViewport', + moveX: 0, + moveY: 0 + }; + expect(MoveViewportAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(MoveViewportAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(MoveViewportAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: MoveViewportAction = { + kind: 'moveViewport', + moveX: 0, + moveY: 0 + }; + const { moveX, moveY } = expected; + expect(MoveViewportAction.create({ moveX, moveY })).to.deep.equals(expected); + }); + }); + }); + + describe('ZoomViewportAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ZoomViewportAction = { + kind: 'zoomViewport', + zoomFactor: 1 + }; + expect(ZoomViewportAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ZoomViewportAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ZoomViewportAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ZoomViewportAction = { + kind: 'zoomViewport', + zoomFactor: 1 + }; + const { zoomFactor } = expected; + expect(ZoomViewportAction.create({ zoomFactor })).to.deep.equals(expected); + }); + }); + }); }); diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index 5c895d2..4cf4ddb 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -65,7 +65,7 @@ export namespace CenterAction { /** * Triggers to fit all or a list of elements into the available diagram canvas. * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `CenterActions`. + * and creating new `FitToScreenActions`. */ export interface FitToScreenAction extends Action, sprotty.FitToScreenAction { kind: typeof FitToScreenAction.KIND; @@ -110,3 +110,59 @@ export namespace FitToScreenAction { }; } } + +/** + * Moves the diagram canvas. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `MoveViewportActions`. + */ +export interface MoveViewportAction extends Action { + kind: typeof MoveViewportAction.KIND; + /** + * The amount to be moved in the x-axis. + */ + moveX: number; + /** + * The amount to be moved in the y-axis. + */ + moveY: number; +} + +export namespace MoveViewportAction { + export const KIND = 'moveViewport'; + + export function is(object: any): object is MoveViewportAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { moveX: number; moveY: number }): MoveViewportAction { + return { kind: KIND, ...options }; + } +} + +/** + * Zooms the diagram canvas. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ZoomViewportAction`. + */ +export interface ZoomViewportAction extends Action { + kind: typeof ZoomViewportAction.KIND; + /** + * Specifies the amount by which the viewport should be zoomed. + * + * A value greater than 1 zooms in, a value less than 1 zooms out. + */ + zoomFactor: number; +} + +export namespace ZoomViewportAction { + export const KIND = 'zoomViewport'; + + export function is(object: any): object is ZoomViewportAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { zoomFactor: number }): ZoomViewportAction { + return { kind: KIND, ...options }; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e60fcf2..1376174 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -20,11 +20,13 @@ export * from './action-protocol/contexts'; export * from './action-protocol/edge-modification'; export * from './action-protocol/element-creation'; export * from './action-protocol/element-hover'; +export * from './action-protocol/element-move'; export * from './action-protocol/element-navigation'; export * from './action-protocol/element-selection'; export * from './action-protocol/element-text-editing'; export * from './action-protocol/element-type-hints'; export * from './action-protocol/element-validation'; +export * from './action-protocol/element-zoom'; export * from './action-protocol/model-data'; export * from './action-protocol/model-edit-mode'; export * from './action-protocol/model-layout'; From 58f1a4218bde18f3632f7b509b4c7cab69b7835e Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Wed, 19 Feb 2025 14:05:22 +0100 Subject: [PATCH 435/566] Fix test cases --- .../src/action-protocol/element-move.spec.ts | 20 +++++++++---------- .../src/action-protocol/element-zoom.spec.ts | 3 +-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/protocol/src/action-protocol/element-move.spec.ts b/packages/protocol/src/action-protocol/element-move.spec.ts index 676b557..4d4caa2 100644 --- a/packages/protocol/src/action-protocol/element-move.spec.ts +++ b/packages/protocol/src/action-protocol/element-move.spec.ts @@ -15,44 +15,44 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { MoveElementAction } from './element-move'; +import { MoveElementRelativeAction } from './element-move'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. */ describe('Element move actions', () => { - describe('MoveElementAction', () => { + describe('MoveElementRelativeAction', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { - const action: MoveElementAction = { - kind: 'moveElement', + const action: MoveElementRelativeAction = { + kind: 'moveElementRelative', elementIds: ['someId'], moveX: 0, moveY: 0, snap: true }; - expect(MoveElementAction.is(action)).to.be.true; + expect(MoveElementRelativeAction.is(action)).to.be.true; }); it('should return false for `undefined`', () => { - expect(MoveElementAction.is(undefined)).to.be.false; + expect(MoveElementRelativeAction.is(undefined)).to.be.false; }); it('should return false for an object that does not have all required interface properties', () => { - expect(MoveElementAction.is({ kind: 'notTheRightOne' })).to.be.false; + expect(MoveElementRelativeAction.is({ kind: 'notTheRightOne' })).to.be.false; }); }); describe('create', () => { it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { - const expected: MoveElementAction = { - kind: 'moveElement', + const expected: MoveElementRelativeAction = { + kind: 'moveElementRelative', elementIds: ['someId'], moveX: 0, moveY: 0, snap: true }; const { elementIds, moveX, moveY } = expected; - expect(MoveElementAction.create({ elementIds, moveX, moveY })).to.deep.equals(expected); + expect(MoveElementRelativeAction.create({ elementIds, moveX, moveY })).to.deep.equals(expected); }); }); }); diff --git a/packages/protocol/src/action-protocol/element-zoom.spec.ts b/packages/protocol/src/action-protocol/element-zoom.spec.ts index 8944273..30d5083 100644 --- a/packages/protocol/src/action-protocol/element-zoom.spec.ts +++ b/packages/protocol/src/action-protocol/element-zoom.spec.ts @@ -15,8 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { ZoomElementAction } from '../../lib'; - +import { ZoomElementAction } from './element-zoom'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. From a3fccaa2b2f8bc84dd80fb766ab0246e3953b20a Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Thu, 6 Mar 2025 13:56:09 +0100 Subject: [PATCH 436/566] Use constant for default grid size - Use injectable constant for zoom factor - Move MoveRelativeElementAction and ZoomActions into client - Combine viewport and element zoom - Introduce shortcut manager to access all available shortcuts - Introduce shortcut module with standalone ui extension for shortcuts - Move resize feature to change bounds and use own module - Fix viewport movement calculation issues on movement of element --- .../src/base/ui-extension/ui-extension.ts | 9 +- packages/client/src/default-modules.ts | 6 +- .../accessibility/accessibility-module.ts | 4 - .../diagram-navigation-tool.ts | 76 ++++------ .../accessibility/global-keylistener-tool.ts | 39 ++--- .../key-shortcut/shortcut-help-module.ts | 42 ------ .../resize-key-tool/resize-key-module.ts | 37 ----- .../resize-key-tool/resize-key-tool.ts | 139 ------------------ .../accessibility/search/search-tool.ts | 48 ++---- ...key-tool.ts => grid-cell-zoom-key-tool.ts} | 65 +++----- .../view-key-tools/view-key-tools-module.ts | 12 +- .../src/features/bounds/bounds-module.ts | 2 +- .../change-bounds/move-element-action.ts} | 2 +- .../change-bounds/move-element-handler.ts | 8 +- .../move-element-key-listener.ts | 8 +- .../resize/resize-default-tool.ts | 71 +++++++++ .../resize/resize-handler.ts} | 29 ++-- .../change-bounds/resize/resize-module.ts | 48 ++++++ .../change-bounds/resize/resize-tool.ts | 110 ++++++++++++++ .../client/src/features/grid/grid-module.ts | 3 +- .../client/src/features/grid/grid-snapper.ts | 4 +- packages/client/src/features/grid/grid.ts | 2 +- .../available-shortcuts-extension.ts} | 60 ++------ .../available-shortcuts-tool.ts} | 24 ++- .../features/shortcuts/shortcuts-manager.ts | 58 ++++++++ .../utils.ts => shortcuts/shortcuts-model.ts} | 12 +- .../features/shortcuts/shortcuts-module.ts | 48 ++++++ .../client/src/features/tools/base-tools.ts | 18 ++- .../tools/change-bounds/change-bounds-tool.ts | 10 +- .../src/features/viewport/viewport-handler.ts | 41 ++---- .../viewport/viewport-key-listener.ts | 55 ++----- .../src/features/viewport/viewport-modules.ts | 26 ++-- .../src/features/viewport/viewport-tool.ts | 50 +++++++ .../viewport/zoom-viewport-action.ts} | 30 ++-- packages/client/src/index.ts | 20 ++- packages/client/src/standalone-modules.ts | 4 + packages/client/src/utils/viewpoint-util.ts | 4 +- packages/glsp-sprotty/src/types.ts | 8 +- .../src/action-protocol/element-move.spec.ts | 59 -------- .../src/action-protocol/element-zoom.spec.ts | 54 ------- .../src/action-protocol/viewport.spec.ts | 31 +--- .../protocol/src/action-protocol/viewport.ts | 27 ---- packages/protocol/src/index.ts | 2 - 43 files changed, 641 insertions(+), 764 deletions(-) delete mode 100644 packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts delete mode 100644 packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts delete mode 100644 packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts rename packages/client/src/features/accessibility/view-key-tools/{grid-zoom-key-tool.ts => grid-cell-zoom-key-tool.ts} (71%) rename packages/{protocol/src/action-protocol/element-move.ts => client/src/features/change-bounds/move-element-action.ts} (97%) create mode 100644 packages/client/src/features/change-bounds/resize/resize-default-tool.ts rename packages/client/src/features/{accessibility/resize-key-tool/resize-key-handler.ts => change-bounds/resize/resize-handler.ts} (90%) create mode 100644 packages/client/src/features/change-bounds/resize/resize-module.ts create mode 100644 packages/client/src/features/change-bounds/resize/resize-tool.ts rename packages/client/src/features/{accessibility/key-shortcut/accessible-key-shortcut.ts => shortcuts/available-shortcuts-extension.ts} (75%) rename packages/client/src/features/{accessibility/key-shortcut/accessible-key-shortcut-tool.ts => shortcuts/available-shortcuts-tool.ts} (67%) create mode 100644 packages/client/src/features/shortcuts/shortcuts-manager.ts rename packages/client/src/features/{viewport/utils.ts => shortcuts/shortcuts-model.ts} (81%) create mode 100644 packages/client/src/features/shortcuts/shortcuts-module.ts create mode 100644 packages/client/src/features/viewport/viewport-tool.ts rename packages/{protocol/src/action-protocol/element-zoom.ts => client/src/features/viewport/zoom-viewport-action.ts} (63%) delete mode 100644 packages/protocol/src/action-protocol/element-move.spec.ts delete mode 100644 packages/protocol/src/action-protocol/element-zoom.spec.ts diff --git a/packages/client/src/base/ui-extension/ui-extension.ts b/packages/client/src/base/ui-extension/ui-extension.ts index 8d19f4f..0d48082 100644 --- a/packages/client/src/base/ui-extension/ui-extension.ts +++ b/packages/client/src/base/ui-extension/ui-extension.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { AbstractUIExtension } from '@eclipse-glsp/sprotty'; +import { AbstractUIExtension, DisposableCollection } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; export const CSS_UI_EXTENSION_CLASS = 'ui-extension'; @@ -26,6 +26,8 @@ export const CSS_HIDDEN_EXTENSION_CLASS = 'hidden'; */ @injectable() export abstract class GLSPAbstractUIExtension extends AbstractUIExtension { + protected toDisposeOnHide = new DisposableCollection(); + protected get diagramContainerId(): string { return this.options.baseDiv; } @@ -116,4 +118,9 @@ export abstract class GLSPAbstractUIExtension extends AbstractUIExtension { protected toggleContainerVisible(): void { this.setContainerVisible(!this.isContainerVisible()); } + + override hide(): void { + super.hide(); + this.toDisposeOnHide.dispose(); + } } diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 21384d1..dff941b 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -34,6 +34,7 @@ import { Container } from 'inversify'; import { defaultModule } from './base/default.module'; import { IDiagramOptions } from './base/model/diagram-loader'; import { boundsModule } from './features/bounds/bounds-module'; +import { resizeModule } from './features/change-bounds/resize/resize-module'; import { commandPaletteModule } from './features/command-palette/command-palette-module'; import { contextMenuModule } from './features/context-menu/context-menu-module'; import { copyPasteModule } from './features/copy-paste/copy-paste-modules'; @@ -48,6 +49,7 @@ import { layoutModule } from './features/layout/layout-module'; import { navigationModule } from './features/navigation/navigation-module'; import { routingModule } from './features/routing/routing-module'; import { selectModule } from './features/select/select-module'; +import { shortcutsModule } from './features/shortcuts/shortcuts-module'; import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-watcher-module'; import { statusModule } from './features/status/status-module'; import { svgMetadataModule } from './features/svg-metadata/svg-metadata-module'; @@ -99,7 +101,9 @@ export const DEFAULT_MODULES = [ validationModule, zorderModule, svgMetadataModule, - statusModule + statusModule, + shortcutsModule, + resizeModule ] as const; /** diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index ae8b2a3..9b4ae58 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -17,10 +17,8 @@ import { FeatureModule } from '@eclipse-glsp/sprotty'; import { configureElementNavigationTool } from './element-navigation/element-navigation-module'; import { configureFocusTrackerTool } from './focus-tracker/focus-tracker-module'; -import { configureShortcutHelpTool } from './key-shortcut/shortcut-help-module'; import { configureKeyboardControlTools } from './keyboard-pointer/keyboard-pointer-module'; import { configureKeyboardToolPaletteTool } from './keyboard-tool-palette/keyboard-tool-palette-module'; -import { configureResizeTools } from './resize-key-tool/resize-key-module'; import { configureSearchPaletteModule } from './search/search-palette-module'; import { configureToastTool } from './toast/toast-module'; import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; @@ -31,10 +29,8 @@ import { configureViewKeyTools } from './view-key-tools/view-key-tools-module'; export const accessibilityModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - configureResizeTools(context); configureViewKeyTools(context); configureSearchPaletteModule(context); - configureShortcutHelpTool(context); configureKeyboardToolPaletteTool(context); configureKeyboardControlTools(context); configureElementNavigationTool(context); diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 8a0016b..8f8280d 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -18,9 +18,7 @@ import { Action, GModelElement, GModelRoot, - IActionDispatcher, KeyListener, - KeyTool, SelectAction, Selectable, TYPES, @@ -32,40 +30,41 @@ import { toArray } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; import { SelectableBoundsAware } from '../../../utils/gmodel-util'; +import { AvailableShortcutsTool } from '../../shortcuts/available-shortcuts-tool'; +import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; +import { BaseTool } from '../../tools/base-tools'; import { RepositionAction } from '../../viewport/reposition'; -import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import { AccessibleKeyShortcutTool } from '../key-shortcut/accessible-key-shortcut-tool'; import { SearchAutocompletePaletteTool } from '../search/search-tool'; import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigator } from './element-navigator'; @injectable() -export class ElementNavigatorTool implements Tool { +export class ElementNavigatorTool extends BaseTool { static ID = 'glsp.diagram-navigation'; + static TOKEN = Symbol.for(ElementNavigatorTool.ID); - isEditTool = false; + get id(): string { + return ElementNavigatorTool.ID; + } - protected elementNavigatorKeyListener: ElementNavigatorKeyListener = new ElementNavigatorKeyListener(this); - @inject(KeyTool) protected readonly keytool: KeyTool; + @inject(TYPES.IShortcutManager) protected readonly shortcutManager: IShortcutManager; @inject(TYPES.IElementNavigator) readonly elementNavigator: ElementNavigator; @inject(TYPES.ILocalElementNavigator) readonly localElementNavigator: ElementNavigator; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; - get id(): string { - return ElementNavigatorTool.ID; - } + protected elementNavigatorKeyListener: ElementNavigatorKeyListener = new ElementNavigatorKeyListener(this); enable(): void { - this.keytool.register(this.elementNavigatorKeyListener); - this.elementNavigatorKeyListener.registerShortcutKey(); - } - - disable(): void { - this.keytool.deregister(this.elementNavigatorKeyListener); + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.elementNavigatorKeyListener), + this.shortcutManager.register(ElementNavigatorTool.TOKEN, [ + { shortcuts: ['ALT', 'N'], description: 'Activate local navigation mode', group: 'Navigation', position: 0 }, + { shortcuts: ['N'], description: 'Activate global navigation mode', group: 'Navigation', position: 1 } + ]) + ); } } @@ -85,29 +84,6 @@ export class ElementNavigatorKeyListener extends KeyListener { super(); } - registerShortcutKey(): void { - this.tool.actionDispatcher.dispatchOnceModelInitialized( - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [ - { shortcuts: ['N'], description: 'Activate default navigation', group: 'Navigation', position: 0 }, - { - shortcuts: ['ALT', 'N'], - description: 'Activate position based navigation', - group: 'Navigation', - position: 1 - }, - { - shortcuts: ['⬅ ⬆ ➡ ⬇'], - description: 'Navigate by relation or neighbors according to navigation mode', - group: 'Navigation', - position: 2 - } - ] - }) - ); - } - override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { this.resetOnEscape(event, element); @@ -130,7 +106,7 @@ export class ElementNavigatorKeyListener extends KeyListener { this.clean(); if (this.mode === NavigationMode.POSITION) { - this.tool.actionDispatcher.dispatchAll([ + this.tool.dispatchActions([ EnableDefaultToolsAction.create(), ShowToastMessageAction.createWithTimeout({ id: Symbol.for(ElementNavigatorKeyListener.name), @@ -138,7 +114,7 @@ export class ElementNavigatorKeyListener extends KeyListener { }) ]); } else if (this.mode === NavigationMode.DEFAULT) { - this.tool.actionDispatcher.dispatchAll([ + this.tool.dispatchActions([ EnableDefaultToolsAction.create(), ShowToastMessageAction.createWithTimeout({ id: Symbol.for(ElementNavigatorKeyListener.name), @@ -155,8 +131,8 @@ export class ElementNavigatorKeyListener extends KeyListener { if (this.matchesActivatePositionNavigation(event)) { if (this.mode !== NavigationMode.POSITION) { this.clean(); - this.tool.actionDispatcher.dispatchAll([ - EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AccessibleKeyShortcutTool.ID]), + this.tool.dispatchActions([ + EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AvailableShortcutsTool.ID]), ShowToastMessageAction.create({ id: Symbol.for(ElementNavigatorKeyListener.name), message: messages.navigation.local_navigation_mode_activated @@ -179,7 +155,7 @@ export class ElementNavigatorKeyListener extends KeyListener { this.navigator?.clean?.(element.root); this.clean(); this.mode = NavigationMode.NONE; - this.tool.actionDispatcher.dispatchAll([ + this.tool.dispatchActions([ EnableDefaultToolsAction.create(), ShowToastMessageAction.createWithTimeout({ id: Symbol.for(ElementNavigatorKeyListener.name), @@ -193,8 +169,8 @@ export class ElementNavigatorKeyListener extends KeyListener { if (this.mode !== NavigationMode.DEFAULT) { this.clean(); - this.tool.actionDispatcher.dispatchAll([ - EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AccessibleKeyShortcutTool.ID]), + this.tool.dispatchActions([ + EnableToolsAction.create([ElementNavigatorTool.ID, SearchAutocompletePaletteTool.ID, AvailableShortcutsTool.ID]), ShowToastMessageAction.create({ id: Symbol.for(ElementNavigatorKeyListener.name), message: messages.navigation.default_navigation_mode_activated @@ -216,7 +192,7 @@ export class ElementNavigatorKeyListener extends KeyListener { this.navigator?.clean?.(element.root); this.clean(); this.mode = NavigationMode.NONE; - this.tool.actionDispatcher.dispatchAll([ + this.tool.dispatchActions([ EnableDefaultToolsAction.create(), ShowToastMessageAction.createWithTimeout({ id: Symbol.for(ElementNavigatorKeyListener.name), diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index a398693..58a653d 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -13,25 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, IActionDispatcher, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; +import { Action, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Tool } from '../../base/tool-manager/tool'; import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; +import type { IShortcutManager } from '../shortcuts/shortcuts-manager'; import { ToolPalette } from '../tool-palette/tool-palette'; +import { BaseEditTool } from '../tools/base-tools'; import { FocusDomAction } from './actions'; -import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from './key-shortcut/accessible-key-shortcut'; import { KeyboardPointerMetadata } from './keyboard-pointer/constants'; -import { KeyboardToolPalette } from './keyboard-tool-palette/keyboard-tool-palette'; @injectable() -export class GlobalKeyListenerTool implements Tool, AccessibleKeyShortcutProvider { +export class GlobalKeyListenerTool extends BaseEditTool { static ID = 'glsp.global-key-listener'; + static TOKEN = Symbol.for(GlobalKeyListenerTool.name); - isEditTool = false; protected alreadyRegistered = false; - @inject(TYPES.IActionDispatcher) - protected actionDispatcher: IActionDispatcher; + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: IShortcutManager; get id(): string { return GlobalKeyListenerTool.ID; @@ -41,28 +40,12 @@ export class GlobalKeyListenerTool implements Tool, AccessibleKeyShortcutProvide if (!this.alreadyRegistered) { this.alreadyRegistered = true; document.addEventListener('keyup', this.trigger.bind(this)); - this.registerShortcutKey(); - } - } - disable(): void { - // It is not possible to remove the handlers after registration - // The handlers need to be available all the time to work correctly - } - - registerShortcutKey(): void { - this.actionDispatcher.onceModelInitialized().then(() => { - this.actionDispatcher.dispatchAll([ - SetAccessibleKeyShortcutAction.create({ - token: KeyboardToolPalette.name, - keys: [{ shortcuts: ['ALT', 'P'], description: 'Focus on tool palette', group: 'Tool-Palette', position: 0 }] - }), - SetAccessibleKeyShortcutAction.create({ - token: 'Graph', - keys: [{ shortcuts: ['ALT', 'G'], description: 'Focus on graph', group: 'Graph', position: 0 }] - }) + this.shortcutManager.register(GlobalKeyListenerTool.TOKEN, [ + { shortcuts: ['ALT', 'P'], description: 'Focus on tool palette', group: 'Tool-Palette', position: 0 }, + { shortcuts: ['ALT', 'G'], description: 'Focus on graph', group: 'Graph', position: 0 } ]); - }); + } } trigger(event: KeyboardEvent): void { diff --git a/packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts b/packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts deleted file mode 100644 index e3c3c64..0000000 --- a/packages/client/src/features/accessibility/key-shortcut/shortcut-help-module.ts +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; -import '../../../../css/key-shortcut.css'; -import { KeyShortcutUIExtension, SetAccessibleKeyShortcutAction } from './accessible-key-shortcut'; -import { AccessibleKeyShortcutTool } from './accessible-key-shortcut-tool'; - -/** - * Handles actions for displaying help/information about keyboard shortcuts. - */ -export const shortcutHelpModule = new FeatureModule( - (bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureShortcutHelpTool(context); - }, - { featureId: Symbol('shortcutHelp') } -); - -export function configureShortcutHelpTool(context: BindingContext): void { - bindAsService(context, TYPES.IDefaultTool, AccessibleKeyShortcutTool); - bindAsService(context, TYPES.IUIExtension, KeyShortcutUIExtension); - configureActionHandler(context, SetAccessibleKeyShortcutAction.KIND, KeyShortcutUIExtension); -} - -export { - /** Deprecated use {@link shortcutHelpModule} instead */ - shortcutHelpModule as glspShortcutHelpModule -}; diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts deleted file mode 100644 index fb3e676..0000000 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-module.ts +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { BindingContext, FeatureModule, TYPES, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; -import { ResizeElementAction, ResizeElementHandler } from './resize-key-handler'; -import { ResizeKeyTool } from './resize-key-tool'; - -/** - * Handles resize actions. - */ -export const resizeKeyModule = new FeatureModule( - (bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - configureResizeTools(context); - }, - { featureId: Symbol('resizeKey') } -); - -export function configureResizeTools(context: BindingContext): void { - context.bind(ResizeElementHandler).toSelf().inSingletonScope(); - - configureActionHandler(context, ResizeElementAction.KIND, ResizeElementHandler); - bindAsService(context, TYPES.IDefaultTool, ResizeKeyTool); -} diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts b/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts deleted file mode 100644 index b8e40fe..0000000 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-tool.ts +++ /dev/null @@ -1,139 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Action, GModelElement, IActionDispatcher, ISnapper, KeyListener, KeyTool, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; -import { inject, injectable, optional } from 'inversify'; -import { SelectionService } from '../../../base/selection-service'; -import { EnableDefaultToolsAction, EnableToolsAction, Tool } from '../../../base/tool-manager/tool'; -import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; -import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; -import * as messages from '../toast/messages.json'; -import { ShowToastMessageAction } from '../toast/toast-handler'; -import { ResizeElementAction, ResizeType } from './resize-key-handler'; - -@injectable() -export class ResizeKeyTool implements Tool { - static ID = 'glsp.resize-key-tool'; - - isEditTool = true; - - @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; - @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; - @inject(SelectionService) readonly selectionService: SelectionService; - - protected resizeKeyListener: ResizeKeyListener = new ResizeKeyListener(this); - - get id(): string { - return ResizeKeyTool.ID; - } - - enable(): void { - this.keytool.register(this.resizeKeyListener); - this.resizeKeyListener.registerShortcutKey(); - } - - disable(): void { - this.keytool.deregister(this.resizeKeyListener); - } -} -@injectable() -export class ResizeKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { - protected isEditMode = false; - protected readonly token = ResizeKeyListener.name; - - constructor(protected readonly tool: ResizeKeyTool) { - super(); - } - - registerShortcutKey(): void { - this.tool.actionDispatcher.dispatchOnceModelInitialized( - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [ - { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 }, - { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, - { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, - { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } - ] - }) - ); - } - - override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { - const actions = []; - const selectedElementsIds = this.tool.selectionService.getSelectedElementIDs(); - - if (this.isEditMode && this.matchesDeactivateResizeModeKeystroke(event)) { - this.isEditMode = false; - - this.tool.actionDispatcher.dispatch( - ShowToastMessageAction.createWithTimeout({ - id: Symbol.for(ResizeKeyListener.name), - message: messages.resize.resize_mode_deactivated - }) - ); - - actions.push(EnableDefaultToolsAction.create()); - } - - if (selectedElementsIds.length > 0) { - if (!this.isEditMode && this.matchesActivateResizeModeKeystroke(event)) { - this.isEditMode = true; - this.tool.actionDispatcher.dispatch( - ShowToastMessageAction.create({ - id: Symbol.for(ResizeKeyListener.name), - message: messages.resize.resize_mode_activated - }) - ); - actions.push(EnableToolsAction.create([ResizeKeyTool.ID])); - } - - if (this.isEditMode) { - if (this.matchesIncreaseSizeKeystroke(event)) { - actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Increase)); - } else if (this.matchesDecreaseSizeKeystroke(event)) { - actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Decrease)); - } else if (this.matchesMinSizeKeystroke(event)) { - actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.MinSize)); - } - } - } - return actions; - } - - protected matchesIncreaseSizeKeystroke(event: KeyboardEvent): boolean { - /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ - return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); - } - - protected matchesActivateResizeModeKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'KeyA', 'alt'); - } - - protected matchesDeactivateResizeModeKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Escape'); - } - - protected matchesMinSizeKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); - } - - protected matchesDecreaseSizeKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract'); - } -} diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index c970d9c..4590f29 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -14,58 +14,40 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - GModelElement, - IActionDispatcher, - KeyListener, - KeyTool, - matchesKeystroke, - SetUIExtensionVisibilityAction, - TYPES -} from '@eclipse-glsp/sprotty'; +import { Action, GModelElement, KeyListener, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { Tool } from '../../../base/tool-manager/tool'; -import { AccessibleKeyShortcutProvider, SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; +import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; +import { BaseTool } from '../../tools/base-tools'; import { SearchAutocompletePalette } from './search-palette'; + @injectable() -export class SearchAutocompletePaletteTool implements Tool { +export class SearchAutocompletePaletteTool extends BaseTool { static readonly ID = 'glsp.search-autocomplete-palette-tool'; + static readonly TOKEN = Symbol.for(SearchAutocompletePaletteTool.ID); protected readonly keyListener = new SearchAutocompletePaletteKeyListener(this); - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; - @inject(KeyTool) protected keyTool: KeyTool; + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: IShortcutManager; get id(): string { return SearchAutocompletePaletteTool.ID; } enable(): void { - this.keyTool.register(this.keyListener); - this.keyListener.registerShortcutKey(); - } - - disable(): void { - this.keyTool.deregister(this.keyListener); + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.keyListener), + this.shortcutManager.register(SearchAutocompletePaletteTool.TOKEN, [ + { shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 } + ]) + ); } } -export class SearchAutocompletePaletteKeyListener extends KeyListener implements AccessibleKeyShortcutProvider { - protected readonly token = SearchAutocompletePalette.name; - +export class SearchAutocompletePaletteKeyListener extends KeyListener { constructor(protected tool: SearchAutocompletePaletteTool) { super(); } - registerShortcutKey(): void { - this.tool.actionDispatcher.dispatchOnceModelInitialized( - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [{ shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 }] - }) - ); - } - override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesSearchActivateKeystroke(event)) { return [ diff --git a/packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts similarity index 71% rename from packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts rename to packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts index f7b07bc..abc9b6c 100644 --- a/packages/client/src/features/accessibility/view-key-tools/grid-zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts @@ -19,9 +19,7 @@ import { CenterAction, GModelElement, GModelRoot, - IActionDispatcher, KeyListener, - KeyTool, Point, SetViewportAction, TYPES, @@ -30,13 +28,12 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import { EditorContextService } from '../../../base/editor-context-service'; import { SelectionService } from '../../../base/selection-service'; -import { Tool } from '../../../base/tool-manager/tool'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; -import { ZoomFactor } from '../../viewport/utils'; +import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; +import { BaseTool } from '../../tools/base-tools'; +import type { ZoomFactors } from '../../viewport/zoom-viewport-action'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; -import { SetAccessibleKeyShortcutAction } from '../key-shortcut/accessible-key-shortcut'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; import * as messages from '../toast/messages.json'; import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; @@ -45,45 +42,42 @@ import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler' * Zoom viewport and elements when its focused and arrow keys are hit. */ @injectable() -export class GridZoomTool implements Tool { - static ID = 'glsp.accessibility-grid-zoom-tool'; - - isEditTool = false; +export class GridCellZoomTool extends BaseTool { + static ID = 'glsp.accessibility-grid-cell-zoom-tool'; + static TOKEN = Symbol.for(GridCellZoomTool.name); protected readonly zoomKeyListener = new GridZoomKeyListener(this); - @inject(KeyTool) protected readonly keytool: KeyTool; - @inject(TYPES.IActionDispatcher) readonly actionDispatcher: IActionDispatcher; + @inject(TYPES.IShortcutManager) protected readonly shortcutManager: IShortcutManager; + @inject(TYPES.ZoomFactors) protected readonly zoomFactors: ZoomFactors; @inject(SelectionService) selectionService: SelectionService; - @inject(EditorContextService) - protected editorContextService: EditorContextService; get id(): string { - return GridZoomTool.ID; + return GridCellZoomTool.ID; } enable(): void { - this.keytool.register(this.zoomKeyListener); - this.zoomKeyListener.registerShortcutKey(); - } - - disable(): void { - this.keytool.deregister(this.zoomKeyListener); + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.zoomKeyListener), + this.shortcutManager.register(GridCellZoomTool.TOKEN, [ + { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 0 } + ]) + ); } handle(action: Action): Action | void { - if (isViewport(this.editorContextService.modelRoot)) { + if (isViewport(this.editorContext.modelRoot)) { let viewportAction: Action | undefined = undefined; - if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === GridZoomTool.ID) { + if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === GridCellZoomTool.ID) { viewportAction = this.zoomKeyListener.setNewZoomFactor( - this.editorContextService.modelRoot, - ZoomFactor.Default.IN, - getAbsolutePositionByPoint(this.editorContextService.modelRoot, action.options.centerCellPosition) + this.editorContext.modelRoot, + this.zoomFactors.in, + getAbsolutePositionByPoint(this.editorContext.modelRoot, action.options.centerCellPosition) ); - } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === GridZoomTool.ID) { + } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === GridCellZoomTool.ID) { if (matchesKeystroke(action.options.event, 'Minus')) { - viewportAction = this.zoomKeyListener.setNewZoomFactor(this.editorContextService.modelRoot, ZoomFactor.Default.OUT); + viewportAction = this.zoomKeyListener.setNewZoomFactor(this.editorContext.modelRoot, this.zoomFactors.out); } else if (matchesKeystroke(action.options.event, 'Digit0', 'ctrl')) { viewportAction = CenterAction.create([]); } @@ -100,21 +94,10 @@ export class GridZoomTool implements Tool { } export class GridZoomKeyListener extends KeyListener { - protected readonly token = GridZoomKeyListener.name; - - constructor(protected tool: GridZoomTool) { + constructor(protected tool: GridCellZoomTool) { super(); } - registerShortcutKey(): void { - this.tool.actionDispatcher.dispatchOnceModelInitialized( - SetAccessibleKeyShortcutAction.create({ - token: this.token, - keys: [{ shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 0 }] - }) - ); - } - setNewZoomFactor(viewport: GModelElement & GModelRoot & Viewport, zoomFactor: number, point?: Point): SetViewportAction { let newViewport: Viewport; const newZoom = viewport.zoom * zoomFactor; @@ -140,7 +123,7 @@ export class GridZoomKeyListener extends KeyListener { if (this.matchesZoomViaGrid(event)) { return [ EnableKeyboardGridAction.create({ - originId: GridZoomTool.ID, + originId: GridCellZoomTool.ID, triggerActions: [] }), diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 23110ff..440e168 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -15,9 +15,10 @@ ********************************************************************************/ import { bindAsService, BindingContext, configureActionHandler, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import { viewportModule } from '../../viewport/viewport-modules'; import { KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; import { DeselectKeyTool } from './deselect-key-tool'; -import { GridZoomTool } from './grid-zoom-key-tool'; +import { GridCellZoomTool } from './grid-cell-zoom-key-tool'; export const viewKeyToolsModule = new FeatureModule( (bind, _unbind, isBound, rebind) => { @@ -25,13 +26,14 @@ export const viewKeyToolsModule = new FeatureModule( configureViewKeyTools(context); }, { - featureId: Symbol('viewKeyTools') + featureId: Symbol('viewKeyTools'), + requires: viewportModule } ); export function configureViewKeyTools(context: Pick): void { - bindAsService(context, TYPES.IDefaultTool, GridZoomTool); - configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, GridZoomTool); - configureActionHandler(context, KeyboardGridKeyboardEventAction.KIND, GridZoomTool); + bindAsService(context, TYPES.IDefaultTool, GridCellZoomTool); + configureActionHandler(context, KeyboardGridCellSelectedAction.KIND, GridCellZoomTool); + configureActionHandler(context, KeyboardGridKeyboardEventAction.KIND, GridCellZoomTool); bindAsService(context, TYPES.IDefaultTool, DeselectKeyTool); } diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 36584de..3557b5e 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -18,7 +18,6 @@ import { HBoxLayouter, HiddenBoundsUpdater, LayoutRegistry, - MoveElementRelativeAction, RequestBoundsCommand, SetBoundsCommand, TYPES, @@ -28,6 +27,7 @@ import { configureCommand, configureLayout } from '@eclipse-glsp/sprotty'; +import { MoveElementRelativeAction } from '../change-bounds/move-element-action'; import { MoveElementHandler } from '../change-bounds/move-element-handler'; import { PositionSnapper } from '../change-bounds/position-snapper'; import { FreeFormLayouter } from './freeform-layout'; diff --git a/packages/protocol/src/action-protocol/element-move.ts b/packages/client/src/features/change-bounds/move-element-action.ts similarity index 97% rename from packages/protocol/src/action-protocol/element-move.ts rename to packages/client/src/features/change-bounds/move-element-action.ts index 8cba69f..0ff82c6 100644 --- a/packages/protocol/src/action-protocol/element-move.ts +++ b/packages/client/src/features/change-bounds/move-element-action.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from './base-protocol'; +import { Action } from '@eclipse-glsp/sprotty'; /** * Action for triggering moving of elements. It is similar to `MoveAction` but is used for moving elements relative diff --git a/packages/client/src/features/change-bounds/move-element-handler.ts b/packages/client/src/features/change-bounds/move-element-handler.ts index b9cb2c8..aee4464 100644 --- a/packages/client/src/features/change-bounds/move-element-handler.ts +++ b/packages/client/src/features/change-bounds/move-element-handler.ts @@ -24,7 +24,6 @@ import { ICommand, ISnapper, MoveAction, - MoveElementRelativeAction, MoveViewportAction, Point, TYPES, @@ -41,6 +40,7 @@ import { SelectableBoundsAware, getElements, isSelectableAndBoundsAware } from ' import { isValidMove } from '../../utils/layout-utils'; import { outsideOfViewport } from '../../utils/viewpoint-util'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; +import { MoveElementRelativeAction } from './move-element-action'; /** * Action handler for moving elements. @@ -98,7 +98,11 @@ export class MoveElementHandler implements IActionHandler { }, toPosition: newPosition }); - if (outsideOfViewport(newPosition, viewport)) { + + const topLeftCorner = newPosition; + const bottomRightCorner = Point.add(newPosition, { x: element.bounds.width, y: element.bounds.height }); + + if (outsideOfViewport(topLeftCorner, viewport) || outsideOfViewport(bottomRightCorner, viewport)) { viewportActions.push(MoveViewportAction.create({ moveX: action.moveX, moveY: action.moveY })); } } diff --git a/packages/client/src/features/change-bounds/move-element-key-listener.ts b/packages/client/src/features/change-bounds/move-element-key-listener.ts index 16bd9bf..8322f04 100644 --- a/packages/client/src/features/change-bounds/move-element-key-listener.ts +++ b/packages/client/src/features/change-bounds/move-element-key-listener.ts @@ -14,10 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { KeyListener, matchesKeystroke, MoveElementRelativeAction, type Action, type GModelElement } from '@eclipse-glsp/sprotty'; +import { isMoveable, KeyListener, matchesKeystroke, type Action, type GModelElement } from '@eclipse-glsp/sprotty'; import type { SelectionService } from '../../base/selection-service'; import { Grid } from '../grid/grid'; import type { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; +import { MoveElementRelativeAction } from './move-element-action'; export class MoveElementKeyListener extends KeyListener { constructor( @@ -29,7 +30,10 @@ export class MoveElementKeyListener extends KeyListener { } override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { - const selectedElementIds = this.selectionService.getSelectedElementIDs(); + const selectedElementIds = this.selectionService + .getSelectedElements() + .filter(element => isMoveable(element)) + .map(element => element.id); const snap = this.changeBoundsManager.usePositionSnap(event); const offsetX = snap ? this.grid.x : 1; const offsetY = snap ? this.grid.y : 1; diff --git a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts new file mode 100644 index 0000000..9c01282 --- /dev/null +++ b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement, type IActionDispatcher } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { SelectionService } from '../../../base/selection-service'; +import { EnableToolsAction } from '../../../base/tool-manager/tool'; +import type { ShortcutManager } from '../../shortcuts/shortcuts-manager'; +import { BaseEditTool } from '../../tools/base-tools'; +import { ResizeKeyTool } from './resize-tool'; + +@injectable() +export class DefaultResizeKeyListener extends KeyListener { + @inject(TYPES.IActionDispatcher) + protected readonly actionDispatcher: IActionDispatcher; + @inject(SelectionService) + protected readonly selectionService: SelectionService; + + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + const selectedElementsIds = this.selectionService.getSelectedElementIDs(); + + if (selectedElementsIds.length > 0) { + if (this.matchesActivateResizeModeKeystroke(event)) { + return [EnableToolsAction.create([ResizeKeyTool.ID])]; + } + } + + return []; + } + + protected matchesActivateResizeModeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'KeyA', 'alt'); + } +} + +@injectable() +export class DefaultResizeKeyTool extends BaseEditTool { + static ID = 'glsp.default-resize-key-tool'; + static TOKEN = Symbol.for(DefaultResizeKeyTool.name); + + get id(): string { + return DefaultResizeKeyTool.ID; + } + + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: ShortcutManager; + @inject(DefaultResizeKeyListener) + protected readonly keyListener: DefaultResizeKeyListener; + + enable(): void { + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.keyListener), + this.shortcutManager.register(DefaultResizeKeyTool.TOKEN, [ + { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 } + ]) + ); + } +} diff --git a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts b/packages/client/src/features/change-bounds/resize/resize-handler.ts similarity index 90% rename from packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts rename to packages/client/src/features/change-bounds/resize/resize-handler.ts index 6979803..07a14f6 100644 --- a/packages/client/src/features/accessibility/resize-key-tool/resize-key-handler.ts +++ b/packages/client/src/features/change-bounds/resize/resize-handler.ts @@ -64,37 +64,28 @@ export namespace ResizeElementAction { } } -/* The ResizeElementHandler class is an implementation of the IActionHandler interface that handles -resizing of elements. */ +/* + * The ResizeElementHandler class is an implementation of the IActionHandler interface that handles + * resizing of elements. + */ @injectable() export class ResizeElementHandler implements IActionHandler { @inject(EditorContextService) protected editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.Grid) + @optional() + protected grid: Grid = Grid.DEFAULT; + @inject(TYPES.ISnapper) + @optional() + protected readonly snapper?: ISnapper; protected debouncedChangeBounds?: DebouncedFunc<() => void>; protected resizeFeedback: FeedbackEmitter; - // Default x resize used if grid is not provided - static readonly defaultResizeX = 20; - - // Default y resize used if grid is not provided - static readonly defaultResizeY = 20; - - @inject(TYPES.Grid) @optional() protected grid: Grid = { - x: ResizeElementHandler.defaultResizeX, - y: ResizeElementHandler.defaultResizeY - }; - - protected isEditMode = false; - - constructor(@inject(TYPES.ISnapper) @optional() protected readonly snapper?: ISnapper) {} - @postConstruct() protected init(): void { this.resizeFeedback = this.feedbackDispatcher.createEmitter(); diff --git a/packages/client/src/features/change-bounds/resize/resize-module.ts b/packages/client/src/features/change-bounds/resize/resize-module.ts new file mode 100644 index 0000000..7c726ca --- /dev/null +++ b/packages/client/src/features/change-bounds/resize/resize-module.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { FeatureModule, TYPES, bindAsService, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { DefaultResizeKeyListener, DefaultResizeKeyTool } from './resize-default-tool'; +import { ResizeElementAction, ResizeElementHandler } from './resize-handler'; +import { ResizeKeyListener, ResizeKeyTool } from './resize-tool'; + +/** + * Handles resize actions. + */ +export const resizeModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + configureActionHandler(context, ResizeElementAction.KIND, ResizeElementHandler); + }, + { featureId: Symbol('resize') } +); + +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const standaloneResizeModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + + bindAsService(context, TYPES.IDefaultTool, DefaultResizeKeyTool); + context.bind(DefaultResizeKeyListener).toSelf(); + bindAsService(context, TYPES.ITool, ResizeKeyTool); + context.bind(ResizeKeyListener).toSelf(); + }, + { featureId: Symbol('standaloneResize'), requires: resizeModule } +); diff --git a/packages/client/src/features/change-bounds/resize/resize-tool.ts b/packages/client/src/features/change-bounds/resize/resize-tool.ts new file mode 100644 index 0000000..4f9d458 --- /dev/null +++ b/packages/client/src/features/change-bounds/resize/resize-tool.ts @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { Disposable } from 'vscode-jsonrpc'; +import { SelectionService } from '../../../base/selection-service'; +import * as messages from '../../accessibility/toast/messages.json'; +import { ShowToastMessageAction } from '../../accessibility/toast/toast-handler'; +import type { ShortcutManager } from '../../shortcuts/shortcuts-manager'; +import { BaseEditTool } from '../../tools/base-tools'; +import { ResizeElementAction, ResizeType } from './resize-handler'; + +@injectable() +export class ResizeKeyListener extends KeyListener { + @inject(SelectionService) + protected readonly selectionService: SelectionService; + + override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { + const actions = []; + const selectedElementsIds = this.selectionService.getSelectedElementIDs(); + + if (selectedElementsIds.length > 0) { + if (this.matchesIncreaseSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Increase)); + } else if (this.matchesDecreaseSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.Decrease)); + } else if (this.matchesMinSizeKeystroke(event)) { + actions.push(ResizeElementAction.create(selectedElementsIds, ResizeType.MinSize)); + } + } + return actions; + } + + enable(): Action[] { + return [ + ShowToastMessageAction.create({ + id: ResizeKeyTool.TOKEN, + message: messages.resize.resize_mode_activated + }) + ]; + } + + disable(): Action[] { + return [ + ShowToastMessageAction.createWithTimeout({ + id: ResizeKeyTool.TOKEN, + message: messages.resize.resize_mode_deactivated + }) + ]; + } + + protected matchesIncreaseSizeKeystroke(event: KeyboardEvent): boolean { + /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ + return event.key === '+' || matchesKeystroke(event, 'NumpadAdd'); + } + + protected matchesMinSizeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); + } + + protected matchesDecreaseSizeKeystroke(event: KeyboardEvent): boolean { + return matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract'); + } +} + +@injectable() +export class ResizeKeyTool extends BaseEditTool { + static ID = 'glsp.resize-key-tool'; + static TOKEN = Symbol.for(ResizeKeyTool.name); + + get id(): string { + return ResizeKeyTool.ID; + } + + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: ShortcutManager; + @inject(ResizeKeyListener) + protected readonly keyListener: ResizeKeyListener; + + enable(): void { + this.dispatchActions(this.keyListener.enable()); + + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.keyListener), + this.shortcutManager.register(ResizeKeyTool.TOKEN, [ + { shortcuts: ['Escape'], description: 'Deactivate resize handler ', group: 'Resize', position: 0 }, + { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, + { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, + { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } + ]), + Disposable.create(() => { + this.dispatchActions(this.keyListener.disable()); + }) + ); + } +} diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index 3e6c264..a9799c6 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -16,6 +16,7 @@ import { FeatureModule, TYPES, bindAsService, configureActionHandler, configureCommand } from '@eclipse-glsp/sprotty'; import '../../../css/grid.css'; +import { Grid } from './grid'; import { GridManager } from './grid-manager'; import { ShowGridAction, ShowGridCommand } from './grid-model'; import { GridSnapper } from './grid-snapper'; @@ -24,7 +25,7 @@ export const gridModule = new FeatureModule( (bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - bind(TYPES.Grid).toConstantValue({ x: 10, y: 10 }); + bind(TYPES.Grid).toConstantValue(Grid.DEFAULT); configureCommand(context, ShowGridCommand); diff --git a/packages/client/src/features/grid/grid-snapper.ts b/packages/client/src/features/grid/grid-snapper.ts index 210edf1..684607d 100644 --- a/packages/client/src/features/grid/grid-snapper.ts +++ b/packages/client/src/features/grid/grid-snapper.ts @@ -36,7 +36,7 @@ import { Grid } from './grid'; */ @injectable() export class GridSnapper implements ISnapper { - constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = { x: 10, y: 10 }) {} + constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = Grid.DEFAULT) {} snap(position: Point, element: GModelElement): Point { return Point.snapToGrid(position, this.grid); @@ -45,7 +45,7 @@ export class GridSnapper implements ISnapper { @injectable() export class GLSPCenterGridSnapper extends CenterGridSnapper { - constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = { x: 10, y: 10 }) { + constructor(@optional() @inject(TYPES.Grid) public readonly grid: Grid = Grid.DEFAULT) { super(); } diff --git a/packages/client/src/features/grid/grid.ts b/packages/client/src/features/grid/grid.ts index 6e5eba3..27d40e5 100644 --- a/packages/client/src/features/grid/grid.ts +++ b/packages/client/src/features/grid/grid.ts @@ -17,5 +17,5 @@ import { Point } from '@eclipse-glsp/sprotty'; export type Grid = Point; export namespace Grid { - export const DEFAULT: Grid = { x: 20, y: 20 }; + export const DEFAULT: Grid = { x: 10, y: 10 }; } diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts b/packages/client/src/features/shortcuts/available-shortcuts-extension.ts similarity index 75% rename from packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts rename to packages/client/src/features/shortcuts/available-shortcuts-extension.ts index 8d619e6..c36b489 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut.ts +++ b/packages/client/src/features/shortcuts/available-shortcuts-extension.ts @@ -14,72 +14,40 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GModelRoot, IActionHandler, ICommand, matchesKeystroke } from '@eclipse-glsp/sprotty'; -import { injectable } from 'inversify'; +import { GModelRoot, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; import { groupBy } from 'lodash'; -import { GLSPAbstractUIExtension } from '../../../base/ui-extension/ui-extension'; - -export interface AccessibleKeyShortcutProvider { - registerShortcutKey(): void; -} - -export interface AccessibleKeyShortcut { - shortcuts: string[]; - description: string; - group: string; - position: number; -} - -export interface SetAccessibleKeyShortcutAction extends Action { - kind: typeof SetAccessibleKeyShortcutAction.KIND; - token: string; - keys: AccessibleKeyShortcut[]; -} - -export namespace SetAccessibleKeyShortcutAction { - export const KIND = 'setAccessibleKeyShortcut'; - - export function is(object: any): object is SetAccessibleKeyShortcutAction { - return Action.hasKind(object, KIND); - } - - export function create(options: { token: string; keys: AccessibleKeyShortcut[] }): SetAccessibleKeyShortcutAction { - return { kind: KIND, token: options.token, keys: options.keys }; - } -} +import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; +import type { IShortcutManager } from './shortcuts-manager'; +import type { ShortcutRegistration } from './shortcuts-model'; @injectable() -export class KeyShortcutUIExtension extends GLSPAbstractUIExtension implements IActionHandler { +export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension { static readonly ID = 'key-shortcut'; protected container: HTMLDivElement; protected shortcutsContainer: HTMLDivElement; - protected registrations: Record = {}; - handle(action: Action): ICommand | Action | void { - if (SetAccessibleKeyShortcutAction.is(action)) { - this.registrations[action.token] = action.keys; - if (this.containerElement) { - this.refreshUI(); - } - } - } + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: IShortcutManager; + id(): string { - return KeyShortcutUIExtension.ID; + return AvailableShortcutsUIExtension.ID; } containerClass(): string { - return KeyShortcutUIExtension.ID; + return AvailableShortcutsUIExtension.ID; } override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.shortcutsContainer.focus(); + this.toDisposeOnHide.push(this.shortcutManager.onDidChange(() => this.refreshUI())); } protected refreshUI(): void { this.shortcutsContainer.innerHTML = ''; - const registrations = Object.values(this.registrations).flatMap(r => r); + const registrations = this.shortcutManager.getRegistrations(); registrations.sort((a, b) => { if (a.group < b.group) { return -1; @@ -145,7 +113,7 @@ export class KeyShortcutUIExtension extends GLSPAbstractUIExtension implements I return shortcutKeys; } - protected createEntry(registration: AccessibleKeyShortcut): HTMLDivElement { + protected createEntry(registration: ShortcutRegistration): HTMLDivElement { const entryRow = document.createElement('tr'); const shortcutElement = document.createElement('td'); const descElement = document.createElement('td'); diff --git a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts b/packages/client/src/features/shortcuts/available-shortcuts-tool.ts similarity index 67% rename from packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts rename to packages/client/src/features/shortcuts/available-shortcuts-tool.ts index d027934..72e5eec 100644 --- a/packages/client/src/features/accessibility/key-shortcut/accessible-key-shortcut-tool.ts +++ b/packages/client/src/features/shortcuts/available-shortcuts-tool.ts @@ -14,29 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { Action, KeyListener, KeyTool, GModelElement, SetUIExtensionVisibilityAction, matchesKeystroke } from '@eclipse-glsp/sprotty'; -import { BaseEditTool } from '../../tools/base-tools'; -import { KeyShortcutUIExtension } from './accessible-key-shortcut'; +import { Action, GModelElement, KeyListener, SetUIExtensionVisibilityAction, matchesKeystroke } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import { BaseTool } from '../tools/base-tools'; +import { AvailableShortcutsUIExtension } from './available-shortcuts-extension'; @injectable() -export class AccessibleKeyShortcutTool extends BaseEditTool { - static ID = 'accessible-key-shortcut-tool'; - - @inject(KeyTool) protected readonly keytool: KeyTool; +export class AvailableShortcutsTool extends BaseTool { + static ID = 'available-shortcuts-tool'; protected shortcutKeyListener = new AccessibleShortcutKeyListener(); get id(): string { - return AccessibleKeyShortcutTool.ID; + return AvailableShortcutsTool.ID; } enable(): void { - this.keytool.register(this.shortcutKeyListener); - } - - override disable(): void { - this.keytool.deregister(this.shortcutKeyListener); + this.toDisposeOnDisable.push(this.keyTool.registerListener(this.shortcutKeyListener)); } } @@ -44,7 +38,7 @@ export class AccessibleShortcutKeyListener extends KeyListener { protected readonly token = Symbol(AccessibleShortcutKeyListener.name); override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { if (this.matchesActivateShortcutHelpKeystroke(event)) { - return [SetUIExtensionVisibilityAction.create({ extensionId: KeyShortcutUIExtension.ID, visible: true })]; + return [SetUIExtensionVisibilityAction.create({ extensionId: AvailableShortcutsUIExtension.ID, visible: true })]; } return []; } diff --git a/packages/client/src/features/shortcuts/shortcuts-manager.ts b/packages/client/src/features/shortcuts/shortcuts-manager.ts new file mode 100644 index 0000000..3066664 --- /dev/null +++ b/packages/client/src/features/shortcuts/shortcuts-manager.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Disposable, Emitter, type Event } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; +import type { ShortcutRegistration } from './shortcuts-model'; + +export interface IShortcutManager { + onDidChange: Event>; + + register(token: symbol, shortcuts: ShortcutRegistration[]): Disposable; + deregister(token: symbol): void; + + getShortcuts(): ReadonlyMap; + getRegistrations(): ShortcutRegistration[]; +} + +@injectable() +export class ShortcutManager implements IShortcutManager { + protected readonly registrations = new Map(); + protected readonly onDidChangeEmitter = new Emitter>(); + onDidChange = this.onDidChangeEmitter.event; + + getShortcuts(): ReadonlyMap { + return this.registrations; + } + + getRegistrations(): ShortcutRegistration[] { + return Array.from(this.registrations.values()).flat(); + } + + register(token: symbol, shortcuts: ShortcutRegistration[]): Disposable { + this.registrations.set(token, shortcuts); + this.onDidChangeEmitter.fire(this.registrations); + + return Disposable.create(() => { + this.deregister(token); + }); + } + + deregister(token: symbol): void { + this.registrations.delete(token); + this.onDidChangeEmitter.fire(this.registrations); + } +} diff --git a/packages/client/src/features/viewport/utils.ts b/packages/client/src/features/shortcuts/shortcuts-model.ts similarity index 81% rename from packages/client/src/features/viewport/utils.ts rename to packages/client/src/features/shortcuts/shortcuts-model.ts index 4bd2878..e868fcd 100644 --- a/packages/client/src/features/viewport/utils.ts +++ b/packages/client/src/features/shortcuts/shortcuts-model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export namespace ZoomFactor { - export namespace Default { - export const IN = 1.2; - export const OUT = 0.8; - } +export interface ShortcutRegistration { + shortcuts: string[]; + description: string; + group: string; + position: number; } diff --git a/packages/client/src/features/shortcuts/shortcuts-module.ts b/packages/client/src/features/shortcuts/shortcuts-module.ts new file mode 100644 index 0000000..df9e037 --- /dev/null +++ b/packages/client/src/features/shortcuts/shortcuts-module.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import '../../../css/key-shortcut.css'; +import { AvailableShortcutsUIExtension } from './available-shortcuts-extension'; +import { AvailableShortcutsTool } from './available-shortcuts-tool'; +import { ShortcutManager } from './shortcuts-manager'; + +/** + * Handles actions for displaying help/information about keyboard shortcuts. + */ +export const shortcutsModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + + bindAsService(context, TYPES.IShortcutManager, ShortcutManager); + }, + { featureId: Symbol('shortcuts') } +); + +/** + * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) + * When integrated into an application frame (e.g Theia/VS Code) this module is typically omitted and/or replaced + * with an application native module. + */ +export const standaloneShortcutsModule = new FeatureModule( + (bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + + bindAsService(context, TYPES.IDefaultTool, AvailableShortcutsTool); + bindAsService(context, TYPES.IUIExtension, AvailableShortcutsUIExtension); + }, + { featureId: Symbol('standaloneShortcuts'), requires: shortcutsModule } +); diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 164f4f9..8058f6a 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -53,10 +53,10 @@ export interface FeedbackAwareTool extends Tool { } /** - * A reusable base implementation for edit {@link Tool}s. + * A reusable base implementation for {@link Tool}s. */ @injectable() -export abstract class BaseEditTool implements FeedbackAwareTool { +export abstract class BaseTool implements FeedbackAwareTool { @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; @inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; @@ -73,10 +73,6 @@ export abstract class BaseEditTool implements FeedbackAwareTool { abstract id: string; - get isEditTool(): boolean { - return true; - } - dispatchActions(actions: Action[]): Promise { return this.actionDispatcher.dispatchAll(actions); } @@ -94,6 +90,16 @@ export abstract class BaseEditTool implements FeedbackAwareTool { } } +/** + * A reusable base implementation for edit {@link Tool}s. + */ +@injectable() +export abstract class BaseEditTool extends BaseTool { + get isEditTool(): boolean { + return true; + } +} + @injectable() export abstract class BaseCreationTool extends BaseEditTool implements IActionHandler { protected abstract isTriggerAction: (obj: any) => obj is T; diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index b759d2a..a457d4e 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -56,6 +56,7 @@ import { GResizeHandle, isResizable } from '../../change-bounds/model'; import { MoveElementKeyListener } from '../../change-bounds/move-element-key-listener'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { Grid } from '../../grid/grid'; +import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseEditTool } from '../base-tools'; import { CSS_ACTIVE_HANDLE, IChangeBoundsManager } from './change-bounds-manager'; import { @@ -87,6 +88,7 @@ export interface IMovementOptions { @injectable() export class ChangeBoundsTool extends BaseEditTool { static ID = 'glsp.change-bounds-tool'; + static TOKEN = Symbol.for(ChangeBoundsTool.ID); @inject(SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @@ -94,6 +96,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; @inject(TYPES.IMovementOptions) @optional() readonly movementOptions: IMovementOptions = { allElementsNeedToBeValid: true }; @inject(TYPES.Grid) @optional() readonly grid?: Grid; + @inject(TYPES.IShortcutManager) protected readonly shortcutManager: IShortcutManager; get id(): string { return ChangeBoundsTool.ID; @@ -112,7 +115,12 @@ export class ChangeBoundsTool extends BaseEditTool { // install move key listener for client-side move updates const createMoveKeyListener = this.createMoveKeyListener(); - this.toDisposeOnDisable.push(this.keyTool.registerListener(createMoveKeyListener)); + this.toDisposeOnDisable.push( + this.keyTool.registerListener(createMoveKeyListener), + this.shortcutManager.register(ChangeBoundsTool.TOKEN, [ + { shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element', group: 'Move', position: 0 } + ]) + ); if (Disposable.is(createMoveKeyListener)) { this.toDisposeOnDisable.push(createMoveKeyListener); } diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 3fc5f7b..9a29a73 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -29,9 +29,7 @@ import { Point, SetViewportAction, TYPES, - Viewport, - ZoomElementAction, - ZoomViewportAction + Viewport } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { EditorContextService } from '../../base/editor-context-service'; @@ -40,6 +38,7 @@ import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction } from '../../base/tool-manager/tool'; import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../utils/gmodel-util'; import { FocusDomAction } from '../accessibility/actions'; +import { ZoomAction } from './zoom-viewport-action'; /** * Focuses the graph on different actions. @@ -134,25 +133,29 @@ export class MoveViewportHandler implements IActionHandler { * Handles zooming in and out of the viewport. */ @injectable() -export class ZoomViewportHandler implements IActionHandler { +export class ZoomHandler implements IActionHandler { @inject(EditorContextService) protected readonly editorContextService: EditorContextService; @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; handle(action: Action): Action | void { - if (ZoomViewportAction.is(action)) { - return this.handleZoomViewport(action); + if (ZoomAction.is(action)) { + if (action.elementIds) { + return this.handleZoomElement(action.elementIds, action.zoomFactor); + } else { + return this.handleZoomViewport(action.zoomFactor); + } } } - protected handleZoomViewport(action: ZoomViewportAction): Action | undefined { + protected handleZoomViewport(zoomFactor: number): Action | undefined { const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); if (!viewport) { return; } - const newZoom = viewport.zoom * action.zoomFactor; + const newZoom = viewport.zoom * zoomFactor; const newViewport = { scroll: viewport.scroll, @@ -161,33 +164,17 @@ export class ZoomViewportHandler implements IActionHandler { return SetViewportAction.create(viewport.id, newViewport, { animate: false }); } -} - -/* - * Handles zooming in and out of the viewport in the direction of the elements. - */ -@injectable() -export class ZoomElementHandler implements IActionHandler { - @inject(EditorContextService) - protected readonly editorContextService: EditorContextService; - @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - - handle(action: Action): void | Action | ICommand { - if (ZoomElementAction.is(action)) { - return this.handleZoomElement(action); - } - } - protected handleZoomElement(action: ZoomElementAction): Action | undefined { + protected handleZoomElement(elementIds: string[], zoomFactor: number): Action | undefined { const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); if (!viewport) { return; } - const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + const elements = getElements(this.editorContextService.modelRoot.index, elementIds, isSelectableAndBoundsAware); const center = this.getCenter(viewport, elements); - const newZoom = viewport.zoom * action.zoomFactor; + const newZoom = viewport.zoom * zoomFactor; const newViewport = { scroll: { diff --git a/packages/client/src/features/viewport/viewport-key-listener.ts b/packages/client/src/features/viewport/viewport-key-listener.ts index 5adb2d1..0a4c934 100644 --- a/packages/client/src/features/viewport/viewport-key-listener.ts +++ b/packages/client/src/features/viewport/viewport-key-listener.ts @@ -14,22 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - CenterAction, - GModelElement, - KeyListener, - matchesKeystroke, - MoveViewportAction, - TYPES, - ZoomElementAction, - ZoomViewportAction -} from '@eclipse-glsp/sprotty'; +import { Action, CenterAction, GModelElement, KeyListener, matchesKeystroke, MoveViewportAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { SelectionService } from '../../base/selection-service'; import { Grid } from '../grid/grid'; import { IChangeBoundsManager } from '../tools/change-bounds/change-bounds-manager'; -import { ZoomFactor } from './utils'; +import { ZoomAction, ZoomFactors } from './zoom-viewport-action'; @injectable() export class MoveViewportKeyListener extends KeyListener { @@ -79,7 +69,7 @@ export class MoveViewportKeyListener extends KeyListener { } @injectable() -export class ZoomViewportKeyListener extends KeyListener { +export class ZoomKeyListener extends KeyListener { @inject(SelectionService) protected readonly selectionService: SelectionService; @@ -88,56 +78,31 @@ export class ZoomViewportKeyListener extends KeyListener { if (selectedElementIds.length === 0) { if (this.matchesZoomOutKeystroke(event)) { - return [ZoomViewportAction.create({ zoomFactor: ZoomFactor.Default.OUT })]; + return [ZoomAction.create({ zoomFactor: ZoomFactors.DEFAULT.out })]; } else if (this.matchesZoomInKeystroke(event)) { - return [ZoomViewportAction.create({ zoomFactor: ZoomFactor.Default.IN })]; + return [ZoomAction.create({ zoomFactor: ZoomFactors.DEFAULT.in })]; } else if (this.matchesMinZoomLevelKeystroke(event)) { return [CenterAction.create(selectedElementIds)]; } - } - - return []; - } - - protected matchesZoomInKeystroke(event: KeyboardEvent): boolean { - /** here event.key is used for '+', as keycode 187 is already declared for 'Equals' in {@link matchesKeystroke}.*/ - return !event.ctrlKey && (event.key === '+' || matchesKeystroke(event, 'NumpadAdd')); - } - - protected matchesMinZoomLevelKeystroke(event: KeyboardEvent): boolean { - return matchesKeystroke(event, 'Digit0', 'ctrl') || matchesKeystroke(event, 'Numpad0', 'ctrl'); - } - - protected matchesZoomOutKeystroke(event: KeyboardEvent): boolean { - return !event.ctrlKey && (matchesKeystroke(event, 'Minus') || matchesKeystroke(event, 'NumpadSubtract')); - } -} - -export class ZoomElementKeyListener extends KeyListener { - @inject(SelectionService) - protected readonly selectionService: SelectionService; - - override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { - const selectedElementIds = this.selectionService.getSelectedElementIDs(); - - if (selectedElementIds.length > 0) { + } else { if (this.matchesZoomOutKeystroke(event)) { if (selectedElementIds.length > 0) { return [ - ZoomElementAction.create({ + ZoomAction.create({ elementIds: selectedElementIds, - zoomFactor: ZoomFactor.Default.OUT + zoomFactor: ZoomFactors.DEFAULT.out }) ]; } } else if (this.matchesZoomInKeystroke(event)) { if (selectedElementIds.length > 0) { - return [ZoomElementAction.create({ elementIds: selectedElementIds, zoomFactor: ZoomFactor.Default.IN })]; + return [ZoomAction.create({ elementIds: selectedElementIds, zoomFactor: ZoomFactors.DEFAULT.in })]; } } else if (this.matchesMinZoomLevelKeystroke(event)) { return [CenterAction.create(selectedElementIds)]; } } + return []; } diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index f620649..ec7f762 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -25,17 +25,17 @@ import { MoveViewportAction, SetViewportCommand, TYPES, - ZoomElementAction, - ZoomMouseListener, - ZoomViewportAction + ZoomMouseListener } from '@eclipse-glsp/sprotty'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { FocusDomAction } from '../accessibility/actions'; import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; import { OriginViewportCommand } from './origin-viewport'; import { RepositionCommand } from './reposition'; -import { MoveViewportHandler, RestoreViewportHandler, ZoomElementHandler, ZoomViewportHandler } from './viewport-handler'; -import { MoveViewportKeyListener, ZoomElementKeyListener, ZoomViewportKeyListener } from './viewport-key-listener'; +import { MoveViewportHandler, RestoreViewportHandler, ZoomHandler } from './viewport-handler'; +import { MoveViewportKeyListener, ZoomKeyListener } from './viewport-key-listener'; +import { ViewportTool } from './viewport-tool'; +import { ZoomAction, ZoomFactors } from './zoom-viewport-action'; export const viewportModule = new FeatureModule( (bind, _unbind, isBound) => { @@ -57,17 +57,15 @@ export const viewportModule = new FeatureModule( configureActionHandler(context, EnableDefaultToolsAction.KIND, RestoreViewportHandler); configureActionHandler(context, FocusDomAction.KIND, RestoreViewportHandler); + bind(TYPES.ZoomFactors).toConstantValue(ZoomFactors.DEFAULT); + + bindAsService(context, TYPES.IDefaultTool, ViewportTool); bind(MoveViewportHandler).toSelf().inSingletonScope(); - bindAsService(context, TYPES.KeyListener, MoveViewportKeyListener); + bind(MoveViewportKeyListener).toSelf(); configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler); - - bind(ZoomViewportHandler).toSelf().inSingletonScope(); - bindAsService(context, TYPES.KeyListener, ZoomViewportKeyListener); - configureActionHandler(context, ZoomViewportAction.KIND, ZoomViewportHandler); - - bind(ZoomElementHandler).toSelf().inSingletonScope(); - bindAsService(context, TYPES.KeyListener, ZoomElementKeyListener); - configureActionHandler(context, ZoomElementAction.KIND, ZoomElementHandler); + bind(ZoomHandler).toSelf().inSingletonScope(); + bind(ZoomKeyListener).toSelf(); + configureActionHandler(context, ZoomAction.KIND, ZoomHandler); }, { featureId: Symbol('viewport') } ); diff --git a/packages/client/src/features/viewport/viewport-tool.ts b/packages/client/src/features/viewport/viewport-tool.ts new file mode 100644 index 0000000..4a2b6cf --- /dev/null +++ b/packages/client/src/features/viewport/viewport-tool.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { TYPES } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import type { IShortcutManager } from '../shortcuts/shortcuts-manager'; +import { BaseTool } from '../tools/base-tools'; +import { MoveViewportKeyListener, ZoomKeyListener } from './viewport-key-listener'; + +@injectable() +export class ViewportTool extends BaseTool { + static readonly ID = 'glsp.viewport-tool'; + static readonly TOKEN = Symbol.for(ViewportTool.ID); + + get id(): string { + return ViewportTool.ID; + } + + @inject(TYPES.IShortcutManager) + protected readonly shortcutManager: IShortcutManager; + @inject(ZoomKeyListener) + protected readonly zoomKeyListener: ZoomKeyListener; + @inject(MoveViewportKeyListener) + protected readonly moveKeyListener: MoveViewportKeyListener; + + enable(): void { + this.toDisposeOnDisable.push( + this.keyTool.registerListener(this.moveKeyListener), + this.keyTool.registerListener(this.zoomKeyListener), + this.shortcutManager.register(ViewportTool.TOKEN, [ + { shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move viewport', group: 'Move', position: 0 }, + { shortcuts: ['+ -'], description: 'Zoom viewport', group: 'Zoom', position: 0 }, + { shortcuts: ['+ -'], description: 'Zoom element', group: 'Zoom', position: 0 } + ]) + ); + } +} diff --git a/packages/protocol/src/action-protocol/element-zoom.ts b/packages/client/src/features/viewport/zoom-viewport-action.ts similarity index 63% rename from packages/protocol/src/action-protocol/element-zoom.ts rename to packages/client/src/features/viewport/zoom-viewport-action.ts index db4d2cf..4e29db8 100644 --- a/packages/protocol/src/action-protocol/element-zoom.ts +++ b/packages/client/src/features/viewport/zoom-viewport-action.ts @@ -14,33 +14,41 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from './base-protocol'; +import { Action } from '@eclipse-glsp/sprotty'; + +export interface ZoomFactors { + in: number; + out: number; +} + +export namespace ZoomFactors { + export const DEFAULT = { in: 1.2, out: 0.8 }; +} /** - * Zooms to given elements. - * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `ZoomElementAction`. + * Zooms the diagram canvas. + * If `elementIds` is provided, the zoom is centered on the elements with the given identifiers. */ -export interface ZoomElementAction extends Action { - kind: typeof ZoomElementAction.KIND; +export interface ZoomAction extends Action { + kind: typeof ZoomAction.KIND; /** * Specifies the elements to be zoomed in/out */ - elementIds: string[]; + elementIds?: string[]; /** * Specifies the amount by which the viewport should be zoomed */ zoomFactor: number; } -export namespace ZoomElementAction { - export const KIND = 'zoomElement'; +export namespace ZoomAction { + export const KIND = 'zoom'; - export function is(object: any): object is ZoomElementAction { + export function is(object: any): object is ZoomAction { return Action.hasKind(object, KIND); } - export function create(options: { elementIds: string[]; zoomFactor: number }): ZoomElementAction { + export function create(options: { elementIds?: string[]; zoomFactor: number }): ZoomAction { return { kind: KIND, ...options }; } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 03d6a91..9a8b164 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -64,9 +64,6 @@ export * from './features/accessibility/element-navigation/position-navigator'; export * from './features/accessibility/focus-tracker/focus-tracker-module'; export * from './features/accessibility/focus-tracker/focus-tracker-tool'; export * from './features/accessibility/global-keylistener-tool'; -export * from './features/accessibility/key-shortcut/accessible-key-shortcut'; -export * from './features/accessibility/key-shortcut/accessible-key-shortcut-tool'; -export * from './features/accessibility/key-shortcut/shortcut-help-module'; export * from './features/accessibility/keyboard-grid/action'; export * from './features/accessibility/keyboard-grid/constants'; export * from './features/accessibility/keyboard-grid/keyboard-grid'; @@ -80,9 +77,6 @@ export * from './features/accessibility/keyboard-pointer/keyboard-pointer-module export * from './features/accessibility/keyboard-pointer/keyboard-pointer-position'; export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette'; export * from './features/accessibility/keyboard-tool-palette/keyboard-tool-palette-module'; -export * from './features/accessibility/resize-key-tool/resize-key-handler'; -export * from './features/accessibility/resize-key-tool/resize-key-module'; -export * from './features/accessibility/resize-key-tool/resize-key-tool'; export * from './features/accessibility/search/search-palette'; export * from './features/accessibility/search/search-palette-module'; export * from './features/accessibility/search/search-tool'; @@ -90,7 +84,7 @@ export * from './features/accessibility/toast/toast-handler'; export * from './features/accessibility/toast/toast-module'; export * from './features/accessibility/toast/toast-tool'; export * from './features/accessibility/view-key-tools/deselect-key-tool'; -export * from './features/accessibility/view-key-tools/grid-zoom-key-tool'; +export * from './features/accessibility/view-key-tools/grid-cell-zoom-key-tool'; export * from './features/accessibility/view-key-tools/view-key-tools-module'; export * from './features/bounds/bounds-module'; export * from './features/bounds/freeform-layout'; @@ -102,10 +96,15 @@ export * from './features/bounds/local-bounds'; export * from './features/bounds/set-bounds-feedback-command'; export * from './features/bounds/vbox-layout'; export * from './features/change-bounds/model'; +export * from './features/change-bounds/move-element-action'; export * from './features/change-bounds/move-element-handler'; export * from './features/change-bounds/movement-restrictor'; export * from './features/change-bounds/point-position-updater'; export * from './features/change-bounds/position-snapper'; +export * from './features/change-bounds/resize/resize-default-tool'; +export * from './features/change-bounds/resize/resize-handler'; +export * from './features/change-bounds/resize/resize-module'; +export * from './features/change-bounds/resize/resize-tool'; export * from './features/change-bounds/snap'; export * from './features/change-bounds/tracker'; export * from './features/command-palette/command-palette'; @@ -167,6 +166,11 @@ export * from './features/save/save-module'; export * from './features/select/select-feedback-command'; export * from './features/select/select-module'; export * from './features/select/select-mouse-listener'; +export * from './features/shortcuts/available-shortcuts-extension'; +export * from './features/shortcuts/available-shortcuts-tool'; +export * from './features/shortcuts/shortcuts-manager'; +export * from './features/shortcuts/shortcuts-model'; +export * from './features/shortcuts/shortcuts-module'; export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/source-model-watcher/source-model-watcher-module'; export * from './features/status/status-module'; @@ -215,10 +219,10 @@ export * from './features/validation/validation-modules'; export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './features/viewport/origin-viewport'; export * from './features/viewport/reposition'; -export * from './features/viewport/utils'; export * from './features/viewport/viewport-handler'; export * from './features/viewport/viewport-key-listener'; export * from './features/viewport/viewport-modules'; +export * from './features/viewport/zoom-viewport-action'; export * from './model'; export * from './re-exports'; export * from './standalone-modules'; diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index ce2dfa3..124a221 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -34,10 +34,12 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { standaloneResizeModule } from './features/change-bounds/resize/resize-module'; import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; import { standaloneExportModule } from './features/export/export-modules'; import { saveModule } from './features/save/save-module'; import { standaloneSelectModule } from './features/select/select-module'; +import { standaloneShortcutsModule } from './features/shortcuts/shortcuts-module'; import { undoRedoModule } from './features/undo-redo/undo-redo-module'; import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; import { standaloneViewportModule } from './features/viewport/viewport-modules'; @@ -97,6 +99,8 @@ export const STANDALONE_MODULES = [ standaloneSelectModule, standaloneExportModule, standaloneDefaultModule, + standaloneShortcutsModule, + standaloneResizeModule, saveModule, undoRedoModule ] as const; diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 9cab913..4c818b6 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -140,8 +140,8 @@ export function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: export function outsideOfViewport(point: Point, viewport: GModelRoot & Viewport): boolean { return ( point.x < viewport.scroll.x || - point.x > viewport.scroll.x + viewport.canvasBounds.width || + point.x > viewport.scroll.x + viewport.canvasBounds.width / viewport.zoom || point.y < viewport.scroll.y || - point.y > viewport.scroll.y + viewport.canvasBounds.height + point.y > viewport.scroll.y + viewport.canvasBounds.height / viewport.zoom ); } diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 2f9dcf1..9ee54c0 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -59,5 +59,11 @@ export const TYPES = { IChangeBoundsManager: Symbol('IChangeBoundsManager'), IGridManager: Symbol('IGridManager'), IDebugManager: Symbol('IDebugManager'), - Grid: Symbol('Grid') + Grid: Symbol('Grid'), + ZoomFactors: Symbol('ZoomFactors'), + /** + * Experimental shortcut manager. + * The API is not stable yet. + */ + IShortcutManager: Symbol('IShortcutManager') }; diff --git a/packages/protocol/src/action-protocol/element-move.spec.ts b/packages/protocol/src/action-protocol/element-move.spec.ts deleted file mode 100644 index 4d4caa2..0000000 --- a/packages/protocol/src/action-protocol/element-move.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -/* eslint-disable max-len */ -import { expect } from 'chai'; -import { MoveElementRelativeAction } from './element-move'; - -/** - * Tests for the utility functions declared in the namespaces of the protocol - * action definitions. - */ -describe('Element move actions', () => { - describe('MoveElementRelativeAction', () => { - describe('is', () => { - it('should return true for an object having the correct type and a value for all required interface properties', () => { - const action: MoveElementRelativeAction = { - kind: 'moveElementRelative', - elementIds: ['someId'], - moveX: 0, - moveY: 0, - snap: true - }; - expect(MoveElementRelativeAction.is(action)).to.be.true; - }); - it('should return false for `undefined`', () => { - expect(MoveElementRelativeAction.is(undefined)).to.be.false; - }); - it('should return false for an object that does not have all required interface properties', () => { - expect(MoveElementRelativeAction.is({ kind: 'notTheRightOne' })).to.be.false; - }); - }); - - describe('create', () => { - it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { - const expected: MoveElementRelativeAction = { - kind: 'moveElementRelative', - elementIds: ['someId'], - moveX: 0, - moveY: 0, - snap: true - }; - const { elementIds, moveX, moveY } = expected; - expect(MoveElementRelativeAction.create({ elementIds, moveX, moveY })).to.deep.equals(expected); - }); - }); - }); -}); diff --git a/packages/protocol/src/action-protocol/element-zoom.spec.ts b/packages/protocol/src/action-protocol/element-zoom.spec.ts deleted file mode 100644 index 30d5083..0000000 --- a/packages/protocol/src/action-protocol/element-zoom.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -/* eslint-disable max-len */ -import { expect } from 'chai'; -import { ZoomElementAction } from './element-zoom'; -/** - * Tests for the utility functions declared in the namespaces of the protocol - * action definitions. - */ -describe('Element zoom actions', () => { - describe('ZoomElementAction', () => { - describe('is', () => { - it('should return true for an object having the correct type and a value for all required interface properties', () => { - const action: ZoomElementAction = { - kind: 'zoomElement', - elementIds: ['someId'], - zoomFactor: 1 - }; - expect(ZoomElementAction.is(action)).to.be.true; - }); - it('should return false for `undefined`', () => { - expect(ZoomElementAction.is(undefined)).to.be.false; - }); - it('should return false for an object that does not have all required interface properties', () => { - expect(ZoomElementAction.is({ kind: 'notTheRightOne' })).to.be.false; - }); - }); - - describe('create', () => { - it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { - const expected: ZoomElementAction = { - kind: 'zoomElement', - elementIds: ['someId'], - zoomFactor: 1 - }; - const { elementIds, zoomFactor } = expected; - expect(ZoomElementAction.create({ elementIds, zoomFactor })).to.deep.equals(expected); - }); - }); - }); -}); diff --git a/packages/protocol/src/action-protocol/viewport.spec.ts b/packages/protocol/src/action-protocol/viewport.spec.ts index bd07613..02f74d1 100644 --- a/packages/protocol/src/action-protocol/viewport.spec.ts +++ b/packages/protocol/src/action-protocol/viewport.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ /* eslint-disable max-len */ import { expect } from 'chai'; -import { CenterAction, FitToScreenAction, MoveViewportAction, ZoomViewportAction } from './viewport'; +import { CenterAction, FitToScreenAction, MoveViewportAction } from './viewport'; describe('Viewport Actions', () => { describe('CenterAction', () => { @@ -133,33 +133,4 @@ describe('Viewport Actions', () => { }); }); }); - - describe('ZoomViewportAction', () => { - describe('is', () => { - it('should return true for an object having the correct type and a value for all required interface properties', () => { - const action: ZoomViewportAction = { - kind: 'zoomViewport', - zoomFactor: 1 - }; - expect(ZoomViewportAction.is(action)).to.be.true; - }); - it('should return false for `undefined`', () => { - expect(ZoomViewportAction.is(undefined)).to.be.false; - }); - it('should return false for an object that does not have all required interface properties', () => { - expect(ZoomViewportAction.is({ kind: 'notTheRightOne' })).to.be.false; - }); - }); - - describe('create', () => { - it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { - const expected: ZoomViewportAction = { - kind: 'zoomViewport', - zoomFactor: 1 - }; - const { zoomFactor } = expected; - expect(ZoomViewportAction.create({ zoomFactor })).to.deep.equals(expected); - }); - }); - }); }); diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index 4cf4ddb..54f23fe 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -139,30 +139,3 @@ export namespace MoveViewportAction { return { kind: KIND, ...options }; } } - -/** - * Zooms the diagram canvas. - * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks - * and creating new `ZoomViewportAction`. - */ -export interface ZoomViewportAction extends Action { - kind: typeof ZoomViewportAction.KIND; - /** - * Specifies the amount by which the viewport should be zoomed. - * - * A value greater than 1 zooms in, a value less than 1 zooms out. - */ - zoomFactor: number; -} - -export namespace ZoomViewportAction { - export const KIND = 'zoomViewport'; - - export function is(object: any): object is ZoomViewportAction { - return Action.hasKind(object, KIND); - } - - export function create(options: { zoomFactor: number }): ZoomViewportAction { - return { kind: KIND, ...options }; - } -} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index 1376174..e60fcf2 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -20,13 +20,11 @@ export * from './action-protocol/contexts'; export * from './action-protocol/edge-modification'; export * from './action-protocol/element-creation'; export * from './action-protocol/element-hover'; -export * from './action-protocol/element-move'; export * from './action-protocol/element-navigation'; export * from './action-protocol/element-selection'; export * from './action-protocol/element-text-editing'; export * from './action-protocol/element-type-hints'; export * from './action-protocol/element-validation'; -export * from './action-protocol/element-zoom'; export * from './action-protocol/model-data'; export * from './action-protocol/model-edit-mode'; export * from './action-protocol/model-layout'; From 765841e9047afbe90959110c8f96c1238a44a0bd Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Mon, 17 Mar 2025 13:58:46 +0100 Subject: [PATCH 437/566] Make the shortcutsmanager part of the base module --- packages/client/src/base/default.module.ts | 4 ++++ .../available-shortcuts-extension.ts | 3 +-- .../shortcuts/available-shortcuts-tool.ts | 2 +- .../shortcuts/shortcuts-manager.ts | 8 ++++++- .../shortcuts/shortcuts-module.ts | 15 +------------ packages/client/src/default-modules.ts | 2 -- .../diagram-navigation-tool.ts | 4 ++-- .../accessibility/global-keylistener-tool.ts | 2 +- .../accessibility/search/search-tool.ts | 2 +- .../view-key-tools/grid-cell-zoom-key-tool.ts | 2 +- .../resize/resize-default-tool.ts | 8 +++++-- .../change-bounds/resize/resize-tool.ts | 8 +++++-- .../src/features/shortcuts/shortcuts-model.ts | 22 ------------------- .../tools/change-bounds/change-bounds-tool.ts | 2 +- .../src/features/viewport/viewport-handler.ts | 18 ++++++--------- .../src/features/viewport/viewport-modules.ts | 4 ++-- .../src/features/viewport/viewport-tool.ts | 16 +++++++++----- packages/client/src/index.ts | 9 ++++---- packages/client/src/standalone-modules.ts | 2 +- 19 files changed, 56 insertions(+), 77 deletions(-) rename packages/client/src/{features => base}/shortcuts/available-shortcuts-extension.ts (97%) rename packages/client/src/{features => base}/shortcuts/available-shortcuts-tool.ts (97%) rename packages/client/src/{features => base}/shortcuts/shortcuts-manager.ts (94%) rename packages/client/src/{features => base}/shortcuts/shortcuts-module.ts (77%) delete mode 100644 packages/client/src/features/shortcuts/shortcuts-model.ts diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 3a83a5d..041bffd 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -51,6 +51,7 @@ import { GModelRegistry } from './model/model-registry'; import { GLSPMousePositionTracker } from './mouse-position-tracker'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { SelectionService } from './selection-service'; +import { ShortcutManager } from './shortcuts/shortcuts-manager'; import { EnableDefaultToolsAction, EnableToolsAction } from './tool-manager/tool'; import { DefaultToolsEnablingKeyListener, ToolManager, ToolManagerActionHandler } from './tool-manager/tool-manager'; import { GLSPUIExtensionRegistry } from './ui-extension/ui-extension-registry'; @@ -142,6 +143,9 @@ export const defaultModule = new FeatureModule( bind(TYPES.IDiagramStartup).toService(GLSPUIExtensionRegistry); bind(TYPES.EmptyArray).toDynamicValue(() => []); + + // Shortcut manager initialization ------------------------------------ + bindAsService(context, TYPES.IShortcutManager, ShortcutManager); }, { featureId: Symbol('default') diff --git a/packages/client/src/features/shortcuts/available-shortcuts-extension.ts b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts similarity index 97% rename from packages/client/src/features/shortcuts/available-shortcuts-extension.ts rename to packages/client/src/base/shortcuts/available-shortcuts-extension.ts index c36b489..a76f028 100644 --- a/packages/client/src/features/shortcuts/available-shortcuts-extension.ts +++ b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts @@ -18,8 +18,7 @@ import { GModelRoot, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { groupBy } from 'lodash'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; -import type { IShortcutManager } from './shortcuts-manager'; -import type { ShortcutRegistration } from './shortcuts-model'; +import type { IShortcutManager, ShortcutRegistration } from './shortcuts-manager'; @injectable() export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension { diff --git a/packages/client/src/features/shortcuts/available-shortcuts-tool.ts b/packages/client/src/base/shortcuts/available-shortcuts-tool.ts similarity index 97% rename from packages/client/src/features/shortcuts/available-shortcuts-tool.ts rename to packages/client/src/base/shortcuts/available-shortcuts-tool.ts index 72e5eec..7ffe382 100644 --- a/packages/client/src/features/shortcuts/available-shortcuts-tool.ts +++ b/packages/client/src/base/shortcuts/available-shortcuts-tool.ts @@ -16,7 +16,7 @@ import { Action, GModelElement, KeyListener, SetUIExtensionVisibilityAction, matchesKeystroke } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import { BaseTool } from '../tools/base-tools'; +import { BaseTool } from '../../features/tools/base-tools'; import { AvailableShortcutsUIExtension } from './available-shortcuts-extension'; @injectable() diff --git a/packages/client/src/features/shortcuts/shortcuts-manager.ts b/packages/client/src/base/shortcuts/shortcuts-manager.ts similarity index 94% rename from packages/client/src/features/shortcuts/shortcuts-manager.ts rename to packages/client/src/base/shortcuts/shortcuts-manager.ts index 3066664..edf6ee6 100644 --- a/packages/client/src/features/shortcuts/shortcuts-manager.ts +++ b/packages/client/src/base/shortcuts/shortcuts-manager.ts @@ -16,7 +16,13 @@ import { Disposable, Emitter, type Event } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; -import type { ShortcutRegistration } from './shortcuts-model'; + +export interface ShortcutRegistration { + shortcuts: string[]; + description: string; + group: string; + position: number; +} export interface IShortcutManager { onDidChange: Event>; diff --git a/packages/client/src/features/shortcuts/shortcuts-module.ts b/packages/client/src/base/shortcuts/shortcuts-module.ts similarity index 77% rename from packages/client/src/features/shortcuts/shortcuts-module.ts rename to packages/client/src/base/shortcuts/shortcuts-module.ts index df9e037..b73632f 100644 --- a/packages/client/src/features/shortcuts/shortcuts-module.ts +++ b/packages/client/src/base/shortcuts/shortcuts-module.ts @@ -18,19 +18,6 @@ import { bindAsService, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; import '../../../css/key-shortcut.css'; import { AvailableShortcutsUIExtension } from './available-shortcuts-extension'; import { AvailableShortcutsTool } from './available-shortcuts-tool'; -import { ShortcutManager } from './shortcuts-manager'; - -/** - * Handles actions for displaying help/information about keyboard shortcuts. - */ -export const shortcutsModule = new FeatureModule( - (bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - - bindAsService(context, TYPES.IShortcutManager, ShortcutManager); - }, - { featureId: Symbol('shortcuts') } -); /** * Feature module that is intended for the standalone deployment of GLSP (i.e. plain webapp) @@ -44,5 +31,5 @@ export const standaloneShortcutsModule = new FeatureModule( bindAsService(context, TYPES.IDefaultTool, AvailableShortcutsTool); bindAsService(context, TYPES.IUIExtension, AvailableShortcutsUIExtension); }, - { featureId: Symbol('standaloneShortcuts'), requires: shortcutsModule } + { featureId: Symbol('standaloneShortcuts') } ); diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index dff941b..d0ca043 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -49,7 +49,6 @@ import { layoutModule } from './features/layout/layout-module'; import { navigationModule } from './features/navigation/navigation-module'; import { routingModule } from './features/routing/routing-module'; import { selectModule } from './features/select/select-module'; -import { shortcutsModule } from './features/shortcuts/shortcuts-module'; import { sourceModelWatcherModule } from './features/source-model-watcher/source-model-watcher-module'; import { statusModule } from './features/status/status-module'; import { svgMetadataModule } from './features/svg-metadata/svg-metadata-module'; @@ -102,7 +101,6 @@ export const DEFAULT_MODULES = [ zorderModule, svgMetadataModule, statusModule, - shortcutsModule, resizeModule ] as const; diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 8f8280d..74d6b05 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -30,11 +30,11 @@ import { toArray } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { AvailableShortcutsTool } from '../../../base/shortcuts/available-shortcuts-tool'; +import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; import { GEdge } from '../../../model'; import { SelectableBoundsAware } from '../../../utils/gmodel-util'; -import { AvailableShortcutsTool } from '../../shortcuts/available-shortcuts-tool'; -import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseTool } from '../../tools/base-tools'; import { RepositionAction } from '../../viewport/reposition'; import { SearchAutocompletePaletteTool } from '../search/search-tool'; diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index 58a653d..a2977ba 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { Action, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import type { IShortcutManager } from '../../base/shortcuts/shortcuts-manager'; import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; -import type { IShortcutManager } from '../shortcuts/shortcuts-manager'; import { ToolPalette } from '../tool-palette/tool-palette'; import { BaseEditTool } from '../tools/base-tools'; import { FocusDomAction } from './actions'; diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index 4590f29..f3e432a 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -16,7 +16,7 @@ import { Action, GModelElement, KeyListener, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; +import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { BaseTool } from '../../tools/base-tools'; import { SearchAutocompletePalette } from './search-palette'; diff --git a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts index abc9b6c..152df43 100644 --- a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts @@ -29,8 +29,8 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { SelectionService } from '../../../base/selection-service'; +import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; -import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseTool } from '../../tools/base-tools'; import type { ZoomFactors } from '../../viewport/zoom-viewport-action'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; diff --git a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts index 9c01282..d2d7c2a 100644 --- a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts +++ b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts @@ -17,9 +17,10 @@ import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement, type IActionDispatcher } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { SelectionService } from '../../../base/selection-service'; +import type { ShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { EnableToolsAction } from '../../../base/tool-manager/tool'; -import type { ShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseEditTool } from '../../tools/base-tools'; +import { isResizable } from '../model'; import { ResizeKeyTool } from './resize-tool'; @injectable() @@ -30,7 +31,10 @@ export class DefaultResizeKeyListener extends KeyListener { protected readonly selectionService: SelectionService; override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { - const selectedElementsIds = this.selectionService.getSelectedElementIDs(); + const selectedElementsIds = this.selectionService + .getSelectedElements() + .filter(isResizable) + .map(e => e.id); if (selectedElementsIds.length > 0) { if (this.matchesActivateResizeModeKeystroke(event)) { diff --git a/packages/client/src/features/change-bounds/resize/resize-tool.ts b/packages/client/src/features/change-bounds/resize/resize-tool.ts index 4f9d458..23fe6d6 100644 --- a/packages/client/src/features/change-bounds/resize/resize-tool.ts +++ b/packages/client/src/features/change-bounds/resize/resize-tool.ts @@ -18,10 +18,11 @@ import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement } import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode-jsonrpc'; import { SelectionService } from '../../../base/selection-service'; +import type { ShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import * as messages from '../../accessibility/toast/messages.json'; import { ShowToastMessageAction } from '../../accessibility/toast/toast-handler'; -import type { ShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseEditTool } from '../../tools/base-tools'; +import { isResizable } from '../model'; import { ResizeElementAction, ResizeType } from './resize-handler'; @injectable() @@ -31,7 +32,10 @@ export class ResizeKeyListener extends KeyListener { override keyDown(element: GModelElement, event: KeyboardEvent): Action[] { const actions = []; - const selectedElementsIds = this.selectionService.getSelectedElementIDs(); + const selectedElementsIds = this.selectionService + .getSelectedElements() + .filter(isResizable) + .map(e => e.id); if (selectedElementsIds.length > 0) { if (this.matchesIncreaseSizeKeystroke(event)) { diff --git a/packages/client/src/features/shortcuts/shortcuts-model.ts b/packages/client/src/features/shortcuts/shortcuts-model.ts deleted file mode 100644 index e868fcd..0000000 --- a/packages/client/src/features/shortcuts/shortcuts-model.ts +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -export interface ShortcutRegistration { - shortcuts: string[]; - description: string; - group: string; - position: number; -} diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index a457d4e..2c87630 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -41,6 +41,7 @@ import { import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; +import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { BoundsAwareModelElement, ResizableModelElement, @@ -56,7 +57,6 @@ import { GResizeHandle, isResizable } from '../../change-bounds/model'; import { MoveElementKeyListener } from '../../change-bounds/move-element-key-listener'; import { IMovementRestrictor } from '../../change-bounds/movement-restrictor'; import { Grid } from '../../grid/grid'; -import type { IShortcutManager } from '../../shortcuts/shortcuts-manager'; import { BaseEditTool } from '../base-tools'; import { CSS_ACTIVE_HANDLE, IChangeBoundsManager } from './change-bounds-manager'; import { diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 9a29a73..b98d7e6 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -106,10 +106,8 @@ export class MoveViewportHandler implements IActionHandler { @inject(EditorContextService) protected readonly editorContextService: EditorContextService; - handle(action: Action): void | Action | ICommand { - if (MoveViewportAction.is(action)) { - return this.handleMoveViewport(action); - } + handle(action: MoveViewportAction): void | Action | ICommand { + return this.handleMoveViewport(action); } protected handleMoveViewport(action: MoveViewportAction): Action | undefined { @@ -139,13 +137,11 @@ export class ZoomHandler implements IActionHandler { @inject(TYPES.IActionDispatcher) protected readonly actionDispatcher: IActionDispatcher; - handle(action: Action): Action | void { - if (ZoomAction.is(action)) { - if (action.elementIds) { - return this.handleZoomElement(action.elementIds, action.zoomFactor); - } else { - return this.handleZoomViewport(action.zoomFactor); - } + handle(action: ZoomAction): Action | void { + if (action.elementIds) { + return this.handleZoomElement(action.elementIds, action.zoomFactor); + } else { + return this.handleZoomViewport(action.zoomFactor); } } diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index ec7f762..3ac1cc8 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -34,7 +34,7 @@ import { OriginViewportCommand } from './origin-viewport'; import { RepositionCommand } from './reposition'; import { MoveViewportHandler, RestoreViewportHandler, ZoomHandler } from './viewport-handler'; import { MoveViewportKeyListener, ZoomKeyListener } from './viewport-key-listener'; -import { ViewportTool } from './viewport-tool'; +import { ViewportKeyTool } from './viewport-tool'; import { ZoomAction, ZoomFactors } from './zoom-viewport-action'; export const viewportModule = new FeatureModule( @@ -59,7 +59,7 @@ export const viewportModule = new FeatureModule( bind(TYPES.ZoomFactors).toConstantValue(ZoomFactors.DEFAULT); - bindAsService(context, TYPES.IDefaultTool, ViewportTool); + bindAsService(context, TYPES.IDefaultTool, ViewportKeyTool); bind(MoveViewportHandler).toSelf().inSingletonScope(); bind(MoveViewportKeyListener).toSelf(); configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler); diff --git a/packages/client/src/features/viewport/viewport-tool.ts b/packages/client/src/features/viewport/viewport-tool.ts index 4a2b6cf..a31d48e 100644 --- a/packages/client/src/features/viewport/viewport-tool.ts +++ b/packages/client/src/features/viewport/viewport-tool.ts @@ -16,17 +16,21 @@ import { TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; -import type { IShortcutManager } from '../shortcuts/shortcuts-manager'; +import type { IShortcutManager } from '../../base/shortcuts/shortcuts-manager'; import { BaseTool } from '../tools/base-tools'; import { MoveViewportKeyListener, ZoomKeyListener } from './viewport-key-listener'; +/** + * This only handles keyboard events for the viewport. + * Mouse specific events are handled directly by MouseListeners without any tool. + */ @injectable() -export class ViewportTool extends BaseTool { - static readonly ID = 'glsp.viewport-tool'; - static readonly TOKEN = Symbol.for(ViewportTool.ID); +export class ViewportKeyTool extends BaseTool { + static readonly ID = 'glsp.viewport-key-tool'; + static readonly TOKEN = Symbol.for(ViewportKeyTool.ID); get id(): string { - return ViewportTool.ID; + return ViewportKeyTool.ID; } @inject(TYPES.IShortcutManager) @@ -40,7 +44,7 @@ export class ViewportTool extends BaseTool { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.moveKeyListener), this.keyTool.registerListener(this.zoomKeyListener), - this.shortcutManager.register(ViewportTool.TOKEN, [ + this.shortcutManager.register(ViewportKeyTool.TOKEN, [ { shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move viewport', group: 'Move', position: 0 }, { shortcuts: ['+ -'], description: 'Zoom viewport', group: 'Zoom', position: 0 }, { shortcuts: ['+ -'], description: 'Zoom element', group: 'Zoom', position: 0 } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 9a8b164..f237f9a 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -41,6 +41,10 @@ export * from './base/mouse-position-tracker'; export * from './base/ranked'; export * from './base/selection-clearing-mouse-listener'; export * from './base/selection-service'; +export * from './base/shortcuts/available-shortcuts-extension'; +export * from './base/shortcuts/available-shortcuts-tool'; +export * from './base/shortcuts/shortcuts-manager'; +export * from './base/shortcuts/shortcuts-module'; export * from './base/tool-manager/tool'; export * from './base/tool-manager/tool-manager'; export * from './base/ui-extension/ui-extension'; @@ -166,11 +170,6 @@ export * from './features/save/save-module'; export * from './features/select/select-feedback-command'; export * from './features/select/select-module'; export * from './features/select/select-mouse-listener'; -export * from './features/shortcuts/available-shortcuts-extension'; -export * from './features/shortcuts/available-shortcuts-tool'; -export * from './features/shortcuts/shortcuts-manager'; -export * from './features/shortcuts/shortcuts-model'; -export * from './features/shortcuts/shortcuts-module'; export * from './features/source-model-watcher/source-model-changed-action-handler'; export * from './features/source-model-watcher/source-model-watcher-module'; export * from './features/status/status-module'; diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 124a221..05056a5 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -34,12 +34,12 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { standaloneShortcutsModule } from './base/shortcuts/shortcuts-module'; import { standaloneResizeModule } from './features/change-bounds/resize/resize-module'; import { standaloneCopyPasteModule } from './features/copy-paste/copy-paste-modules'; import { standaloneExportModule } from './features/export/export-modules'; import { saveModule } from './features/save/save-module'; import { standaloneSelectModule } from './features/select/select-module'; -import { standaloneShortcutsModule } from './features/shortcuts/shortcuts-module'; import { undoRedoModule } from './features/undo-redo/undo-redo-module'; import { standaloneMarkerNavigatorModule } from './features/validation/validation-modules'; import { standaloneViewportModule } from './features/viewport/viewport-modules'; From 9850ab1f99bff774b51a2a4bfdad86d0b6c906de Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 26 Mar 2025 15:33:03 +0100 Subject: [PATCH 438/566] Hide sprotty sprotty reconnect handles (#419) Follow-up for https://github.com/eclipse-glsp/glsp/issues/1490 Ensure that the unwanted sprotty reconnect handles are not rendered. --- packages/client/src/views/routing-point-handle-view.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client/src/views/routing-point-handle-view.tsx b/packages/client/src/views/routing-point-handle-view.tsx index 853ab40..3c41f0d 100644 --- a/packages/client/src/views/routing-point-handle-view.tsx +++ b/packages/client/src/views/routing-point-handle-view.tsx @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - import { SRoutingHandleView, type GRoutingHandle, type RenderingContext, type RoutedPoint } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import type { VNode } from 'snabbdom'; @@ -23,7 +22,7 @@ import { isReconnectHandle } from '../features/reconnect/model'; export class GRoutingHandleView extends SRoutingHandleView { override render(handle: Readonly, context: RenderingContext, args?: { route?: RoutedPoint[] }): VNode { // We have our own handle view for the reconnect handles - if (isReconnectHandle(handle)) { + if (!isReconnectHandle(handle) && (handle.kind === 'source' || handle.kind === 'target')) { return undefined as any; } From 1eeea7ffaafee09c49e7bf6a13edd4e2ec285488 Mon Sep 17 00:00:00 2001 From: Haydar Metin Date: Thu, 27 Mar 2025 09:14:25 +0100 Subject: [PATCH 439/566] Allow to listen to selection changes in autocompleter (#420) --- .../auto-complete/auto-complete-widget.ts | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index de938e6..ddd700e 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -84,6 +84,7 @@ export class AutoCompleteWidget { protected validationDecorator: IValidationDecorator = IValidationDecorator.NO_DECORATION; protected textSubmitHandler?: TextSubmitHandler; + protected observer?: MutationObserver; constructor( public autoSuggestionSettings: AutoCompleteSettings, @@ -185,6 +186,12 @@ export class AutoCompleteWidget { render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => this.renderSuggestions(item, currentValue), customize: (input, inputRect, container, maxHeight) => { this.customizeInputElement(input, inputRect, container, maxHeight); + + const selectedSuggestionChanged = this.options?.selectedSuggestionChanged; + if (selectedSuggestionChanged) { + this.observer = new MutationObserver(mutations => this.handleContainerMutations(mutations, selectedSuggestionChanged)); + this.observer.observe(container, { childList: true, attributes: true, subtree: true }); + } } }; } @@ -199,17 +206,21 @@ export class AutoCompleteWidget { container.style.position = 'fixed'; if (this.containerElement) { this.containerElement.appendChild(container); + } - if (this.options && this.options.selectedSuggestionChanged) { - const selectedElement = container.querySelector('.selected'); - // eslint-disable-next-line no-null/no-null - if (selectedElement !== null && selectedElement !== undefined) { - const index = Array.from(container.children).indexOf(selectedElement); - this.options.selectedSuggestionChanged(this.contextActions?.[index]); - } else { - this.options.selectedSuggestionChanged(undefined); - } - } + this.container = container; + } + + protected container: HTMLDivElement; + protected handleContainerMutations(mutations: MutationRecord[], selectionChanged: (action: LabeledAction | undefined) => void): void { + const selectedElement = this.container.querySelector('.selected'); + // Trigger selection changed event + // eslint-disable-next-line no-null/no-null + if (selectedElement !== null && selectedElement !== undefined) { + const index = Array.from(this.container.children).indexOf(selectedElement); + selectionChanged(this.contextActions?.[index]); + } else { + selectionChanged(undefined); } } From 678299a2ddec04911459e4eb232fea453e7e6212 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 27 Mar 2025 16:01:05 +0000 Subject: [PATCH 440/566] Provide more flexible way to adapt messages (#421) - Only read messages once from JSON and maintain structure - Provide functions to update messages and listen to updates - Use messages when registering shortcuts (label and groups) - Use messages for aria-labels - Use messages for user-facing toasts Fixes https://github.com/eclipse-glsp/glsp/issues/1496 --- .../base-autocomplete-palette.ts | 3 +- packages/client/src/base/messages.json | 82 ++++++++++++++++ packages/client/src/base/messages.ts | 96 +++++++++++++++++++ .../available-shortcuts-extension.ts | 9 +- .../diagram-navigation-tool.ts | 22 ++++- .../focus-tracker/focus-tracker-tool.ts | 6 +- .../accessibility/global-keylistener-tool.ts | 21 +++- .../keyboard-tool-palette.ts | 60 +++--------- .../accessibility/search/search-palette.ts | 5 +- .../accessibility/search/search-tool.ts | 14 ++- .../accessibility/toast/messages.json | 18 ---- .../view-key-tools/grid-cell-zoom-key-tool.ts | 15 ++- .../resize/resize-default-tool.ts | 14 ++- .../change-bounds/resize/resize-tool.ts | 36 +++++-- .../src/features/tool-palette/tool-palette.ts | 94 +++++++++++------- .../tools/change-bounds/change-bounds-tool.ts | 14 ++- .../src/features/viewport/viewport-tool.ts | 28 +++++- packages/client/src/index.ts | 1 + .../client/src/views/glsp-projection-view.tsx | 3 +- packages/protocol/src/utils/disposable.ts | 10 ++ 20 files changed, 407 insertions(+), 144 deletions(-) create mode 100644 packages/client/src/base/messages.json create mode 100644 packages/client/src/base/messages.ts delete mode 100644 packages/client/src/features/accessibility/toast/messages.json diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index b80f9d6..1776e76 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -19,6 +19,7 @@ import { inject } from 'inversify'; import '../../../css/autocomplete-palette.css'; import { GLSPAbstractUIExtension } from '../ui-extension/ui-extension'; import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete-widget'; +import { messages } from '../messages'; /** * A reusable base implementation for `UIExtensions` that want to provide autocomplete functionality @@ -27,7 +28,7 @@ import { AutoCompleteWidget, CloseReason, toActionArray } from './auto-complete- */ export abstract class BaseAutocompletePalette extends GLSPAbstractUIExtension { protected readonly autoSuggestionSettings = { - noSuggestionsMessage: 'No suggestions available', + noSuggestionsMessage: messages.autocomplete.no_suggestions, suggestionsClass: 'command-palette-suggestions', debounceWaitMs: 50, showOnFocus: true diff --git a/packages/client/src/base/messages.json b/packages/client/src/base/messages.json new file mode 100644 index 0000000..f4d893b --- /dev/null +++ b/packages/client/src/base/messages.json @@ -0,0 +1,82 @@ +{ + "navigation": { + "default_navigation_mode_activated": "Navigation On: Use arrow keys to select preceding (←) or succeding (→) elements. Use the up (↑) and down (↓) arrows to navigate paths. Press 'N' to exit.", + "default_navigation_mode_deactivated": "Navigation Off: Press 'N' for default navigation, 'ALT+N' for position based navigation.", + "local_navigation_mode_activated": "Position based Nav On: Navigate nearest elements using arrow keys: (↑) for above, (↓) for below, (←) for previous, (→) for next element. Press 'ALT+N' to exit.", + "local_navigation_mode_deactivated": "Position based Nav Off: Press 'N' for default navigation, 'ALT+N' for position based navigation.", + "shortcut_local_mode": "Activate local navigation mode", + "shortcut_global_mode": "Activate global navigation mode" + }, + "focus": { + "focus_not_set": "Focus not set", + "focus_on": "Currently focused: ", + "focus_off": "Currently no element is focused.", + "focus_within": "Within", + "shortcut_focus_palette": "Focus on tool palette", + "shortcut_focus_graph": "Focus on graph" + }, + "resize": { + "resize_mode_activated": "Resize On: Use plus(+) and minus(-) to resize, 'CTRL'+'0' for default size. Press 'ESC' to exit.", + "resize_mode_deactivated": "Resize Off: Press 'ALT'+'A' for resize mode.", + "shortcut_activate": "Activate resize mode for selected element", + "shortcut_deactivate": "Deactivate resize handler", + "shortcut_increase": "Increase size of element", + "shortcut_decrease": "Decrease size of element", + "shortcut_reset": "Set element size to default" + }, + "move": { + "shortcut_move": "Move element" + }, + "grid": { + "zoom_in_grid": "Select a digit from 1-9 to zoom in on the respective box.", + "shortcut_zoom_in": "Zoom in via grid" + }, + "search": { + "shortcut_activate": "Activate search for elements", + "placeholder": "Search for elements...", + "label": "Search Field" + }, + "tool_palette": { + "label": "Tool Palette", + "delete_button": "Enable deletion tool", + "marquee_button": "Enable marquee tool", + "marquee_message": "Currently marquee tool is only usable with mouse.", + "search_placeholder": "Search...", + "search_button": "Filter palette entries", + "selection_button": "Enable selection tool", + "validate_button": "Validate model", + "reset_viewport_button": "Reset Viewport", + "toggle_grid_button": "Toggle Grid", + "debug_mode_button": "Debug Mode", + "minimize": "Minimize palette", + "maximize": "Maximize palette", + "no_items": "No results found." + }, + "viewport": { + "shortcut_move_viewport": "Move viewport", + "shortcut_zoom_viewport": "Zoom viewport", + "shortcut_zoom_element": "Zoom element" + }, + "shortcut": { + "title": "Keyboard Shortcuts", + "header_command": "Command", + "header_shortcut": "Keybinding", + "menu_title": "Shortcut Menu", + "group_move": "Move", + "group_graph": "Graph", + "group_resize": "Resize", + "group_focus": "Focus", + "group_zoom": "Zoom", + "group_navigation": "Navigation", + "group_grid": "Grid", + "group_search": "Search", + "group_tool_palette": "Tool Palette", + "group_viewport": "Viewport" + }, + "diagram": { + "label": "Diagram" + }, + "autocomplete": { + "no_suggestions": "No suggestions available" + } +} diff --git a/packages/client/src/base/messages.ts b/packages/client/src/base/messages.ts new file mode 100644 index 0000000..f221e56 --- /dev/null +++ b/packages/client/src/base/messages.ts @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Disposable, Emitter } from '@eclipse-glsp/sprotty'; +import * as rawMessages from './messages.json'; + +// Helper type to allow dynamic properties inside nested objects with known keys +type WithDynamicProperties = T & Record; + +type DynamicDeepPartial = WithDynamicProperties<{ + [K in keyof T]?: T[K] extends object ? DynamicDeepPartial : T[K]; +}>; + +const deepUpdate = (target: any, updates: any): void => { + for (const key in updates) { + // Guard against prototype pollution and block __proto__ and constructor + if (!Object.prototype.hasOwnProperty.call(updates, key) || key === '__proto__' || key === 'constructor') { + continue; + } + + if (updates[key] && typeof updates[key] === 'object' && !Array.isArray(updates[key])) { + if (!target[key]) { + target[key] = {}; + } + deepUpdate(target[key], updates[key]); + } else { + target[key] = updates[key]; + } + } +}; + +/** + * Type-safe access to the known messages defined within the framework. + */ +export type Messages = WithDynamicProperties<{ + [K in keyof typeof rawMessages]: (typeof rawMessages)[K] extends object + ? WithDynamicProperties<(typeof rawMessages)[K]> + : (typeof rawMessages)[K]; +}>; + +/** + * The messages object containing all known messages. + */ +export const messages: Messages = rawMessages; +const messagesUpdatedEmitter = new Emitter(); +/** + * Event that is fired when the messages are updated. + */ +export const onMessagesUpdated = messagesUpdatedEmitter.event; + +/** + * Update the messages with the given set of messages. This may include overwriting existing messages or adding new ones. + * + * @param updates The updates to apply to the messages object. + */ +export const updateMessages = (updates: DynamicDeepPartial): void => { + deepUpdate(messages, updates); + messagesUpdatedEmitter.fire(messages); +}; + +/** + * Executes the given listener with the current messages and re-executes it whenever the messages are updated. + * If the listener returns a disposable, it will be disposed before the listener is called again. + * + * @param listener The listener to re-execute when the messages are updated. + * @param options Options to control the behavior of the listener execution. + * @returns A disposable that can be used to clean up the listener. + */ +export function repeatOnMessagesUpdated( + listener: (messages?: Messages) => unknown, + options: { initial: boolean } = { initial: true } +): Disposable { + let cleanup = options.initial ? listener(messages) : {}; + + const updateListener = onMessagesUpdated(() => { + Disposable.dispose(cleanup); + cleanup = listener(); + }); + + return Disposable.create(() => { + Disposable.dispose(cleanup); + updateListener.dispose(); + }); +} diff --git a/packages/client/src/base/shortcuts/available-shortcuts-extension.ts b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts index a76f028..67fa7dc 100644 --- a/packages/client/src/base/shortcuts/available-shortcuts-extension.ts +++ b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts @@ -18,6 +18,7 @@ import { GModelRoot, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { groupBy } from 'lodash'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; +import { messages } from '../messages'; import type { IShortcutManager, ShortcutRegistration } from './shortcuts-manager'; @injectable() @@ -71,8 +72,8 @@ export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension { commandCell.classList.add('column-title'); - commandCell.innerText = 'Command'; - keybindingCell.innerText = 'Keybinding'; + commandCell.innerText = messages.shortcut.header_command; + keybindingCell.innerText = messages.shortcut.header_shortcut; headerRow.appendChild(commandCell); headerRow.appendChild(keybindingCell); @@ -134,7 +135,7 @@ export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension { // create title const menuTitle = document.createElement('h3'); menuTitle.classList.add('menu-header'); - menuTitle.innerText = 'Keyboard Shortcuts'; + menuTitle.innerText = messages.shortcut.title; this.container.appendChild(menuTitle); const closeBtn = document.createElement('button'); @@ -158,7 +159,7 @@ export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension { this.container.appendChild(this.shortcutsContainer); containerElement.appendChild(this.container); - containerElement.ariaLabel = 'Shortcut-Menu'; + containerElement.ariaLabel = messages.shortcut.menu_title; this.refreshUI(); } diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index 74d6b05..f5be8a7 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -30,6 +30,7 @@ import { toArray } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import { AvailableShortcutsTool } from '../../../base/shortcuts/available-shortcuts-tool'; import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; @@ -38,7 +39,6 @@ import { SelectableBoundsAware } from '../../../utils/gmodel-util'; import { BaseTool } from '../../tools/base-tools'; import { RepositionAction } from '../../viewport/reposition'; import { SearchAutocompletePaletteTool } from '../search/search-tool'; -import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; import { ElementNavigator } from './element-navigator'; @@ -60,10 +60,22 @@ export class ElementNavigatorTool extends BaseTool { enable(): void { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.elementNavigatorKeyListener), - this.shortcutManager.register(ElementNavigatorTool.TOKEN, [ - { shortcuts: ['ALT', 'N'], description: 'Activate local navigation mode', group: 'Navigation', position: 0 }, - { shortcuts: ['N'], description: 'Activate global navigation mode', group: 'Navigation', position: 1 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(ElementNavigatorTool.TOKEN, [ + { + shortcuts: ['ALT', 'N'], + description: messages.navigation.shortcut_local_mode, + group: messages.shortcut.group_navigation, + position: 0 + }, + { + shortcuts: ['N'], + description: messages.navigation.shortcut_global_mode, + group: messages.shortcut.group_navigation, + position: 1 + } + ]) + ) ); } } diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts index 7af8dd2..58ce2d8 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -16,8 +16,8 @@ import { IActionDispatcher, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages } from '../../../base/messages'; import { Tool } from '../../../base/tool-manager/tool'; -import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; @injectable() @@ -51,7 +51,7 @@ export class FocusTrackerTool implements Tool { } protected async focusOut(event: FocusEvent): Promise { - await this.showToast('Focus not set'); + await this.showToast(messages.focus.focus_not_set); } protected async focusIn(event: FocusEvent): Promise { @@ -68,7 +68,7 @@ export class FocusTrackerTool implements Tool { if (parent === undefined && textMessage !== undefined) { message = textMessage; } else if (parent !== undefined && textMessage === undefined) { - message = `Focus is in ${parent.ariaLabel}`; + message = `${messages.focus.focus_within} ${parent.ariaLabel}`; } else if (parent !== undefined && textMessage !== undefined) { message = `${parent.ariaLabel} -> ${textMessage}`; } diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index a2977ba..c616f98 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { Action, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../base/messages'; import type { IShortcutManager } from '../../base/shortcuts/shortcuts-manager'; import { KeyboardGridMetadata, KeyboardNodeGridMetadata } from '../accessibility/keyboard-grid/constants'; import { ToolPalette } from '../tool-palette/tool-palette'; @@ -41,10 +42,22 @@ export class GlobalKeyListenerTool extends BaseEditTool { this.alreadyRegistered = true; document.addEventListener('keyup', this.trigger.bind(this)); - this.shortcutManager.register(GlobalKeyListenerTool.TOKEN, [ - { shortcuts: ['ALT', 'P'], description: 'Focus on tool palette', group: 'Tool-Palette', position: 0 }, - { shortcuts: ['ALT', 'G'], description: 'Focus on graph', group: 'Graph', position: 0 } - ]); + repeatOnMessagesUpdated(() => + this.shortcutManager.register(GlobalKeyListenerTool.TOKEN, [ + { + shortcuts: ['ALT', 'P'], + description: messages.focus.shortcut_focus_palette, + group: messages.shortcut.group_tool_palette, + position: 0 + }, + { + shortcuts: ['ALT', 'G'], + description: messages.focus.shortcut_focus_graph, + group: messages.shortcut.group_graph, + position: 0 + } + ]) + ); } } diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index 3edcbc1..ac91d05 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -26,28 +26,18 @@ import { TriggerNodeCreationAction } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; +import { messages } from '../../../base/messages'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../../base/tool-manager/tool'; -import { - changeCodiconClass, - compare, - createIcon, - createToolGroup, - EnableToolPaletteAction, - ToolPalette -} from '../../tool-palette/tool-palette'; +import { compare, createIcon, createToolGroup, EnableToolPaletteAction, ToolPalette } from '../../tool-palette/tool-palette'; import { MouseDeleteTool } from '../../tools/deletion/delete-tool'; import { MarqueeMouseTool } from '../../tools/marquee-selection/marquee-mouse-tool'; import { FocusDomAction } from '../actions'; import { EdgeAutocompletePaletteMetadata } from '../edge-autocomplete/edge-autocomplete-palette'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; import { KeyboardNodeGridMetadata } from '../keyboard-grid/constants'; -import * as messages from '../toast/messages.json'; import { ShowToastMessageAction } from '../toast/toast-handler'; const SEARCH_ICON_ID = 'search'; -const PALETTE_ICON_ID = 'tools'; -const CHEVRON_DOWN_ICON_ID = 'chevron-right'; -const PALETTE_HEIGHT = '500px'; const SELECTION_TOOL_KEY: KeyCode[] = ['Digit1', 'Numpad1']; const DELETION_TOOL_KEY: KeyCode[] = ['Digit2', 'Numpad2']; const MARQUEE_TOOL_KEY: KeyCode[] = ['Digit3', 'Numpad3']; @@ -103,9 +93,10 @@ export class KeyboardToolPalette extends ToolPalette { } protected override initializeContents(_containerElement: HTMLElement): void { - this.containerElement.setAttribute('aria-label', 'Tool Palette'); + this.containerElement.setAttribute('aria-label', messages.tool_palette.label); this.containerElement.tabIndex = 20; this.containerElement.classList.add('accessibility-tool-palette'); + this.addMinimizePaletteButton(); this.createHeader(); this.createBody(); this.lastActiveButton = this.defaultToolsButton; @@ -147,31 +138,6 @@ export class KeyboardToolPalette extends ToolPalette { } } - protected override addMinimizePaletteButton(): void { - const baseDiv = document.getElementById(this.options.baseDiv); - const minPaletteDiv = document.createElement('div'); - minPaletteDiv.classList.add('minimize-palette-button'); - this.containerElement.classList.add('collapsible-palette'); - if (baseDiv) { - const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); - const minimizeIcon = createIcon(CHEVRON_DOWN_ICON_ID); - this.updateMinimizePaletteButtonTooltip(minPaletteDiv); - minimizeIcon.onclick = _event => { - if (this.isPaletteMaximized()) { - this.containerElement.style.overflow = 'hidden'; - this.containerElement.style.maxHeight = '0px'; - } else { - this.containerElement.style.overflow = 'visible'; - this.containerElement.style.maxHeight = PALETTE_HEIGHT; - } - this.updateMinimizePaletteButtonTooltip(minPaletteDiv); - changeCodiconClass(minimizeIcon, PALETTE_ICON_ID); - changeCodiconClass(minimizeIcon, CHEVRON_DOWN_ICON_ID); - }; - insertedDiv.appendChild(minimizeIcon); - } - } - protected override createBody(): void { const bodyDiv = document.createElement('div'); bodyDiv.classList.add('palette-body'); @@ -199,11 +165,11 @@ export class KeyboardToolPalette extends ToolPalette { if (this.paletteItems.length === 0) { const noResultsDiv = document.createElement('div'); - noResultsDiv.innerText = 'No results found.'; + noResultsDiv.innerText = messages.tool_palette.no_items; noResultsDiv.classList.add('tool-button'); bodyDiv.appendChild(noResultsDiv); } - // Remove existing body to refresh filtered entries + // Replace existing body to refresh filtered entries if (this.bodyDiv) { this.containerElement.removeChild(this.bodyDiv); } @@ -261,7 +227,7 @@ export class KeyboardToolPalette extends ToolPalette { protected override createDefaultToolButton(): HTMLElement { const button = createIcon('inspect'); button.id = 'btn_default_tools'; - button.title = 'Enable selection tool'; + button.title = messages.tool_palette.selection_button; button.onclick = this.onClickStaticToolButton(button); button.appendChild(this.createKeyboardShotcut(SELECTION_TOOL_KEY[0])); @@ -270,7 +236,7 @@ export class KeyboardToolPalette extends ToolPalette { protected override createMouseDeleteToolButton(): HTMLElement { const deleteToolButton = createIcon('chrome-close'); - deleteToolButton.title = 'Enable deletion tool'; + deleteToolButton.title = messages.tool_palette.delete_button; deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); deleteToolButton.appendChild(this.createKeyboardShotcut(DELETION_TOOL_KEY[0])); @@ -279,10 +245,10 @@ export class KeyboardToolPalette extends ToolPalette { protected override createMarqueeToolButton(): HTMLElement { const marqueeToolButton = createIcon('screen-full'); - marqueeToolButton.title = 'Enable marquee tool'; + marqueeToolButton.title = messages.tool_palette.marquee_button; const toastMessageAction = ShowToastMessageAction.createWithTimeout({ id: Symbol.for(ElementNavigatorKeyListener.name), - message: messages.tool_palette.marqueeTool + message: messages.tool_palette.marquee_message }); marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID, toastMessageAction); marqueeToolButton.appendChild(this.createKeyboardShotcut(MARQUEE_TOOL_KEY[0])); @@ -292,7 +258,7 @@ export class KeyboardToolPalette extends ToolPalette { protected override createValidateButton(): HTMLElement { const validateToolButton = createIcon('pass'); - validateToolButton.title = 'Validate model'; + validateToolButton.title = messages.tool_palette.validate_button; validateToolButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds)); @@ -330,7 +296,7 @@ export class KeyboardToolPalette extends ToolPalette { } }; searchIcon.classList.add('search-icon'); - searchIcon.title = 'Filter palette entries'; + searchIcon.title = messages.tool_palette.search_button; searchIcon.appendChild(this.createKeyboardShotcut(SEARCH_TOOL_KEY[0])); return searchIcon; @@ -342,7 +308,7 @@ export class KeyboardToolPalette extends ToolPalette { searchField.tabIndex = 21; searchField.id = this.containerElement.id + '_search_field'; searchField.type = 'text'; - searchField.placeholder = 'Search...'; + searchField.placeholder = messages.tool_palette.search_placeholder; searchField.style.display = 'none'; searchField.onkeyup = ev => { this.requestFilterUpdate(this.searchField.value); diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index 3d8bcc9..a34ca19 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -34,6 +34,7 @@ import { BaseAutocompletePalette } from '../../../base/auto-complete/base-autoco import { AutocompleteSuggestion, IAutocompleteSuggestionProvider } from '../../../base/auto-complete/autocomplete-suggestion-providers'; import { applyCssClasses, deleteCssClasses } from '../../../base/feedback/css-feedback'; +import { messages } from '../../../base/messages'; import { RepositionAction } from '../../../features/viewport/reposition'; import { GEdge } from '../../../model'; @@ -121,8 +122,8 @@ export class SearchAutocompletePalette extends BaseAutocompletePalette { protected override initializeContents(containerElement: HTMLElement): void { super.initializeContents(containerElement); - this.autocompleteWidget.inputField.placeholder = 'Search for elements'; - containerElement.setAttribute('aria-label', 'Search Field'); + this.autocompleteWidget.inputField.placeholder = messages.search.placeholder; + containerElement.setAttribute('aria-label', messages.search.label); } protected getSuggestionProviders(root: Readonly, input: string): IAutocompleteSuggestionProvider[] { return [ diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index f3e432a..696b4e4 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -16,6 +16,7 @@ import { Action, GModelElement, KeyListener, matchesKeystroke, SetUIExtensionVisibilityAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { BaseTool } from '../../tools/base-tools'; import { SearchAutocompletePalette } from './search-palette'; @@ -36,9 +37,16 @@ export class SearchAutocompletePaletteTool extends BaseTool { enable(): void { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.keyListener), - this.shortcutManager.register(SearchAutocompletePaletteTool.TOKEN, [ - { shortcuts: ['CTRL', 'F'], description: 'Activate search for elements', group: 'Search', position: 0 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(SearchAutocompletePaletteTool.TOKEN, [ + { + shortcuts: ['CTRL', 'F'], + description: messages.search.shortcut_activate, + group: messages.shortcut.group_search, + position: 0 + } + ]) + ) ); } } diff --git a/packages/client/src/features/accessibility/toast/messages.json b/packages/client/src/features/accessibility/toast/messages.json deleted file mode 100644 index 8a14a5e..0000000 --- a/packages/client/src/features/accessibility/toast/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "navigation": { - "default_navigation_mode_activated": "Navigation On: Use arrow keys to select preceding (←) or succeding (→) elements. Use the up (↑) and down (↓) arrows to navigate paths. Press 'N' to exit.", - "default_navigation_mode_deactivated": "Navigation Off: Press 'N' for default navigation, 'ALT+N' for position based navigation.", - "local_navigation_mode_activated": "Position based Nav On: Navigate nearest elements using arrow keys: (↑) for above, (↓) for below, (←) for previous, (→) for next element. Press 'ALT+N' to exit.", - "local_navigation_mode_deactivated": "Position based Nav Off: Press 'N' for default navigation, 'ALT+N' for position based navigation." - }, - "focus": { - "focus_on": "Currently focused: ", - "focus_off": "Currently no element is focused." - }, - "resize": { - "resize_mode_activated": "Resize On: Use plus(+) and minus(-) to resize, 'CTRL'+'0' for default size. Press 'ESC' to exit.", - "resize_mode_deactivated": "Resize Off: Press 'ALT'+'A' for resize mode." - }, - "grid": { "zoom_in_grid": "Select a digit from 1-9 to zoom in on the respective box." }, - "tool_palette": { "marqueeTool": "Currently marquee tool is only usable with mouse." } -} diff --git a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts index 152df43..c281421 100644 --- a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts @@ -28,6 +28,7 @@ import { matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import { SelectionService } from '../../../base/selection-service'; import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { getAbsolutePositionByPoint } from '../../../utils/viewpoint-util'; @@ -35,7 +36,6 @@ import { BaseTool } from '../../tools/base-tools'; import type { ZoomFactors } from '../../viewport/zoom-viewport-action'; import { ElementNavigatorKeyListener } from '../element-navigation/diagram-navigation-tool'; import { EnableKeyboardGridAction, KeyboardGridCellSelectedAction, KeyboardGridKeyboardEventAction } from '../keyboard-grid/action'; -import * as messages from '../toast/messages.json'; import { HideToastAction, ShowToastMessageAction } from '../toast/toast-handler'; /** @@ -59,9 +59,16 @@ export class GridCellZoomTool extends BaseTool { enable(): void { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.zoomKeyListener), - this.shortcutManager.register(GridCellZoomTool.TOKEN, [ - { shortcuts: ['CTRL', '+'], description: 'Zoom in via Grid', group: 'Zoom', position: 0 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(GridCellZoomTool.TOKEN, [ + { + shortcuts: ['CTRL', '+'], + description: messages.grid.shortcut_zoom_in, + group: messages.shortcut.group_zoom, + position: 0 + } + ]) + ) ); } diff --git a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts index d2d7c2a..cae6ec1 100644 --- a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts +++ b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts @@ -16,6 +16,7 @@ import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement, type IActionDispatcher } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import { SelectionService } from '../../../base/selection-service'; import type { ShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { EnableToolsAction } from '../../../base/tool-manager/tool'; @@ -67,9 +68,16 @@ export class DefaultResizeKeyTool extends BaseEditTool { enable(): void { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.keyListener), - this.shortcutManager.register(DefaultResizeKeyTool.TOKEN, [ - { shortcuts: ['ALT', 'A'], description: 'Activate resize mode for selected element', group: 'Resize', position: 0 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(DefaultResizeKeyTool.TOKEN, [ + { + shortcuts: ['ALT', 'A'], + description: messages.resize.shortcut_activate, + group: messages.shortcut.group_resize, + position: 0 + } + ]) + ) ); } } diff --git a/packages/client/src/features/change-bounds/resize/resize-tool.ts b/packages/client/src/features/change-bounds/resize/resize-tool.ts index 23fe6d6..1d85b7c 100644 --- a/packages/client/src/features/change-bounds/resize/resize-tool.ts +++ b/packages/client/src/features/change-bounds/resize/resize-tool.ts @@ -17,9 +17,9 @@ import { KeyListener, matchesKeystroke, TYPES, type Action, type GModelElement } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode-jsonrpc'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import { SelectionService } from '../../../base/selection-service'; import type { ShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; -import * as messages from '../../accessibility/toast/messages.json'; import { ShowToastMessageAction } from '../../accessibility/toast/toast-handler'; import { BaseEditTool } from '../../tools/base-tools'; import { isResizable } from '../model'; @@ -100,12 +100,34 @@ export class ResizeKeyTool extends BaseEditTool { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.keyListener), - this.shortcutManager.register(ResizeKeyTool.TOKEN, [ - { shortcuts: ['Escape'], description: 'Deactivate resize handler ', group: 'Resize', position: 0 }, - { shortcuts: ['+'], description: 'Increase size of element', group: 'Resize', position: 1 }, - { shortcuts: ['-'], description: 'Increase size of element', group: 'Resize', position: 2 }, - { shortcuts: ['CTRL', '0'], description: 'Set element size to default', group: 'Resize', position: 3 } - ]), + repeatOnMessagesUpdated(() => + this.shortcutManager.register(ResizeKeyTool.TOKEN, [ + { + shortcuts: ['Escape'], + description: messages.resize.shortcut_deactivate, + group: messages.shortcut.group_resize, + position: 0 + }, + { + shortcuts: ['+'], + description: messages.resize.shortcut_increase, + group: messages.shortcut.group_resize, + position: 1 + }, + { + shortcuts: ['-'], + description: messages.resize.shortcut_decrease, + group: messages.shortcut.group_resize, + position: 2 + }, + { + shortcuts: ['CTRL', '0'], + description: messages.resize.shortcut_reset, + group: messages.shortcut.group_resize, + position: 3 + } + ]) + ), Disposable.create(() => { this.dispatchActions(this.keyListener.disable()); }) diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index e74f75e..fd35338 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -35,6 +35,7 @@ import { import { inject, injectable, optional, postConstruct } from 'inversify'; import { EditorContextService, IEditModeListener } from '../../base/editor-context-service'; import { FocusTracker } from '../../base/focus/focus-tracker'; +import { messages, repeatOnMessagesUpdated } from '../../base/messages'; import { IDiagramStartup } from '../../base/model/diagram-loader'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension'; @@ -89,6 +90,8 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected paletteItems: PaletteItem[]; protected paletteItemsCopy: PaletteItem[] = []; protected dynamic = false; + protected toggleButton?: HTMLElement; + protected headerDiv?: HTMLElement; protected bodyDiv?: HTMLElement; protected lastActiveButton?: HTMLElement; protected defaultToolsButton: HTMLElement; @@ -105,6 +108,13 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl @postConstruct() postConstruct(): void { this.editorContext.onEditModeChanged(change => this.editModeChanged(change.newValue, change.oldValue)); + repeatOnMessagesUpdated( + () => { + this.createHeader(); + this.addMinimizePaletteButton(); + }, + { initial: false } + ); } override initialize(): boolean { @@ -115,10 +125,11 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl } protected initializeContents(containerElement: HTMLElement): void { + this.addMinimizePaletteButton(); this.createHeader(); this.createBody(); this.lastActiveButton = this.defaultToolsButton; - containerElement.setAttribute('aria-label', 'Tool-Palette'); + containerElement.setAttribute('aria-label', messages.tool_palette.label); } protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { @@ -128,32 +139,40 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected addMinimizePaletteButton(): void { const baseDiv = document.getElementById(this.options.baseDiv); - const minPaletteDiv = document.createElement('div'); - minPaletteDiv.classList.add('minimize-palette-button'); + if (!baseDiv) { + return; + } + const toggleButton = document.createElement('div'); + toggleButton.classList.add('minimize-palette-button'); this.containerElement.classList.add('collapsible-palette'); - if (baseDiv) { - const insertedDiv = baseDiv.insertBefore(minPaletteDiv, baseDiv.firstChild); - const minimizeIcon = createIcon(CHEVRON_DOWN_ICON_ID); - this.updateMinimizePaletteButtonTooltip(minPaletteDiv); - minimizeIcon.onclick = _event => { - if (this.isPaletteMaximized()) { - this.containerElement.style.maxHeight = '0px'; - } else { - this.containerElement.style.maxHeight = PALETTE_HEIGHT; - } - this.updateMinimizePaletteButtonTooltip(minPaletteDiv); - changeCodiconClass(minimizeIcon, PALETTE_ICON_ID); - changeCodiconClass(minimizeIcon, CHEVRON_DOWN_ICON_ID); - }; - insertedDiv.appendChild(minimizeIcon); + const minimizeIcon = createIcon(CHEVRON_DOWN_ICON_ID); + this.updateMinimizePaletteButtonTooltip(toggleButton); + minimizeIcon.onclick = _event => { + if (this.isPaletteMaximized()) { + this.containerElement.style.maxHeight = '0px'; + } else { + this.containerElement.style.maxHeight = PALETTE_HEIGHT; + } + this.updateMinimizePaletteButtonTooltip(toggleButton); + changeCodiconClass(minimizeIcon, PALETTE_ICON_ID); + changeCodiconClass(minimizeIcon, CHEVRON_DOWN_ICON_ID); + }; + toggleButton.appendChild(minimizeIcon); + + // Replace existing header to refresh + if (this.toggleButton) { + this.toggleButton.replaceWith(toggleButton); + } else { + baseDiv.insertBefore(toggleButton, baseDiv.firstChild); } + this.toggleButton = toggleButton; } protected updateMinimizePaletteButtonTooltip(button: HTMLDivElement): void { if (this.isPaletteMaximized()) { - button.title = 'Minimize palette'; + button.title = messages.tool_palette.minimize; } else { - button.title = 'Maximize palette'; + button.title = messages.tool_palette.maximize; } } @@ -176,26 +195,33 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl }); if (this.paletteItems.length === 0) { const noResultsDiv = document.createElement('div'); - noResultsDiv.innerText = 'No results found.'; + noResultsDiv.innerText = messages.tool_palette.no_items; noResultsDiv.classList.add('tool-button'); bodyDiv.appendChild(noResultsDiv); } // Remove existing body to refresh filtered entries if (this.bodyDiv) { - this.containerElement.removeChild(this.bodyDiv); + this.bodyDiv.replaceWith(bodyDiv); + } else { + this.containerElement.appendChild(bodyDiv); } - this.containerElement.appendChild(bodyDiv); this.bodyDiv = bodyDiv; } protected createHeader(): void { - this.addMinimizePaletteButton(); const headerCompartment = document.createElement('div'); headerCompartment.classList.add('palette-header'); headerCompartment.append(this.createHeaderTitle()); headerCompartment.appendChild(this.createHeaderTools()); headerCompartment.appendChild((this.searchField = this.createHeaderSearchField())); - this.containerElement.appendChild(headerCompartment); + + // Replace existing header to refresh + if (this.headerDiv) { + this.headerDiv.replaceWith(headerCompartment); + } else { + this.containerElement.appendChild(headerCompartment); + } + this.headerDiv = headerCompartment; } protected createHeaderTools(): HTMLElement { @@ -237,7 +263,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createDefaultToolButton(): HTMLElement { const button = createIcon('inspect'); button.id = 'btn_default_tools'; - button.title = 'Enable selection tool'; + button.title = messages.tool_palette.selection_button; button.onclick = this.onClickStaticToolButton(button); button.ariaLabel = button.title; button.tabIndex = 1; @@ -246,7 +272,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createMouseDeleteToolButton(): HTMLElement { const deleteToolButton = createIcon('chrome-close'); - deleteToolButton.title = 'Enable deletion tool'; + deleteToolButton.title = messages.tool_palette.delete_button; deleteToolButton.onclick = this.onClickStaticToolButton(deleteToolButton, MouseDeleteTool.ID); deleteToolButton.ariaLabel = deleteToolButton.title; deleteToolButton.tabIndex = 1; @@ -255,7 +281,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createMarqueeToolButton(): HTMLElement { const marqueeToolButton = createIcon('screen-full'); - marqueeToolButton.title = 'Enable marquee tool'; + marqueeToolButton.title = messages.tool_palette.marquee_button; marqueeToolButton.onclick = this.onClickStaticToolButton(marqueeToolButton, MarqueeMouseTool.ID); marqueeToolButton.ariaLabel = marqueeToolButton.title; marqueeToolButton.tabIndex = 1; @@ -264,7 +290,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createValidateButton(): HTMLElement { const validateActionButton = createIcon('pass'); - validateActionButton.title = 'Validate model'; + validateActionButton.title = messages.tool_palette.validate_button; validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds, { reason: MarkersReason.BATCH })); @@ -277,7 +303,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createResetViewportButton(): HTMLElement { const resetViewportButton = createIcon('screen-normal'); - resetViewportButton.title = 'Reset Viewport'; + resetViewportButton.title = messages.tool_palette.reset_viewport_button; resetViewportButton.onclick = _event => { this.actionDispatcher.dispatch(OriginViewportAction.create()); resetViewportButton.focus(); @@ -289,7 +315,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createToggleGridButton(): HTMLElement { const toggleGridButton = createIcon('symbol-numeric'); - toggleGridButton.title = 'Toggle Grid'; + toggleGridButton.title = messages.tool_palette.toggle_grid_button; toggleGridButton.onclick = () => { if (this.gridManager?.isGridVisible) { toggleGridButton.classList.remove(CLICKED_CSS_CLASS); @@ -309,7 +335,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl protected createToggleDebugButton(): HTMLElement { const toggleDebugButton = createIcon('debug'); - toggleDebugButton.title = 'Debug Mode'; + toggleDebugButton.title = messages.tool_palette.debug_mode_button; toggleDebugButton.onclick = () => { if (this.debugManager?.isDebugEnabled) { toggleDebugButton.classList.remove(CLICKED_CSS_CLASS); @@ -341,7 +367,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl } }; searchIcon.classList.add('search-icon'); - searchIcon.title = 'Filter palette entries'; + searchIcon.title = messages.tool_palette.search_button; searchIcon.ariaLabel = searchIcon.title; searchIcon.tabIndex = 1; return searchIcon; @@ -352,7 +378,7 @@ export class ToolPalette extends GLSPAbstractUIExtension implements IActionHandl searchField.classList.add('search-input'); searchField.id = this.containerElement.id + '_search_field'; searchField.type = 'text'; - searchField.placeholder = 'Search...'; + searchField.placeholder = messages.tool_palette.search_placeholder; searchField.style.display = 'none'; searchField.onkeyup = () => this.requestFilterUpdate(this.searchField.value); searchField.onkeydown = ev => this.clearOnEscape(ev); diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 2c87630..f4bcecc 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -40,6 +40,7 @@ import { } from '@eclipse-glsp/sprotty'; import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener'; import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter'; +import { messages, repeatOnMessagesUpdated } from '../../../base/messages'; import { ISelectionListener, SelectionService } from '../../../base/selection-service'; import type { IShortcutManager } from '../../../base/shortcuts/shortcuts-manager'; import { @@ -117,9 +118,16 @@ export class ChangeBoundsTool extends BaseEditTool { const createMoveKeyListener = this.createMoveKeyListener(); this.toDisposeOnDisable.push( this.keyTool.registerListener(createMoveKeyListener), - this.shortcutManager.register(ChangeBoundsTool.TOKEN, [ - { shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move element', group: 'Move', position: 0 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(ChangeBoundsTool.TOKEN, [ + { + shortcuts: ['⬅ ⬆ ➡ ⬇'], + description: messages.move.shortcut_move, + group: messages.shortcut.group_move, + position: 0 + } + ]) + ) ); if (Disposable.is(createMoveKeyListener)) { this.toDisposeOnDisable.push(createMoveKeyListener); diff --git a/packages/client/src/features/viewport/viewport-tool.ts b/packages/client/src/features/viewport/viewport-tool.ts index a31d48e..f40898a 100644 --- a/packages/client/src/features/viewport/viewport-tool.ts +++ b/packages/client/src/features/viewport/viewport-tool.ts @@ -16,6 +16,7 @@ import { TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { messages, repeatOnMessagesUpdated } from '../../base/messages'; import type { IShortcutManager } from '../../base/shortcuts/shortcuts-manager'; import { BaseTool } from '../tools/base-tools'; import { MoveViewportKeyListener, ZoomKeyListener } from './viewport-key-listener'; @@ -44,11 +45,28 @@ export class ViewportKeyTool extends BaseTool { this.toDisposeOnDisable.push( this.keyTool.registerListener(this.moveKeyListener), this.keyTool.registerListener(this.zoomKeyListener), - this.shortcutManager.register(ViewportKeyTool.TOKEN, [ - { shortcuts: ['⬅ ⬆ ➡ ⬇'], description: 'Move viewport', group: 'Move', position: 0 }, - { shortcuts: ['+ -'], description: 'Zoom viewport', group: 'Zoom', position: 0 }, - { shortcuts: ['+ -'], description: 'Zoom element', group: 'Zoom', position: 0 } - ]) + repeatOnMessagesUpdated(() => + this.shortcutManager.register(ViewportKeyTool.TOKEN, [ + { + shortcuts: ['⬅ ⬆ ➡ ⬇'], + description: messages.viewport.shortcut_move_viewport, + group: messages.shortcut.group_move, + position: 0 + }, + { + shortcuts: ['+ -'], + description: messages.viewport.shortcut_zoom_viewport, + group: messages.shortcut.group_zoom, + position: 0 + }, + { + shortcuts: ['+ -'], + description: messages.viewport.shortcut_zoom_element, + group: messages.shortcut.group_zoom, + position: 0 + } + ]) + ) ); } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index f237f9a..e3496e8 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -33,6 +33,7 @@ export * from './base/feedback/set-model-command'; export * from './base/feedback/update-model-command'; export * from './base/focus/focus-state-change-action'; export * from './base/focus/focus-tracker'; +export * from './base/messages'; export * from './base/model/diagram-loader'; export * from './base/model/glsp-model-source'; export * from './base/model/model-initialization-constraint'; diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index febddbb..61ccc39 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -34,6 +34,7 @@ import { } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional } from 'inversify'; import { VNode, VNodeStyle, h } from 'snabbdom'; +import { messages } from '../base/messages'; import { GridStyle, IGridManager } from '../features/grid/grid-manager'; import { GridProperty } from '../features/grid/grid-style'; @@ -53,7 +54,7 @@ export class GLSPProjectionView extends ProjectedViewportView {
); setAttr(rootNode, 'tabindex', 1); - setAttr(rootNode, 'aria-label', 'Diagram'); + setAttr(rootNode, 'aria-label', messages.diagram.label); return rootNode; } diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 5804d9d..77572f3 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -49,6 +49,16 @@ export namespace Disposable { export function create(cb: () => void): Disposable { return { dispose: cb }; } + + /** + * Disposes the given object if it is a {@link Disposable}. + * @param value The object that should be disposed + */ + export function dispose(value: unknown): void { + if (is(value)) { + value.dispose(); + } + } } /** From 117e9d4cf97e437eff24191c5e6ec4c74961a840 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 3 Apr 2025 14:44:46 +0000 Subject: [PATCH 441/566] Ensure ConsoleLogger logs the correct log level of the message (#106) --- .../server/src/common/utils/console-logger.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/server/src/common/utils/console-logger.ts b/packages/server/src/common/utils/console-logger.ts index 4a1c6e2..f003f56 100644 --- a/packages/server/src/common/utils/console-logger.ts +++ b/packages/server/src/common/utils/console-logger.ts @@ -16,7 +16,7 @@ import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { injectable } from 'inversify'; -import { LogLevel, Logger, LoggerConfigOptions, LoggerFactory, NullLogger, getRequestParentName } from './logger'; +import { LogLevel, Logger, LoggerConfigOptions, LoggerFactory, NullLogger, asLogLevel, getRequestParentName } from './logger'; /** * Simple logger implementation that forwards logging calls to the `console` and @@ -33,48 +33,55 @@ export class ConsoleLogger extends Logger { info(message: string, ...params: any[]): void { if (LogLevel.info <= this.logLevel) { - console.info(this.logMessage(message, params)); + console.info(this.logMessage(LogLevel.info, message, ...params)); } } warn(message: string, ...params: any[]): void { if (LogLevel.warn <= this.logLevel) { - console.warn(this.logMessage(message, params)); + console.warn(this.logMessage(LogLevel.warn, message, ...params)); } } error(message: string, ...params: any[]): void { if (LogLevel.error <= this.logLevel) { - console.error(this.logMessage(message, params)); + console.error(this.logMessage(LogLevel.error, message, ...params)); } } debug(message: string, ...params: any[]): void { if (LogLevel.debug <= this.logLevel) { - console.debug(this.logMessage(message, params)); + console.debug(this.logMessage(LogLevel.debug, message, ...params)); } } - logMessage(message: string, ...params: any[]): string { - const timestamp = new Date().toLocaleTimeString(); - const caller = this.caller ? `[${this.caller}]` : ''; - const additional = this.logAdditionals(...params); - return `${timestamp} ${this.logLevel} - ${caller} ${message} ${additional}`; + logMessage(level: LogLevel, message: string, ...params: any[]): string { + return `${this.logTimestamp()} ${asLogLevel(level)} - ${this.logCaller()} ${message} ${this.logAdditionals(...params)}`; + } + + protected logCaller(): string { + return this.caller ? `[${this.caller}]` : ''; + } + + protected logTimestamp(): string { + return new Date().toLocaleTimeString(); } logAdditionals(...params: any[]): string { - if (!params || params.length === 0) { - return ''; - } - return params.map(param => this.stringify(param)).join(',\n'); + return !params || params.length === 0 ? '' : params.map(param => this.stringify(param)).join(this.logAdditionalsSeparator()); + } + + protected logAdditionalsSeparator(): string { + return ',\n'; } stringify(param: unknown): string { - if (param instanceof Error) { - return `${param.message} - ${param.stack || ''}`; - } - return JSON.stringify(param, undefined, 4); + return param instanceof Error ? this.stringifyError(param) : JSON.stringify(param, undefined, 4); + } + + protected stringifyError(error: Error): string { + return `${error.message} + ${error.stack || ''}`; } } From 9eceb1c64f75c719245b8b00701e70a6f2f42014 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Apr 2025 10:49:33 +0200 Subject: [PATCH 442/566] Improve winston configuration (#107) * Improve winston configuration Avoid unnecessary instanciation of the winston logger if logging is disabled via options (noConsoleLog and noFilelog are true) * Address review feedback --- packages/server/src/node/di/app-module.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/server/src/node/di/app-module.ts b/packages/server/src/node/di/app-module.ts index 8404d62..eec57d6 100644 --- a/packages/server/src/node/di/app-module.ts +++ b/packages/server/src/node/di/app-module.ts @@ -16,7 +16,7 @@ import { BindingContext } from '@eclipse-glsp/protocol/lib/di'; import { ContainerModule } from 'inversify'; import * as winston from 'winston'; -import { InjectionContainer, LogLevel, Logger, LoggerFactory, getRequestParentName } from '../../common'; +import { InjectionContainer, LogLevel, Logger, LoggerFactory, NullLogger, getRequestParentName } from '../../common'; import { LaunchOptions } from '../launch/cli-parser'; import { WinstonLogger } from './winston-logger'; @@ -74,7 +74,6 @@ export function configureWinstonLogger( rebind = true, baseLoggerCreator: (launchOptions: T) => winston.Logger = createWinstonInstance ): void { - const baseLogger = baseLoggerCreator(options); if (rebind) { if (context.isBound(Logger)) { context.unbind(Logger); @@ -83,11 +82,19 @@ export function configureWinstonLogger( context.unbind(LoggerFactory); } } - - context.bind(Logger).toDynamicValue(dynamicContext => new WinstonLogger(baseLogger, getRequestParentName(dynamicContext))); context.bind(LoggerFactory).toFactory(dynamicContext => (caller: string) => { const logger = dynamicContext.container.get(Logger); logger.caller = caller; return logger; }); + + // if no logging is enabled, bind the NullLogger + if (!options.consoleLog && !options.fileLog) { + context.bind(Logger).to(NullLogger).inSingletonScope(); + return; + } + + const baseLogger = baseLoggerCreator(options); + + context.bind(Logger).toDynamicValue(dynamicContext => new WinstonLogger(baseLogger, getRequestParentName(dynamicContext))); } From 838fabdcc11f6214aab8cd240bf325d3c748711d Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 4 Apr 2025 10:30:02 +0200 Subject: [PATCH 443/566] v2.4.0 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index e7d3370..1fb9f3b 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.4.0-next", + "version": "2.4.0", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.4.0-next", + "@eclipse-glsp/config": "2.4.0", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index 109f988..a3e6425 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.4.0-next", - "@eclipse-glsp/nyc-config": "2.4.0-next", + "@eclipse-glsp/mocha-config": "2.4.0", + "@eclipse-glsp/nyc-config": "2.4.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index d573b77..8ba493a 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.4.0-next", - "@eclipse-glsp/prettier-config": "2.4.0-next", - "@eclipse-glsp/ts-config": "2.4.0-next", + "@eclipse-glsp/eslint-config": "2.4.0", + "@eclipse-glsp/prettier-config": "2.4.0", + "@eclipse-glsp/ts-config": "2.4.0", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 5079198..372b5bd 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.4.0-next", + "version": "2.4.0", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.4.0-next", - "@eclipse-glsp/config": "2.4.0-next", - "@eclipse-glsp/config-test": "2.4.0-next" + "@eclipse-glsp/cli": "2.4.0", + "@eclipse-glsp/config": "2.4.0", + "@eclipse-glsp/config-test": "2.4.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 4973228..4e20cf6 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 6036036..3c32ebf 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index d5066d1..30c457c 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index e1847c4..c803267 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index d74e382..7791c15 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From 8716fa1e5919e227b39cb118e91dd6b74dce6e27 Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 4 Apr 2025 10:46:15 +0200 Subject: [PATCH 444/566] Switch to 2.5.0-next version --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 1fb9f3b..64f52f5 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.4.0", + "version": "2.5.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.4.0", + "@eclipse-glsp/config": "2.5.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index a3e6425..b20c000 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.4.0", - "@eclipse-glsp/nyc-config": "2.4.0", + "@eclipse-glsp/mocha-config": "2.5.0-next", + "@eclipse-glsp/nyc-config": "2.5.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 8ba493a..82a6a8d 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.4.0", - "@eclipse-glsp/prettier-config": "2.4.0", - "@eclipse-glsp/ts-config": "2.4.0", + "@eclipse-glsp/eslint-config": "2.5.0-next", + "@eclipse-glsp/prettier-config": "2.5.0-next", + "@eclipse-glsp/ts-config": "2.5.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 372b5bd..d42acb4 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.4.0", + "version": "2.5.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.4.0", - "@eclipse-glsp/config": "2.4.0", - "@eclipse-glsp/config-test": "2.4.0" + "@eclipse-glsp/cli": "2.5.0-next", + "@eclipse-glsp/config": "2.5.0-next", + "@eclipse-glsp/config-test": "2.5.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 4e20cf6..d9ee8cf 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 3c32ebf..2c0e106 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 30c457c..3023f75 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index c803267..0bc63c5 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 7791c15..3365942 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From c678a52e3ca7fe6514be813da12b338b9c09668d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Apr 2025 11:16:46 +0200 Subject: [PATCH 445/566] v2.4.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index e5b51d6..43a0732 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.4.0-next", + "version": "2.4.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.4.0-next", + "@eclipse-glsp/client": "2.4.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 2368569..166dccd 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.4.0-next", + "version": "2.4.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.4.0-next", - "@eclipse-glsp/client": "2.4.0-next", + "@eclipse-glsp-examples/workflow-glsp": "2.4.0", + "@eclipse-glsp/client": "2.4.0", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index 70a1f4a..b0e0e7a 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.3.0" + "version": "2.4.0" } diff --git a/packages/client/package.json b/packages/client/package.json index 4a2c37b..7a33e4e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.4.0-next", + "version": "2.4.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.4.0-next", + "@eclipse-glsp/sprotty": "2.4.0", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 966a159..888f55c 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.4.0-next", + "@eclipse-glsp/protocol": "2.4.0", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 0b311d6..56efaf2 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.4.0-next", + "version": "2.4.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From e3d4f32d2211c517ffdf8df75f3d52a623c2d03d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Apr 2025 12:10:48 +0200 Subject: [PATCH 446/566] Switch to 2.5.0-next versions --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- examples/workflow-standalone/scripts/config.json | 2 +- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 43a0732..322ce18 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.4.0", + "version": "2.5.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.4.0", + "@eclipse-glsp/client": "2.5.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 166dccd..374ac2c 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.4.0", + "version": "2.5.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.4.0", - "@eclipse-glsp/client": "2.4.0", + "@eclipse-glsp-examples/workflow-glsp": "2.5.0-next", + "@eclipse-glsp/client": "2.5.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json index b0e0e7a..eae82b0 100644 --- a/examples/workflow-standalone/scripts/config.json +++ b/examples/workflow-standalone/scripts/config.json @@ -1,4 +1,4 @@ { "fileName": "workflow-server", - "version": "2.4.0" + "version": "next" } diff --git a/packages/client/package.json b/packages/client/package.json index 7a33e4e..ff1699a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.4.0", + "version": "2.5.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.4.0", + "@eclipse-glsp/sprotty": "2.5.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 888f55c..ec288de 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.4.0", + "@eclipse-glsp/protocol": "2.5.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 56efaf2..06c84bd 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.4.0", + "version": "2.5.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 980f61f4e8af85a11a14388aed52a4f70e84eb63 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Apr 2025 12:24:43 +0200 Subject: [PATCH 447/566] v2.4.0 --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 998fc2f..e512278 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.4.0-next", + "version": "2.4.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 30513ac..a61553c 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.4.0-next", + "version": "2.4.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.4.0-next", - "@eclipse-glsp/server": "2.4.0-next", + "@eclipse-glsp/layout-elk": "2.4.0", + "@eclipse-glsp/server": "2.4.0", "inversify": "^6.1.3" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 28fb41f..91318ff 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.4.0-next", + "version": "2.4.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.4.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index c51bf9d..1408304 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.4.0-next", + "version": "2.4.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.4.0-next", + "@eclipse-glsp/server": "2.4.0", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index cc578e4..e1777ff 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.4.0-next", + "version": "2.4.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.4.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.4.0", + "@eclipse-glsp/protocol": "2.4.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From bb64bdfba03c6f32b261bf50300e4d990bfbea42 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 4 Apr 2025 12:50:00 +0200 Subject: [PATCH 448/566] Switch to 2.5.0-next versions --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index e512278..439d71d 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.4.0", + "version": "2.5.0-next", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index a61553c..788ad17 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.4.0", + "version": "2.5.0-next", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.4.0", - "@eclipse-glsp/server": "2.4.0", + "@eclipse-glsp/layout-elk": "2.5.0-next", + "@eclipse-glsp/server": "2.5.0-next", "inversify": "^6.1.3" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 91318ff..0df33c0 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.4.0", + "version": "2.5.0-next", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.4.0" + "@eclipse-glsp/protocol": "next" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index 1408304..ed738ed 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.4.0", + "version": "2.5.0-next", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.4.0", + "@eclipse-glsp/server": "2.5.0-next", "elkjs": "^0.7.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index e1777ff..301ce48 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.4.0", + "version": "2.5.0-next", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.4.0", - "@eclipse-glsp/protocol": "2.4.0", + "@eclipse-glsp/graph": "2.5.0-next", + "@eclipse-glsp/protocol": "next", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From 9c5f447b416cb4ff88fdf32c36a286642dd5b73b Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 19 May 2025 12:57:07 +0200 Subject: [PATCH 449/566] Fix text selection issues in chrome (#425) Chrome's default selection behavior when pressing the shift key messes with styles/selection hightlighting in the diagram By setting user-select:none for the diagram svg we can fix this issue. Default html text selection does not work for svg text elements anyways, so this change does not break any existing behavior. --- packages/client/css/glsp-sprotty.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/css/glsp-sprotty.css b/packages/client/css/glsp-sprotty.css index 2c5efeb..10f76c3 100644 --- a/packages/client/css/glsp-sprotty.css +++ b/packages/client/css/glsp-sprotty.css @@ -247,6 +247,7 @@ border-style: solid; border-width: 1px; border-color: #bbb; + user-select: none; } .sprotty text { From affdb936c413c7df9aef674a6ed67a79bfcbdca9 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 10 Jun 2025 10:24:58 +0200 Subject: [PATCH 450/566] GLSP-1525: Fix render error for new categories (#426) Update `StructuredCompartmentView` to render a rectangle with size 0,0 if either the width or height of the element is 0. This avoids that size migh end up in negatives (-1) after svg translations are applied. Fixes https://github.com/eclipse-glsp/glsp/issues/1525 --- packages/client/src/views/compartments.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index e7e182c..b309d21 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /** @jsx svg */ -import { GCompartment, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; +import { Dimension, GCompartment, RenderingContext, ShapeView, svg } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; @@ -24,10 +24,10 @@ export class StructureCompartmentView extends ShapeView { if (!this.isVisible(model, context)) { return undefined; } - + const rectSize = Dimension.isValid(model.size) ? model.size : Dimension.ZERO; return ( - + {context.renderChildren(model)} ); From 77ff5ef99f050fd2eae022fb3966baf40451dd88 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 11 Jun 2025 10:03:21 +0200 Subject: [PATCH 451/566] GLSP-1532: Introduce ProposalString utility type (#427) - Introduce `ProposalType` utility type. This type is an augmented string type that exposes possible default values that can be picked up by content assist and code completion tools. It can be used as replacement for any plain string to provide a better developer experience - Update protocol actions that have generic string properties with default values to use the new utility type in their create() function Also: Extend/improve documentation --- .../src/action-protocol/element-selection.ts | 18 +++++++++++ .../src/action-protocol/element-validation.ts | 30 +++++++++++------ .../src/action-protocol/model-edit-mode.ts | 17 ++++++---- packages/protocol/src/utils/type-util.ts | 32 +++++++++++++++++++ 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts index e10fb89..23b5c99 100644 --- a/packages/protocol/src/action-protocol/element-selection.ts +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -61,14 +61,32 @@ export namespace SelectAction { }; } + /** + * Creates a new {@link SelectAction} to add the given elements to the existing selection. + * @param selectedElementsIDs The identifiers of the elements to add to the selection. + * @returns the corresponding {@link SelectAction} + */ export function addSelection(selectedElementsIDs: string[]): SelectAction { return create({ selectedElementsIDs }); } + /** + * Creates a new {@link SelectAction} + * to remove the given elements from the existing selection. + * @param deselectedElementsIDs The identifiers of the elements to remove from the selection. + * @return the corresponding {@link SelectAction} + */ export function removeSelection(deselectedElementsIDs: string[]): SelectAction { return create({ deselectedElementsIDs }); } + /** + * Creates a new {@link SelectAction} + * to set the selection to the given elements. + * This replaces the current selection with the given elements. + * @param selectedElementsIDs The identifiers of the elements to select. + * @returns the corresponding {@link SelectAction} + */ export function setSelection(selectedElementsIDs: string[]): SelectAction { return create({ selectedElementsIDs, deselectedElementsIDs: true }); } diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index 4b67345..7f1305a 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { hasArrayProp } from '../utils/type-util'; +import { ProposalString, hasArrayProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; /** @@ -48,14 +48,16 @@ export namespace MarkerKind { } /** - * The default reasons for markers. + * Utility type for the reason of markers, which offers the default values `batch` and `live` as + * proposals. Any other string value can be used as custom reason as well. */ -export namespace MarkersReason { +export type MarkersReason = ProposalString<(typeof MarkersReason)[keyof typeof MarkersReason]>; +export const MarkersReason = { /** Markers resulting from a batch validation */ - export const BATCH = 'batch'; + BATCH: 'batch', /** Markers resulting from a live validation */ - export const LIVE = 'live'; -} + LIVE: 'live' +} as const; /** * Action to retrieve markers for the specified model elements. Sent from the client to the server. @@ -71,7 +73,9 @@ export interface RequestMarkersAction extends RequestAction { elementsIDs: string[]; /** - * The reason for this request, such as `batch` or `live` validation. `batch` by default. + * The reason for this request. + * Default values are `batch` and `live`, but custom reasons can be used as well. + * If not specified, the default value is `batch`. */ reason?: string; } @@ -83,7 +87,10 @@ export namespace RequestMarkersAction { return RequestAction.hasKind(object, KIND) && hasArrayProp(object, 'elementsIDs'); } - export function create(elementsIDs: string[], options: { requestId?: string; reason?: string } = {}): RequestMarkersAction { + export function create( + elementsIDs: string[], + options: { requestId?: string; reason?: R } = {} + ): RequestMarkersAction { return { kind: KIND, requestId: '', @@ -112,7 +119,7 @@ export interface SetMarkersAction extends ResponseAction { /** * The reason for message, such as `batch` or `live` validation. */ - reason?: string; + reason?: MarkersReason; } export namespace SetMarkersAction { @@ -122,7 +129,10 @@ export namespace SetMarkersAction { return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); } - export function create(markers: Marker[], options: { responseId?: string; reason?: string } = {}): SetMarkersAction { + export function create( + markers: Marker[], + options: { responseId?: string; reason?: R } = {} + ): SetMarkersAction { return { kind: KIND, responseId: '', diff --git a/packages/protocol/src/action-protocol/model-edit-mode.ts b/packages/protocol/src/action-protocol/model-edit-mode.ts index 4c0fa08..8e0d9b2 100644 --- a/packages/protocol/src/action-protocol/model-edit-mode.ts +++ b/packages/protocol/src/action-protocol/model-edit-mode.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { hasStringProp } from '../utils/type-util'; +import { ProposalString, hasStringProp } from '../utils/type-util'; import { Action } from './base-protocol'; /** @@ -27,6 +27,7 @@ export interface SetEditModeAction extends Action { /** * The new edit mode of the diagram. + * Default values are `readonly` and `editable`, but custom modes can be used as well. */ editMode: string; } @@ -38,7 +39,7 @@ export namespace SetEditModeAction { return Action.hasKind(object, KIND) && hasStringProp(object, 'editMode'); } - export function create(editMode: string): SetEditModeAction { + export function create(editMode: E): SetEditModeAction { return { kind: KIND, editMode @@ -47,9 +48,11 @@ export namespace SetEditModeAction { } /** - * The potential default values for the `editMode` property of a {@link SetEditModeAction}. + * Utility type for the edit mode, which offers the default values `readonly` and `editable` as + * proposals. Any other string value can be used as custom edit mode as well. */ -export namespace EditMode { - export const READONLY = 'readonly'; - export const EDITABLE = 'editable'; -} +export type EditMode = ProposalString<(typeof EditMode)[keyof typeof EditMode]>; +export const EditMode = { + READONLY: 'readonly', + EDITABLE: 'editable' +} as const; diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 78890ac..74fdabb 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -92,14 +92,38 @@ export function toTypeGuard(constructor: Constructor): TypeGuard { return (element: any): element is G => element instanceof constructor; } +/** + * Utility type that represents a string value that is augment with proposals for + * default/common values.Code completion in editors can pick up the proposals while + * still allowing to also define any other string value. + * @template T The type of the string proposal as union. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type ProposalString = T | (string & {}); + /** * Utility type that represents an arbitrary function. Should be used instead * of the default `Function` type which is considered to be unsafe. */ export type SafeFunction = (...args: any[]) => T; +/** + * Utility type that represents a value that might be a function or a value of type `T`. + * This is useful to allow functions to be passed as parameters but also allow + * simple values to be passed. + * @template T The type of the value that might be returned by the function. + */ export type MaybeFunction = T | SafeFunction; +/** + * Utility function that calls a given function if it is a function, otherwise returns the value. + * This is useful to allow functions to be passed as parameters but also allow + * simple values to be passed. + * @template T The type of the value that might be returned by the function. + * @param maybeFun The value that might be a function or a value of type `T`. + * @param args The arguments to pass to the function if it is called. + * @returns The result of the function call or the value itself if it is not a function. + */ export function call(maybeFun: MaybeFunction, ...args: any[]): T { return typeof maybeFun === 'function' ? (maybeFun as SafeFunction)(...args) : maybeFun; } @@ -107,6 +131,14 @@ export function call(maybeFun: MaybeFunction, ...args: any[]): T { export type MaybeActions = MaybeFunction; export namespace MaybeActions { + /** + * Utility function that converts a given `MaybeActions` value into an array of actions. + * If the value is a function, it will be called to get the actions. + * If the value is an array, it will be returned as is. + * If the value is undefined, an empty array will be returned. + * @param actions The value that might be a function or an array of actions. + * @returns An array of actions. + */ export function asArray(actions?: MaybeActions): Action[] { const cleanup = actions ? call(actions) : []; return cleanup ? toArray(cleanup) : []; From ffd0e2b5916857ad6ef019164b5a6bbacfdca0cb Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 9 Jul 2025 10:47:19 +0200 Subject: [PATCH 452/566] GLSP-1544: Localize issue marker severity in popup (#428) * GLSP-1544: Localize issue marker severity in popup` Ensure that the severity displayed in the hover poup of issue markers is properly configurable via the generic i18n messages approach. Fix https://github.com/eclipse-glsp/glsp/issues/1544 * Restore uppercase for severity messages --- packages/client/src/base/messages.json | 5 ++++ packages/client/src/features/hover/hover.ts | 28 +++++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/client/src/base/messages.json b/packages/client/src/base/messages.json index f4d893b..22003c9 100644 --- a/packages/client/src/base/messages.json +++ b/packages/client/src/base/messages.json @@ -78,5 +78,10 @@ }, "autocomplete": { "no_suggestions": "No suggestions available" + }, + "issue_marker": { + "severity_error": "ERROR", + "severity_warning": "WARNING", + "severity_info": "INFO" } } diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index e5136fa..ade60ec 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -13,27 +13,28 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { Action, Bounds, EMPTY_ROOT, + GIssueSeverity, + GModelElement, + GModelElementSchema, + GModelRootSchema, + GPreRenderedElementSchema, HoverFeedbackAction, HoverMouseListener, IActionHandler, ICommand, - GPreRenderedElementSchema, RequestPopupModelAction, - GModelElement, - GModelElementSchema, - SetPopupModelAction, - GModelRootSchema + SetPopupModelAction } from '@eclipse-glsp/sprotty'; +import { injectable } from 'inversify'; import { FocusStateChangedAction } from '../../base/focus/focus-state-change-action'; +import { messages } from '../../base/messages'; import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool'; import { EdgeCreationTool } from '../tools/edge-creation/edge-creation-tool'; import { GIssueMarker, getSeverity } from '../validation/issue-marker'; - @injectable() export class GlspHoverMouseListener extends HoverMouseListener implements IActionHandler { protected enableHover = true; @@ -107,7 +108,18 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio } protected createIssueMessage(marker: GIssueMarker): string { - return '
    ' + marker.issues.map(i => '
  • ' + i.severity.toUpperCase() + ': ' + i.message + '
  • ').join('') + '
'; + return '
    ' + marker.issues.map(i => '
  • ' + this.getSeverityString(i.severity) + ': ' + i.message + '
  • ').join('') + '
'; + } + + protected getSeverityString(severity: GIssueSeverity): string { + switch (severity) { + case 'error': + return messages.issue_marker.severity_error; + case 'warning': + return messages.issue_marker.severity_warning; + case 'info': + return messages.issue_marker.severity_info; + } } protected modifyBounds(bounds: Bounds): Bounds { From d6f08af5a6b3bf1539ed84e8e7376a1f34c6b65a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 22 Jul 2025 09:38:03 +0200 Subject: [PATCH 453/566] GLSP-1548: Add visibility check to RoundedCornerNodeView (#430) Fixes eclipse-glsp/glsp/issues/1548 --- packages/client/src/views/rounded-corner-view.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 68e02eb..fc7b87b 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -23,6 +23,10 @@ import { RoundedCornerWrapper } from './rounded-corner'; @injectable() export class RoundedCornerNodeView extends RectangularNodeView { override render(node: Readonly, context: RenderingContext): VNode | undefined { + if (!this.isVisible(node, context)) { + return undefined; + } + const cornerRadius = CornerRadius.from(node); if (!cornerRadius) { return this.renderWithoutRadius(node, context); From 9529fc8a0f6c06df29aab3fc9fc44dc5ecb19bf9 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 17 Jul 2025 10:51:53 +0200 Subject: [PATCH 454/566] GLSP-1313: Update elkjs - Update to elkjs 0.10.1 - Ensure that Logger can used in elkLayoutModule - Update GLSPElkLayoutEngine - Fix typing issue of GModelState.editMode Fixes eclipse-glsp/glsp/issues/1313 --- packages/layout-elk/package.json | 2 +- packages/layout-elk/src/di.config.ts | 16 +++- .../layout-elk/src/glsp-elk-layout-engine.ts | 93 ++++++++----------- .../src/common/features/model/model-state.ts | 6 +- 4 files changed, 57 insertions(+), 60 deletions(-) diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index ed738ed..f55da83 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@eclipse-glsp/server": "2.5.0-next", - "elkjs": "^0.7.1" + "elkjs": "^0.10.1" }, "peerDependencies": { "inversify": "^6.1.3" diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 853fab6..291d792 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LayoutEngine, ModelState } from '@eclipse-glsp/server'; +import { LayoutEngine, Logger, LoggerFactory, ModelState, NullLogger } from '@eclipse-glsp/server'; import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled'; import { ContainerModule } from 'inversify'; import { DefaultElementFilter, ElementFilter } from './element-filter'; @@ -69,7 +69,7 @@ export interface ElkModuleOptions { * @returns A DI module that can be loaded as additional module when configuring a diagram module for a GLSP server. */ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerModule { - return new ContainerModule(bind => { + return new ContainerModule((bind, unbind, isBound, rebind) => { if (options.elementFilter) { bind(ElementFilter).to(options.elementFilter).inSingletonScope(); } else { @@ -107,5 +107,17 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo }) .inSingletonScope(); bind(LayoutEngine).toService(GlspElkLayoutEngine); + + if (!isBound(Logger)) { + bind(Logger).to(NullLogger).inSingletonScope(); + } + + if (!isBound(LoggerFactory)) { + bind(LoggerFactory).toFactory(dynamicContext => (caller: string) => { + const logger = dynamicContext.container.get(Logger); + logger.caller = caller; + return logger; + }); + } }); } diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index 9373731..660c8c1 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -14,7 +14,6 @@ ********************************************************************************/ import { findParent, - findParentByClass, GCompartment, GEdge, GGraph, @@ -26,12 +25,13 @@ import { GPort, GShapeElement, LayoutEngine, + Logger, MaybePromise, ModelState, Point } from '@eclipse-glsp/server'; -import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkPrimitiveEdge, ElkShape } from 'elkjs/lib/elk-api'; -import { injectable } from 'inversify'; +import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkShape } from 'elkjs/lib/elk-api'; +import { inject, injectable } from 'inversify'; import { ElementFilter } from './element-filter'; import { LayoutConfigurator } from './layout-configurator'; @@ -50,9 +50,12 @@ export const ElkFactory = Symbol('ElkFactory'); */ @injectable() export class GlspElkLayoutEngine implements LayoutEngine { + @inject(Logger) + protected readonly logger: Logger; + protected readonly elk: ELK; - protected elkEdges: ElkEdge[]; + protected elkEdges: ElkExtendedEdge[]; protected idToElkElement: Map; constructor( @@ -82,7 +85,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { if (model instanceof GGraph) { const graph = this.transformGraph(model); this.elkEdges.forEach(elkEdge => { - const parent = this.findCommonAncestor(elkEdge as ElkPrimitiveEdge); + const parent = this.findCommonAncestor(elkEdge); if (parent) { parent.edges!.push(elkEdge); } @@ -121,9 +124,13 @@ export class GlspElkLayoutEngine implements LayoutEngine { return result; } - protected findCommonAncestor(elkEdge: ElkPrimitiveEdge): ElkNode | undefined { - const source = this.modelState.index.get(elkEdge.source); - const target = this.modelState.index.get(elkEdge.target); + protected findCommonAncestor(elkEdge: ElkExtendedEdge): ElkNode | undefined { + if (elkEdge.sources.length === 0 || elkEdge.targets.length === 0) { + this.logger.warn('Edges with multiple sources or targets are not supported by the GLSPElkLayoutEngine', elkEdge); + return undefined; + } + const source = this.modelState.index.get(elkEdge.sources[0]); + const target = this.modelState.index.get(elkEdge.targets[0]); if (!source || !target) { return undefined; } @@ -135,14 +142,15 @@ export class GlspElkLayoutEngine implements LayoutEngine { return undefined; } + let ancestor: ElkGraphElement | undefined; if (sourceParent === targetParent) { - return this.idToElkElement.get(sourceParent.id); + ancestor = this.idToElkElement.get(sourceParent.id); } else if (source === targetParent) { - return this.idToElkElement.get(source.id); + ancestor = this.idToElkElement.get(source.id); } else if (target === sourceParent) { - return this.idToElkElement.get(target.id); + ancestor = this.idToElkElement.get(target.id); } - return undefined; + return ancestor as ElkNode | undefined; } protected transformGraph(graph: GGraph): ElkGraphElement { @@ -153,7 +161,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { if (graph.children) { elkGraph.children = this.findChildren(graph, GNode).map(child => this.transformToElk(child)) as ElkNode[]; elkGraph.edges = []; - this.elkEdges.push(...(this.findChildren(graph, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); + this.elkEdges.push(...(this.findChildren(graph, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); } this.idToElkElement.set(graph.id, elkGraph); @@ -169,7 +177,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { if (node.children) { elkNode.children = this.findChildren(node, GNode).map(child => this.transformToElk(child)) as ElkNode[]; elkNode.edges = []; - this.elkEdges.push(...(this.findChildren(node, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); + this.elkEdges.push(...(this.findChildren(node, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); elkNode.labels = this.findChildren(node, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; elkNode.ports = this.findChildren(node, GPort).map(child => this.transformToElk(child)) as ElkPort[]; @@ -193,38 +201,26 @@ export class GlspElkLayoutEngine implements LayoutEngine { } protected transformEdge(edge: GEdge): ElkEdge { - const elkEdge: ElkPrimitiveEdge = { + const elkEdge: ElkExtendedEdge = { id: edge.id, - source: edge.sourceId, - target: edge.targetId, + sources: [edge.sourceId], + targets: [edge.targetId], layoutOptions: this.configurator.apply(edge) }; - const sourceElement = this.modelState.index.get(edge.sourceId); - if (sourceElement instanceof GPort) { - const parentNode = findParentByClass(sourceElement, GNode); - if (parentNode) { - elkEdge.source = parentNode.id; - elkEdge.sourcePort = sourceElement.id; - } - } - - const targetElement = this.modelState.index.get(edge.targetId); - if (sourceElement instanceof GPort) { - const parentNode = findParentByClass(targetElement, GNode); - if (parentNode) { - elkEdge.target = parentNode.id; - elkEdge.targetPort = targetElement.id; - } - } if (edge.children) { elkEdge.labels = this.findChildren(edge, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; } const points = edge.routingPoints; if (points && points.length >= 2) { - elkEdge.sourcePoint = points[0]; - elkEdge.bendPoints = points.slice(1, points.length - 1); - elkEdge.targetPoint = points[points.length - 1]; + elkEdge.sections = [ + { + id: edge.id + ':section', + startPoint: points[0], + bendPoints: points.slice(1, points.length - 1), + endPoint: points[points.length - 1] + } + ]; } this.idToElkElement.set(edge.id, elkEdge); return elkEdge; @@ -248,7 +244,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { }; if (port.children) { elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; - this.elkEdges.push(...(this.findChildren(port, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); + this.elkEdges.push(...(this.findChildren(port, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); } this.transformShape(elkPort, port); this.idToElkElement.set(port.id, elkPort); @@ -294,7 +290,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { if (elkShape.labels) { for (const elkLabel of elkShape.labels) { - const label = this.modelState.index.findByClass(elkLabel.id, GLabel); + const label = elkLabel.id ? this.modelState.index.findByClass(elkLabel.id, GLabel) : undefined; if (label) { this.applyShape(label, elkLabel); } @@ -302,10 +298,10 @@ export class GlspElkLayoutEngine implements LayoutEngine { } } - protected applyEdge(edge: GEdge, elkEdge: ElkEdge): void { + protected applyEdge(edge: GEdge, elkEdge: ElkExtendedEdge): void { const points: Point[] = []; - if ((elkEdge as any).sections && (elkEdge as any).sections.length > 0) { - const section = (elkEdge as ElkExtendedEdge).sections[0]; + if (elkEdge.sections && elkEdge.sections.length > 0) { + const section = elkEdge.sections[0]; if (section.startPoint) { points.push(section.startPoint); } @@ -315,23 +311,12 @@ export class GlspElkLayoutEngine implements LayoutEngine { if (section.endPoint) { points.push(section.endPoint); } - } else { - const section = elkEdge as ElkPrimitiveEdge; - if (section.sourcePoint) { - points.push(section.sourcePoint); - } - if (section.bendPoints) { - points.push(...section.bendPoints); - } - if (section.targetPoint) { - points.push(section.targetPoint); - } } edge.routingPoints = points; if (elkEdge.labels) { elkEdge.labels.forEach(elkLabel => { - const label = this.modelState.index.findByClass(elkLabel.id, GLabel); + const label = elkLabel.id ? this.modelState.index.findByClass(elkLabel.id, GLabel) : undefined; if (label) { this.applyShape(label, elkLabel); } diff --git a/packages/server/src/common/features/model/model-state.ts b/packages/server/src/common/features/model/model-state.ts index c35b9ea..4f7087d 100644 --- a/packages/server/src/common/features/model/model-state.ts +++ b/packages/server/src/common/features/model/model-state.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GModelRoot } from '@eclipse-glsp/graph'; -import { EditMode } from '@eclipse-glsp/protocol'; +import { EditMode, ProposalString } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ClientId } from '../../di/service-identifiers'; import { GModelIndex } from './gmodel-index'; @@ -29,7 +29,7 @@ export interface ModelState { clear(key: string): void; readonly root: GModelRoot; updateRoot(newRoot: GModelRoot): void; - editMode: string; + editMode: ProposalString; sourceUri?: string; clientId: string; readonly isReadonly: boolean; @@ -53,7 +53,7 @@ export class DefaultModelState implements ModelState { protected _root: GModelRoot; - editMode = EditMode.EDITABLE; + editMode: ProposalString = EditMode.EDITABLE; set

(key: string, property: P): void { this.properties.set(key, property); From 0c4503a43328200c41616f918a0ccf3165839e51 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 22 Jul 2025 08:40:00 +0200 Subject: [PATCH 455/566] Address review feedback --- .../layout-elk/src/glsp-elk-layout-engine.ts | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index 660c8c1..b760e73 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -30,7 +30,7 @@ import { ModelState, Point } from '@eclipse-glsp/server'; -import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkShape } from 'elkjs/lib/elk-api'; +import { ELK, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkShape } from 'elkjs/lib/elk-api'; import { inject, injectable } from 'inversify'; import { ElementFilter } from './element-filter'; import { LayoutConfigurator } from './layout-configurator'; @@ -74,13 +74,18 @@ export class GlspElkLayoutEngine implements LayoutEngine { this.elkEdges = []; this.idToElkElement = new Map(); - const elkGraph = this.transformToElk(root) as ElkNode; + const elkGraph = this.transformToElk(root); return this.elk.layout(elkGraph).then(result => { this.applyLayout(result); return root; }); } + protected transformToElk(model: GGraph): ElkNode; + protected transformToElk(model: GNode): ElkNode; + protected transformToElk(model: GEdge): ElkExtendedEdge; + protected transformToElk(model: GLabel): ElkLabel; + protected transformToElk(model: GPort): ElkPort; protected transformToElk(model: GModelElement): ElkGraphElement { if (model instanceof GGraph) { const graph = this.transformGraph(model); @@ -159,9 +164,9 @@ export class GlspElkLayoutEngine implements LayoutEngine { layoutOptions: this.configurator.apply(graph) }; if (graph.children) { - elkGraph.children = this.findChildren(graph, GNode).map(child => this.transformToElk(child)) as ElkNode[]; + elkGraph.children = this.findChildren(graph, GNode).map(child => this.transformToElk(child)); elkGraph.edges = []; - this.elkEdges.push(...(this.findChildren(graph, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); + this.elkEdges.push(...this.findChildren(graph, GEdge).map(child => this.transformToElk(child))); } this.idToElkElement.set(graph.id, elkGraph); @@ -175,12 +180,12 @@ export class GlspElkLayoutEngine implements LayoutEngine { }; if (node.children) { - elkNode.children = this.findChildren(node, GNode).map(child => this.transformToElk(child)) as ElkNode[]; + elkNode.children = this.findChildren(node, GNode).map(child => this.transformToElk(child)); elkNode.edges = []; - this.elkEdges.push(...(this.findChildren(node, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); + this.elkEdges.push(...this.findChildren(node, GEdge).map(child => this.transformToElk(child))); - elkNode.labels = this.findChildren(node, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; - elkNode.ports = this.findChildren(node, GPort).map(child => this.transformToElk(child)) as ElkPort[]; + elkNode.labels = this.findChildren(node, GLabel).map(child => this.transformToElk(child)); + elkNode.ports = this.findChildren(node, GPort).map(child => this.transformToElk(child)); } this.transformShape(elkNode, node); @@ -200,7 +205,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { } } - protected transformEdge(edge: GEdge): ElkEdge { + protected transformEdge(edge: GEdge): ElkExtendedEdge { const elkEdge: ElkExtendedEdge = { id: edge.id, sources: [edge.sourceId], @@ -209,7 +214,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { }; if (edge.children) { - elkEdge.labels = this.findChildren(edge, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; + elkEdge.labels = this.findChildren(edge, GLabel).map(child => this.transformToElk(child)); } const points = edge.routingPoints; if (points && points.length >= 2) { @@ -243,8 +248,8 @@ export class GlspElkLayoutEngine implements LayoutEngine { layoutOptions: this.configurator.apply(port) }; if (port.children) { - elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; - this.elkEdges.push(...(this.findChildren(port, GEdge).map(child => this.transformToElk(child)) as ElkExtendedEdge[])); + elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)); + this.elkEdges.push(...this.findChildren(port, GEdge).map(child => this.transformToElk(child))); } this.transformShape(elkPort, port); this.idToElkElement.set(port.id, elkPort); From 60896c80ca01de146297776f496351dfa24a2115 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 25 Aug 2025 08:16:13 +0000 Subject: [PATCH 456/566] Enrich LayoutOperation by introducing a dedicated trigger action (#433) - Add viewport to editor context to ensure consistent access - Use trigger action for standalone example - Fix issue with wrong handling in LayoutOperation.is --------- Co-authored-by: Jonathan Menzies --- .../client/src/base/editor-context-service.ts | 25 +++++++++- .../view-key-tools/grid-cell-zoom-key-tool.ts | 8 ++-- .../change-bounds/move-element-handler.ts | 9 ++-- .../helper-lines/helper-line-feedback.ts | 6 ++- .../src/features/layout/layout-module.ts | 4 +- .../layout/trigger-layout-action-handler.ts | 35 ++++++++++++++ .../src/features/viewport/origin-viewport.ts | 14 ++++-- .../src/features/viewport/reposition.ts | 13 ++++-- .../src/features/viewport/viewport-handler.ts | 16 ++----- packages/client/src/index.ts | 4 ++ packages/client/src/standalone-modules.ts | 4 +- .../src/action-protocol/model-layout.spec.ts | 32 +++++++++++-- .../src/action-protocol/model-layout.ts | 46 ++++++++++++++++++- 13 files changed, 173 insertions(+), 43 deletions(-) create mode 100644 packages/client/src/features/layout/trigger-layout-action-handler.ts diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 69854ef..7dc986c 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -17,6 +17,7 @@ import { Action, AnyObject, Args, + Bounds, CommandStack, Disposable, DisposableCollection, @@ -24,17 +25,21 @@ import { EditorContext, Emitter, Event, + findParentByFeature, GModelElement, GModelRoot, IActionDispatcher, IActionHandler, + isViewport, LazyInjector, MaybePromise, MousePositionTracker, + Point, SetDirtyStateAction, SetEditModeAction, TYPES, - ValueChange + ValueChange, + Viewport } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { FocusChange, FocusTracker } from './focus/focus-tracker'; @@ -235,6 +240,24 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this._modelRoot; } + get viewport(): Readonly | undefined { + return this._modelRoot ? findParentByFeature(this._modelRoot, isViewport) : undefined; + } + + get viewportData(): Readonly { + const viewport = this.viewport; + // default values aligned with GetViewportCommand + return { + scroll: viewport?.scroll ?? Point.ORIGIN, + zoom: viewport?.zoom ?? 1 + }; + } + + get canvasBounds(): Readonly { + // default value aligned with the initialization of canvasBounds in GModelRoot + return this._modelRoot?.canvasBounds ?? Bounds.EMPTY; + } + get selectedElements(): Readonly[] { return this.selectionService.getSelectedElements(); } diff --git a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts index c281421..da5df5a 100644 --- a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts @@ -24,7 +24,6 @@ import { SetViewportAction, TYPES, Viewport, - isViewport, matchesKeystroke } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; @@ -73,18 +72,19 @@ export class GridCellZoomTool extends BaseTool { } handle(action: Action): Action | void { - if (isViewport(this.editorContext.modelRoot)) { + const viewport = this.editorContext.viewport; + if (viewport) { let viewportAction: Action | undefined = undefined; if (KeyboardGridCellSelectedAction.is(action) && action.options.originId === GridCellZoomTool.ID) { viewportAction = this.zoomKeyListener.setNewZoomFactor( - this.editorContext.modelRoot, + viewport, this.zoomFactors.in, getAbsolutePositionByPoint(this.editorContext.modelRoot, action.options.centerCellPosition) ); } else if (KeyboardGridKeyboardEventAction.is(action) && action.options.originId === GridCellZoomTool.ID) { if (matchesKeystroke(action.options.event, 'Minus')) { - viewportAction = this.zoomKeyListener.setNewZoomFactor(this.editorContext.modelRoot, this.zoomFactors.out); + viewportAction = this.zoomKeyListener.setNewZoomFactor(viewport, this.zoomFactors.out); } else if (matchesKeystroke(action.options.event, 'Digit0', 'ctrl')) { viewportAction = CenterAction.create([]); } diff --git a/packages/client/src/features/change-bounds/move-element-handler.ts b/packages/client/src/features/change-bounds/move-element-handler.ts index aee4464..3b02272 100644 --- a/packages/client/src/features/change-bounds/move-element-handler.ts +++ b/packages/client/src/features/change-bounds/move-element-handler.ts @@ -27,9 +27,7 @@ import { MoveViewportAction, Point, TYPES, - findParentByFeature, - isBoundsAware, - isViewport + isBoundsAware } from '@eclipse-glsp/sprotty'; import { inject, injectable, optional, postConstruct } from 'inversify'; import { DebouncedFunc, debounce } from 'lodash'; @@ -79,15 +77,14 @@ export class MoveElementHandler implements IActionHandler { } protected handleMoveElement(action: MoveElementRelativeAction): void { - const modelRoot = this.editorContextService.modelRoot; - const viewport = findParentByFeature(modelRoot, isViewport); + const viewport = this.editorContextService.viewport; if (!viewport) { return; } const viewportActions: Action[] = []; const elementMoves: ElementMove[] = []; - const elements = getElements(modelRoot.index, action.elementIds, isSelectableAndBoundsAware); + const elements = getElements(viewport.index, action.elementIds, isSelectableAndBoundsAware); for (const element of elements) { const newPosition = this.getTargetBounds(element, action); elementMoves.push({ diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index 403eba9..a64437a 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -28,7 +28,6 @@ import { TYPES, Viewport, equalUpTo, - findParentByFeature, isBoundsAware, isDecoration, isViewport @@ -36,6 +35,7 @@ import { import { inject, injectable } from 'inversify'; import { partition } from 'lodash'; import '../../../css/helper-lines.css'; +import { EditorContextService } from '../../base/editor-context-service'; import { FeedbackCommand } from '../../base/feedback/feedback-command'; import { BoundsAwareModelElement, @@ -96,6 +96,8 @@ export namespace DrawHelperLinesFeedbackAction { export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { static readonly KIND = DrawHelperLinesFeedbackAction.KIND; + @inject(EditorContextService) protected readonly editorContext: EditorContextService; + protected elementIds: string[]; protected elementLines: HelperLineType[]; protected viewportLines: ViewportLineType[]; @@ -159,7 +161,7 @@ export class DrawHelperLinesFeedbackCommand extends FeedbackCommand { protected calcHelperLines(elements: BoundsAwareModelElement[], bounds: Bounds, context: CommandExecutionContext): HelperLine[] { const helperLines: HelperLine[] = []; - const viewport = findParentByFeature(context.root, isViewport); + const viewport = this.editorContext.viewport; if (viewport) { helperLines.push(...this.calcHelperLinesForViewport(viewport, bounds, this.viewportLines)); } diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 5db6df3..4cf6b51 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -13,19 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { FeatureModule, configureActionHandler } from '@eclipse-glsp/sprotty'; +import { FeatureModule, TriggerLayoutAction, configureActionHandler } from '@eclipse-glsp/sprotty'; import { AlignElementsAction, AlignElementsActionHandler, ResizeElementsAction, ResizeElementsActionHandler } from './layout-elements-action'; +import { TriggerLayoutActionHandler } from './trigger-layout-action-handler'; export const layoutModule = new FeatureModule( (bind, _unbind, isBound) => { const context = { bind, isBound }; configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); + configureActionHandler(context, TriggerLayoutAction.KIND, TriggerLayoutActionHandler); }, { featureId: Symbol('layout') } ); diff --git a/packages/client/src/features/layout/trigger-layout-action-handler.ts b/packages/client/src/features/layout/trigger-layout-action-handler.ts new file mode 100644 index 0000000..1f9ca2b --- /dev/null +++ b/packages/client/src/features/layout/trigger-layout-action-handler.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, IActionHandler, ICommand, LayoutOperation, TriggerLayoutAction } from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../../base/editor-context-service'; + +/** + * The handler for {@link TriggerLayoutAction}s. + * This handler provides some client-level information to the layout operation. + */ +@injectable() +export class TriggerLayoutActionHandler implements IActionHandler { + @inject(EditorContextService) protected editorContext: EditorContextService; + + handle(action: TriggerLayoutAction): ICommand | Action | void { + return LayoutOperation.create([], { + args: action.args, + canvasBounds: this.editorContext.canvasBounds, + viewport: this.editorContext.viewportData + }); + } +} diff --git a/packages/client/src/features/viewport/origin-viewport.ts b/packages/client/src/features/viewport/origin-viewport.ts index 7c621a4..c9c87c6 100644 --- a/packages/client/src/features/viewport/origin-viewport.ts +++ b/packages/client/src/features/viewport/origin-viewport.ts @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Bounds, BoundsAwareViewportCommand, GModelRoot, TYPES, Viewport, isViewport, limitViewport } from '@eclipse-glsp/sprotty'; +import { Action, Bounds, BoundsAwareViewportCommand, GModelRoot, TYPES, Viewport, limitViewport } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../../base/editor-context-service'; export interface OriginViewportAction extends Action { kind: typeof OriginViewportAction.KIND; @@ -42,6 +43,8 @@ export namespace OriginViewportAction { export class OriginViewportCommand extends BoundsAwareViewportCommand { static readonly KIND = OriginViewportAction.KIND; + @inject(EditorContextService) protected readonly editorContext: EditorContextService; + constructor(@inject(TYPES.Action) protected action: OriginViewportAction) { super(action.animate); } @@ -51,14 +54,15 @@ export class OriginViewportCommand extends BoundsAwareViewportCommand { } protected override initialize(model: GModelRoot): void { - if (!isViewport(model)) { + const viewport = this.editorContext.viewport; + if (!viewport) { return; } - this.oldViewport = { scroll: model.scroll, zoom: model.zoom }; - const newViewport = this.getNewViewport(Bounds.EMPTY, model); + this.oldViewport = { scroll: viewport.scroll, zoom: viewport.zoom }; + const newViewport = this.getNewViewport(Bounds.EMPTY, viewport); if (newViewport) { const { zoomLimits, horizontalScrollLimits, verticalScrollLimits } = this.viewerOptions; - this.newViewport = limitViewport(newViewport, model.canvasBounds, horizontalScrollLimits, verticalScrollLimits, zoomLimits); + this.newViewport = limitViewport(newViewport, viewport.canvasBounds, horizontalScrollLimits, verticalScrollLimits, zoomLimits); } } diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index bef6b0b..03e19ba 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -26,10 +26,10 @@ import { TYPES, Viewport, getRouteBounds, - hasArrayProp, - isViewport + hasArrayProp } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; +import { EditorContextService } from '../../base/editor-context-service'; import { GEdge } from '../../model'; import { calcElementAndRoute } from '../../utils/gmodel-util'; @@ -60,6 +60,8 @@ export namespace RepositionAction { export class RepositionCommand extends BoundsAwareViewportCommand { static readonly KIND = RepositionAction.KIND; + @inject(EditorContextService) protected readonly editorContext: EditorContextService; + constructor(@inject(TYPES.Action) protected action: RepositionAction) { super(true); } @@ -89,11 +91,12 @@ export class RepositionCommand extends BoundsAwareViewportCommand { return undefined; } - if (isViewport(model)) { - if (this.isFullyVisible(combinedElementBounds, model)) { + const viewport = this.editorContext.viewport; + if (viewport) { + if (this.isFullyVisible(combinedElementBounds, viewport)) { return undefined; } else { - const zoom = model.zoom; + const zoom = viewport.zoom; const centerOfElements = Bounds.center(combinedElementBounds); const canvasCenter = Dimension.center(model.canvasBounds); const scrollCenter = Point.subtract(centerOfElements, canvasCenter); diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index b98d7e6..375e839 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -146,28 +146,20 @@ export class ZoomHandler implements IActionHandler { } protected handleZoomViewport(zoomFactor: number): Action | undefined { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + const viewport = this.editorContextService.viewport; if (!viewport) { return; } - - const newZoom = viewport.zoom * zoomFactor; - - const newViewport = { - scroll: viewport.scroll, - zoom: newZoom - }; - - return SetViewportAction.create(viewport.id, newViewport, { animate: false }); + return SetViewportAction.create(viewport.id, { scroll: viewport.scroll, zoom: viewport.zoom * zoomFactor }, { animate: false }); } protected handleZoomElement(elementIds: string[], zoomFactor: number): Action | undefined { - const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport); + const viewport = this.editorContextService.viewport; if (!viewport) { return; } - const elements = getElements(this.editorContextService.modelRoot.index, elementIds, isSelectableAndBoundsAware); + const elements = getElements(viewport.index, elementIds, isSelectableAndBoundsAware); const center = this.getCenter(viewport, elements); const newZoom = viewport.zoom * zoomFactor; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index e3496e8..e8bc7d4 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -103,6 +103,7 @@ export * from './features/bounds/vbox-layout'; export * from './features/change-bounds/model'; export * from './features/change-bounds/move-element-action'; export * from './features/change-bounds/move-element-handler'; +export * from './features/change-bounds/move-element-key-listener'; export * from './features/change-bounds/movement-restrictor'; export * from './features/change-bounds/point-position-updater'; export * from './features/change-bounds/position-snapper'; @@ -160,6 +161,7 @@ export * from './features/label-edit/edit-label-validator'; export * from './features/label-edit/label-edit-module'; export * from './features/layout/layout-elements-action'; export * from './features/layout/layout-module'; +export * from './features/layout/trigger-layout-action-handler'; export * from './features/navigation/navigation-action-handler'; export * from './features/navigation/navigation-module'; export * from './features/navigation/navigation-target-resolver'; @@ -222,6 +224,7 @@ export * from './features/viewport/reposition'; export * from './features/viewport/viewport-handler'; export * from './features/viewport/viewport-key-listener'; export * from './features/viewport/viewport-modules'; +export * from './features/viewport/viewport-tool'; export * from './features/viewport/zoom-viewport-action'; export * from './model'; export * from './re-exports'; @@ -241,3 +244,4 @@ export * from './views/glsp-projection-view'; export * from './views/issue-marker-view'; export * from './views/rounded-corner'; export * from './views/rounded-corner-view'; +export * from './views/routing-point-handle-view'; diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index 05056a5..c0bd4c3 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -23,11 +23,11 @@ import { ICommand, ILogger, KeyListener, - LayoutOperation, MessageAction, ModuleConfiguration, StartProgressAction, TYPES, + TriggerLayoutAction, UpdateProgressAction, bindAsService, configureActionHandler, @@ -75,7 +75,7 @@ export class FallbackActionHandler implements IActionHandler { export class LayoutKeyListener extends KeyListener { override keyDown(_element: GModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyL', 'ctrlCmd', 'shift')) { - return [LayoutOperation.create()]; + return [TriggerLayoutAction.create()]; } return []; } diff --git a/packages/protocol/src/action-protocol/model-layout.spec.ts b/packages/protocol/src/action-protocol/model-layout.spec.ts index 620068d..de73718 100644 --- a/packages/protocol/src/action-protocol/model-layout.spec.ts +++ b/packages/protocol/src/action-protocol/model-layout.spec.ts @@ -16,7 +16,7 @@ /* eslint-disable max-len */ import { expect } from 'chai'; import { Dimension, Point } from 'sprotty-protocol'; -import { ComputedBoundsAction, LayoutOperation, RequestBoundsAction } from './model-layout'; +import { ComputedBoundsAction, LayoutOperation, RequestBoundsAction, TriggerLayoutAction } from './model-layout'; /** * Tests for the utility functions declared in the namespaces of the protocol * action definitions. @@ -106,13 +106,39 @@ describe('Model layout actions', () => { }); }); + describe('TriggerLayoutAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: TriggerLayoutAction = { + kind: 'triggerLayout' + }; + expect(TriggerLayoutAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(TriggerLayoutAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(TriggerLayoutAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: TriggerLayoutAction = { + kind: 'triggerLayout', + args: { customArg: 'argValue' } + }; + expect(TriggerLayoutAction.create({ args: { customArg: 'argValue' } })).to.deep.equals(expected); + }); + }); + }); + describe('LayoutOperation', () => { describe('is', () => { it('should return true for an object having the correct type and a value for all required interface properties', () => { const action: LayoutOperation = { kind: 'layout', - isOperation: true, - elementIds: [] + isOperation: true }; expect(LayoutOperation.is(action)).to.be.true; }); diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index dc3c229..452a838 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Bounds, Viewport } from 'sprotty-protocol'; import * as sprotty from 'sprotty-protocol/lib/actions'; import { GModelRootSchema } from '../model/model-schema'; import { hasArrayProp, hasObjectProp } from '../utils/type-util'; @@ -124,16 +125,29 @@ export interface LayoutOperation extends Operation, Omit Date: Mon, 25 Aug 2025 09:28:12 +0000 Subject: [PATCH 457/566] Ensure we provide selected element ids as part of layout operation (#434) --- .../client/src/features/layout/trigger-layout-action-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/features/layout/trigger-layout-action-handler.ts b/packages/client/src/features/layout/trigger-layout-action-handler.ts index 1f9ca2b..e40344b 100644 --- a/packages/client/src/features/layout/trigger-layout-action-handler.ts +++ b/packages/client/src/features/layout/trigger-layout-action-handler.ts @@ -26,7 +26,7 @@ export class TriggerLayoutActionHandler implements IActionHandler { @inject(EditorContextService) protected editorContext: EditorContextService; handle(action: TriggerLayoutAction): ICommand | Action | void { - return LayoutOperation.create([], { + return LayoutOperation.create(this.editorContext.get().selectedElementIds, { args: action.args, canvasBounds: this.editorContext.canvasBounds, viewport: this.editorContext.viewportData From bfbfddf75372d198476a8f0534f16500a93d3066 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 25 Aug 2025 11:05:02 +0000 Subject: [PATCH 458/566] Use LayoutOperation as optional parameter for layout engine (#111) Relates to https://github.com/eclipse-glsp/glsp/issues/1561 --------- Co-authored-by: Jonathan Menzies --- packages/layout-elk/src/glsp-elk-layout-engine.ts | 3 ++- packages/server/src/common/features/layout/layout-engine.ts | 5 +++-- .../src/common/features/layout/layout-operation-handler.ts | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index b760e73..ba917d5 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -25,6 +25,7 @@ import { GPort, GShapeElement, LayoutEngine, + LayoutOperation, Logger, MaybePromise, ModelState, @@ -66,7 +67,7 @@ export class GlspElkLayoutEngine implements LayoutEngine { ) { this.elk = elkFactory(); } - layout(): MaybePromise { + layout(operation?: LayoutOperation): MaybePromise { const root = this.modelState.root; if (!(root instanceof GGraph)) { return root; diff --git a/packages/server/src/common/features/layout/layout-engine.ts b/packages/server/src/common/features/layout/layout-engine.ts index 206672a..0830731 100644 --- a/packages/server/src/common/features/layout/layout-engine.ts +++ b/packages/server/src/common/features/layout/layout-engine.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { GModelRoot } from '@eclipse-glsp/graph'; -import { MaybePromise } from '@eclipse-glsp/protocol'; +import { LayoutOperation, MaybePromise } from '@eclipse-glsp/protocol'; /** * A layout engine is able to compute layout information for a model. @@ -23,9 +23,10 @@ import { MaybePromise } from '@eclipse-glsp/protocol'; export interface LayoutEngine { /** * Computes a layout for the model state and modify the model accordingly. + * @param operation the operation that triggered this layout * @returns the layouted {@link GModelRoot}. */ - layout(): MaybePromise; + layout(operation?: LayoutOperation): MaybePromise; } export const LayoutEngine = Symbol('LayoutEngine'); diff --git a/packages/server/src/common/features/layout/layout-operation-handler.ts b/packages/server/src/common/features/layout/layout-operation-handler.ts index e490397..3dd7b9f 100644 --- a/packages/server/src/common/features/layout/layout-operation-handler.ts +++ b/packages/server/src/common/features/layout/layout-operation-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LayoutOperation, MaybePromise, Operation } from '@eclipse-glsp/protocol'; +import { LayoutOperation, MaybePromise } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { Command } from '../../command/command'; import { GModelRecordingCommand } from '../../command/recording-command'; @@ -56,7 +56,7 @@ export class LayoutOperationHandler extends OperationHandler { return new GModelRecordingCommand(this.modelState, this.serializer, () => this.executeOperation(operation)); } - protected async executeOperation(_operation: Operation): Promise { - await this.layoutEngine?.layout(); + protected async executeOperation(operation: LayoutOperation): Promise { + await this.layoutEngine?.layout(operation); } } From ac43d8cb08abc4ff5d99ee5bf43e5a0168e3a5a1 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Aug 2025 08:39:33 +0200 Subject: [PATCH 459/566] GLSP-1530: Fix hGrab/vGrab layout calculation (#435) Ensure that child layout options are only retrieved if the child has a layout feature Fixes eclipse-glsp/glsp/issues/1530 --- packages/client/src/features/bounds/hbox-layout.ts | 9 ++++++++- packages/client/src/features/bounds/vbox-layout.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index 991df93..717cdba 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -26,6 +26,7 @@ import { Point, StatefulLayouter, isBoundsAware, + isLayoutContainer, isLayoutableChild } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; @@ -215,7 +216,13 @@ export class HBoxLayouterExt extends HBoxLayouter { } protected override getChildLayoutOptions(child: GChildElement, containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { - return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as HBoxLayoutOptionsExt; + const filteredOptions = this.filterContainerOptions(containerOptions); + + if (!isLayoutableChild(child) && !isLayoutContainer(child)) { + return filteredOptions; + } + + return super.getChildLayoutOptions(child, filteredOptions) as HBoxLayoutOptionsExt; } protected override getLayoutOptions(element: GModelElement): HBoxLayoutOptionsExt { diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index 4e38c66..f4ccc0b 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -26,6 +26,7 @@ import { VBoxLayoutOptions, VBoxLayouter, isBoundsAware, + isLayoutContainer, isLayoutableChild } from '@eclipse-glsp/sprotty'; import { injectable } from 'inversify'; @@ -215,7 +216,13 @@ export class VBoxLayouterExt extends VBoxLayouter { } protected override getChildLayoutOptions(child: GChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { - return super.getChildLayoutOptions(child, this.filterContainerOptions(containerOptions)) as VBoxLayoutOptionsExt; + const filteredOptions = this.filterContainerOptions(containerOptions); + + if (!isLayoutableChild(child) && !isLayoutContainer(child)) { + return filteredOptions; + } + + return super.getChildLayoutOptions(child, filteredOptions) as VBoxLayoutOptionsExt; } protected override getLayoutOptions(element: GModelElement): VBoxLayoutOptionsExt { From c2b6faf18248692f09345cee0c687879fa6c135b Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Aug 2025 16:25:30 +0200 Subject: [PATCH 460/566] Fix copyright header violations (#437) --- examples/workflow-standalone/scripts/start-example-server.ts | 2 +- examples/workflow-standalone/src/app.ts | 2 +- packages/client/src/base/auto-complete/auto-complete-widget.ts | 2 +- .../client/src/base/auto-complete/base-autocomplete-palette.ts | 2 +- packages/client/src/base/default.module.ts | 2 +- packages/client/src/base/editor-context-service.ts | 2 +- .../client/src/base/shortcuts/available-shortcuts-extension.ts | 2 +- packages/client/src/base/shortcuts/available-shortcuts-tool.ts | 2 +- packages/client/src/base/shortcuts/shortcuts-module.ts | 2 +- packages/client/src/base/ui-extension/ui-extension.ts | 2 +- packages/client/src/default-modules.ts | 2 +- .../client/src/features/accessibility/accessibility-module.ts | 2 +- .../edge-autocomplete/edge-autocomplete-palette.ts | 2 +- .../accessibility/edge-autocomplete/edge-autocomplete-tool.ts | 2 +- .../accessibility/element-navigation/diagram-navigation-tool.ts | 2 +- .../element-navigation/left-right-top-bottom-navigator.ts | 2 +- .../accessibility/element-navigation/position-navigator.ts | 2 +- .../features/accessibility/focus-tracker/focus-tracker-tool.ts | 2 +- .../src/features/accessibility/global-keylistener-tool.ts | 2 +- .../src/features/accessibility/keyboard-grid/keyboard-grid.ts | 2 +- .../features/accessibility/keyboard-grid/keyboard-node-grid.ts | 2 +- .../keyboard-tool-palette/keyboard-tool-palette.ts | 2 +- .../src/features/accessibility/search/search-palette-module.ts | 2 +- .../client/src/features/accessibility/search/search-palette.ts | 2 +- .../client/src/features/accessibility/search/search-tool.ts | 2 +- .../accessibility/view-key-tools/grid-cell-zoom-key-tool.ts | 2 +- .../accessibility/view-key-tools/view-key-tools-module.ts | 2 +- packages/client/src/features/bounds/bounds-module.ts | 2 +- packages/client/src/features/bounds/hbox-layout.ts | 2 +- packages/client/src/features/bounds/vbox-layout.ts | 2 +- .../client/src/features/change-bounds/move-element-action.ts | 2 +- .../client/src/features/change-bounds/move-element-handler.ts | 2 +- .../src/features/change-bounds/resize/resize-default-tool.ts | 2 +- .../client/src/features/change-bounds/resize/resize-handler.ts | 2 +- .../client/src/features/change-bounds/resize/resize-module.ts | 2 +- .../features/command-palette/server-command-palette-provider.ts | 2 +- .../src/features/context-menu/server-context-menu-provider.ts | 2 +- packages/client/src/features/copy-paste/copy-paste-handler.ts | 2 +- packages/client/src/features/grid/grid-module.ts | 2 +- packages/client/src/features/grid/grid-snapper.ts | 2 +- packages/client/src/features/grid/grid.ts | 2 +- .../client/src/features/helper-lines/helper-line-feedback.ts | 2 +- packages/client/src/features/hover/hover.ts | 2 +- packages/client/src/features/label-edit/edit-label-validator.ts | 2 +- .../client/src/features/layout/layout-elements-action.spec.ts | 2 +- packages/client/src/features/layout/layout-module.ts | 2 +- .../src/features/navigation/navigation-target-resolver.ts | 2 +- packages/client/src/features/tool-palette/tool-palette.ts | 2 +- packages/client/src/features/tools/base-tools.ts | 2 +- .../src/features/tools/change-bounds/change-bounds-tool.ts | 2 +- .../src/features/tools/node-creation/container-manager.ts | 2 +- packages/client/src/features/viewport/origin-viewport.ts | 2 +- packages/client/src/features/viewport/reposition.ts | 2 +- packages/client/src/features/viewport/viewport-handler.ts | 2 +- packages/client/src/features/viewport/viewport-key-listener.ts | 2 +- packages/client/src/features/viewport/viewport-modules.ts | 2 +- packages/client/src/features/viewport/zoom-viewport-action.ts | 2 +- packages/client/src/index.ts | 2 +- packages/client/src/standalone-modules.ts | 2 +- packages/client/src/utils/viewpoint-util.ts | 2 +- packages/client/src/views/base-view-module.ts | 2 +- packages/client/src/views/compartments.tsx | 2 +- packages/client/src/views/glsp-projection-view.tsx | 2 +- packages/client/src/views/rounded-corner-view.tsx | 2 +- packages/glsp-sprotty/src/api-override.ts | 2 +- packages/glsp-sprotty/src/re-exports.ts | 2 +- packages/glsp-sprotty/src/types.ts | 2 +- packages/protocol/src/action-protocol/element-selection.ts | 2 +- packages/protocol/src/action-protocol/element-validation.ts | 2 +- packages/protocol/src/action-protocol/model-edit-mode.ts | 2 +- packages/protocol/src/action-protocol/model-layout.spec.ts | 2 +- packages/protocol/src/action-protocol/model-layout.ts | 2 +- packages/protocol/src/action-protocol/model-saving.ts | 2 +- packages/protocol/src/action-protocol/viewport.spec.ts | 2 +- packages/protocol/src/action-protocol/viewport.ts | 2 +- .../src/client-server-protocol/base-glsp-client.spec.ts | 2 +- .../protocol/src/client-server-protocol/base-glsp-client.ts | 2 +- .../jsonrpc/base-jsonrpc-glsp-client.spec.ts | 2 +- packages/protocol/src/index.ts | 2 +- packages/protocol/src/utils/disposable.ts | 2 +- packages/protocol/src/utils/event.ts | 2 +- packages/protocol/src/utils/type-util.spec.ts | 2 +- packages/protocol/src/utils/type-util.ts | 2 +- 83 files changed, 83 insertions(+), 83 deletions(-) diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts index 0e6c097..9242488 100644 --- a/examples/workflow-standalone/scripts/start-example-server.ts +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index 94dc720..0b1d28c 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index ddd700e..46aeeb4 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts index 1776e76..f227026 100644 --- a/packages/client/src/base/auto-complete/base-autocomplete-palette.ts +++ b/packages/client/src/base/auto-complete/base-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index 041bffd..da6f2b4 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 7dc986c..94166e5 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/shortcuts/available-shortcuts-extension.ts b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts index 67fa7dc..60132a3 100644 --- a/packages/client/src/base/shortcuts/available-shortcuts-extension.ts +++ b/packages/client/src/base/shortcuts/available-shortcuts-extension.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/shortcuts/available-shortcuts-tool.ts b/packages/client/src/base/shortcuts/available-shortcuts-tool.ts index 7ffe382..ed52ffe 100644 --- a/packages/client/src/base/shortcuts/available-shortcuts-tool.ts +++ b/packages/client/src/base/shortcuts/available-shortcuts-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/shortcuts/shortcuts-module.ts b/packages/client/src/base/shortcuts/shortcuts-module.ts index b73632f..f6c7b73 100644 --- a/packages/client/src/base/shortcuts/shortcuts-module.ts +++ b/packages/client/src/base/shortcuts/shortcuts-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/base/ui-extension/ui-extension.ts b/packages/client/src/base/ui-extension/ui-extension.ts index 0d48082..2ca3938 100644 --- a/packages/client/src/base/ui-extension/ui-extension.ts +++ b/packages/client/src/base/ui-extension/ui-extension.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index d0ca043..9bc7e52 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/accessibility-module.ts b/packages/client/src/features/accessibility/accessibility-module.ts index 9b4ae58..023b035 100644 --- a/packages/client/src/features/accessibility/accessibility-module.ts +++ b/packages/client/src/features/accessibility/accessibility-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts index 3f58688..95805ca 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts index 63a6729..eb768e4 100644 --- a/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts +++ b/packages/client/src/features/accessibility/edge-autocomplete/edge-autocomplete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts index f5be8a7..07c09ee 100644 --- a/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts +++ b/packages/client/src/features/accessibility/element-navigation/diagram-navigation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts index f92ccf7..5270f4c 100644 --- a/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/left-right-top-bottom-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts index f529ade..fe92e6e 100644 --- a/packages/client/src/features/accessibility/element-navigation/position-navigator.ts +++ b/packages/client/src/features/accessibility/element-navigation/position-navigator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts index 58ce2d8..266dfed 100644 --- a/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts +++ b/packages/client/src/features/accessibility/focus-tracker/focus-tracker-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/global-keylistener-tool.ts b/packages/client/src/features/accessibility/global-keylistener-tool.ts index c616f98..bbc239b 100644 --- a/packages/client/src/features/accessibility/global-keylistener-tool.ts +++ b/packages/client/src/features/accessibility/global-keylistener-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts index 6711be9..6788a4b 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts index 54d3b14..053699f 100644 --- a/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts +++ b/packages/client/src/features/accessibility/keyboard-grid/keyboard-node-grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts index ac91d05..968429a 100644 --- a/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +++ b/packages/client/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette-module.ts b/packages/client/src/features/accessibility/search/search-palette-module.ts index 0aeb5e9..a2158c0 100644 --- a/packages/client/src/features/accessibility/search/search-palette-module.ts +++ b/packages/client/src/features/accessibility/search/search-palette-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-palette.ts b/packages/client/src/features/accessibility/search/search-palette.ts index a34ca19..3b157b5 100644 --- a/packages/client/src/features/accessibility/search/search-palette.ts +++ b/packages/client/src/features/accessibility/search/search-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/search/search-tool.ts b/packages/client/src/features/accessibility/search/search-tool.ts index 696b4e4..d3acf23 100644 --- a/packages/client/src/features/accessibility/search/search-tool.ts +++ b/packages/client/src/features/accessibility/search/search-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts index da5df5a..a620aec 100644 --- a/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts +++ b/packages/client/src/features/accessibility/view-key-tools/grid-cell-zoom-key-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts index 440e168..3a12835 100644 --- a/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts +++ b/packages/client/src/features/accessibility/view-key-tools/view-key-tools-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/bounds-module.ts b/packages/client/src/features/bounds/bounds-module.ts index 3557b5e..a7b6aa4 100644 --- a/packages/client/src/features/bounds/bounds-module.ts +++ b/packages/client/src/features/bounds/bounds-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/hbox-layout.ts b/packages/client/src/features/bounds/hbox-layout.ts index 717cdba..d264f64 100644 --- a/packages/client/src/features/bounds/hbox-layout.ts +++ b/packages/client/src/features/bounds/hbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/bounds/vbox-layout.ts b/packages/client/src/features/bounds/vbox-layout.ts index f4ccc0b..81bb263 100644 --- a/packages/client/src/features/bounds/vbox-layout.ts +++ b/packages/client/src/features/bounds/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/move-element-action.ts b/packages/client/src/features/change-bounds/move-element-action.ts index 0ff82c6..0a4a578 100644 --- a/packages/client/src/features/change-bounds/move-element-action.ts +++ b/packages/client/src/features/change-bounds/move-element-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/move-element-handler.ts b/packages/client/src/features/change-bounds/move-element-handler.ts index 3b02272..4eb438f 100644 --- a/packages/client/src/features/change-bounds/move-element-handler.ts +++ b/packages/client/src/features/change-bounds/move-element-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts index cae6ec1..f0fdee6 100644 --- a/packages/client/src/features/change-bounds/resize/resize-default-tool.ts +++ b/packages/client/src/features/change-bounds/resize/resize-default-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/resize/resize-handler.ts b/packages/client/src/features/change-bounds/resize/resize-handler.ts index 07a14f6..86cac1c 100644 --- a/packages/client/src/features/change-bounds/resize/resize-handler.ts +++ b/packages/client/src/features/change-bounds/resize/resize-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/change-bounds/resize/resize-module.ts b/packages/client/src/features/change-bounds/resize/resize-module.ts index 7c726ca..35b0f5a 100644 --- a/packages/client/src/features/change-bounds/resize/resize-module.ts +++ b/packages/client/src/features/change-bounds/resize/resize-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 24a5178..25826db 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 62e221d..40fff62 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index 0a4a365..5d3c754 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-module.ts b/packages/client/src/features/grid/grid-module.ts index a9799c6..8109d7e 100644 --- a/packages/client/src/features/grid/grid-module.ts +++ b/packages/client/src/features/grid/grid-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Axon Ivy AG and others. + * Copyright (c) 2023-2025 Axon Ivy AG and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid-snapper.ts b/packages/client/src/features/grid/grid-snapper.ts index 684607d..8f15a64 100644 --- a/packages/client/src/features/grid/grid-snapper.ts +++ b/packages/client/src/features/grid/grid-snapper.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/grid/grid.ts b/packages/client/src/features/grid/grid.ts index 27d40e5..f5b4c82 100644 --- a/packages/client/src/features/grid/grid.ts +++ b/packages/client/src/features/grid/grid.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 Axon Ivy AG and others. + * Copyright (c) 2024-2025 Axon Ivy AG and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/helper-lines/helper-line-feedback.ts b/packages/client/src/features/helper-lines/helper-line-feedback.ts index a64437a..5f56ee4 100644 --- a/packages/client/src/features/helper-lines/helper-line-feedback.ts +++ b/packages/client/src/features/helper-lines/helper-line-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index ade60ec..d7749cb 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/label-edit/edit-label-validator.ts b/packages/client/src/features/label-edit/edit-label-validator.ts index 7320750..0c32311 100644 --- a/packages/client/src/features/label-edit/edit-label-validator.ts +++ b/packages/client/src/features/label-edit/edit-label-validator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-elements-action.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts index 3780654..0376c31 100644 --- a/packages/client/src/features/layout/layout-elements-action.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2023 EclipseSource and others. + * Copyright (c) 2019-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/layout-module.ts b/packages/client/src/features/layout/layout-module.ts index 4cf6b51..72f19cc 100644 --- a/packages/client/src/features/layout/layout-module.ts +++ b/packages/client/src/features/layout/layout-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index a297f30..2056dee 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index fd35338..2e2d1a3 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/base-tools.ts b/packages/client/src/features/tools/base-tools.ts index 8058f6a..176ec5e 100644 --- a/packages/client/src/features/tools/base-tools.ts +++ b/packages/client/src/features/tools/base-tools.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2024 EclipseSource and others. + * Copyright (c) 2020-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index f4bcecc..7a1c785 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/tools/node-creation/container-manager.ts b/packages/client/src/features/tools/node-creation/container-manager.ts index d99cf6a..9dcabf5 100644 --- a/packages/client/src/features/tools/node-creation/container-manager.ts +++ b/packages/client/src/features/tools/node-creation/container-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/origin-viewport.ts b/packages/client/src/features/viewport/origin-viewport.ts index c9c87c6..ed7a119 100644 --- a/packages/client/src/features/viewport/origin-viewport.ts +++ b/packages/client/src/features/viewport/origin-viewport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 Axon Ivy AG and others. + * Copyright (c) 2024-2025 Axon Ivy AG and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/reposition.ts b/packages/client/src/features/viewport/reposition.ts index 03e19ba..ff814a5 100644 --- a/packages/client/src/features/viewport/reposition.ts +++ b/packages/client/src/features/viewport/reposition.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-handler.ts b/packages/client/src/features/viewport/viewport-handler.ts index 375e839..95109e3 100644 --- a/packages/client/src/features/viewport/viewport-handler.ts +++ b/packages/client/src/features/viewport/viewport-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Business Informatics Group (TU Wien) and others. + * Copyright (c) 2023-2025 Business Informatics Group (TU Wien) and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-key-listener.ts b/packages/client/src/features/viewport/viewport-key-listener.ts index 0a4c934..5ab5c28 100644 --- a/packages/client/src/features/viewport/viewport-key-listener.ts +++ b/packages/client/src/features/viewport/viewport-key-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/viewport-modules.ts b/packages/client/src/features/viewport/viewport-modules.ts index 3ac1cc8..2e7ca7d 100644 --- a/packages/client/src/features/viewport/viewport-modules.ts +++ b/packages/client/src/features/viewport/viewport-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/viewport/zoom-viewport-action.ts b/packages/client/src/features/viewport/zoom-viewport-action.ts index 4e29db8..75ff047 100644 --- a/packages/client/src/features/viewport/zoom-viewport-action.ts +++ b/packages/client/src/features/viewport/zoom-viewport-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index e8bc7d4..cb1625a 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/standalone-modules.ts b/packages/client/src/standalone-modules.ts index c0bd4c3..ff6aea2 100644 --- a/packages/client/src/standalone-modules.ts +++ b/packages/client/src/standalone-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index 4c818b6..6be7c5d 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index c6937ce..175c5d2 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/compartments.tsx b/packages/client/src/views/compartments.tsx index b309d21..c848a2e 100644 --- a/packages/client/src/views/compartments.tsx +++ b/packages/client/src/views/compartments.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/glsp-projection-view.tsx b/packages/client/src/views/glsp-projection-view.tsx index 61ccc39..dd41d05 100644 --- a/packages/client/src/views/glsp-projection-view.tsx +++ b/packages/client/src/views/glsp-projection-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2024 TypeFox and others. + * Copyright (c) 2017-2025 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index fc7b87b..77b0c0c 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/api-override.ts b/packages/glsp-sprotty/src/api-override.ts index 06b75fd..1916d98 100644 --- a/packages/glsp-sprotty/src/api-override.ts +++ b/packages/glsp-sprotty/src/api-override.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 80d9b29..61024ce 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 9ee54c0..9923000 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts index 23b5c99..ea33b53 100644 --- a/packages/protocol/src/action-protocol/element-selection.ts +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index 7f1305a..90108e3 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-edit-mode.ts b/packages/protocol/src/action-protocol/model-edit-mode.ts index 8e0d9b2..b78e021 100644 --- a/packages/protocol/src/action-protocol/model-edit-mode.ts +++ b/packages/protocol/src/action-protocol/model-edit-mode.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2023 EclipseSource and others. + * Copyright (c) 2020-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-layout.spec.ts b/packages/protocol/src/action-protocol/model-layout.spec.ts index de73718..e45a879 100644 --- a/packages/protocol/src/action-protocol/model-layout.spec.ts +++ b/packages/protocol/src/action-protocol/model-layout.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts index 452a838..3c7154f 100644 --- a/packages/protocol/src/action-protocol/model-layout.ts +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 STMicroelectronics and others. + * Copyright (c) 2021-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index cb5ae63..f09a05b 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/viewport.spec.ts b/packages/protocol/src/action-protocol/viewport.spec.ts index 02f74d1..b3b566b 100644 --- a/packages/protocol/src/action-protocol/viewport.spec.ts +++ b/packages/protocol/src/action-protocol/viewport.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index 54f23fe..c7a3f97 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2023 STMicroelectronics and others. + * Copyright (c) 2021-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts index d24ac73..ff38d9e 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/base-glsp-client.ts b/packages/protocol/src/client-server-protocol/base-glsp-client.ts index 084b271..133f039 100644 --- a/packages/protocol/src/client-server-protocol/base-glsp-client.ts +++ b/packages/protocol/src/client-server-protocol/base-glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts index 6d2290f..6dc0895 100644 --- a/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +++ b/packages/protocol/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e60fcf2..d53863a 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/disposable.ts b/packages/protocol/src/utils/disposable.ts index 77572f3..c68b3b6 100644 --- a/packages/protocol/src/utils/disposable.ts +++ b/packages/protocol/src/utils/disposable.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/event.ts b/packages/protocol/src/utils/event.ts index ef61fa8..0773eb2 100644 --- a/packages/protocol/src/utils/event.ts +++ b/packages/protocol/src/utils/event.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 EclipseSource and others. + * Copyright (c) 2023-2024 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.spec.ts b/packages/protocol/src/utils/type-util.spec.ts index fbc34a4..6fe23ea 100644 --- a/packages/protocol/src/utils/type-util.spec.ts +++ b/packages/protocol/src/utils/type-util.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 74fdabb..ed3d8e1 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021-2024 EclipseSource and others. + * Copyright (c) 2021-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From 0190005273254d7b7de2a32c576e90bb81f35866 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Aug 2025 16:25:45 +0200 Subject: [PATCH 461/566] Fix copyright header violations (#112) --- packages/layout-elk/src/di.config.ts | 2 +- packages/layout-elk/src/glsp-elk-layout-engine.ts | 2 +- packages/server/src/common/features/layout/layout-engine.ts | 2 +- .../src/common/features/layout/layout-operation-handler.ts | 2 +- packages/server/src/common/features/model/model-state.ts | 2 +- .../src/common/features/model/request-model-action-handler.ts | 2 +- .../navigation/request-navigation-targets-action-handler.ts | 2 +- .../server/src/common/gmodel/change-bounds-operation-handler.ts | 2 +- .../common/gmodel/change-routing-points-operation-handler.ts | 2 +- .../server/src/common/operations/compound-operation-handler.ts | 2 +- .../server/src/common/operations/operation-action-handler.ts | 2 +- packages/server/src/common/utils/console-logger.ts | 2 +- packages/server/src/common/utils/layout-util.ts | 2 +- packages/server/src/node/di/app-module.ts | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 291d792..2832852 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index ba917d5..0828415 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2018-2024 TypeFox and others. + * Copyright (c) 2018-2025 TypeFox and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. diff --git a/packages/server/src/common/features/layout/layout-engine.ts b/packages/server/src/common/features/layout/layout-engine.ts index 0830731..19b761a 100644 --- a/packages/server/src/common/features/layout/layout-engine.ts +++ b/packages/server/src/common/features/layout/layout-engine.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/layout/layout-operation-handler.ts b/packages/server/src/common/features/layout/layout-operation-handler.ts index 3dd7b9f..db574ba 100644 --- a/packages/server/src/common/features/layout/layout-operation-handler.ts +++ b/packages/server/src/common/features/layout/layout-operation-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/model/model-state.ts b/packages/server/src/common/features/model/model-state.ts index 4f7087d..f25d5d9 100644 --- a/packages/server/src/common/features/model/model-state.ts +++ b/packages/server/src/common/features/model/model-state.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/model/request-model-action-handler.ts b/packages/server/src/common/features/model/request-model-action-handler.ts index af0f0ab..2b054dd 100644 --- a/packages/server/src/common/features/model/request-model-action-handler.ts +++ b/packages/server/src/common/features/model/request-model-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts b/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts index 82a47a1..a5645b2 100644 --- a/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts +++ b/packages/server/src/common/features/navigation/request-navigation-targets-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2024 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/gmodel/change-bounds-operation-handler.ts b/packages/server/src/common/gmodel/change-bounds-operation-handler.ts index 681f60b..aba6d8d 100644 --- a/packages/server/src/common/gmodel/change-bounds-operation-handler.ts +++ b/packages/server/src/common/gmodel/change-bounds-operation-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts b/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts index 0e3024d..c1bc4d2 100644 --- a/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts +++ b/packages/server/src/common/gmodel/change-routing-points-operation-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/operations/compound-operation-handler.ts b/packages/server/src/common/operations/compound-operation-handler.ts index f3ea4dd..2b014ef 100644 --- a/packages/server/src/common/operations/compound-operation-handler.ts +++ b/packages/server/src/common/operations/compound-operation-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/operations/operation-action-handler.ts b/packages/server/src/common/operations/operation-action-handler.ts index 21e3962..f9f1054 100644 --- a/packages/server/src/common/operations/operation-action-handler.ts +++ b/packages/server/src/common/operations/operation-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/console-logger.ts b/packages/server/src/common/utils/console-logger.ts index f003f56..5bbb230 100644 --- a/packages/server/src/common/utils/console-logger.ts +++ b/packages/server/src/common/utils/console-logger.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/common/utils/layout-util.ts b/packages/server/src/common/utils/layout-util.ts index 6407f3d..2ddb0a3 100644 --- a/packages/server/src/common/utils/layout-util.ts +++ b/packages/server/src/common/utils/layout-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2023 EclipseSource and others. + * Copyright (c) 2022-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/server/src/node/di/app-module.ts b/packages/server/src/node/di/app-module.ts index eec57d6..06c1a4d 100644 --- a/packages/server/src/node/di/app-module.ts +++ b/packages/server/src/node/di/app-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2025 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at From f793b96c1fc477ee29cb71477a5947aad9a46654 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 28 Aug 2025 11:52:12 +0200 Subject: [PATCH 462/566] GLSP-1531: Introduce viewport change event (#438) * GLSP-1531: Introduce viewport change event Fixes eclipse-glsp/glsp/issues/1531 * Address review feedback --- packages/client/src/base/command-stack.ts | 32 ++-- packages/client/src/base/default.module.ts | 2 + .../client/src/base/editor-context-service.ts | 51 +++--- .../client/src/base/focus/focus-tracker.ts | 11 +- .../src/base/model/model-change-service.ts | 151 ++++++++++++++++++ packages/glsp-sprotty/src/api-override.ts | 57 ++++++- packages/glsp-sprotty/src/re-exports.ts | 7 +- packages/glsp-sprotty/src/types.ts | 1 + 8 files changed, 263 insertions(+), 49 deletions(-) create mode 100644 packages/client/src/base/model/model-change-service.ts diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index a740fec..3bb9634 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2024 EclipseSource and others. + * Copyright (c) 2019-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,25 +15,37 @@ ********************************************************************************/ import { CommandExecutionContext, + CommandExecutionData, CommandStack, Disposable, DisposableCollection, + Emitter, Event, GModelRoot, ICommand, - LazyInjector, - SetModelCommand, - UpdateModelCommand + ICommandStack, + LazyInjector } from '@eclipse-glsp/sprotty'; -import { inject, injectable, preDestroy } from 'inversify'; +import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { EditorContextService } from './editor-context-service'; @injectable() -export class GLSPCommandStack extends CommandStack implements Disposable { +export class GLSPCommandStack extends CommandStack implements ICommandStack, Disposable { @inject(LazyInjector) protected lazyInjector: LazyInjector; protected toDispose = new DisposableCollection(); + protected onCommandExecutedEmitter = new Emitter(); + get onCommandExecuted(): Event { + return this.onCommandExecutedEmitter.event; + } + + @postConstruct() + protected override initialize(): void { + super.initialize(); + this.toDispose.push(this.onCommandExecutedEmitter); + } + @preDestroy() dispose(): void { this.toDispose.dispose(); @@ -95,13 +107,7 @@ export class GLSPCommandStack extends CommandStack implements Disposable { } override async execute(command: ICommand): Promise { const result = await super.execute(command); - if (command instanceof SetModelCommand || command instanceof UpdateModelCommand) { - this.notifyListeners(result); - } + this.onCommandExecutedEmitter.fire({ command, newRoot: result }); return result; } - - protected notifyListeners(root: Readonly): void { - this.editorContext.notifyModelRootChanged(root, this); - } } diff --git a/packages/client/src/base/default.module.ts b/packages/client/src/base/default.module.ts index da6f2b4..1b2317c 100644 --- a/packages/client/src/base/default.module.ts +++ b/packages/client/src/base/default.module.ts @@ -46,6 +46,7 @@ import { FocusStateChangedAction } from './focus/focus-state-change-action'; import { FocusTracker } from './focus/focus-tracker'; import { DiagramLoader } from './model/diagram-loader'; import { GLSPModelSource } from './model/glsp-model-source'; +import { ModelChangeService } from './model/model-change-service'; import { DefaultModelInitializationConstraint, ModelInitializationConstraint } from './model/model-initialization-constraint'; import { GModelRegistry } from './model/model-registry'; import { GLSPMousePositionTracker } from './mouse-position-tracker'; @@ -77,6 +78,7 @@ export const defaultModule = new FeatureModule( bind(TYPES.IEditorContextServiceProvider).toProvider( ctx => async () => ctx.container.get(EditorContextService) ); + bind(TYPES.IModelChangeService).to(ModelChangeService).inSingletonScope(); configureActionHandler(context, SetEditModeAction.KIND, EditorContextService); configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService); diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 94166e5..b7b97e6 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -15,10 +15,8 @@ ********************************************************************************/ import { Action, - AnyObject, Args, Bounds, - CommandStack, Disposable, DisposableCollection, EditMode, @@ -44,6 +42,7 @@ import { import { inject, injectable, postConstruct, preDestroy } from 'inversify'; import { FocusChange, FocusTracker } from './focus/focus-tracker'; import { IDiagramOptions, IDiagramStartup } from './model/diagram-loader'; +import { IModelChangeService, ViewportChange } from './model/model-change-service'; import { SelectionChange, SelectionService } from './selection-service'; /** @@ -85,6 +84,9 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra @inject(SelectionService) protected selectionService: SelectionService; + @inject(TYPES.IModelChangeService) + protected modelChangeService: IModelChangeService; + @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; @@ -118,13 +120,11 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this.onDirtyStateChangedEmitter.event; } - protected _modelRoot?: Readonly; /** * Event that is fired when the model root of the diagram changes i.e. after the `CommandStack` has processed a model update. */ - protected onModelRootChangedEmitter = new Emitter>(); get onModelRootChanged(): Event> { - return this.onModelRootChangedEmitter.event; + return this.modelChangeService.onModelRootChanged; } /** @@ -143,6 +143,14 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra return this.selectionService.onSelectionChanged; } + /** + * Event that is fired when the viewport of the diagram changes i.e. after the `CommandStack` has processed a viewport update. + * By default, this event is only fired if the viewport was changed via a `SetViewportCommand` or `BoundsAwareViewportCommand` + */ + get onViewportChanged(): Event { + return this.modelChangeService.onViewportChanged; + } + protected toDispose = new DisposableCollection(); @postConstruct() @@ -151,6 +159,11 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra this.toDispose.push(this.onEditModeChangedEmitter, this.onDirtyStateChangedEmitter); } + @preDestroy() + dispose(): void { + this.toDispose.dispose(); + } + preLoadDiagram(): MaybePromise { this.lazyInjector.getAll(TYPES.IGModelRootListener).forEach(listener => { this.onModelRootChanged(event => listener.modelRootChanged(event)); @@ -160,11 +173,6 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra }); } - @preDestroy() - dispose(): void { - this.toDispose.dispose(); - } - get(args?: Args): EditorContext { return { selectedElementIds: Array.from(this.selectionService.getSelectedElementIDs()), @@ -181,21 +189,6 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra }; } - /** - * Notifies the service about a model root change. This method should not be called - * directly. It is called by the `CommandStack` after a model update has been processed. - * @throws an error if the notifier is not a `CommandStack` - * @param root the new model root - * @param notifier the object that triggered the model root change - */ - notifyModelRootChanged(root: Readonly, notifier: AnyObject): void { - if (!(notifier instanceof CommandStack)) { - throw new Error('Invalid model root change notification. Notifier is not an instance of `CommandStack`.'); - } - this._modelRoot = root; - this.onModelRootChangedEmitter.fire(root); - } - handle(action: Action): void { if (SetEditModeAction.is(action)) { this.handleSetEditModeAction(action); @@ -234,14 +227,14 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra } get modelRoot(): Readonly { - if (!this._modelRoot) { + if (!this.modelChangeService.currentRoot) { throw new Error('Model root not available yet'); } - return this._modelRoot; + return this.modelChangeService.currentRoot; } get viewport(): Readonly | undefined { - return this._modelRoot ? findParentByFeature(this._modelRoot, isViewport) : undefined; + return this.modelRoot ? findParentByFeature(this.modelRoot, isViewport) : undefined; } get viewportData(): Readonly { @@ -255,7 +248,7 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra get canvasBounds(): Readonly { // default value aligned with the initialization of canvasBounds in GModelRoot - return this._modelRoot?.canvasBounds ?? Bounds.EMPTY; + return this.modelRoot?.canvasBounds ?? Bounds.EMPTY; } get selectedElements(): Readonly[] { diff --git a/packages/client/src/base/focus/focus-tracker.ts b/packages/client/src/base/focus/focus-tracker.ts index c76ba21..2832377 100644 --- a/packages/client/src/base/focus/focus-tracker.ts +++ b/packages/client/src/base/focus/focus-tracker.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Emitter, Event, IActionHandler, ICommand, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; -import { inject, injectable } from 'inversify'; +import { Action, Disposable, Emitter, Event, IActionHandler, ICommand, TYPES, ViewerOptions } from '@eclipse-glsp/sprotty'; +import { inject, injectable, preDestroy } from 'inversify'; import { FocusStateChangedAction } from './focus-state-change-action'; export interface FocusChange { @@ -29,7 +29,7 @@ export interface FocusChange { * Allows querying of the current focus state and the focused root diagram element and the currently focused element within the diagram. */ @injectable() -export class FocusTracker implements IActionHandler { +export class FocusTracker implements IActionHandler, Disposable { protected inActiveCssClass = 'inactive'; protected _hasFocus = true; protected _focusElement: HTMLOrSVGElement | null; @@ -77,4 +77,9 @@ export class FocusTracker implements IActionHandler { } this.onFocusChangedEmitter.fire({ hasFocus: this.hasFocus, focusElement: this.focusElement, diagramElement: this.diagramElement }); } + + @preDestroy() + dispose(): void { + this.onFocusChangedEmitter.dispose(); + } } diff --git a/packages/client/src/base/model/model-change-service.ts b/packages/client/src/base/model/model-change-service.ts new file mode 100644 index 0000000..1b0991c --- /dev/null +++ b/packages/client/src/base/model/model-change-service.ts @@ -0,0 +1,151 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + BoundsAwareViewportCommand, + Disposable, + DisposableCollection, + Emitter, + Event, + GModelRoot, + ICommand, + ICommandStack, + LazyInjector, + SetModelCommand, + SetViewportCommand, + TYPES, + UpdateModelCommand, + Viewport, + almostEquals, + isViewport +} from '@eclipse-glsp/sprotty'; +import { inject, postConstruct, preDestroy } from 'inversify'; + +/** + * Service that tracks changes to the model root and the viewport. + * Allows to register listeners that are notified when the model root or the viewport changes. + * The current model root can be queried at any time. + */ +export interface IModelChangeService { + /** The current model root */ + readonly currentRoot: Readonly | undefined; + /** + * Event that is fired when the model root of the diagram changes i.e. after the `CommandStack` has processed a model update. + */ + onModelRootChanged: Event>; + + /** + * Event that is fired when the viewport of the diagram changes i.e. after the `CommandStack` has processed a viewport update. + * By default, this event is only fired if the viewport was changed via a `SetViewportCommand` or `BoundsAwareViewportCommand` + */ + onViewportChanged: Event; +} + +/** + * Event data for the {@link IModelChangeService.onViewportChanged} event. + */ +export interface ViewportChange { + /** The new viewport */ + newViewport: Readonly; + /** The old viewport */ + oldViewport?: Readonly; +} + +export class ModelChangeService implements IModelChangeService, Disposable { + @inject(LazyInjector) + protected lazyInjector: LazyInjector; + protected _currentRoot?: Readonly; + protected lastViewport?: Readonly; + protected toDispose = new DisposableCollection(); + + get currentRoot(): Readonly | undefined { + return this._currentRoot; + } + + protected get commandStack(): ICommandStack { + return this.lazyInjector.get(TYPES.ICommandStack); + } + + protected onModelRootChangedEmitter = new Emitter>(); + get onModelRootChanged(): Event> { + return this.onModelRootChangedEmitter.event; + } + + protected onViewportChangedEmitter = new Emitter(); + get onViewportChanged(): Event { + return this.onViewportChangedEmitter.event; + } + + @postConstruct() + protected initialize(): void { + this.toDispose.push(this.onModelRootChangedEmitter, this.onViewportChangedEmitter); + this.commandStack.onCommandExecuted(data => this.handleCommandExecution(data.command, data.newRoot)); + } + + @preDestroy() + dispose(): void { + this.toDispose.dispose(); + } + + protected handleCommandExecution(command: ICommand, newRoot: GModelRoot): void { + if (this.isModelRootChangeCommand(command)) { + this.handleModelRootChangeCommand(command, newRoot); + } + if (this.isViewportChangeCommand(command)) { + this.handleViewportChangeCommand(command, newRoot); + } + } + + protected isModelRootChangeCommand(command: ICommand): boolean { + return command instanceof SetModelCommand || command instanceof UpdateModelCommand; + } + + protected isViewportChangeCommand(command: ICommand): boolean { + return command instanceof SetViewportCommand || command instanceof BoundsAwareViewportCommand; + } + + protected handleModelRootChangeCommand(command: ICommand, newRoot: GModelRoot): void { + this._currentRoot = newRoot; + this.lastViewport = this.toViewport(newRoot); + this.onModelRootChangedEmitter.fire(newRoot); + } + + protected handleViewportChangeCommand(command: ICommand, newRoot: GModelRoot): void { + const viewport = this.toViewport(newRoot); + if (!viewport) { + return; + } + + if (this.hasViewportChanged(viewport)) { + this.onViewportChangedEmitter.fire({ newViewport: viewport, oldViewport: this.lastViewport }); + this.lastViewport = viewport; + } + } + + protected hasViewportChanged(newViewport: Readonly): boolean { + if (!this.lastViewport) { + return true; + } + return !( + almostEquals(newViewport.zoom, this.lastViewport.zoom) && + almostEquals(newViewport.scroll.x, this.lastViewport.scroll.x) && + almostEquals(newViewport.scroll.y, this.lastViewport.scroll.y) + ); + } + + protected toViewport(root: Readonly): Readonly | undefined { + return isViewport(root) ? { scroll: root.scroll, zoom: root.zoom } : undefined; + } +} diff --git a/packages/glsp-sprotty/src/api-override.ts b/packages/glsp-sprotty/src/api-override.ts index 1916d98..19a71f4 100644 --- a/packages/glsp-sprotty/src/api-override.ts +++ b/packages/glsp-sprotty/src/api-override.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, LabeledAction, Point, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; +import { Action, Event, LabeledAction, Point, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { @@ -25,6 +25,7 @@ import { IActionHandler as SIActionHandler, IButtonHandler as SIButtonHandler, ICommandPaletteActionProvider as SICommandPaletteActionProvider, + ICommandStack as SICommandStack, IContextMenuItemProvider as SIContextMenuItemProvider, IVNodePostprocessor as SIVNodePostprocessor, KeyListener as SKeyListener, @@ -198,3 +199,57 @@ export interface IActionDispatcher extends SIActionDispatcher { } export type IActionDispatcherProvider = () => Promise; + +/** + * Data structure that is passed to the `onCommandExecuted` event. + */ +export interface CommandExecutionData { + /** The command that has been executed successfully */ + command: ICommand; + /** The new model root after the command execution */ + newRoot: GModelRoot; +} + +export interface ICommandStack extends SICommandStack { + /** + * Executes the given command on the current model and returns a + * Promise for the new result. + * + * Unless it is a special command, it is pushed to the undo stack + * such that it can be rolled back later and the redo stack is + * cleared. + */ + execute(command: ICommand): Promise; + + /** + * Executes all of the given commands. As opposed to calling + * execute() multiple times, the Viewer is only updated once after + * the last command has been executed. + */ + executeAll(commands: ICommand[]): Promise; + + /** + * Client-side undo/redo is not supported in GLSP. The server is responsible for handling undo/redo requests. + * This method is required to maintain compatibility with the sprotty API. + * Implementation should always be a no-op that returns the current model. + */ + undo(): Promise; + + /** + * Client-side undo/redo is not supported in GLSP. The server is responsible for handling undo/redo requests. + * This method is required to maintain compatibility with the sprotty API. + * Implementation should always be a no-op that returns the current model. + */ + redo(): Promise; + + /** + * Event fired after a command has been successfully executed on the stack. (i.e. after the model has been updated). + */ + onCommandExecuted: Event; +} + +/** + * As part of the event cylce, the ICommandStack should be injected + * using a provider to avoid cyclic injection dependencies. + */ +export type CommandStackProvider = () => Promise; diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 61024ce..1573660 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -44,7 +44,8 @@ export * from 'sprotty/lib/base/animations/easing'; export * from 'sprotty/lib/base/commands/command'; export * from 'sprotty/lib/base/commands/command-registration'; -export * from 'sprotty/lib/base/commands/command-stack'; +// Exclude ICommandStack and ICommandStackProvider. Exported via api-override module instead +export { CommandStack, CommandStackState } from 'sprotty/lib/base/commands/command-stack'; export * from 'sprotty/lib/base/commands/command-stack-options'; export * from 'sprotty/lib/base/features/initialize-canvas'; @@ -268,11 +269,11 @@ export { SBezierCreateHandleView as GBezierCreateHandleView, SCompartmentView as GCompartmentView, SLabelView as GLabelView, - SRoutingHandleView, JumpingPolylineEdgeView, PolylineEdgeView, PolylineEdgeViewWithGapsOnIntersections, - SGraphView + SGraphView, + SRoutingHandleView } from 'sprotty/lib/graph/views'; // ------------------ Library ------------------ diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 9923000..94cc7d6 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -61,6 +61,7 @@ export const TYPES = { IDebugManager: Symbol('IDebugManager'), Grid: Symbol('Grid'), ZoomFactors: Symbol('ZoomFactors'), + IModelChangeService: Symbol('IModelChangeService'), /** * Experimental shortcut manager. * The API is not stable yet. From 3f5fb3004fbb4ddab1933bc5fd4e5ee537f49108 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 28 Aug 2025 10:24:19 +0000 Subject: [PATCH 463/566] Adapt BringToFront to only support order-agnostic layouts (#436) Fixes https://github.com/eclipse-glsp/glsp/issues/1554 --- packages/client/src/default-modules.ts | 4 +- .../features/zorder/bring-to-front-command.ts | 46 +++++++++++ .../src/features/zorder/zorder-module.ts | 26 +++++++ packages/client/src/index.ts | 2 + packages/glsp-sprotty/src/feature-modules.ts | 4 +- packages/glsp-sprotty/src/index.ts | 3 +- packages/glsp-sprotty/src/layout-override.ts | 77 +++++++++++++++++++ packages/glsp-sprotty/src/re-exports.ts | 9 +-- 8 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/features/zorder/bring-to-front-command.ts create mode 100644 packages/client/src/features/zorder/zorder-module.ts create mode 100644 packages/glsp-sprotty/src/layout-override.ts diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 9bc7e52..2c187c0 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -27,8 +27,7 @@ import { expandModule, fadeModule, modelSourceModule, - resolveContainerConfiguration, - zorderModule + resolveContainerConfiguration } from '@eclipse-glsp/sprotty'; import { Container } from 'inversify'; import { defaultModule } from './base/default.module'; @@ -62,6 +61,7 @@ import { nodeCreationToolModule } from './features/tools/node-creation/node-crea import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; import { markerNavigatorModule, validationModule } from './features/validation/validation-modules'; import { viewportModule } from './features/viewport/viewport-modules'; +import { zorderModule } from './features/zorder/zorder-module'; export const DEFAULT_MODULES = [ defaultModule, diff --git a/packages/client/src/features/zorder/bring-to-front-command.ts b/packages/client/src/features/zorder/bring-to-front-command.ts new file mode 100644 index 0000000..b17da35 --- /dev/null +++ b/packages/client/src/features/zorder/bring-to-front-command.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + LayoutContainer, + LayoutRegistry, + SprottyBringToFrontCommand, + TYPES, + ZOrderElement, + isLayoutContainer +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; + +@injectable() +export class BringToFrontCommand extends SprottyBringToFrontCommand { + @inject(TYPES.LayoutRegistry) protected layoutRegistry: LayoutRegistry; + + protected getLayoutContainer(zorder: ZOrderElement): LayoutContainer | undefined { + return isLayoutContainer(zorder.element.parent) ? zorder.element.parent : undefined; + } + + protected shouldBringToFront(zorder: ZOrderElement): boolean { + // only re-order children if the layout supports it + const layoutContainer = this.getLayoutContainer(zorder); + return !layoutContainer || (this.layoutRegistry.get(layoutContainer.layout)?.orderAgnostic ?? true); + } + + protected override bringToFront(zorder: ZOrderElement): void { + if (this.shouldBringToFront(zorder)) { + super.bringToFront(zorder); + } + } +} diff --git a/packages/client/src/features/zorder/zorder-module.ts b/packages/client/src/features/zorder/zorder-module.ts new file mode 100644 index 0000000..f2d1b50 --- /dev/null +++ b/packages/client/src/features/zorder/zorder-module.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { FeatureModule, configureCommand } from '@eclipse-glsp/sprotty'; +import { boundsModule } from '../bounds/bounds-module'; +import { BringToFrontCommand } from './bring-to-front-command'; + +export const zorderModule = new FeatureModule( + (bind, _unbind, isBound) => { + const context = { bind, isBound }; + configureCommand(context, BringToFrontCommand); + }, + { featureId: Symbol('zorder'), requires: [boundsModule] } +); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index cb1625a..3c22ff3 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -226,6 +226,8 @@ export * from './features/viewport/viewport-key-listener'; export * from './features/viewport/viewport-modules'; export * from './features/viewport/viewport-tool'; export * from './features/viewport/zoom-viewport-action'; +export * from './features/zorder/bring-to-front-command'; +export * from './features/zorder/zorder-module'; export * from './model'; export * from './re-exports'; export * from './standalone-modules'; diff --git a/packages/glsp-sprotty/src/feature-modules.ts b/packages/glsp-sprotty/src/feature-modules.ts index 5b8631e..3dff6bf 100644 --- a/packages/glsp-sprotty/src/feature-modules.ts +++ b/packages/glsp-sprotty/src/feature-modules.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 EclipseSource and others. + * Copyright (c) 2023-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -26,7 +26,6 @@ import sprottyFadeModule from 'sprotty/lib/features/fade/di.config'; import sprottyMoveModule from 'sprotty/lib/features/move/di.config'; import sprottyOpenModule from 'sprotty/lib/features/open/di.config'; import sprottyUpdateModule from 'sprotty/lib/features/update/di.config'; -import sprottyZorderModule from 'sprotty/lib/features/zorder/di.config'; import sprottyModelSourceModule from 'sprotty/lib/model-source/di.config'; export const buttonModule = new FeatureModule(sprottyButtonModule.registry, { featureId: Symbol('button') }); @@ -39,7 +38,6 @@ export const modelSourceModule = new FeatureModule(sprottyModelSourceModule.regi export const moveModule = new FeatureModule(sprottyMoveModule.registry, { featureId: Symbol('move') }); export const openModule = new FeatureModule(sprottyOpenModule.registry, { featureId: Symbol('open') }); export const updateModule = new FeatureModule(sprottyUpdateModule.registry, { featureId: Symbol('update') }); -export const zorderModule = new FeatureModule(sprottyZorderModule.registry, { featureId: Symbol('zorder') }); export const edgeJunctionModule = new FeatureModule(sprottyEdgeJunctionModule.registry, { featureId: Symbol('edgeJunction') }); export { sprottyDefaultModule }; diff --git a/packages/glsp-sprotty/src/index.ts b/packages/glsp-sprotty/src/index.ts index 95916b0..0fbe2ec 100644 --- a/packages/glsp-sprotty/src/index.ts +++ b/packages/glsp-sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 EclipseSource and others. + * Copyright (c) 2024-2025 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,6 +15,7 @@ ********************************************************************************/ export * from './api-override'; export * from './feature-modules'; +export * from './layout-override'; export * from './re-exports'; export * from './svg-views-override'; export * from './types'; diff --git a/packages/glsp-sprotty/src/layout-override.ts b/packages/glsp-sprotty/src/layout-override.ts new file mode 100644 index 0000000..f128f88 --- /dev/null +++ b/packages/glsp-sprotty/src/layout-override.ts @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2025 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable, interfaces, multiInject, optional } from 'inversify'; +import { + ILogger, + InstanceRegistry, + HBoxLayouter as SHBoxLayouter, + ILayout as SLayout, + LayoutRegistration as SLayoutRegistration, + VBoxLayouter as SVBoxLayouter, + TYPES, + configureLayout as sconfigureLayout +} from 'sprotty'; +import { AbstractLayout as SAbstractLayout } from 'sprotty/lib/features/bounds/abstract-layout'; +import { AbstractLayoutOptions } from './re-exports'; + +export interface ILayout extends SLayout { + /** Flag to indicate whether the layouting of the children is independant from their order. */ + orderAgnostic?: boolean; +} + +export interface LayoutRegistration extends SLayoutRegistration { + factory: () => ILayout; +} + +@injectable() +export class LayoutRegistry extends InstanceRegistry { + @inject(TYPES.ILogger) logger: ILogger; + + constructor(@multiInject(TYPES.LayoutRegistration) @optional() layouts: LayoutRegistration[] = []) { + super(); + layouts.forEach(layout => { + if (this.hasKey(layout.layoutKind)) { + this.logger.warn('Layout kind is already defined: ', layout.layoutKind); + } else { + this.register(layout.layoutKind, layout.factory()); + } + }); + } +} + +export function configureLayout( + context: { bind: interfaces.Bind; isBound: interfaces.IsBound }, + kind: string, + constr: interfaces.ServiceIdentifier +): void { + return sconfigureLayout(context, kind, constr); +} + +@injectable() +export abstract class AbstractLayout extends SAbstractLayout implements ILayout { + orderAgnostic?: boolean = true; +} + +@injectable() +export class VBoxLayouter extends SVBoxLayouter implements ILayout { + orderAgnostic?: boolean = false; +} + +@injectable() +export class HBoxLayouter extends SHBoxLayouter implements ILayout { + orderAgnostic?: boolean = false; +} diff --git a/packages/glsp-sprotty/src/re-exports.ts b/packages/glsp-sprotty/src/re-exports.ts index 1573660..b26ec76 100644 --- a/packages/glsp-sprotty/src/re-exports.ts +++ b/packages/glsp-sprotty/src/re-exports.ts @@ -95,11 +95,10 @@ export * from 'sprotty/lib/base/views/vnode-utils'; // export * from 'sprotty/lib/base/types'; // ------------------ Features ------------------ -export * from 'sprotty/lib/features/bounds/abstract-layout'; export * from 'sprotty/lib/features/bounds/bounds-manipulation'; -export * from 'sprotty/lib/features/bounds/hbox-layout'; +export { HBoxLayoutOptions } from 'sprotty/lib/features/bounds/hbox-layout'; export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; -export * from 'sprotty/lib/features/bounds/layout'; +export { Layouter, StatefulLayouter } from 'sprotty/lib/features/bounds/layout'; export { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; export { InternalBoundsAware as BoundsAware, @@ -122,7 +121,7 @@ export { } from 'sprotty/lib/features/bounds/model'; // exclude stack layout as its not supported in GLSP // export * from 'sprotty/lib/features/bounds/stack-layout'; -export * from 'sprotty/lib/features/bounds/vbox-layout'; +export { VBoxLayoutOptions } from 'sprotty/lib/features/bounds/vbox-layout'; export * from 'sprotty/lib/features/bounds/views'; export { ButtonHandlerRegistry, IButtonHandlerRegistration, configureButtonHandler } from 'sprotty/lib/features/button/button-handler'; @@ -247,7 +246,7 @@ export * from 'sprotty/lib/features/viewport/viewport'; export { ViewportRootElementImpl as GViewportRootElement } from 'sprotty/lib/features/viewport/viewport-root'; export * from 'sprotty/lib/features/viewport/zoom'; -export * from 'sprotty/lib/features/zorder/zorder'; +export { BringToFrontCommand as SprottyBringToFrontCommand, ZOrderElement } from 'sprotty/lib/features/zorder/zorder'; // ------------------ Graph ------------------ // Alias SModel types From 2833eb1b05e4e082318708cf6234a064c90f2cc2 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 1 Sep 2025 09:55:39 +0200 Subject: [PATCH 464/566] GLSP-1563: Update to node 20 (#1564) * GLSP-1563: Update to node 20 Update minimum node version requirement to node 20 Update ci docker images Update Jenkinsfile Also clean up scripts in root package.json (Remove no longer needed publish scripts and all script) Part of: https://github.com/eclipse-glsp/glsp/issues/1563 * Update docker/ci/README.md Co-authored-by: Haydar Metin --------- Co-authored-by: Haydar Metin --- dev-packages/ts-config/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index a651d06..72c2810 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -20,7 +20,7 @@ "downlevelIteration": true, "resolveJsonModule": true, "module": "CommonJS", - "moduleResolution": "Node", + "moduleResolution": "node16", "target": "ES2019", "jsx": "react", "lib": ["ES2019", "dom"], From 453825f177ca17c7339cbc477e0ed86df649c667 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 1 Sep 2025 10:40:39 +0200 Subject: [PATCH 465/566] Update changelog (#1565) Update changelog Small follow-up after node20 update: Revert changes to module resolution Update minimum required ts version accordingly --- dev-packages/ts-config/package.json | 2 +- dev-packages/ts-config/tsconfig.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 3365942..b6d0bf1 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -30,7 +30,7 @@ "mocha.json" ], "peerDependencies": { - "typescript": ">=4.9.x" + "typescript": ">=5.x" }, "publishConfig": { "access": "public" diff --git a/dev-packages/ts-config/tsconfig.json b/dev-packages/ts-config/tsconfig.json index 72c2810..eb1589c 100644 --- a/dev-packages/ts-config/tsconfig.json +++ b/dev-packages/ts-config/tsconfig.json @@ -19,8 +19,8 @@ "emitDecoratorMetadata": true, "downlevelIteration": true, "resolveJsonModule": true, - "module": "CommonJS", - "moduleResolution": "node16", + "module": "commonjs", + "moduleResolution": "node", "target": "ES2019", "jsx": "react", "lib": ["ES2019", "dom"], From 9f5d69c60c6f37de10d961528ff6a7c71292cd8a Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 1 Sep 2025 11:28:21 +0200 Subject: [PATCH 466/566] GLSP-1563: Update to node 20 (#439) Update minimum node version requirement to node 20 Update to next glsp-dev packages Update changelog Part of: eclipse-glsp/glsp/issues/1563 --- packages/client/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3c22ff3..ed0cbc2 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -36,6 +36,7 @@ export * from './base/focus/focus-tracker'; export * from './base/messages'; export * from './base/model/diagram-loader'; export * from './base/model/glsp-model-source'; +export * from './base/model/model-change-service'; export * from './base/model/model-initialization-constraint'; export * from './base/model/model-registry'; export * from './base/mouse-position-tracker'; From 8908b73c643f46a1822d84498592d888d4a7a9ad Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 7 Sep 2025 18:15:53 +0200 Subject: [PATCH 467/566] Update download script (#442) Update the download script for the example server. - Remove static config via config file (removes overhead of updating the file for releases) - Introduce optional version parameter, if not specificed the version wil be derived from the package.json - Remove dependency to shell.js and use node builtins instead --- examples/workflow-standalone/package.json | 1 - .../workflow-standalone/scripts/config.json | 4 - .../scripts/start-example-server.ts | 75 +++++++++++-------- 3 files changed, 43 insertions(+), 37 deletions(-) delete mode 100644 examples/workflow-standalone/scripts/config.json diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 374ac2c..f8020cd 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -37,7 +37,6 @@ "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { - "@types/shelljs": "0.8.12", "@types/tar": "6.1.5", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.7.1", diff --git a/examples/workflow-standalone/scripts/config.json b/examples/workflow-standalone/scripts/config.json deleted file mode 100644 index eae82b0..0000000 --- a/examples/workflow-standalone/scripts/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "fileName": "workflow-server", - "version": "next" -} diff --git a/examples/workflow-standalone/scripts/start-example-server.ts b/examples/workflow-standalone/scripts/start-example-server.ts index 9242488..47957ea 100644 --- a/examples/workflow-standalone/scripts/start-example-server.ts +++ b/examples/workflow-standalone/scripts/start-example-server.ts @@ -13,33 +13,36 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import * as sh from 'shelljs'; import { extract } from 'tar'; -import * as config from './config.json'; -const serverDirPath = path.resolve(__dirname, '..', 'server'); +import * as packageJson from '../../../package.json'; -async function run() { - const serverFile = await downloadIfNecessary(); - console.log(); +const SERVER_DIR_PATH = path.resolve(__dirname, '..', 'server'); +const FILE_NAME = 'wf-glsp-server-node'; +async function run() { const port = parsePortArg(); const host = parseHostArg(); + const version = parseVersion(); + process.chdir(SERVER_DIR_PATH); + + const serverFile = await downloadIfNecessary(version); + console.log(); - sh.cd(serverDirPath); - sh.exec(`node ${serverFile} -w --port ${port} --host ${host}`); + execSync(`node ${serverFile} -w --port ${port} --host ${host}`, { stdio: 'inherit' }); } -async function downloadIfNecessary(): Promise { - console.log(`Check if server executable with version ${config.version} is present.`); +async function downloadIfNecessary(version: string): Promise { + console.log(`Check if server executable with version ${version} is present.`); - const existingServer = fs.readdirSync(serverDirPath).find(file => file.startsWith(config.fileName)); + const existingServer = fs.readdirSync(SERVER_DIR_PATH).find(file => file.startsWith(FILE_NAME)); if (existingServer) { - const existingVersion = existingServer.replace(config.fileName + '-', '').replace('.js', ''); - const latestVersion = sh - .exec(`npm show @eclipse-glsp-examples/workflow-server-bundled@${config.version} version`, { silent: true }) - .stdout.trim(); + const existingVersion = existingServer.replace(FILE_NAME + '-', '').replace('.js', ''); + const latestVersion = execSync(`npm show @eclipse-glsp-examples/workflow-server-bundled@${version} version`, { + encoding: 'utf-8' + }).trim(); if (existingVersion === latestVersion) { console.log('Server executable already present. Skip download"'); return existingServer; @@ -48,32 +51,31 @@ async function downloadIfNecessary(): Promise { console.log('Server executable with correct version not found. Download from npm.'); if (existingServer) { - fs.rmSync(path.resolve(serverDirPath, existingServer)); - fs.rmSync(path.resolve(serverDirPath, existingServer.replace('.js', '.js.map'))); + fs.rmSync(path.resolve(SERVER_DIR_PATH, existingServer)); + fs.rmSync(path.resolve(SERVER_DIR_PATH, existingServer.replace('.js', '.js.map'))); } - sh.cd(serverDirPath); - const packResultJson = sh - .exec(`npm pack @eclipse-glsp-examples/workflow-server-bundled@${config.version} --json`, { silent: true }) - .stdout.trim(); - const version = JSON.parse(packResultJson)[0].version; - const tarBall = fs.readdirSync(serverDirPath).find(file => file.endsWith('.tar.gz') || file.endsWith('.tgz'))!; + const packResultJson = execSync(`npm pack @eclipse-glsp-examples/workflow-server-bundled@${version} --json`, { + encoding: 'utf-8' + }).trim(); + const newVersion = JSON.parse(packResultJson)[0].version; + const tarBall = fs.readdirSync(SERVER_DIR_PATH).find(file => file.endsWith('.tar.gz') || file.endsWith('.tgz'))!; console.log('Extract downloaded server tarball'); await extract({ file: tarBall, - cwd: serverDirPath + cwd: SERVER_DIR_PATH }); - const tempDir = path.resolve(serverDirPath, 'package'); - fs.copyFileSync(path.resolve(tempDir, 'wf-glsp-server-node.js'), path.resolve(serverDirPath, `${config.fileName}-${version}.js`)); + const tempDir = path.resolve(SERVER_DIR_PATH, 'package'); + fs.copyFileSync(path.resolve(tempDir, 'wf-glsp-server-node.js'), path.resolve(SERVER_DIR_PATH, `${FILE_NAME}-${newVersion}.js`)); fs.copyFileSync( path.resolve(tempDir, 'wf-glsp-server-node.js.map'), - path.resolve(serverDirPath, `${config.fileName}-${version}.js.map`) + path.resolve(SERVER_DIR_PATH, `${FILE_NAME}-${newVersion}.js.map`) ); console.log('Remove temporary files'); fs.rmSync(tempDir, { force: true, recursive: true }); - fs.rmSync(path.resolve(serverDirPath, tarBall), { force: true }); - return `${config.fileName}-${version}.js`; + fs.rmSync(path.resolve(SERVER_DIR_PATH, tarBall), { force: true }); + return `${FILE_NAME}-${newVersion}.js`; } function parsePortArg(): number { @@ -82,7 +84,7 @@ function parsePortArg(): number { if (portIndex >= 0) { port = parseInt(process.argv[portIndex + 1]); } - if(isNaN(port)) { + if (isNaN(port)) { console.error('Invalid port number'); process.exit(1); } @@ -92,14 +94,23 @@ function parsePortArg(): number { function parseHostArg(): string { let host = 'localhost'; const hostIndex = process.argv.indexOf('--host'); - if(hostIndex >= 0) { + if (hostIndex >= 0) { host = process.argv[hostIndex + 1]; } - if(typeof host !== 'string') { + if (typeof host !== 'string') { console.error('Invalid host'); process.exit(1); } return host; } +function parseVersion(): string { + const versionIndex = process.argv.indexOf('--version'); + if (versionIndex >= 0) { + return process.argv[versionIndex + 1]; + } + // If no version is specified, use the version of the current package + return packageJson.version.endsWith('-next') ? 'next' : packageJson.version; +} + run(); From f22a10e171dd5c3b280656eedca29872e1ac1224 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 7 Sep 2025 22:22:47 +0200 Subject: [PATCH 468/566] v2.5.0 --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 64f52f5..33a9a0d 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.5.0-next", + "version": "2.5.0", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.5.0-next", + "@eclipse-glsp/config": "2.5.0", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index b20c000..d87537a 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.5.0-next", - "@eclipse-glsp/nyc-config": "2.5.0-next", + "@eclipse-glsp/mocha-config": "2.5.0", + "@eclipse-glsp/nyc-config": "2.5.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 82a6a8d..82ae869 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.5.0-next", - "@eclipse-glsp/prettier-config": "2.5.0-next", - "@eclipse-glsp/ts-config": "2.5.0-next", + "@eclipse-glsp/eslint-config": "2.5.0", + "@eclipse-glsp/prettier-config": "2.5.0", + "@eclipse-glsp/ts-config": "2.5.0", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index d42acb4..12de9e4 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.5.0-next", + "version": "2.5.0", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.5.0-next", - "@eclipse-glsp/config": "2.5.0-next", - "@eclipse-glsp/config-test": "2.5.0-next" + "@eclipse-glsp/cli": "2.5.0", + "@eclipse-glsp/config": "2.5.0", + "@eclipse-glsp/config-test": "2.5.0" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index d9ee8cf..718304c 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 2c0e106..8e5039e 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index 3023f75..ff47aca 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index 0bc63c5..e954641 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index b6d0bf1..9e8fd3b 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From d65f8e1cae02090dbef7152e5e139e834404a151 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 7 Sep 2025 22:55:25 +0200 Subject: [PATCH 469/566] v2.5.0 --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 322ce18..ab97cde 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.5.0-next", + "version": "2.5.0", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.5.0-next", + "@eclipse-glsp/client": "2.5.0", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index f8020cd..97b8c30 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.5.0-next", + "version": "2.5.0", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.5.0-next", - "@eclipse-glsp/client": "2.5.0-next", + "@eclipse-glsp-examples/workflow-glsp": "2.5.0", + "@eclipse-glsp/client": "2.5.0", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/packages/client/package.json b/packages/client/package.json index ff1699a..7bf320b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.5.0-next", + "version": "2.5.0", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.5.0-next", + "@eclipse-glsp/sprotty": "2.5.0", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index ec288de..00235d0 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.5.0-next", + "@eclipse-glsp/protocol": "2.5.0", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 06c84bd..c958320 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.5.0-next", + "version": "2.5.0", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From 88eb9689d06d3bdb1b89209f2dec00ed9bd1a199 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Sun, 7 Sep 2025 23:04:05 +0200 Subject: [PATCH 470/566] v2.5.0 --- examples/workflow-server-bundled/package.json | 2 +- examples/workflow-server/package.json | 6 +++--- packages/graph/package.json | 4 ++-- packages/layout-elk/package.json | 4 ++-- packages/server/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/workflow-server-bundled/package.json b/examples/workflow-server-bundled/package.json index 439d71d..2cb03e8 100644 --- a/examples/workflow-server-bundled/package.json +++ b/examples/workflow-server-bundled/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server-bundled", - "version": "2.5.0-next", + "version": "2.5.0", "description": "GLSP node server for the workflow example (bundled)", "keywords": [ "eclipse", diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 788ad17..e442f0a 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-server", - "version": "2.5.0-next", + "version": "2.5.0", "description": "GLSP node server for the workflow example", "keywords": [ "eclipse", @@ -56,8 +56,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp/layout-elk": "2.5.0-next", - "@eclipse-glsp/server": "2.5.0-next", + "@eclipse-glsp/layout-elk": "2.5.0", + "@eclipse-glsp/server": "2.5.0", "inversify": "^6.1.3" }, "devDependencies": { diff --git a/packages/graph/package.json b/packages/graph/package.json index 0df33c0..b06a878 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/graph", - "version": "2.5.0-next", + "version": "2.5.0", "description": "The typescript implementation of the GLSP graphical model (GModel)", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "next" + "@eclipse-glsp/protocol": "2.5.0" }, "devDependencies": { "@types/uuid": "8.3.1" diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index f55da83..a23cb86 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/layout-elk", - "version": "2.5.0-next", + "version": "2.5.0", "description": "Integration of ELK graph layout algorithms in GLSP Node Server", "keywords": [ "eclipse", @@ -49,7 +49,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/server": "2.5.0-next", + "@eclipse-glsp/server": "2.5.0", "elkjs": "^0.10.1" }, "peerDependencies": { diff --git a/packages/server/package.json b/packages/server/package.json index 301ce48..eb14734 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/server", - "version": "2.5.0-next", + "version": "2.5.0", "description": "A js server framework for Eclipse GLSP", "keywords": [ "eclipse", @@ -58,8 +58,8 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/graph": "2.5.0-next", - "@eclipse-glsp/protocol": "next", + "@eclipse-glsp/graph": "2.5.0", + "@eclipse-glsp/protocol": "2.5.0", "@types/uuid": "8.3.1", "commander": "^8.3.0", "fast-json-patch": "^3.1.0", From b7cb7b30ce817e76811daa7cf672161bdb62fabe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:18:16 +0200 Subject: [PATCH 471/566] Switch to nightly 2.6.0-next versions (#1574) * Fix changelog * Switch to nightly 2.6.0-next versions --------- Co-authored-by: Tobias Ortmayr Co-authored-by: eclipse-glsp-bot <57175764+eclipse-glsp-bot@users.noreply.github.com> --- dev-packages/cli/package.json | 4 ++-- dev-packages/config-test/package.json | 6 +++--- dev-packages/config/package.json | 8 ++++---- dev-packages/dev/package.json | 8 ++++---- dev-packages/eslint-config/package.json | 2 +- dev-packages/mocha-config/package.json | 2 +- dev-packages/nyc-config/package.json | 2 +- dev-packages/prettier-config/package.json | 2 +- dev-packages/ts-config/package.json | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 33a9a0d..891b9f3 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/cli", - "version": "2.5.0", + "version": "2.6.0-next", "description": "CLI Tooling & scripts for GLSP components", "keywords": [ "eclipse", @@ -50,7 +50,7 @@ "shelljs": "^0.8.5" }, "devDependencies": { - "@eclipse-glsp/config": "2.5.0", + "@eclipse-glsp/config": "2.6.0-next", "@types/glob": "^8.1.0", "@types/node-fetch": "^2.6.6", "@types/readline-sync": "^1.4.5", diff --git a/dev-packages/config-test/package.json b/dev-packages/config-test/package.json index d87537a..465d2c3 100644 --- a/dev-packages/config-test/package.json +++ b/dev-packages/config-test/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config-test", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Meta package that provides Mocha and nyc configurations for GLSP projects", "keywords": [ "eclipse", @@ -25,8 +25,8 @@ } ], "dependencies": { - "@eclipse-glsp/mocha-config": "2.5.0", - "@eclipse-glsp/nyc-config": "2.5.0", + "@eclipse-glsp/mocha-config": "2.6.0-next", + "@eclipse-glsp/nyc-config": "2.6.0-next", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/chai": "^4.3.7", "@types/mocha": "^10.0.2", diff --git a/dev-packages/config/package.json b/dev-packages/config/package.json index 82ae869..a29d972 100644 --- a/dev-packages/config/package.json +++ b/dev-packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Meta package that provides Typescript, eslint and prettier configurations and common dev dependencies for GLSP projects", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/eslint-config": "2.5.0", - "@eclipse-glsp/prettier-config": "2.5.0", - "@eclipse-glsp/ts-config": "2.5.0", + "@eclipse-glsp/eslint-config": "2.6.0-next", + "@eclipse-glsp/prettier-config": "2.6.0-next", + "@eclipse-glsp/ts-config": "2.6.0-next", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", diff --git a/dev-packages/dev/package.json b/dev-packages/dev/package.json index 12de9e4..ef39c74 100644 --- a/dev-packages/dev/package.json +++ b/dev-packages/dev/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/dev", - "version": "2.5.0", + "version": "2.6.0-next", "description": "All-in-one meta package that provides the GLSP development and test configuration packages, as well as the GLSP CLI package", "keywords": [ "eclipse", @@ -24,9 +24,9 @@ } ], "dependencies": { - "@eclipse-glsp/cli": "2.5.0", - "@eclipse-glsp/config": "2.5.0", - "@eclipse-glsp/config-test": "2.5.0" + "@eclipse-glsp/cli": "2.6.0-next", + "@eclipse-glsp/config": "2.6.0-next", + "@eclipse-glsp/config-test": "2.6.0-next" }, "publishConfig": { "access": "public" diff --git a/dev-packages/eslint-config/package.json b/dev-packages/eslint-config/package.json index 718304c..8999ef9 100644 --- a/dev-packages/eslint-config/package.json +++ b/dev-packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/eslint-config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Shared ESLint configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/mocha-config/package.json b/dev-packages/mocha-config/package.json index 8e5039e..da0f1da 100644 --- a/dev-packages/mocha-config/package.json +++ b/dev-packages/mocha-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/mocha-config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Shared Mocha test configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/nyc-config/package.json b/dev-packages/nyc-config/package.json index ff47aca..80bc0d3 100644 --- a/dev-packages/nyc-config/package.json +++ b/dev-packages/nyc-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/nyc-config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Shared nyc configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/prettier-config/package.json b/dev-packages/prettier-config/package.json index e954641..9f4f7b8 100644 --- a/dev-packages/prettier-config/package.json +++ b/dev-packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/prettier-config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Shared Prettier configuration for GLSP projects", "keywords": [ "eclipse", diff --git a/dev-packages/ts-config/package.json b/dev-packages/ts-config/package.json index 9e8fd3b..83a347f 100644 --- a/dev-packages/ts-config/package.json +++ b/dev-packages/ts-config/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/ts-config", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Shared Typescript configuration for GLSP projects", "keywords": [ "eclipse", From f79062d2dd0d8fcc2cba0ac09683059025e6f47b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:16:53 +0200 Subject: [PATCH 472/566] Switch to nightly 2.6.0-next versions (#444) --- examples/workflow-glsp/package.json | 4 ++-- examples/workflow-standalone/package.json | 6 +++--- packages/client/package.json | 4 ++-- packages/glsp-sprotty/package.json | 4 ++-- packages/protocol/package.json | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index ab97cde..0f8240a 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp-examples/workflow-glsp", - "version": "2.5.0", + "version": "2.6.0-next", "description": "GLSP diagrams for the Workflow DSL", "keywords": [ "glsp", @@ -40,7 +40,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/client": "2.5.0", + "@eclipse-glsp/client": "2.6.0-next", "balloon-css": "^0.5.0" }, "devDependencies": { diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index 97b8c30..8847289 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -1,6 +1,6 @@ { "name": "workflow-standalone", - "version": "2.5.0", + "version": "2.6.0-next", "private": true, "description": "Standalone browser-app for the Workflow example", "homepage": "https://www.eclipse.org/glsp/", @@ -32,8 +32,8 @@ "watch:bundle": "webpack -w" }, "dependencies": { - "@eclipse-glsp-examples/workflow-glsp": "2.5.0", - "@eclipse-glsp/client": "2.5.0", + "@eclipse-glsp-examples/workflow-glsp": "2.6.0-next", + "@eclipse-glsp/client": "2.6.0-next", "inversify-logger-middleware": "^3.1.0" }, "devDependencies": { diff --git a/packages/client/package.json b/packages/client/package.json index 7bf320b..e021c9e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/client", - "version": "2.5.0", + "version": "2.6.0-next", "description": "A sprotty-based client for GLSP", "keywords": [ "eclipse", @@ -46,7 +46,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/sprotty": "2.5.0", + "@eclipse-glsp/sprotty": "2.6.0-next", "autocompleter": "^9.1.2", "file-saver": "^2.0.5", "lodash": "4.17.21", diff --git a/packages/glsp-sprotty/package.json b/packages/glsp-sprotty/package.json index 00235d0..497757c 100644 --- a/packages/glsp-sprotty/package.json +++ b/packages/glsp-sprotty/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/sprotty", - "version": "2.5.0", + "version": "2.6.0-next", "description": "Augmented reexport of the sprotty API for GLSP", "homepage": "https://www.eclipse.org/glsp/", "bugs": "https://github.com/eclipse-glsp/glsp/issues", @@ -33,7 +33,7 @@ "watch": "tsc -w" }, "dependencies": { - "@eclipse-glsp/protocol": "2.5.0", + "@eclipse-glsp/protocol": "2.6.0-next", "autocompleter": "^9.1.0", "snabbdom": "~3.5.1", "sprotty": "1.4.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index c958320..6237f45 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-glsp/protocol", - "version": "2.5.0", + "version": "2.6.0-next", "description": "The protocol definition for client-server communication in GLSP", "keywords": [ "eclipse", From c4daa1aa146256452f5f46f9cdeae685d506e748 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 16 Sep 2025 15:32:20 +0200 Subject: [PATCH 473/566] GLSP-1363 Refactor CLI application (#1571) * GLSP-1363 Refactor CLI application Remove dependency to shelljs for CLI application, refactor release/releng commands and introduce bundling with esbuild ## Remove dependency to shelljs Shelljs has some issues in its core architecture which prevent it from proper bundling. We only use a very small subset of shelljs features. Therefore I opted for removing the dependency alltogether and introduce replacement functions in our utility library that directly use node builtins under the hood ## Refactor release/releng commands - Remove the existing release commands and introduce a new set of releng commands that can also be used in CI and enable (partly) automated releases - These commands have already been used for the 2.5.0 release (i.e. have been tested and work) - Introduce helpers for working with (reading, updating etc.) workspace package jsons ## Bundle application with es build - Introduce bundling with es build for the CLI package - Package is now dependency free and ships the CLI app in one bundle (+ source maps) - Update package scripts accordingly - Introduce proper watch mode for CLI package - Introduce vs code lauch config for debugin the CLI application. When starting the config the desired arguments/commands can be passed via input box ## Add workflows - Add dispatch workflow to trigger a new release preparation PR for a repository Fixes eclipse-glsp/glsp/issues/1363 * Update prepare-release.yml * Fix typos Fix typos in command description Ensure that non-glsp workspace dependencies also get properly updated when using the version command --- dev-packages/README.md | 1 + dev-packages/cli/README.md | 181 ++++++++--- dev-packages/cli/esbuild.js | 89 +++++ dev-packages/cli/package.json | 45 ++- dev-packages/cli/src/{app.ts => cli.ts} | 18 +- dev-packages/cli/src/commands/check-header.ts | 50 +-- .../cli/src/commands/coverage-report.ts | 74 ++--- .../cli/src/commands/generate-index.ts | 16 +- .../cli/src/commands/release/common.ts | 306 ------------------ .../src/commands/release/release-client.ts | 55 ---- .../release/release-eclipse-integration.ts | 113 ------- .../commands/release/release-java-server.ts | 73 ----- .../commands/release/release-server-node.ts | 61 ---- .../release/release-theia-integration.ts | 65 ---- .../release/release-vscode-integration.ts | 67 ---- .../cli/src/commands/release/release.ts | 215 ------------ .../cli/src/commands/releng/common.ts | 258 +++++++++++++++ .../cli/src/commands/releng/prepare.ts | 247 ++++++++++++++ .../cli/src/commands/releng/publish.ts | 125 +++++++ .../cli/src/commands/releng/releng.ts | 27 ++ .../cli/src/commands/releng/version.ts | 300 +++++++++++++++++ dev-packages/cli/src/commands/update-next.ts | 104 +++--- dev-packages/cli/src/util/command-util.ts | 54 ---- dev-packages/cli/src/util/file-util.ts | 227 +++++++++++++ dev-packages/cli/src/util/git-util.ts | 98 +++--- dev-packages/cli/src/util/index.ts | 22 ++ dev-packages/cli/src/util/package-util.ts | 154 +++++++++ dev-packages/cli/src/util/process-util.ts | 219 +++++++++++++ dev-packages/cli/src/util/validation-util.ts | 22 +- dev-packages/cli/tsconfig.json | 2 +- .../eslint-config/configs/errors.eslintrc.js | 4 +- 31 files changed, 2007 insertions(+), 1285 deletions(-) create mode 100644 dev-packages/cli/esbuild.js rename dev-packages/cli/src/{app.ts => cli.ts} (78%) delete mode 100644 dev-packages/cli/src/commands/release/common.ts delete mode 100644 dev-packages/cli/src/commands/release/release-client.ts delete mode 100644 dev-packages/cli/src/commands/release/release-eclipse-integration.ts delete mode 100644 dev-packages/cli/src/commands/release/release-java-server.ts delete mode 100644 dev-packages/cli/src/commands/release/release-server-node.ts delete mode 100644 dev-packages/cli/src/commands/release/release-theia-integration.ts delete mode 100644 dev-packages/cli/src/commands/release/release-vscode-integration.ts delete mode 100644 dev-packages/cli/src/commands/release/release.ts create mode 100644 dev-packages/cli/src/commands/releng/common.ts create mode 100644 dev-packages/cli/src/commands/releng/prepare.ts create mode 100644 dev-packages/cli/src/commands/releng/publish.ts create mode 100644 dev-packages/cli/src/commands/releng/releng.ts create mode 100644 dev-packages/cli/src/commands/releng/version.ts delete mode 100644 dev-packages/cli/src/util/command-util.ts create mode 100644 dev-packages/cli/src/util/file-util.ts create mode 100644 dev-packages/cli/src/util/index.ts create mode 100644 dev-packages/cli/src/util/package-util.ts create mode 100644 dev-packages/cli/src/util/process-util.ts diff --git a/dev-packages/README.md b/dev-packages/README.md index e331619..fed630d 100644 --- a/dev-packages/README.md +++ b/dev-packages/README.md @@ -1,4 +1,5 @@ # Eclipse GLSP - Dev Packages [![build-status](https://img.shields.io/jenkins/build?jobUrl=https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp/job/master)](https://ci.eclipse.org/glsp/job/eclipse-glsp/job/glsp-client/job/master) + Common shared development packages for Eclipse GLSP components that are implemented with Typescript. ## Components diff --git a/dev-packages/cli/README.md b/dev-packages/cli/README.md index 6ca2665..0bb99e6 100644 --- a/dev-packages/cli/README.md +++ b/dev-packages/cli/README.md @@ -11,6 +11,24 @@ Install `@eclipse-glsp/cli` as a dev dependency in your application. yarn add @eclipse-glsp/cli --dev ``` +## Usage + +```console +Usage: glsp [options] [command] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + coverageReport [options] Generate a test coverage report for a glsp component + checkHeaders [options] Validates the copyright year range (end year) of license header files + updateNext|u [options] [rootDir] Updates all `next` dependencies in GLSP project to the latest version + generateIndex [options] Generate index files in a given source directory. + releng Commands for GLSP release engineering (Linux only, intended for CI/Maintainer use). + help [command] display help for command +``` + ## checkHeaders The `checkHeaders` command can be used to validate the copyright year (range) of license headers. @@ -23,19 +41,21 @@ $ glsp checkHeaders -h Usage: glsp checkHeaders [options] -Validates the copyright year range of license header files +Validates the copyright year range (end year) of license header files Arguments: rootDir The starting directory for the check Options: - -t, --type The scope of the check. In addition to a full recursive check, is also possible to only consider pending changes or the last commit (choices: "full", "changes", "lastCommit", default: - "full") + -t, --type The scope of the check. In addition to a full recursive check, is also possible to only + consider pending changes or the last commit (choices: "full", "changes", "lastCommit", + default: "full") -f, --fileExtensions File extensions that should be checked (default: ["ts","tsx"]) - -e, --exclude File patterns that should be excluded from the check. New exclude patterns are added to the default patterns (default: [**/@(node_modules|lib|dist|bundle)/**]) - --no-exclude-defaults Disables the default excludes patterns. Only explicitly passed exclude patterns (-e, --exclude) are considered + -e, --exclude File patterns that should be excluded from the check. New exclude patterns are added to + the default patterns (default: [**/@(node_modules|lib|dist|bundle)/**]) + --no-exclude-defaults Disables the default excludes patterns. Only explicitly passed exclude patterns (-e, + --exclude) are considered -j, --json Also persist validation results as json file (default: false) - -s, --severity The severity of validation results that should be printed. (choices: "error", "warn", "ok", default: "error" (only)) -a, --autoFix Auto apply & commit fixes without prompting the user (default: false) -h, --help display help for command ``` @@ -52,37 +72,12 @@ Usage: glsp coverageReport [options] Generate a test coverage report for a glsp component Options: - -p, --projectRoot The root directory of the GLSP component (default: "") + -p, --projectRoot The root directory of the GLSP component (default: + "") -c, --coverageScript + + diff --git a/examples/workflow-server-mcp-demo/public/mcp-service-worker.js b/examples/workflow-server-mcp-demo/public/mcp-service-worker.js new file mode 100644 index 0000000..c82b6d8 --- /dev/null +++ b/examples/workflow-server-mcp-demo/public/mcp-service-worker.js @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +// Proxies `fetch('/mcp', ...)` calls from the page to the embedded Web Worker via a MessageChannel +// the page hands over at boot. Lets the page use plain `fetch` (or the MCP SDK's standard +// `StreamableHTTPClientTransport`) without the worker needing to host an HTTP listener. + +const PENDING_TIMEOUT_MS = 60_000; + +self.addEventListener('install', () => self.skipWaiting()); +self.addEventListener('activate', event => event.waitUntil(self.clients.claim())); + +let workerPort; +let resolveWorkerPort; +let workerPortPromise = new Promise(resolve => { + resolveWorkerPort = resolve; +}); +const pending = new Map(); +let nextId = 1; + +function failPending(id, status, statusText, message) { + const entry = pending.get(id); + if (!entry) return; + pending.delete(id); + clearTimeout(entry.timeout); + entry.resolve({ type: 'mcp-response', id, status, statusText, headers: { 'content-type': 'text/plain' }, body: message }); +} + +function failAllPending(reason) { + for (const id of [...pending.keys()]) { + failPending(id, 503, 'Service Unavailable', reason); + } +} + +self.addEventListener('message', event => { + const data = event.data; + if (!data || data.type !== 'mcp-init-port') { + return; + } + const port = event.ports[0]; + if (!port) { + return; + } + // Port replacement (page reload, worker swap) — orphan any in-flight resolvers cleanly. + if (workerPort && workerPort !== port) { + failAllPending('MCP worker port replaced before response arrived'); + workerPortPromise = new Promise(resolve => { + resolveWorkerPort = resolve; + }); + } + workerPort = port; + workerPort.onmessage = portEvent => { + const reply = portEvent.data; + if (!reply || reply.type !== 'mcp-response') { + return; + } + const entry = pending.get(reply.id); + if (entry) { + pending.delete(reply.id); + clearTimeout(entry.timeout); + entry.resolve(reply); + } + }; + workerPort.start(); + resolveWorkerPort(workerPort); +}); + +self.addEventListener('fetch', event => { + if (new URL(event.request.url).pathname !== '/mcp') { + return; + } + event.respondWith(handleMcp(event.request)); +}); + +async function handleMcp(request) { + if (!workerPort) { + await workerPortPromise; + } + const id = `${Date.now()}-${nextId++}`; + const headers = {}; + request.headers.forEach((value, key) => { + headers[key] = value; + }); + const body = request.method === 'GET' || request.method === 'HEAD' ? null : await request.text(); + const reply = await new Promise(resolve => { + const timeout = setTimeout( + () => failPending(id, 504, 'Gateway Timeout', `MCP worker bridge timed out after ${PENDING_TIMEOUT_MS}ms`), + PENDING_TIMEOUT_MS + ); + pending.set(id, { resolve, timeout }); + workerPort.postMessage({ type: 'mcp-request', id, url: request.url, method: request.method, headers, body }); + }); + return new Response(reply.body, { + status: reply.status, + statusText: reply.statusText, + headers: reply.headers + }); +} diff --git a/examples/workflow-server-mcp-demo/scripts/prepare-dist.mjs b/examples/workflow-server-mcp-demo/scripts/prepare-dist.mjs new file mode 100644 index 0000000..e50c526 --- /dev/null +++ b/examples/workflow-server-mcp-demo/scripts/prepare-dist.mjs @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +// Populates `dist/` with everything `serve` needs that webpack doesn't emit: +// - verbatim files from `public/` (index.html, mcp-service-worker.js) +// - the worker bundle from `@eclipse-glsp-examples/workflow-server-bundled-web` +// Webpack writes `index.bundle.js` into the same dir as a separate step. + +import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; + +const require = createRequire(import.meta.url); +const here = new URL('..', import.meta.url).pathname; +const dist = join(here, 'dist'); + +mkdirSync(dist, { recursive: true }); + +// 1. Verbatim assets from ./public/ +const publicDir = join(here, 'public'); +if (existsSync(publicDir)) { + for (const entry of readdirSync(publicDir)) { + const source = join(publicDir, entry); + if (statSync(source).isFile()) { + copyFileSync(source, join(dist, entry)); + } + } +} + +// 2. Worker bundle from the bundled-web dependency +const workerPackage = dirname(require.resolve('@eclipse-glsp-examples/workflow-server-bundled-web/package.json')); +const workerFiles = ['wf-glsp-server-webworker.js', 'wf-glsp-server-webworker.js.map']; +for (const file of workerFiles) { + const source = join(workerPackage, file); + if (!existsSync(source)) { + console.error(`[prepare-dist] Missing ${source} — build workflow-server first.`); + process.exit(1); + } + copyFileSync(source, join(dist, file)); +} + +console.log('[prepare-dist] dist/ populated (public assets + worker bundle)'); diff --git a/examples/workflow-server-mcp-demo/src/index.js b/examples/workflow-server-mcp-demo/src/index.js new file mode 100644 index 0000000..ed07cbd --- /dev/null +++ b/examples/workflow-server-mcp-demo/src/index.js @@ -0,0 +1,905 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { BrowserMessageReader, BrowserMessageWriter, createMessageConnection } from 'vscode-jsonrpc/browser'; + +const GLSP_PROTOCOL_VERSION = '1.0.0'; +const MCP_PROTOCOL_VERSION = '2025-03-26'; +const MCP_URL = '/mcp'; +const DIAGRAM_TYPE = 'workflow-diagram'; +const CLIENT_SESSION_ID = 'smoke-' + Math.random().toString(36).slice(2, 9); + +// Server-pushed action kinds we want forwarded to us via JSON-RPC `process` notifications. +// The framework's `ClientActionForwarder` consults this list; anything not in it triggers a +// "No handler registered" error on the server. Extend as new server-side actions surface. +const CLIENT_ACTION_KINDS = [ + 'status', + 'startProgress', + 'endProgress', + 'requestBounds', + 'setDirtyState', + 'setMarkers', + 'setEditMode', + 'updateModel', + 'setModel', + 'selectAll', + 'selectAction', + 'elementSelected', + 'sourceModelChanged' +]; + +const SVG_NS = 'http://www.w3.org/2000/svg'; +const DIRTY_RENDER_DEBOUNCE_MS = 80; + +const dom = { + log: document.getElementById('log'), + bootStatus: document.getElementById('boot-status'), + systemIndicator: document.getElementById('system-indicator'), + glspSession: document.getElementById('glsp-session'), + mcpSession: document.getElementById('mcp-session'), + btnMcpInit: document.getElementById('btn-mcp-init'), + btnMcpTools: document.getElementById('btn-mcp-tools'), + btnMcpSessionInfo: document.getElementById('btn-mcp-session-info'), + btnMcpElementTypes: document.getElementById('btn-mcp-element-types'), + btnMcpQuery: document.getElementById('btn-mcp-query'), + btnMcpValidate: document.getElementById('btn-mcp-validate'), + btnMcpCreate: document.getElementById('btn-mcp-create'), + btnMcpMove: document.getElementById('btn-mcp-move'), + btnMcpDelete: document.getElementById('btn-mcp-delete'), + btnMcpUndo: document.getElementById('btn-mcp-undo'), + btnMcpRedo: document.getElementById('btn-mcp-redo'), + btnMcpTerminate: document.getElementById('btn-mcp-terminate'), + btnClear: document.getElementById('btn-clear'), + lastResultTitle: document.getElementById('last-result-title'), + lastResult: document.getElementById('last-result'), + diagramDirty: document.getElementById('diagram-dirty'), + diagram: document.getElementById('diagram-canvas'), + infoBar: document.getElementById('info-bar'), + statusLine: document.getElementById('status-line'), + progressRow: document.getElementById('progress-row'), + progressTitle: document.getElementById('progress-title'), + progressBar: document.getElementById('progress-bar') +}; + +function setSystemState(state, label) { + if (!dom.systemIndicator) return; + dom.systemIndicator.dataset.state = state; + dom.systemIndicator.textContent = label; +} + +// ---------- log + helpers ---------- + +function logEntry(kind, summary, payload) { + const entry = document.createElement('details'); + if (kind === 'error') entry.classList.add('error'); + entry.open = true; + const sum = document.createElement('summary'); + sum.textContent = `[${new Date().toLocaleTimeString()}] ${summary}`; + entry.appendChild(sum); + const pre = document.createElement('pre'); + pre.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2); + entry.appendChild(pre); + dom.log.prepend(entry); +} + +function headersToObject(headers) { + const result = {}; + headers.forEach((value, key) => { + result[key] = value; + }); + return result; +} + +// ---------- GLSP client ---------- + +/** + * Thin wrapper around `vscode-jsonrpc/browser` that knows the handful of GLSP requests + * this smoke needs: `initialize`, `initializeClientSession`, plus `process` notifications + * for action message round-trips (`requestModel` out, server-pushed actions in). + */ +class GlspClient { + /** @param {Worker} worker */ + constructor(worker) { + const reader = new BrowserMessageReader(worker); + const writer = new BrowserMessageWriter(worker); + this.connection = createMessageConnection(reader, writer); + this.connection.onError(err => logEntry('error', 'GLSP JSON-RPC error', String(err))); + this._actionHandler = null; + this.connection.onNotification('process', message => { + if (this._actionHandler && message && message.action) { + this._actionHandler(message.action); + } + }); + this.connection.listen(); + } + + /** @param {(action: any) => void} handler */ + onAction(handler) { + this._actionHandler = handler; + } + + async initialize(applicationId, mcpServerOptions) { + logEntry('request', '→ GLSP initialize', { mcpServer: mcpServerOptions }); + const result = await this.connection.sendRequest('initialize', { + applicationId, + protocolVersion: GLSP_PROTOCOL_VERSION, + mcpServer: mcpServerOptions + }); + logEntry('response', '← GLSP initialize', result); + return result; + } + + async initializeClientSession(clientSessionId, diagramType, clientActionKinds) { + const params = { diagramType, clientSessionId, clientActionKinds, args: {} }; + logEntry('request', '→ GLSP initializeClientSession', params); + await this.connection.sendRequest('initializeClientSession', params); + logEntry('response', '← GLSP initializeClientSession', { ok: true }); + } + + /** Send an action to the server via `process` notification. */ + dispatchAction(clientId, action) { + this.connection.sendNotification('process', { clientId, action }); + } +} + +// ---------- bounds reply (RequestBoundsAction → ComputedBoundsAction) ---------- + +// The workflow MCP serializer drops task elements whose label child has no Dimension. A +// real Sprotty-based client computes those via DOM measurement and replies; here we +// estimate per element type so new nodes survive the serializer's filter. +function estimateBounds(element) { + const type = typeof element.type === 'string' ? element.type : ''; + if (type === 'label:heading' || type.startsWith('label')) { + const textLen = typeof element.text === 'string' ? element.text.length : 5; + return { width: Math.max(20, textLen * 7), height: 16 }; + } + if (type.startsWith('task')) return { width: 80, height: 30 }; + if (type === 'icon') return { width: 25, height: 20 }; + if (type.startsWith('activityNode')) return { width: 32, height: 32 }; + return { width: 60, height: 30 }; +} + +function collectMissingBounds(root) { + const bounds = []; + const stack = [root]; + while (stack.length) { + const element = stack.pop(); + if (!element) continue; + const size = element.size; + const hasSize = size && Number.isFinite(size.width) && Number.isFinite(size.height) && size.width > 0 && size.height > 0; + if (typeof element.id === 'string' && !hasSize) { + bounds.push({ elementId: element.id, newSize: estimateBounds(element) }); + } + if (Array.isArray(element.children)) { + for (const child of element.children) stack.push(child); + } + } + return bounds; +} + +function buildComputedBoundsAction(requestAction) { + // `ComputedBoundsActionHandler` discards the reply unless `revision` matches the + // current `modelState.root.revision`. Mirror the request's newRoot revision. + return { + kind: 'computedBounds', + bounds: collectMissingBounds(requestAction.newRoot), + revision: requestAction.newRoot?.revision, + responseId: requestAction.requestId + }; +} + +// ---------- MCP client ---------- + +let rpcId = 1; +let mcpSessionId = ''; + +function mcpHeaders() { + return mcpSessionId ? { 'mcp-session-id': mcpSessionId, 'mcp-protocol-version': MCP_PROTOCOL_VERSION } : {}; +} + +// Parse a Streamable HTTP MCP response body. The transport returns either plain JSON or +// SSE-framed (`event:` / `data:` lines); we want the JSON payload either way. +async function readResponseBody(response) { + const text = await response.text(); + if (!text) return ''; + const trimmed = text.trim(); + if (trimmed.startsWith('event:') || trimmed.startsWith('data:')) { + for (const line of text.split('\n')) { + const stripped = line.trim(); + if (stripped.startsWith('data:')) { + try { + return JSON.parse(stripped.slice(5).trim()); + } catch { + /* fall through to raw text */ + } + } + } + } + try { + return JSON.parse(text); + } catch { + return text; + } +} + +async function mcpFetch(extraHeaders, jsonBody, summary, options = {}) { + const headers = { + accept: 'application/json, text/event-stream', + 'content-type': 'application/json', + ...extraHeaders + }; + const method = options.method ?? 'POST'; + const fetchInit = { method, headers }; + if (jsonBody !== undefined) { + fetchInit.body = JSON.stringify(jsonBody); + logEntry('request', `→ ${method} ${MCP_URL}`, { headers, body: jsonBody }); + } else { + logEntry('request', `→ ${method} ${MCP_URL}`, { headers }); + } + try { + const response = await fetch(MCP_URL, fetchInit); + const responseHeaders = headersToObject(response.headers); + const body = await readResponseBody(response); + logEntry(response.ok ? 'response' : 'error', `← ${response.status} ${response.statusText} ${summary}`, { + headers: responseHeaders, + body + }); + // `internal` calls are infrastructure round-trips (e.g. the auto `diagram-model` fetch after + // each mutation) — they shouldn't clobber the user-visible last-result panel. + if (!options.internal) { + renderLastResult(summary, body); + } + return { response, headers: responseHeaders, body }; + } catch (err) { + logEntry('error', `← ${summary} failed`, String(err)); + throw err; + } +} + +function mcpToolCall(name, args, summary, options) { + return mcpFetch( + mcpHeaders(), + { jsonrpc: '2.0', id: rpcId++, method: 'tools/call', params: { name, arguments: args } }, + summary, + options + ); +} + +// ---------- last-result panel ---------- + +// Pretty-print the most recent MCP response. Recognises the common shapes the demo's tools +// emit (arrays of {name, description, …}, plain primitive objects, text-only `content`) and +// falls back to indented JSON when it can't summarise meaningfully. +function renderLastResult(summary, body) { + if (!dom.lastResult || !dom.lastResultTitle) return; + dom.lastResultTitle.textContent = summary; + dom.lastResult.replaceChildren(); + const payload = pickRenderPayload(body); + dom.lastResult.appendChild(renderPayload(payload)); +} + +// Strip the JSON-RPC envelope down to the bit a human cares about. +function pickRenderPayload(body) { + if (!body || typeof body !== 'object') return body; + if (body.error) return { error: body.error }; + const result = body.result; + if (!result || typeof result !== 'object') return result ?? body; + // `tools/list` / `resources/list` / `prompts/list` returns { tools | resources | prompts: [...] }. + if (Array.isArray(result.tools)) return result.tools; + if (Array.isArray(result.resources)) return result.resources; + if (Array.isArray(result.prompts)) return result.prompts; + // `tools/call` returns structuredContent (preferred) or content[]. + if (result.structuredContent && typeof result.structuredContent === 'object') return result.structuredContent; + if (Array.isArray(result.content)) return result.content; + return result; +} + +function renderPayload(payload) { + if (Array.isArray(payload) && payload.length > 0 && payload.every(item => item && typeof item === 'object' && !Array.isArray(item))) { + return renderObjectArrayTable(payload); + } + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + // Plain `content` entries from tools/call — `[{ type: 'text', text: '…' }]` flattened to text. + if (typeof payload.type === 'string' && typeof payload.text === 'string') { + const pre = document.createElement('pre'); + pre.textContent = payload.text; + return pre; + } + if (Object.values(payload).every(v => typeof v !== 'object' || v === null)) { + return renderKeyValueTable(payload); + } + } + const pre = document.createElement('pre'); + pre.textContent = JSON.stringify(payload, null, 2); + return pre; +} + +function renderObjectArrayTable(items) { + // Pick a small set of "interesting" columns. `name`/`title` and `description` cover the + // tools/list, resources/list, and query-elements shapes; fall back to whatever keys exist. + const preferred = ['name', 'title', 'description', 'id', 'type', 'elementTypeId', 'label']; + const present = preferred.filter(key => items.some(item => key in item)); + const keys = present.length > 0 ? present : Object.keys(items[0]).slice(0, 4); + const table = document.createElement('table'); + const thead = document.createElement('thead'); + const headRow = document.createElement('tr'); + for (const key of keys) { + const th = document.createElement('th'); + th.textContent = key; + headRow.appendChild(th); + } + thead.appendChild(headRow); + table.appendChild(thead); + const tbody = document.createElement('tbody'); + for (const item of items) { + const row = document.createElement('tr'); + for (const key of keys) { + const td = document.createElement('td'); + td.className = key === keys[0] ? 'key' : 'value'; + const value = item[key]; + td.textContent = value === undefined ? '' : typeof value === 'string' ? value : JSON.stringify(value); + row.appendChild(td); + } + tbody.appendChild(row); + } + table.appendChild(tbody); + return table; +} + +function renderKeyValueTable(payload) { + const table = document.createElement('table'); + const tbody = document.createElement('tbody'); + for (const [key, value] of Object.entries(payload)) { + const row = document.createElement('tr'); + const keyCell = document.createElement('td'); + keyCell.className = 'key'; + keyCell.textContent = key; + const valueCell = document.createElement('td'); + valueCell.className = 'value'; + valueCell.textContent = value === null ? 'null' : String(value); + row.append(keyCell, valueCell); + tbody.appendChild(row); + } + table.appendChild(tbody); + return table; +} + +// ---------- info bar (server-pushed actions) ---------- + +function showInfoBar() { + dom.infoBar.classList.add('visible'); + document.getElementById('server-messages-section')?.classList.remove('section-hidden'); +} + +function renderStatus(action) { + const severity = (action.severity ?? 'info').toLowerCase(); + const message = (action.message ?? '').trim(); + // `{severity: 'NONE', message: ''}` is a "clear status" beat between meaningful + // updates; render that as a quiet idle line rather than verbatim text. + if (severity === 'none' && !message) { + dom.statusLine.textContent = 'All quiet.'; + } else if (!message) { + dom.statusLine.textContent = severity[0].toUpperCase() + severity.slice(1); + } else { + dom.statusLine.textContent = message; + } + showInfoBar(); +} + +function startProgress(action) { + dom.progressTitle.textContent = action.title ?? 'In progress…'; + dom.progressBar.removeAttribute('value'); + dom.progressRow.style.display = 'flex'; + showInfoBar(); +} + +function endProgress() { + dom.progressRow.style.display = 'none'; +} + +// ---------- diagram rendering ---------- + +function nodeCssClass(type) { + if (type.startsWith('task:automated')) return 'diagram-node task-automated'; + if (type.startsWith('task:manual')) return 'diagram-node task-manual'; + if (type.startsWith('activityNode')) return 'diagram-node activity-node'; + return 'diagram-node'; +} + +function elementLabel(element) { + if (typeof element.label === 'string' && element.label.trim()) return element.label; + if (typeof element.name === 'string' && element.name.trim()) return element.name; + if (typeof element.type === 'string' && element.type.includes(':')) { + return element.type.slice(element.type.lastIndexOf(':') + 1); + } + return element.id; +} + +// Painted geometry for a node, derived from its bounding rect. Diamonds shrink to +// 75% of the rect so they don't read as overscaled. Synchronization bars (fork/join) +// align to the long axis of the bounding rect — vertical when the rect is tall +// (workflow's canonical 10×50), horizontal when wide. +const DIAMOND_SCALE = 0.75; +const BAR_THICKNESS = 4; +function nodeShape(node) { + const type = node.type ?? ''; + const x = node.position.x; + const y = node.position.y; + const w = node.size.width; + const h = node.size.height; + const cx = x + w / 2; + const cy = y + h / 2; + if (type === 'activityNode:decision' || type === 'activityNode:merge') { + return { kind: 'diamond', cx, cy, halfW: (w * DIAMOND_SCALE) / 2, halfH: (h * DIAMOND_SCALE) / 2 }; + } + if (type === 'activityNode:fork' || type === 'activityNode:join') { + if (h >= w) { + // Vertical bar — thin column along the long (vertical) axis. + return { kind: 'rect', x: cx - BAR_THICKNESS / 2, y, width: BAR_THICKNESS, height: h }; + } + return { kind: 'rect', x, y: cy - BAR_THICKNESS / 2, width: w, height: BAR_THICKNESS }; + } + return { kind: 'rect', x, y, width: w, height: h }; +} + +// Clip the line from the shape's center toward (fromX,fromY) to the shape's boundary, +// so edges meet the painted shape instead of disappearing into it or stopping short. +function clipLineToShape(shape, fromX, fromY) { + if (shape.kind === 'diamond') { + const dx = fromX - shape.cx; + const dy = fromY - shape.cy; + if (dx === 0 && dy === 0) return { x: shape.cx, y: shape.cy }; + // Diamond |X|/halfW + |Y|/halfH = 1 → r = 1 / (|dx|/halfW + |dy|/halfH). + const r = 1 / (Math.abs(dx) / shape.halfW + Math.abs(dy) / shape.halfH); + return { x: shape.cx + dx * r, y: shape.cy + dy * r }; + } + const cx = shape.x + shape.width / 2; + const cy = shape.y + shape.height / 2; + const dx = fromX - cx; + const dy = fromY - cy; + if (dx === 0 && dy === 0) return { x: cx, y: cy }; + const scale = Math.min(shape.width / 2 / Math.abs(dx || Infinity), shape.height / 2 / Math.abs(dy || Infinity)); + return { x: cx + dx * scale, y: cy + dy * scale }; +} + +function svg(tag, attrs = {}) { + const node = document.createElementNS(SVG_NS, tag); + for (const [key, value] of Object.entries(attrs)) { + node.setAttribute(key, String(value)); + } + return node; +} + +function partitionElements(elements) { + const nodes = []; + const edges = []; + const nodeIndex = new Map(); + for (const element of elements) { + const type = element.type ?? ''; + if (typeof element.sourceId === 'string' && typeof element.targetId === 'string') { + edges.push(element); + } else if (element.position && element.size && (type.startsWith('task') || type.startsWith('activityNode'))) { + nodes.push(element); + nodeIndex.set(element.id, element); + } + } + return { nodes, edges, nodeIndex }; +} + +function computeViewBox(nodes) { + let minX = Infinity, + minY = Infinity, + maxX = -Infinity, + maxY = -Infinity; + for (const node of nodes) { + const { x, y } = node.position; + const { width, height } = node.size; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x + width > maxX) maxX = x + width; + if (y + height > maxY) maxY = y + height; + } + const padding = 20; + const width = Math.max(maxX - minX + padding * 2, 200); + const height = Math.max(maxY - minY + padding * 2, 100); + return { x: minX - padding, y: minY - padding, width, height }; +} + +function renderDiagram(elements) { + dom.diagram.replaceChildren(); + const { nodes, edges, nodeIndex } = partitionElements(elements); + if (nodes.length === 0) { + dom.diagram.classList.remove('visible'); + logEntry('error', 'No renderable nodes in diagram-model output', { elements }); + return; + } + + const viewBox = computeViewBox(nodes); + dom.diagram.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`); + const displayWidth = Math.min(viewBox.width, 960); + dom.diagram.setAttribute('width', String(displayWidth)); + dom.diagram.setAttribute('height', String(viewBox.height * (displayWidth / viewBox.width))); + + const defs = svg('defs'); + const marker = svg('marker', { id: 'arrow', markerWidth: 10, markerHeight: 10, refX: 8, refY: 3, orient: 'auto' }); + marker.appendChild(svg('path', { d: 'M0,0 L0,6 L8,3 z', class: 'diagram-edge-arrow' })); + defs.appendChild(marker); + dom.diagram.appendChild(defs); + + // Precompute painted geometry once per node so edge clipping and shape painting + // agree on where the visible boundary actually is. + const shapes = new Map(); + for (const node of nodes) { + shapes.set(node.id, nodeShape(node)); + } + + // Edges first so nodes paint on top. Both endpoints clip to the painted shape so + // the arrow head lands on the shape's edge, not behind it or short of it. + for (const edge of edges) { + const sourceShape = shapes.get(edge.sourceId); + const targetShape = shapes.get(edge.targetId); + if (!sourceShape || !targetShape) continue; + const sourceCx = sourceShape.kind === 'diamond' ? sourceShape.cx : sourceShape.x + sourceShape.width / 2; + const sourceCy = sourceShape.kind === 'diamond' ? sourceShape.cy : sourceShape.y + sourceShape.height / 2; + const targetCx = targetShape.kind === 'diamond' ? targetShape.cx : targetShape.x + targetShape.width / 2; + const targetCy = targetShape.kind === 'diamond' ? targetShape.cy : targetShape.y + targetShape.height / 2; + const start = clipLineToShape(sourceShape, targetCx, targetCy); + const end = clipLineToShape(targetShape, sourceCx, sourceCy); + dom.diagram.appendChild( + svg('line', { + x1: start.x, + y1: start.y, + x2: end.x, + y2: end.y, + class: 'diagram-edge', + 'marker-end': 'url(#arrow)' + }) + ); + } + + for (const node of nodes) { + const type = node.type ?? ''; + const cssClass = nodeCssClass(type); + const shape = shapes.get(node.id); + if (shape.kind === 'diamond') { + const points = `${shape.cx},${shape.cy - shape.halfH} ${shape.cx + shape.halfW},${shape.cy} ${shape.cx},${shape.cy + shape.halfH} ${shape.cx - shape.halfW},${shape.cy}`; + dom.diagram.appendChild(svg('polygon', { points, class: cssClass })); + } else if (type.startsWith('activityNode')) { + // Bar — flat filled rect, no inset label. + dom.diagram.appendChild( + svg('rect', { x: shape.x, y: shape.y, width: shape.width, height: shape.height, rx: 1.5, class: cssClass }) + ); + } else { + dom.diagram.appendChild( + svg('rect', { x: shape.x, y: shape.y, width: shape.width, height: shape.height, rx: 4, class: cssClass }) + ); + const text = svg('text', { + x: shape.x + shape.width / 2, + y: shape.y + shape.height / 2, + class: 'diagram-label' + }); + text.textContent = elementLabel(node); + dom.diagram.appendChild(text); + } + } + + dom.diagram.classList.add('visible'); +} + +async function fetchAndRenderDiagram() { + const result = await mcpToolCall('diagram-model', { sessionId: CLIENT_SESSION_ID }, '(diagram-model)', { internal: true }); + const elements = result.body?.result?.structuredContent?.elements; + if (Array.isArray(elements)) { + renderDiagram(elements); + } else { + logEntry('error', 'diagram-model response missing structuredContent.elements', result.body); + } +} + +// Re-render on every `updateModel` push (debounced). `updateModel` is the canonical +// GLSP signal carrying the new model root — Sprotty-based clients swap the root on +// this action; we re-fetch `diagram-model` for the same effect. +let updateRenderTimer = 0; +function scheduleModelRender() { + if (!mcpSessionId || updateRenderTimer) return; + updateRenderTimer = setTimeout(() => { + updateRenderTimer = 0; + fetchAndRenderDiagram().catch(err => logEntry('error', 'auto re-render on updateModel failed', String(err))); + }, DIRTY_RENDER_DEBOUNCE_MS); +} + +// ---------- boot ---------- + +let glsp; + +async function bootGlsp(worker) { + glsp = new GlspClient(worker); + glsp.onAction(action => { + switch (action.kind) { + case 'status': + renderStatus(action); + break; + case 'updateModel': + scheduleModelRender(); + break; + case 'setDirtyState': + if (dom.diagramDirty) { + dom.diagramDirty.hidden = !action.isDirty; + } + break; + case 'startProgress': + startProgress(action); + break; + case 'endProgress': + endProgress(); + break; + case 'requestBounds': + glsp.dispatchAction(CLIENT_SESSION_ID, buildComputedBoundsAction(action)); + break; + default: + logEntry('response', `← server-pushed action ${action.kind}`, action); + } + }); + + await glsp.initialize('workflow-server-bundled-web-smoke', { name: 'workflow-glsp', route: MCP_URL }); + + dom.glspSession.value = CLIENT_SESSION_ID; + await glsp.initializeClientSession(CLIENT_SESSION_ID, DIAGRAM_TYPE, CLIENT_ACTION_KINDS); + + // Trigger model load. `WorkflowMockModelStorage` ignores the URI and always serves + // its bundled example1.json, so any non-empty `sourceUri` works. + logEntry('request', '→ GLSP process(requestModel)', { sourceUri: 'example1.json' }); + glsp.dispatchAction(CLIENT_SESSION_ID, { + kind: 'requestModel', + options: { sourceUri: 'example1.json', diagramType: DIAGRAM_TYPE } + }); +} + +async function bootSw(worker) { + if (!('serviceWorker' in navigator)) { + throw new Error('Service Workers not supported in this browser'); + } + await navigator.serviceWorker.register('./mcp-service-worker.js'); + await navigator.serviceWorker.ready; + if (!navigator.serviceWorker.controller) { + // First registration: SW activated but the page isn't controlled yet. With + // `skipWaiting`/`clients.claim` this usually fires within a tick. + await new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', resolve, { once: true }); + }); + } + // One MessageChannel; the page holds neither end. Port 1 goes to the Service Worker + // (incoming MCP fetches), port 2 to the Web Worker (MCP request handler). + const channel = new MessageChannel(); + navigator.serviceWorker.controller.postMessage({ type: 'mcp-init-port' }, [channel.port1]); + worker.postMessage({ type: 'mcp-init-port' }, [channel.port2]); +} + +async function boot() { + // Cache-bust the worker bundle URL per page load. Browsers HTTP-cache `new Worker(url)` + // fetches; without this, rebuilding the bundle takes effect only after a Service Worker + // unregister or cache flush. + const worker = new Worker(`./wf-glsp-server-webworker.js?v=${Date.now()}`); + worker.addEventListener('error', event => logEntry('error', 'Worker error', event.message ?? String(event))); + + try { + await Promise.all([bootGlsp(worker), bootSw(worker)]); + dom.bootStatus.textContent = `Connected. Workflow session ${CLIENT_SESSION_ID} is open.`; + dom.bootStatus.classList.add('ready'); + setSystemState('ready', 'Ready'); + dom.btnMcpInit.disabled = false; + } catch (err) { + dom.bootStatus.textContent = `Boot failed — ${err.message ?? err}`; + dom.bootStatus.classList.add('failed'); + setSystemState('failed', 'Failed'); + logEntry('error', 'Boot failed', String(err)); + } +} + +// ---------- button wiring ---------- + +dom.btnMcpInit.addEventListener('click', async () => { + dom.btnMcpInit.disabled = true; + const result = await mcpFetch( + {}, + { + jsonrpc: '2.0', + id: rpcId++, + method: 'initialize', + params: { + protocolVersion: MCP_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: { name: 'glsp-mcp-smoke', version: '0.0.1' } + } + }, + '(initialize)' + ); + mcpSessionId = result.headers['mcp-session-id'] ?? ''; + if (!mcpSessionId) { + logEntry('error', 'No mcp-session-id in response headers', result.headers); + dom.btnMcpInit.disabled = false; + return; + } + dom.mcpSession.value = mcpSessionId; + // Demote the primary treatment once init has succeeded — every action is now equal. + dom.btnMcpInit.classList.remove('primary'); + dom.btnMcpTools.disabled = false; + dom.btnMcpSessionInfo.disabled = false; + dom.btnMcpElementTypes.disabled = false; + dom.btnMcpQuery.disabled = false; + dom.btnMcpValidate.disabled = false; + dom.btnMcpCreate.disabled = false; + dom.btnMcpTerminate.disabled = false; + // Move/Delete activate after the first create; Undo/Redo follow the dispatched-commands stack. + await mcpFetch(mcpHeaders(), { jsonrpc: '2.0', method: 'notifications/initialized' }, '(initialized)'); + await fetchAndRenderDiagram(); + dom.btnMcpInit.disabled = false; +}); + +dom.btnMcpTools.addEventListener('click', () => { + mcpFetch(mcpHeaders(), { jsonrpc: '2.0', id: rpcId++, method: 'tools/list' }, '(tools/list)'); +}); + +dom.btnMcpSessionInfo.addEventListener('click', () => { + mcpToolCall('session-info', {}, '(session-info)'); +}); + +dom.btnMcpElementTypes.addEventListener('click', () => { + mcpToolCall('element-types', { sessionId: CLIENT_SESSION_ID }, '(element-types)'); +}); + +dom.btnMcpQuery.addEventListener('click', () => { + mcpToolCall('query-elements', { sessionId: CLIENT_SESSION_ID }, '(query-elements)'); +}); + +dom.btnMcpValidate.addEventListener('click', () => { + mcpToolCall('validate-diagram', { sessionId: CLIENT_SESSION_ID }, '(validate-diagram)'); +}); + +dom.btnMcpUndo.addEventListener('click', async () => { + const count = undoStack[undoStack.length - 1]; + if (!count) return; + dom.btnMcpUndo.disabled = true; + await mcpToolCall('undo', { sessionId: CLIENT_SESSION_ID, commandsToUndo: count }, `(undo · ${count})`); + undoStack.pop(); + redoStack.push(count); + updateUndoRedoButtons(); +}); +dom.btnMcpRedo.addEventListener('click', async () => { + const count = redoStack[redoStack.length - 1]; + if (!count) return; + dom.btnMcpRedo.disabled = true; + await mcpToolCall('redo', { sessionId: CLIENT_SESSION_ID, commandsToRedo: count }, `(redo · ${count})`); + redoStack.pop(); + undoStack.push(count); + updateUndoRedoButtons(); +}); + +// Each click drops a new manual task with an incrementing position so repeated clicks +// don't overlap. The `updateModel` push from the server auto-triggers a re-render. +let createOffset = 0; +const recentTaskIds = []; +// Each mutating tool reports `dispatchedCommands` — the number of underlying GLSP commands the +// call produced (e.g. create-with-label = 2 commands). Track per-call counts so Undo / Redo can +// roll back / replay a full user action instead of just its last sub-command. +const undoStack = []; +const redoStack = []; +function updateUndoRedoButtons() { + dom.btnMcpUndo.disabled = undoStack.length === 0; + dom.btnMcpRedo.disabled = redoStack.length === 0; +} +function trackDispatched(toolResult) { + const count = toolResult?.body?.result?.structuredContent?.dispatchedCommands; + if (typeof count === 'number' && count > 0) { + undoStack.push(count); + redoStack.length = 0; + updateUndoRedoButtons(); + } +} +function rememberCreatedTaskIds(toolResult) { + const created = toolResult?.body?.result?.structuredContent?.createdNodes; + if (!Array.isArray(created)) return; + for (const node of created) { + // ElementIdentitySchema fields: `id` is the (aliased) element id. + if (node && typeof node.id === 'string') { + recentTaskIds.push(node.id); + } + } + if (recentTaskIds.length > 0) { + dom.btnMcpMove.disabled = false; + dom.btnMcpDelete.disabled = false; + } +} +dom.btnMcpCreate.addEventListener('click', async () => { + dom.btnMcpCreate.disabled = true; + createOffset += 1; + const result = await mcpToolCall( + 'create-nodes', + { + sessionId: CLIENT_SESSION_ID, + nodes: [ + { + elementTypeId: 'task:manual', + position: { x: 40, y: 30 + createOffset * 50 }, + text: `Task ${createOffset}` + } + ] + }, + '(create-nodes)' + ); + rememberCreatedTaskIds(result); + trackDispatched(result); + dom.btnMcpCreate.disabled = false; +}); + +// Move/delete target the most recently created task. Both rely on `updateModel` +// for the diagram re-render. +dom.btnMcpMove.addEventListener('click', async () => { + const elementId = recentTaskIds[recentTaskIds.length - 1]; + if (!elementId) return; + dom.btnMcpMove.disabled = true; + const result = await mcpToolCall( + 'modify-nodes', + { + sessionId: CLIENT_SESSION_ID, + nodes: [{ elementId, position: { x: 220, y: 30 + createOffset * 50 } }] + }, + '(modify-nodes · move)' + ); + trackDispatched(result); + dom.btnMcpMove.disabled = false; +}); +dom.btnMcpDelete.addEventListener('click', async () => { + const elementId = recentTaskIds.pop(); + if (!elementId) return; + dom.btnMcpDelete.disabled = true; + const result = await mcpToolCall('delete-elements', { sessionId: CLIENT_SESSION_ID, elementIds: [elementId] }, '(delete-elements)'); + trackDispatched(result); + if (recentTaskIds.length === 0) { + dom.btnMcpMove.disabled = true; + dom.btnMcpDelete.disabled = true; + } else { + dom.btnMcpDelete.disabled = false; + } +}); + +// Terminate the current MCP session via the spec's `DELETE /mcp` op. After this the server +// drops the per-session state; subsequent tool calls fail with 404 until the user re-initialises. +dom.btnMcpTerminate.addEventListener('click', async () => { + if (!mcpSessionId) return; + dom.btnMcpTerminate.disabled = true; + await mcpFetch(mcpHeaders(), undefined, '(terminate)', { method: 'DELETE' }); + mcpSessionId = ''; + dom.mcpSession.value = ''; + // Tool buttons can no longer succeed against this session; the user must re-Initialize. + dom.btnMcpTools.disabled = true; + dom.btnMcpSessionInfo.disabled = true; + dom.btnMcpElementTypes.disabled = true; + dom.btnMcpQuery.disabled = true; + dom.btnMcpValidate.disabled = true; + dom.btnMcpCreate.disabled = true; + dom.btnMcpMove.disabled = true; + dom.btnMcpDelete.disabled = true; + dom.btnMcpUndo.disabled = true; + dom.btnMcpRedo.disabled = true; + dom.btnMcpInit.disabled = false; + dom.btnMcpInit.classList.add('primary'); +}); + +dom.btnClear.addEventListener('click', () => { + dom.log.innerHTML = ''; +}); + +boot(); diff --git a/examples/workflow-server-mcp-demo/webpack.config.js b/examples/workflow-server-mcp-demo/webpack.config.js new file mode 100644 index 0000000..126e0a7 --- /dev/null +++ b/examples/workflow-server-mcp-demo/webpack.config.js @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +const path = require('path'); + +// Bundle `vscode-jsonrpc/browser` plus the page-side script into a single file emitted into +// `dist/`, alongside the verbatim assets copied from `public/` and the worker bundle synced +// from `@eclipse-glsp-examples/workflow-server-bundled-web`. `serve` then serves `dist/`. +module.exports = { + entry: path.resolve(__dirname, 'src', 'index.js'), + output: { + filename: 'index.bundle.js', + path: path.resolve(__dirname, 'dist') + }, + mode: 'development', + devtool: 'source-map', + target: 'web', + resolve: { + extensions: ['.js'] + } +}; diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 9307c94..3614ee6 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -46,7 +46,7 @@ "browser.js" ], "scripts": { - "build": "tsc -b && yarn bundle", + "build": "tsc -b && yarn bundle && yarn bundle:browser", "bundle": "webpack", "bundle:browser": "webpack --env target=webworker ", "clean": "rimraf lib *.tsbuildinfo", diff --git a/examples/workflow-server/src/browser/app.ts b/examples/workflow-server/src/browser/app.ts index a25c261..7140def 100644 --- a/examples/workflow-server/src/browser/app.ts +++ b/examples/workflow-server/src/browser/app.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 STMicroelectronics and others. + * Copyright (c) 2022-2026 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,12 +17,17 @@ import 'reflect-metadata'; import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk'; import { createAppModule, LogLevel, WorkerServerLauncher } from '@eclipse-glsp/server/browser'; +import { McpWorkerBridge } from '@eclipse-glsp/server-mcp/browser'; import { Container } from 'inversify'; import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator'; +import { WorkflowMcpDiagramModule } from '../common/mcp/workflow-mcp-diagram-module'; import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module'; import { WorkflowMockModelStorage } from './mock-model-storage'; -export async function launch(argv?: string[]): Promise { +export async function launch(_argv?: string[]): Promise { + // Bridge must be created before any await so postMessages that arrive on the next event-loop tick aren't dropped. + const bridge = new McpWorkerBridge(); + const appContainer = new Container(); appContainer.load(createAppModule({ logLevel: LogLevel.info })); @@ -35,11 +40,13 @@ export async function launch(argv?: string[]): Promise { const serverModule = new WorkflowServerModule().configureDiagramModule( new WorkflowDiagramModule(() => WorkflowMockModelStorage), - elkLayoutModule + elkLayoutModule, + new WorkflowMcpDiagramModule() ); - launcher.configure(serverModule); + launcher.configure(serverModule, bridge.createServerModule()); + // Resolves only on connection close — must be the last await. await launcher.start({}); } diff --git a/examples/workflow-server/src/common/index.ts b/examples/workflow-server/src/common/index.ts index d1627b2..452c517 100644 --- a/examples/workflow-server/src/common/index.ts +++ b/examples/workflow-server/src/common/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 STMicroelectronics and others. + * Copyright (c) 2023-2026 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,9 +33,9 @@ export * from './labeledit/workflow-label-edit-validator'; export * from './layout/workflow-layout-configurator'; export * from './marker/workflow-model-validator'; export * from './mcp/workflow-element-types-provider'; +export * from './mcp/workflow-mcp-diagram-module'; export * from './mcp/workflow-mcp-label-provider'; export * from './mcp/workflow-mcp-model-serializer'; -export * from './mcp/workflow-mcp-module'; export * from './model/workflow-navigation-target-resolver'; export * from './provider/abstract-next-or-previous-navigation-target-provider'; export * from './provider/next-node-navigation-target-provider'; diff --git a/examples/workflow-server/src/common/mcp/workflow-mcp-module.ts b/examples/workflow-server/src/common/mcp/workflow-mcp-diagram-module.ts similarity index 72% rename from examples/workflow-server/src/common/mcp/workflow-mcp-module.ts rename to examples/workflow-server/src/common/mcp/workflow-mcp-diagram-module.ts index 436b9f5..70252b9 100644 --- a/examples/workflow-server/src/common/mcp/workflow-mcp-module.ts +++ b/examples/workflow-server/src/common/mcp/workflow-mcp-diagram-module.ts @@ -15,29 +15,14 @@ ********************************************************************************/ import { BindingTarget } from '@eclipse-glsp/server'; -import { - DefaultMcpDiagramModule, - DefaultMcpServerModule, - ElementTypesProvider, - McpLabelProvider, - McpModelSerializer -} from '@eclipse-glsp/server-mcp'; +import { DefaultMcpDiagramModule, ElementTypesProvider, McpLabelProvider, McpModelSerializer } from '@eclipse-glsp/server-mcp'; import { WorkflowElementTypesProvider } from './workflow-element-types-provider'; import { WorkflowMcpLabelProvider } from './workflow-mcp-label-provider'; import { WorkflowMcpModelSerializer } from './workflow-mcp-model-serializer'; -/** - * Workflow-specific server-scope MCP module. Currently no server-scope customizations beyond - * the defaults — every diagram-type-specific override lives on {@link WorkflowMcpDiagramModule}. - * Kept as the named entry point and as a hook for future server-scope extensions. - */ -export class WorkflowMcpServerModule extends DefaultMcpServerModule {} - /** * Workflow-specific diagram-scope MCP module — swaps in the workflow-aware * {@link McpLabelProvider}, {@link McpModelSerializer}, and {@link ElementTypesProvider}. - * `LayoutMcpToolHandler` ships in the default tool set and self-skips when no `LayoutEngine` - * is bound, so workflow doesn't need to add it explicitly. */ export class WorkflowMcpDiagramModule extends DefaultMcpDiagramModule { protected override bindLabelProvider(): BindingTarget { diff --git a/examples/workflow-server/src/node/app.ts b/examples/workflow-server/src/node/app.ts index a3b5ac3..2033998 100644 --- a/examples/workflow-server/src/node/app.ts +++ b/examples/workflow-server/src/node/app.ts @@ -20,8 +20,9 @@ import { GModelStorage, Logger, SocketServerLauncher, WebSocketServerLauncher, c import { Container } from 'inversify'; import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator'; -import { WorkflowMcpDiagramModule, WorkflowMcpServerModule } from '../common/mcp/workflow-mcp-module'; +import { WorkflowMcpDiagramModule } from '../common/mcp/workflow-mcp-diagram-module'; import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module'; +import { WorkflowMcpServerModule } from './mcp/workflow-mcp-module'; import { createWorkflowCliParser } from './workflow-cli-parser'; async function launch(argv?: string[]): Promise { diff --git a/examples/workflow-server/src/node/index.ts b/examples/workflow-server/src/node/index.ts index fca7741..8df6a5b 100644 --- a/examples/workflow-server/src/node/index.ts +++ b/examples/workflow-server/src/node/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 EclipseSource and others. + * Copyright (c) 2022-2026 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,5 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './mcp/workflow-mcp-module'; export * from './reexport'; export * from './workflow-cli-parser'; diff --git a/examples/workflow-server/src/node/mcp/workflow-mcp-module.ts b/examples/workflow-server/src/node/mcp/workflow-mcp-module.ts new file mode 100644 index 0000000..7566952 --- /dev/null +++ b/examples/workflow-server/src/node/mcp/workflow-mcp-module.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { NodeMcpServerModule } from '@eclipse-glsp/server-mcp/node'; + +/** + * Workflow-scope MCP module — adopter hook for future workflow-specific server-scope bindings. + * Currently a no-op subclass; diagram-scope customizations live on `WorkflowMcpDiagramModule`. + */ +export class WorkflowMcpServerModule extends NodeMcpServerModule {} diff --git a/examples/workflow-server/webpack.config.js b/examples/workflow-server/webpack.config.js index 6e4ca85..ac7dad0 100644 --- a/examples/workflow-server/webpack.config.js +++ b/examples/workflow-server/webpack.config.js @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022-2026 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -const webpack = require('webpack'); const path = require('path'); const buildRoot = path.resolve(__dirname, 'lib'); diff --git a/packages/server-mcp/ARCHITECTURE.md b/packages/server-mcp/ARCHITECTURE.md index 737d4e3..32d5d57 100644 --- a/packages/server-mcp/ARCHITECTURE.md +++ b/packages/server-mcp/ARCHITECTURE.md @@ -208,10 +208,21 @@ The tag is exported as `MCP_SERVER_READY_MSG` so IDE integrations can parse the ### Transport -The MCP server uses the **Streamable HTTP transport** (`StreamableHTTPServerTransport` from the MCP SDK). HTTP `POST` carries client → server JSON-RPC; `GET` returns the server → client SSE stream (with `Last-Event-ID` resumability); `DELETE` terminates a session. Sessions are multiplexed on a single endpoint via the `mcp-session-id` header. +The MCP server uses the **Streamable HTTP transport** (`WebStandardStreamableHTTPServerTransport` from the MCP SDK). HTTP `POST` carries client → server JSON-RPC; `GET` returns the server → client SSE stream (with `Last-Event-ID` resumability); `DELETE` terminates a session. Sessions are multiplexed on a single endpoint via the `mcp-session-id` header. A periodic server-initiated `ping` keeps the SSE GET stream alive across chat-idle periods, so client-side read timeouts (e.g. undici's 5-min `bodyTimeout`) don't force a reconnect cycle. +#### Portable handler — Node, browser, and web-runtime targets + +The launcher exposes a Fetch-API `handleRequest(req: Request): Promise` that any runtime with a `fetch`-shaped listener can drive — Node (via `@hono/node-server`), Bun, Deno, Cloudflare Workers, and in-page Web Workers. Two concrete launcher subclasses ship: + +- `McpServerLauncher` (Node) binds a Hono listener and announces the loopback URL. +- `WebMcpServerLauncher` (browser / web-runtime) returns no transport endpoint; the adopter wires `launcher.getRequestHandler()` into their own listener. + +For the browser/Web-Worker case where the GLSP client and the MCP server share a tab, `McpWorkerBridge` (in `@eclipse-glsp/server-mcp/browser`) plumbs Service-Worker→Web-Worker `MessageChannel` traffic into the launcher. The matching page-side proxy — a Service Worker that intercepts `fetch('/mcp', …)` and forwards each `Request` over a `MessageChannel` — is host-side scaffolding that adopters own. The browser demo at `examples/workflow-server-mcp-demo/` is the canonical end-to-end reference, including a working `mcp-service-worker.js`. + +Auth and shared session state for non-loopback deploys (Cloudflare DurableObjects-style multi-isolate setups) are explicitly out of scope — adopters wrap `getRequestHandler()` with their own middleware. + --- ## Deployment Model @@ -245,7 +256,9 @@ Both the GLSP server itself and this MCP server default to **random port allocat Two paths, depending on the client class: -- **IDE-internal MCP clients** (VS Code Copilot chat, Theia AI, etc.) should consume the resolved URL programmatically. The GLSP IDE integration knows it via the `MCP_SERVER_READY_MSG` stdout marker and is best placed to register that URL with the IDE's native MCP infrastructure. _This automatic registration is intended follow-up work in the GLSP VS Code and Theia integrations_ — at the time of writing, adopters wire it up manually using the URL surfaced in `InitializeResult.mcpServer`. +- **IDE-internal MCP clients** (VS Code Copilot chat, Theia AI, etc.) consume the resolved URL programmatically — the GLSP IDE integration reads it from `InitializeResult.mcpServer` (or, on the spawn side, the `MCP_SERVER_READY_MSG` stdout marker) and registers it with the host IDE's native MCP infrastructure: + - **Theia**: `@eclipse-glsp/theia-mcp-integration` ships a `FrontendApplicationContribution` that auto-registers every GLSP server's MCP URL with `@theia/ai-mcp` on startup. No adopter wiring beyond installing the package. + - **VS Code**: `@eclipse-glsp/vscode-integration` exposes a `GlspMcpServerProvider` (a `vscode.McpServerDefinitionProvider` implementation). Adopters declare an `mcpServerDefinitionProviders` contribution in their extension's `package.json`, register the provider via `vscode.lm.registerMcpServerDefinitionProvider`, and feed it the GLSP `InitializeResult` via `addServer(...)`. See `example/workflow/extension/src/workflow-extension.ts` in the integration repo for the canonical wiring. - **External MCP clients** (Claude Desktop, web clients, etc.) are configured separately by the user with a stable URL. For these, pick a fixed port and document it in the adopter's setup guide. --- diff --git a/packages/server-mcp/README.md b/packages/server-mcp/README.md index 6cdcc05..0532e9c 100644 --- a/packages/server-mcp/README.md +++ b/packages/server-mcp/README.md @@ -27,7 +27,8 @@ Load the MCP container modules in your GLSP server's DI configuration: ```typescript import { GModelStorage, WebSocketServerLauncher, createAppModule } from '@eclipse-glsp/server/node'; import { Container } from 'inversify'; -import { DefaultMcpDiagramModule, DefaultMcpServerModule } from '@eclipse-glsp/server-mcp'; +import { DefaultMcpDiagramModule } from '@eclipse-glsp/server-mcp'; +import { NodeMcpServerModule } from '@eclipse-glsp/server-mcp/node'; const appContainer = new Container(); appContainer.load(createAppModule(options)); @@ -38,16 +39,29 @@ const serverModule = new MyServerModule().configureDiagramModule(new MyDiagramMo const launcher = appContainer.resolve(WebSocketServerLauncher); // Launcher-level bindings — must not be part of `configureDiagramModule`. -launcher.configure(serverModule, new DefaultMcpServerModule()); +launcher.configure(serverModule, new NodeMcpServerModule()); ``` The two modules are deliberately separate because they bind into different container scopes: - `DefaultMcpDiagramModule` is mounted inside `configureDiagramModule`, so each `ClientSession.container` gets its own per-session services (`McpIdAliasService`, `McpModelSerializer`, the diagram-scope handler registries). -- `DefaultMcpServerModule` is mounted at the launcher container, so the MCP HTTP server, the option holder, and the server-scope tool/resource handlers live as launcher singletons. +- `NodeMcpServerModule` is mounted at the launcher container, so the MCP HTTP server, the option holder, and the server-scope tool/resource handlers live as launcher singletons. The MCP server itself is started lazily on the first GLSP `InitializeAction` that carries an `mcpServer` configuration. +### Browser / Web Worker target + +For browser, Bun, Deno, Cloudflare Workers, or any Fetch-shaped runtime, use the `browser` subpath. `McpWorkerBridge` wires `postMessage` traffic into the launcher for the common Service-Worker → Web-Worker setup: + +```typescript +import { McpWorkerBridge } from '@eclipse-glsp/server-mcp/browser'; + +const bridge = new McpWorkerBridge(); +launcher.configure(serverModule, bridge.createServerModule()); +``` + +The page-side proxy — a Service Worker that intercepts `fetch('/mcp', …)` and forwards each `Request` to the bridge over a `MessageChannel` — is host-side scaffolding that adopters own. See `examples/workflow-server-mcp-demo/mcp-service-worker.js` for a working reference implementation that the workflow browser demo uses end-to-end. + ## Further reading See [ARCHITECTURE.md](./ARCHITECTURE.md) for the architecture, security model, configuration surface, deployment guidance, and the extension cookbook. The workflow example (`examples/workflow-server`) is the canonical reference for adopter wiring; each shipped handler carries an LLM-facing `description` field that doubles as developer-facing documentation. diff --git a/packages/server-mcp/browser.d.ts b/packages/server-mcp/browser.d.ts new file mode 100644 index 0000000..46e1032 --- /dev/null +++ b/packages/server-mcp/browser.d.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './lib/browser/index'; diff --git a/packages/server-mcp/browser.js b/packages/server-mcp/browser.js new file mode 100644 index 0000000..35eb1c6 --- /dev/null +++ b/packages/server-mcp/browser.js @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +'use strict'; + +module.exports = require('./lib/browser/index'); diff --git a/packages/server-mcp/common.d.ts b/packages/server-mcp/common.d.ts new file mode 100644 index 0000000..2deed3f --- /dev/null +++ b/packages/server-mcp/common.d.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './lib/common/index'; diff --git a/packages/server-mcp/common.js b/packages/server-mcp/common.js new file mode 100644 index 0000000..407053c --- /dev/null +++ b/packages/server-mcp/common.js @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +'use strict'; + +module.exports = require('./lib/common/index'); diff --git a/packages/server-mcp/node.d.ts b/packages/server-mcp/node.d.ts new file mode 100644 index 0000000..0551c66 --- /dev/null +++ b/packages/server-mcp/node.d.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './lib/node/index'; diff --git a/packages/server-mcp/node.js b/packages/server-mcp/node.js new file mode 100644 index 0000000..d24a5c4 --- /dev/null +++ b/packages/server-mcp/node.js @@ -0,0 +1,18 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +'use strict'; + +module.exports = require('./lib/node/index'); diff --git a/packages/server-mcp/package.json b/packages/server-mcp/package.json index 1efd5ca..a72964a 100644 --- a/packages/server-mcp/package.json +++ b/packages/server-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@eclipse-glsp/server-mcp", "version": "2.7.0-next", - "description": "Extension of the GLSP Node Server for the Model Context Protocol", + "description": "Model Context Protocol (MCP) server for the GLSP TypeScript server — runs on Node, browser, and Fetch-API runtimes", "keywords": [ "eclipse", "graphics", @@ -29,16 +29,25 @@ "url": "https://projects.eclipse.org/projects/ecd.glsp" } ], - "main": "lib/index", - "types": "lib/index", + "main": "lib/node/index", + "browser": { + "lib/node/index": "./lib/browser/index" + }, + "types": "lib/common/index", "files": [ "lib", - "src" + "src", + "node.js", + "node.d.ts", + "browser.js", + "browser.d.ts", + "common.js", + "common.d.ts" ], "scripts": { "build": "tsc -b", "clean": "rimraf lib *.tsbuildinfo coverage .nyc_output", - "generate:index": "glsp generateIndex src -f -s", + "generate:index": "glsp generateIndex src/browser src/common src/node -f -s", "lint": "eslint --ext .ts,.tsx ./src", "test": "mocha --config ../../.mocharc \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "yarn test --reporter mocha-ctrf-json-reporter", @@ -47,11 +56,8 @@ }, "dependencies": { "@eclipse-glsp/server": "2.7.0-next", - "@modelcontextprotocol/sdk": "^1.29.0", - "express": "^5.2.1" - }, - "devDependencies": { - "@types/express": "^5.0.6" + "@hono/node-server": "^1.19.9", + "@modelcontextprotocol/sdk": "^1.29.0" }, "peerDependencies": { "inversify": "^6.1.3" diff --git a/packages/server-mcp/src/browser/index.ts b/packages/server-mcp/src/browser/index.ts new file mode 100644 index 0000000..d19d7a1 --- /dev/null +++ b/packages/server-mcp/src/browser/index.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from './modules/browser-mcp-server-module'; +export * from './reexport'; +export * from './server/browser-mcp-request-context'; +export * from './server/mcp-worker-bridge'; +export * from './server/web-mcp-server-launcher'; diff --git a/packages/server-mcp/src/browser/modules/browser-mcp-server-module.ts b/packages/server-mcp/src/browser/modules/browser-mcp-server-module.ts new file mode 100644 index 0000000..5244a3b --- /dev/null +++ b/packages/server-mcp/src/browser/modules/browser-mcp-server-module.ts @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { BindingTarget } from '@eclipse-glsp/server'; +import { AbstractMcpServerModule, DEFAULT_MCP_OPTIONS } from '../../common/modules/abstract-mcp-server-module'; +import { AbstractMcpServerLauncher } from '../../common/server/abstract-mcp-server-launcher'; +import { McpServerDefaults } from '../../common/server/mcp-options'; +import { McpRequestContext } from '../../common/server/mcp-request-context'; +import { BrowserMcpRequestContext } from '../server/browser-mcp-request-context'; +import { WebMcpServerLauncher } from '../server/web-mcp-server-launcher'; + +/** + * Default {@link AbstractMcpServerModule} entry point for browser/web hosts (Bun, Deno, Cloudflare + * Worker, Service Worker, in-page Worker). Ships GLSP-default option values (see + * {@link BrowserMcpServerModule.DEFAULT_OPTIONS}) on top of the abstract module's hook defaults, + * and binds the web {@link WebMcpServerLauncher} + {@link BrowserMcpRequestContext}. Adopter-provided + * overrides via the constructor merge on top. + * + * Node-only deploy-side fields (`host`, `allowedHosts`, `allowedOrigins`, `acknowledgedNoAuth`) + * are intentionally omitted from defaults — adopters set these for their deployment, or run their + * own auth middleware around {@link AbstractMcpServerLauncher.getRequestHandler}. + * + * @experimental The MCP integration is under active development. Option names, schema shapes, + * and handler contracts MAY change in minor releases until the feature graduates from + * experimental status. + */ +export class BrowserMcpServerModule extends AbstractMcpServerModule { + static readonly DEFAULT_OPTIONS: McpServerDefaults = { ...DEFAULT_MCP_OPTIONS }; + + constructor(overrides: McpServerDefaults = {}) { + super({ ...BrowserMcpServerModule.DEFAULT_OPTIONS, ...overrides }); + } + + protected override bindMcpServerLauncher(): BindingTarget { + return WebMcpServerLauncher; + } + + protected override bindMcpRequestContext(): BindingTarget { + return BrowserMcpRequestContext; + } +} diff --git a/packages/server-mcp/src/browser/reexport.ts b/packages/server-mcp/src/browser/reexport.ts new file mode 100644 index 0000000..be944aa --- /dev/null +++ b/packages/server-mcp/src/browser/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; diff --git a/packages/server-mcp/src/browser/server/browser-mcp-request-context.ts b/packages/server-mcp/src/browser/server/browser-mcp-request-context.ts new file mode 100644 index 0000000..cc1e4f6 --- /dev/null +++ b/packages/server-mcp/src/browser/server/browser-mcp-request-context.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { McpRequestContext, McpRequestExtra } from '../../common/server/mcp-request-context'; + +/** + * Browser-compatible {@link McpRequestContext} that holds one request context at a time. + * Hosts must serialise dispatches so they don't overwrite each other (e.g. via {@link McpWorkerBridge}). + */ +@injectable() +export class BrowserMcpRequestContext implements McpRequestContext { + protected current: McpRequestExtra | undefined; + protected concurrencyWarned = false; + + run(extra: McpRequestExtra, callback: () => R): R { + // Warn once if an adopter forgot to serialise dispatches — silent context corruption would be hard to debug. + if (this.current !== undefined && !this.concurrencyWarned) { + this.concurrencyWarned = true; + console.warn( + 'BrowserMcpRequestContext: concurrent run() detected — request contexts will overwrite each other. ' + + 'Serialise MCP dispatches (e.g. via McpWorkerBridge or an adopter-side queue around launcher.handleRequest).' + ); + } + const prior = this.current; + this.current = extra; + let result: R; + try { + result = callback(); + } catch (error) { + this.current = prior; + throw error; + } + if (result instanceof Promise) { + return result.finally(() => { + this.current = prior; + }) as unknown as R; + } + this.current = prior; + return result; + } + + getStore(): McpRequestExtra | undefined { + return this.current; + } +} diff --git a/packages/server-mcp/src/browser/server/mcp-worker-bridge.ts b/packages/server-mcp/src/browser/server/mcp-worker-bridge.ts new file mode 100644 index 0000000..d1298e6 --- /dev/null +++ b/packages/server-mcp/src/browser/server/mcp-worker-bridge.ts @@ -0,0 +1,200 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Disposable } from '@eclipse-glsp/server'; +import { AbstractMcpServerLauncher } from '../../common/server/abstract-mcp-server-launcher'; +import { McpServerDefaults } from '../../common/server/mcp-options'; +import { BrowserMcpServerModule } from '../modules/browser-mcp-server-module'; + +export const MCP_REQUEST_MESSAGE_TYPE = 'mcp-request'; +export const MCP_RESPONSE_MESSAGE_TYPE = 'mcp-response'; +export const MCP_INIT_PORT_MESSAGE_TYPE = 'mcp-init-port'; + +export interface McpRequestMessage { + type: typeof MCP_REQUEST_MESSAGE_TYPE; + id: string; + url: string; + method: string; + headers: Record; + body?: string; +} + +export interface McpResponseMessage { + type: typeof MCP_RESPONSE_MESSAGE_TYPE; + id: string; + status: number; + statusText: string; + headers: Record; + /** `ReadableStream` on Chromium (streams SSE); buffered `ArrayBuffer` on browsers without transferable streams. */ + body: ReadableStream | ArrayBuffer | null; +} + +/** Carries the SW↔Worker `MessagePort` transfer that wires MCP traffic over a dedicated channel. */ +export interface McpInitPortMessage { + type: typeof MCP_INIT_PORT_MESSAGE_TYPE; +} + +export function isMcpRequestMessage(value: unknown): value is McpRequestMessage { + return Object(value) === value && (value as { type?: unknown }).type === MCP_REQUEST_MESSAGE_TYPE; +} + +export function isMcpInitPortMessage(value: unknown): value is McpInitPortMessage { + return Object(value) === value && (value as { type?: unknown }).type === MCP_INIT_PORT_MESSAGE_TYPE; +} + +/** Probes whether the current realm can transfer `ReadableStream` via `postMessage` (Chromium yes; Firefox / Safari no). */ +export function canTransferReadableStream(): boolean { + try { + const probe = new ReadableStream(); + structuredClone(probe, { transfer: [probe] }); + return true; + } catch { + return false; + } +} + +/** Minimal Worker scope contract — defined locally so the package compiles without the `webworker` lib. */ +export interface McpBridgeScope { + addEventListener(type: 'message', listener: (event: MessageEvent) => void): void; + removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void; + postMessage(message: unknown, transfer?: Transferable[]): void; +} + +/** + * Bridges `postMessage` traffic to an {@link AbstractMcpServerLauncher} for browser / Web Worker + * adopters that proxy MCP requests via Service-Worker → Web-Worker plumbing. + * See `ARCHITECTURE.md` (Portable handler) for the message protocol and queueing semantics. + */ +export class McpWorkerBridge implements Disposable { + protected resolveLauncher!: (launcher: AbstractMcpServerLauncher) => void; + protected readonly launcherReady: Promise; + protected readonly listener = (event: MessageEvent): void => this.onMessage(event); + /** Serialise dispatches — `BrowserMcpRequestContext` is a single-slot store, not concurrency-safe. */ + protected dispatchChain: Promise = Promise.resolve(); + protected readonly streamTransferable: boolean = canTransferReadableStream(); + protected bufferingWarned = false; + + constructor(protected readonly scope: McpBridgeScope = self as unknown as McpBridgeScope) { + this.launcherReady = new Promise(resolve => { + this.resolveLauncher = resolve; + }); + this.scope.addEventListener('message', this.listener); + } + + dispose(): void { + this.scope.removeEventListener('message', this.listener); + } + + /** Returns a {@link BrowserMcpServerModule} that registers its launcher with this bridge on DI activation. */ + createServerModule(overrides?: McpServerDefaults): BrowserMcpServerModule { + const bridge = this; + class BridgedBrowserMcpServerModule extends BrowserMcpServerModule { + protected override onMcpServerLauncherActivated(launcher: AbstractMcpServerLauncher): void { + bridge.bindLauncher(launcher); + } + } + return new BridgedBrowserMcpServerModule(overrides); + } + + /** First call binds; subsequent calls are no-ops. */ + bindLauncher(launcher: AbstractMcpServerLauncher): void { + this.resolveLauncher(launcher); + } + + protected onMessage(event: MessageEvent): void { + const data: unknown = event.data; + if (isMcpRequestMessage(data)) { + this.enqueueDispatch(data, event.ports[0]); + return; + } + if (isMcpInitPortMessage(data)) { + const port = event.ports[0]; + if (!port) { + return; + } + port.onmessage = portEvent => { + const portData: unknown = portEvent.data; + if (isMcpRequestMessage(portData)) { + this.enqueueDispatch(portData, port); + } + }; + port.start(); + } + } + + protected enqueueDispatch(request: McpRequestMessage, port: MessagePort | undefined): void { + this.dispatchChain = this.dispatchChain + .then(() => this.dispatch(request, port)) + .catch(err => console.error('MCP worker bridge failed:', err)); + } + + protected async dispatch(request: McpRequestMessage, port: MessagePort | undefined): Promise { + try { + const launcher = await this.launcherReady; + const init: RequestInit = { method: request.method, headers: request.headers }; + if (typeof request.body === 'string') { + init.body = request.body; + } + const response = await launcher.handleRequest(new Request(request.url, init)); + const headers: Record = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + this.reply(port, { + type: MCP_RESPONSE_MESSAGE_TYPE, + id: request.id, + status: response.status, + statusText: response.statusText, + headers, + body: await this.encodeBody(response) + }); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + this.reply(port, { + type: MCP_RESPONSE_MESSAGE_TYPE, + id: request.id, + status: 500, + statusText: 'Internal Server Error', + headers: { 'content-type': 'text/plain' }, + body: await this.encodeBody(new Response(`MCP worker bridge error: ${message}`)) + }); + } + } + + /** Stream on Chromium; buffer to `ArrayBuffer` (still transferable) on browsers without transferable streams. */ + protected async encodeBody(response: Response): Promise | ArrayBuffer | null> { + if (this.streamTransferable) { + return response.body; + } + if (!this.bufferingWarned) { + this.bufferingWarned = true; + console.warn( + 'McpWorkerBridge: ReadableStream is not transferable in this browser — buffering MCP response bodies. ' + + 'Chunked / SSE streaming will deliver as a single chunk; non-streaming MCP calls are unaffected.' + ); + } + return response.arrayBuffer(); + } + + protected reply(port: MessagePort | undefined, message: McpResponseMessage): void { + const transfer: Transferable[] = message.body ? [message.body] : []; + if (port) { + port.postMessage(message, transfer); + } else { + this.scope.postMessage(message, transfer); + } + } +} diff --git a/packages/server-mcp/src/browser/server/web-mcp-server-launcher.ts b/packages/server-mcp/src/browser/server/web-mcp-server-launcher.ts new file mode 100644 index 0000000..66f93c2 --- /dev/null +++ b/packages/server-mcp/src/browser/server/web-mcp-server-launcher.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { AbstractMcpServerLauncher, FullMcpServerConfiguration, TransportEndpoint } from '../../common/server/abstract-mcp-server-launcher'; + +/** + * Web target for {@link AbstractMcpServerLauncher}. Web targets (Cloudflare Worker, Deno Deploy, + * Bun, Service Worker, in-page Worker) have no socket to bind; the adopter plugs the inherited + * {@link AbstractMcpServerLauncher.getRequestHandler} into their own listener. `bindTransport` + * is therefore a no-op. + * + * **Adopter responsibilities.** Authentication is the adopter's job. The MCP server has no + * built-in auth — wrap `getRequestHandler()` with whatever middleware your deployment requires + * (bearer token, mTLS at proxy, Cloudflare Access, OAuth, etc.). Session state lives in memory + * inside the launcher; multi-isolate / multi-region deployments that need shared session state + * must subclass and override the session store. + * + * @see `examples/workflow-server-bundled-web/smoke.html` for a Web Worker-based example. + */ +@injectable() +export class WebMcpServerLauncher extends AbstractMcpServerLauncher { + protected async bindTransport(_config: FullMcpServerConfiguration): Promise { + return {}; + } +} diff --git a/packages/server-mcp/src/index.ts b/packages/server-mcp/src/common/index.ts similarity index 94% rename from packages/server-mcp/src/index.ts rename to packages/server-mcp/src/common/index.ts index 9b4b935..2bd1ff4 100644 --- a/packages/server-mcp/src/index.ts +++ b/packages/server-mcp/src/common/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2025-2026 EclipseSource and others. + * Copyright (c) 2026 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ + +export * from './modules/abstract-mcp-server-module'; export * from './modules/mcp-diagram-module'; -export * from './modules/mcp-server-module'; export * from './prompts/handlers/describe-diagram-mcp-prompt-handler'; export * from './prompts/handlers/suggest-improvements-mcp-prompt-handler'; export * from './resources/handlers/diagram-png-mcp-resource-handler'; export * from './resources/handlers/diagram-svg-mcp-resource-handler'; export * from './resources/services/element-types-provider'; export * from './resources/services/mcp-model-serializer'; +export * from './server/abstract-mcp-server-launcher'; export * from './server/glsp-mcp-server'; export * from './server/lru-event-store'; export * from './server/mcp-diagram-handler-dispatcher'; @@ -28,7 +30,6 @@ export * from './server/mcp-diagram-prompt-handler-registry'; export * from './server/mcp-diagram-resource-handler-registry'; export * from './server/mcp-diagram-tool-handler-registry'; export * from './server/mcp-handler-shared'; -export * from './server/mcp-http-transport'; export * from './server/mcp-id-alias-service'; export * from './server/mcp-input-schemas'; export * from './server/mcp-label-provider'; @@ -40,7 +41,6 @@ export * from './server/mcp-progress-reporter'; export * from './server/mcp-prompt-handler'; export * from './server/mcp-request-context'; export * from './server/mcp-resource-handler'; -export * from './server/mcp-server-launcher'; export * from './server/mcp-session'; export * from './server/mcp-tool-handler'; export * from './tools/handlers/count-elements-mcp-tool-handler'; diff --git a/packages/server-mcp/src/modules/mcp-server-module.ts b/packages/server-mcp/src/common/modules/abstract-mcp-server-module.ts similarity index 76% rename from packages/server-mcp/src/modules/mcp-server-module.ts rename to packages/server-mcp/src/common/modules/abstract-mcp-server-module.ts index e63a591..74def41 100644 --- a/packages/server-mcp/src/modules/mcp-server-module.ts +++ b/packages/server-mcp/src/common/modules/abstract-mcp-server-module.ts @@ -29,24 +29,24 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { interfaces } from 'inversify'; import { DescribeDiagramMcpPromptHandler } from '../prompts/handlers/describe-diagram-mcp-prompt-handler'; import { SuggestImprovementsMcpPromptHandler } from '../prompts/handlers/suggest-improvements-mcp-prompt-handler'; -import { ElementTypesMcpToolHandler } from '../tools/handlers/element-types-mcp-tool-handler'; -import { SessionInfoMcpToolHandler } from '../tools/handlers/session-info-mcp-tool-handler'; +import { AbstractMcpServerLauncher } from '../server/abstract-mcp-server-launcher'; import { DefaultGLSPMcpServer, GLSPMcpServerFactory } from '../server/glsp-mcp-server'; +import { LruEventStore } from '../server/lru-event-store'; import { DefaultMcpDiagramHandlerDispatcher, McpDiagramHandlerDispatcher } from '../server/mcp-diagram-handler-dispatcher'; import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from '../server/mcp-log-level-registry'; -import { LruEventStore } from '../server/lru-event-store'; -import { McpHttpTransport } from '../server/mcp-http-transport'; import { McpLogger } from '../server/mcp-logger'; import { McpServerDefaults, McpServerOptions } from '../server/mcp-options'; import { McpProgressReporter } from '../server/mcp-progress-reporter'; import { McpPromptHandler } from '../server/mcp-prompt-handler'; +import { McpRequestContext } from '../server/mcp-request-context'; import { McpResourceHandler } from '../server/mcp-resource-handler'; -import { McpServerLauncher } from '../server/mcp-server-launcher'; import { McpToolHandler } from '../server/mcp-tool-handler'; +import { ElementTypesMcpToolHandler } from '../tools/handlers/element-types-mcp-tool-handler'; +import { SessionInfoMcpToolHandler } from '../tools/handlers/session-info-mcp-tool-handler'; /** * GLSP-generic default agent persona — adopters typically pass a product-specific persona to - * the {@link DefaultMcpServerModule} constructor (e.g. workflow-server might say "You are the + * the concrete server module's constructor (e.g. workflow-server might say "You are the * Workflow Modeling Agent..."). * * **Spec note.** This is wired to the MCP `instructions` field, which the spec describes as @@ -55,7 +55,7 @@ import { McpToolHandler } from '../server/mcp-tool-handler'; * via `mcpDefaults.agentPersona`. Don't trim on autopilot if a "concise" interpretation drifts * back: the verbose form materially improves LLM tool-use compliance for graphical modelling. */ -const DEFAULT_AGENT_PERSONA = ` +export const DEFAULT_AGENT_PERSONA = ` You are the GLSP Modeling Agent. Your primary goal is to assist in the creation and modification of graphical models using the GLSP MCP server. You have to adhere to the following principles: - MCP-Interaction: Any modeling related activity has to occur using the MCP server. @@ -73,6 +73,17 @@ GLSP MCP server. You have to adhere to the following principles: \`action: "center-on-elements"\` — to point the user at the elements you reference. `; +/** + * Target-neutral default option values shared by every {@link AbstractMcpServerModule} subclass. + * Node and Browser concrete modules spread this and add their own target-specific fields + * (e.g. `host`/`allowedHosts` on Node). + */ +export const DEFAULT_MCP_OPTIONS: McpServerDefaults = { + dataMode: 'tools', + agentPersona: DEFAULT_AGENT_PERSONA, + eventStoreLimit: LruEventStore.DEFAULT_LIMIT +}; + /** * Multi-binding helper for MCP handler classes. Singleton-scoped sibling of core's * `MultiBinding` — same `binding.add(...)` / `binding.rebind(old, new)` adopter shape, but @@ -91,9 +102,11 @@ export class McpHandlerMultiBinding extends AbstractMultiBinding extends AbstractMultiBinding extends AbstractMultiBinding { + this.onMcpServerLauncherActivated(launcher); + return launcher; + }); + // The launcher is aliased under two additional service identifiers so core's existing // multi-bindings pick it up alongside the rest of the server's contributions/listeners. - bind(GLSPServerInitializer).toService(McpServerLauncher); - bind(GLSPServerListener).toService(McpServerLauncher); - applyBindingTarget(context, McpHttpTransport, this.bindMcpHttpTransport()).inSingletonScope(); + bind(GLSPServerInitializer).toService(AbstractMcpServerLauncher); + bind(GLSPServerListener).toService(AbstractMcpServerLauncher); applyBindingTarget(context, McpDiagramHandlerDispatcher, this.bindMcpDiagramHandlerDispatcher()).inSingletonScope(); applyBindingTarget(context, McpServerOptions, this.bindMcpServerOptions()).inSingletonScope(); applyBindingTarget(context, McpServerDefaults, this.bindMcpServerDefaults()); applyBindingTarget(context, McpLogger, this.bindMcpLogger()).inSingletonScope(); applyBindingTarget(context, McpLogLevelRegistry, this.bindMcpLogLevelRegistry()).inSingletonScope(); applyBindingTarget(context, McpProgressReporter, this.bindMcpProgressReporter()).inSingletonScope(); + applyBindingTarget(context, McpRequestContext, this.bindMcpRequestContext()).inSingletonScope(); applyBindingTarget(context, GLSPMcpServerFactory, this.bindGLSPMcpServerFactory()); this.configureMultiBinding(new McpHandlerMultiBinding(McpToolHandler), binding => this.configureToolHandlers(binding as McpHandlerMultiBinding) @@ -160,18 +173,23 @@ export abstract class AbstractMcpServerModule extends GLSPModule { } /** - * {@link McpServerLauncher} binding. Bound as a singleton AND aliased to - * `GLSPServerInitializer` + `GLSPServerListener` (the launcher implements both). - * Override to swap in a custom launcher impl. + * {@link AbstractMcpServerLauncher} binding. Bound as a singleton AND aliased to + * `GLSPServerInitializer` + `GLSPServerListener` (the launcher implements both). Concrete + * modules return the target-specific launcher class (Node, Web, ...). */ - protected bindMcpServerLauncher(): BindingTarget { - return McpServerLauncher; - } + protected abstract bindMcpServerLauncher(): BindingTarget; - /** {@link McpHttpTransport} binding. Override to swap to a different transport implementation. */ - protected bindMcpHttpTransport(): BindingTarget { - return McpHttpTransport; - } + /** + * Hook fired once when the {@link AbstractMcpServerLauncher} singleton is first resolved by + * the DI container — lets adopters obtain the instance without subclassing it. No-op by default. + */ + protected onMcpServerLauncherActivated(_launcher: AbstractMcpServerLauncher): void {} + + /** + * {@link McpRequestContext} binding. Concrete modules return the target-specific async-context + * implementation (e.g. Node's `AsyncLocalStorage`-backed variant or a browser-friendly one). + */ + protected abstract bindMcpRequestContext(): BindingTarget; /** * {@link McpDiagramHandlerDispatcher} binding. Owns diagram-scope handler discovery, @@ -196,16 +214,12 @@ export abstract class AbstractMcpServerModule extends GLSPModule { return { constantValue: this.defaultOptions }; } - /** - * {@link McpLogger} binding. Bound on the server container; per-session containers inherit - * it, so handlers at any scope can inject it; routes through the active MCP request via - * {@link mcpRequestContext}. - */ + /** {@link McpLogger} binding — routes through the active MCP request via {@link McpRequestContext}. */ protected bindMcpLogger(): BindingTarget { return McpLogger; } - /** {@link McpProgressReporter} binding. Same scope/lifecycle story as {@link bindMcpLogger}. */ + /** {@link McpProgressReporter} binding. */ protected bindMcpProgressReporter(): BindingTarget { return McpProgressReporter; } @@ -259,31 +273,3 @@ export abstract class AbstractMcpServerModule extends GLSPModule { binding.add(SuggestImprovementsMcpPromptHandler); } } - -/** - * Default {@link AbstractMcpServerModule} entry point. Ships GLSP-default option values (see - * {@link DEFAULT_OPTIONS}) on top of the abstract module's hook defaults. Adopter-provided - * overrides via the constructor merge on top. - * - * @experimental The MCP integration is under active development. Option names, schema shapes, - * and handler contracts MAY change in minor releases until the feature graduates from - * experimental status. - */ -export class DefaultMcpServerModule extends AbstractMcpServerModule { - static readonly DEFAULT_OPTIONS: McpServerDefaults = { - host: '127.0.0.1', - allowedHosts: ['127.0.0.1', 'localhost'], - // `allowedOrigins` deliberately undefined: accept absent Origin (typical for desktop-IDE - // MCP clients) and rely on Host validation to gate DNS-rebinding. Adopters whose - // deployment is browser-fronted set this explicitly to their frontend's origin. - dataMode: 'tools', - agentPersona: DEFAULT_AGENT_PERSONA, - // 10K events per session is generous for typical workloads (a few MB) and large enough - // that disconnects within seconds recover via `Last-Event-ID` resumability. - eventStoreLimit: LruEventStore.DEFAULT_LIMIT - }; - - constructor(overrides: McpServerDefaults = {}) { - super({ ...DefaultMcpServerModule.DEFAULT_OPTIONS, ...overrides }); - } -} diff --git a/packages/server-mcp/src/modules/mcp-diagram-module.ts b/packages/server-mcp/src/common/modules/mcp-diagram-module.ts similarity index 100% rename from packages/server-mcp/src/modules/mcp-diagram-module.ts rename to packages/server-mcp/src/common/modules/mcp-diagram-module.ts diff --git a/packages/server-mcp/src/prompts/handlers/describe-diagram-mcp-prompt-handler.ts b/packages/server-mcp/src/common/prompts/handlers/describe-diagram-mcp-prompt-handler.ts similarity index 100% rename from packages/server-mcp/src/prompts/handlers/describe-diagram-mcp-prompt-handler.ts rename to packages/server-mcp/src/common/prompts/handlers/describe-diagram-mcp-prompt-handler.ts diff --git a/packages/server-mcp/src/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts b/packages/server-mcp/src/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts similarity index 100% rename from packages/server-mcp/src/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts rename to packages/server-mcp/src/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts diff --git a/packages/server-mcp/src/resources/handlers/diagram-png-mcp-resource-handler.ts b/packages/server-mcp/src/common/resources/handlers/diagram-png-mcp-resource-handler.ts similarity index 100% rename from packages/server-mcp/src/resources/handlers/diagram-png-mcp-resource-handler.ts rename to packages/server-mcp/src/common/resources/handlers/diagram-png-mcp-resource-handler.ts diff --git a/packages/server-mcp/src/resources/handlers/diagram-svg-mcp-resource-handler.ts b/packages/server-mcp/src/common/resources/handlers/diagram-svg-mcp-resource-handler.ts similarity index 100% rename from packages/server-mcp/src/resources/handlers/diagram-svg-mcp-resource-handler.ts rename to packages/server-mcp/src/common/resources/handlers/diagram-svg-mcp-resource-handler.ts diff --git a/packages/server-mcp/src/resources/services/element-types-provider.ts b/packages/server-mcp/src/common/resources/services/element-types-provider.ts similarity index 100% rename from packages/server-mcp/src/resources/services/element-types-provider.ts rename to packages/server-mcp/src/common/resources/services/element-types-provider.ts diff --git a/packages/server-mcp/src/resources/services/mcp-model-serializer.ts b/packages/server-mcp/src/common/resources/services/mcp-model-serializer.ts similarity index 100% rename from packages/server-mcp/src/resources/services/mcp-model-serializer.ts rename to packages/server-mcp/src/common/resources/services/mcp-model-serializer.ts diff --git a/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.spec.ts b/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.spec.ts new file mode 100644 index 0000000..1beffd5 --- /dev/null +++ b/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.spec.ts @@ -0,0 +1,458 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + ClientSessionManager, + InitializeParameters, + InitializeResult, + Logger, + NullLogger, + McpInitializeResult +} from '@eclipse-glsp/server'; +import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; +import { expect } from 'chai'; +import { Container, ContainerModule } from 'inversify'; +import * as z from 'zod/v4'; +import { AbstractMcpServerLauncher, FullMcpServerConfiguration, TransportEndpoint } from './abstract-mcp-server-launcher'; +import { DefaultGLSPMcpServer, GLSPMcpServerFactory } from './glsp-mcp-server'; +import { McpDiagramHandlerDispatcher } from './mcp-diagram-handler-dispatcher'; +import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from './mcp-log-level-registry'; +import { McpServerDefaults, McpServerOptions } from './mcp-options'; + +class StubDispatcher implements McpDiagramHandlerDispatcher { + harvest(): void { + /* no-op */ + } + reset(): void { + /* no-op */ + } + hasDiagramTools(): boolean { + return false; + } + hasDiagramResources(): boolean { + return false; + } + hasDiagramPrompts(): boolean { + return false; + } + registerAll(): void { + /* no-op */ + } +} + +class StubClientSessionManager { + addListener(): boolean { + return true; + } + removeListener(): boolean { + return true; + } + getSessions(): unknown[] { + return []; + } + getSession(): unknown { + return undefined; + } + getSessionsByType(): unknown[] { + return []; + } + disposeClientSession(): void { + /* no-op */ + } + addClientSession(): boolean { + return true; + } +} + +interface RegisteredAuthCapture { + authInfo?: AuthInfo; + invoked: boolean; +} + +/** + * Test subclass: `bindTransport` is a no-op so the test drives `handleRequest` directly against + * synthetic `Request` objects, asserting the routed `Response`. + */ +class TestNodeMcpServerLauncher extends AbstractMcpServerLauncher { + readonly authCapture: RegisteredAuthCapture = { invoked: false }; + + protected async bindTransport(_config: FullMcpServerConfiguration): Promise { + return {}; + } + + /** + * Register an `echo` tool on each new GLSP MCP server so we can drive a tool/list + tools/call + * round-trip and capture the `extra.authInfo` argument forwarded by the SDK. + */ + protected override createGlspMcpServer(config: FullMcpServerConfiguration): DefaultGLSPMcpServer { + const server = super.createGlspMcpServer(config) as DefaultGLSPMcpServer; + const capture = this.authCapture; + server.registerTool( + 'echo', + { description: 'Echoes back the supplied message.', inputSchema: { message: z.string() } }, + async ({ message }, extra) => { + capture.invoked = true; + capture.authInfo = extra?.authInfo; + return { content: [{ type: 'text', text: message }] }; + } + ); + return server; + } +} + +function buildLauncher(): TestNodeMcpServerLauncher { + const container = new Container(); + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new NullLogger()); + bind(McpServerOptions).toSelf().inSingletonScope(); + bind(McpServerDefaults).toConstantValue({}); + bind(McpDiagramHandlerDispatcher).toConstantValue(new StubDispatcher()); + bind(McpLogLevelRegistry).to(DefaultMcpLogLevelRegistry).inSingletonScope(); + bind(ClientSessionManager).toConstantValue(new StubClientSessionManager()); + const factory: GLSPMcpServerFactory = (mcpServer, options) => new DefaultGLSPMcpServer(mcpServer, options, new NullLogger()); + bind(GLSPMcpServerFactory).toConstantValue(factory); + bind(TestNodeMcpServerLauncher).toSelf().inSingletonScope(); + }) + ); + return container.get(TestNodeMcpServerLauncher); +} + +const INIT_BODY = { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2025-06-18', + capabilities: {}, + clientInfo: { name: 'spec-client', version: '0.0.1' } + } +}; + +async function initLauncher( + launcher: TestNodeMcpServerLauncher, + overrides: Partial<{ route: string; port: number; host: string; name: string; options: Record }> = {} +): Promise { + const params: InitializeParameters = { + applicationId: 'spec-app', + clientSessionId: 'spec-session', + protocolVersion: '1.0.0', + args: {}, + mcpServer: { + port: overrides.port ?? 0, + host: overrides.host ?? '127.0.0.1', + route: overrides.route ?? '/mcp', + name: overrides.name ?? 'spec', + options: overrides.options ?? {} + } + } as unknown as InitializeParameters; + const baseResult: InitializeResult = { protocolVersion: '1.0.0', serverActions: {} } as unknown as InitializeResult; + return launcher.initializeServer({} as never, params, baseResult); +} + +/** + * Read an SSE stream into a list of `data:` payloads. The Web-standard transport streams JSON-RPC + * responses over `text/event-stream`; we only need to find the first complete event for assertions. + */ +async function readSseFirstData(response: Response, timeoutMs = 2000): Promise { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Response has no body'); + } + const decoder = new TextDecoder(); + let buffer = ''; + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + const { value, done } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + for (const block of buffer.split('\n\n')) { + const dataLine = block.split('\n').find(line => line.startsWith('data: ')); + if (dataLine) { + reader.cancel().catch(() => undefined); + return JSON.parse(dataLine.slice('data: '.length)); + } + } + } + reader.cancel().catch(() => undefined); + throw new Error('Timed out waiting for SSE data event'); +} + +function postInit(): Request { + return new Request('http://test/mcp', { + method: 'POST', + headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' }, + body: JSON.stringify(INIT_BODY) + }); +} + +function postJson(sessionId: string | undefined, body: unknown): Request { + const headers: Record = { + 'content-type': 'application/json', + accept: 'application/json, text/event-stream' + }; + if (sessionId !== undefined) headers['mcp-session-id'] = sessionId; + return new Request('http://test/mcp', { method: 'POST', headers, body: JSON.stringify(body) }); +} + +describe('AbstractMcpServerLauncher.handleRequest', () => { + let launcher: TestNodeMcpServerLauncher; + + beforeEach(async () => { + launcher = buildLauncher(); + await initLauncher(launcher); + }); + + afterEach(() => { + launcher.dispose(); + }); + + it('POST initialize creates a session and returns 200 with mcp-session-id header', async () => { + const response = await launcher.handleRequest(postInit()); + expect(response.status).to.equal(200); + expect(response.headers.get('mcp-session-id'), 'must echo a session id after initialize').to.be.a('string'); + }); + + it('POST to a known session dispatches to its transport (tools/call round-trip)', async () => { + const initResponse = await launcher.handleRequest(postInit()); + const sessionId = initResponse.headers.get('mcp-session-id')!; + // Read+discard the init SSE response so the transport is ready for the next POST. + await readSseFirstData(initResponse).catch(() => undefined); + + // Per MCP spec, send `notifications/initialized` after the initialize handshake. + await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', method: 'notifications/initialized', params: {} })); + + const callResponse = await launcher.handleRequest( + postJson(sessionId, { + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { name: 'echo', arguments: { message: 'hello' } } + }) + ); + expect(callResponse.status).to.equal(200); + const payload = (await readSseFirstData(callResponse)) as { result?: { content?: Array<{ text?: string }> } }; + expect(payload.result?.content?.[0]?.text).to.equal('hello'); + }); + + it('POST without session id, non-initialize body → 400 with JSON-RPC error envelope', async () => { + const response = await launcher.handleRequest(postJson(undefined, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} })); + expect(response.status).to.equal(400); + const payload = (await response.json()) as { jsonrpc: string; error: { code: number; message: string }; id: unknown }; + expect(payload.jsonrpc).to.equal('2.0'); + // eslint-disable-next-line no-null/no-null + expect(payload.id).to.equal(null); + expect(payload.error.code).to.equal(-32000); + expect(payload.error.message).to.match(/No valid session ID/); + }); + + it('POST with unknown session id → 404 with JSON-RPC error envelope', async () => { + const response = await launcher.handleRequest( + postJson('not-a-real-session', { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }) + ); + expect(response.status).to.equal(404); + const payload = (await response.json()) as { error: { code: number } }; + expect(payload.error.code).to.equal(-32001); + }); + + it('GET with accept: text/event-stream on a known session → 200, text/event-stream', async () => { + const initResponse = await launcher.handleRequest(postInit()); + const sessionId = initResponse.headers.get('mcp-session-id')!; + await readSseFirstData(initResponse).catch(() => undefined); + + const getResponse = await launcher.handleRequest( + new Request('http://test/mcp', { + method: 'GET', + headers: { accept: 'text/event-stream', 'mcp-session-id': sessionId } + }) + ); + expect(getResponse.status).to.equal(200); + expect(getResponse.headers.get('content-type')).to.match(/text\/event-stream/); + getResponse.body?.cancel().catch(() => undefined); + }); + + it('GET on unknown session → 404', async () => { + const response = await launcher.handleRequest( + new Request('http://test/mcp', { + method: 'GET', + headers: { accept: 'text/event-stream', 'mcp-session-id': 'nope' } + }) + ); + expect(response.status).to.equal(404); + }); + + it('DELETE on known session fires onSessionClosed; subsequent POSTs return 404', async () => { + const closedIds: string[] = []; + launcher.onSessionClosed(sessionId => closedIds.push(sessionId)); + + const initResponse = await launcher.handleRequest(postInit()); + const sessionId = initResponse.headers.get('mcp-session-id')!; + await readSseFirstData(initResponse).catch(() => undefined); + + const deleteResponse = await launcher.handleRequest( + new Request('http://test/mcp', { method: 'DELETE', headers: { 'mcp-session-id': sessionId } }) + ); + expect(deleteResponse.status).to.be.lessThan(300); + expect(closedIds).to.include(sessionId); + + const followUp = await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })); + expect(followUp.status).to.equal(404); + }); + + it('Non-init POST with unsupported MCP-Protocol-Version → 400 with JSON-RPC error envelope', async () => { + const response = await launcher.handleRequest( + new Request('http://test/mcp', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'mcp-session-id': 'anything', + 'mcp-protocol-version': '1999-01-01' + }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }) + }) + ); + expect(response.status).to.equal(400); + const payload = (await response.json()) as { error: { code: number; message: string } }; + expect(payload.error.code).to.equal(-32000); + expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/); + }); + + it('POST with no MCP-Protocol-Version header passes the version gate (falls through to session-id check)', async () => { + // Spec: server defaults to `2025-03-26` when the header is absent. We assert by + // sending no session id and observing the 400 "No valid session ID" — proving the + // version gate didn't short-circuit. + const response = await launcher.handleRequest( + new Request('http://test/mcp', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }) + }) + ); + expect(response.status).to.equal(400); + const payload = (await response.json()) as { error: { message: string } }; + expect(payload.error.message).to.match(/No valid session ID/); + }); + + it('authInfo from handleRequest reaches the registered tool handler extra', async () => { + const initResponse = await launcher.handleRequest(postInit()); + const sessionId = initResponse.headers.get('mcp-session-id')!; + await readSseFirstData(initResponse).catch(() => undefined); + + await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', method: 'notifications/initialized', params: {} })); + + const authInfo: AuthInfo = { token: 'bearer-xyz', clientId: 'spec-client', scopes: ['mcp:tools'] }; + const callResponse = await launcher.handleRequest( + postJson(sessionId, { + jsonrpc: '2.0', + id: 3, + method: 'tools/call', + params: { name: 'echo', arguments: { message: 'auth-check' } } + }), + { authInfo } + ); + await readSseFirstData(callResponse).catch(() => undefined); + + expect(launcher.authCapture.invoked).to.equal(true); + expect(launcher.authCapture.authInfo?.token).to.equal('bearer-xyz'); + expect(launcher.authCapture.authInfo?.clientId).to.equal('spec-client'); + }); + + it('onSessionInitialized fires on initialize', async () => { + const sessionIds: string[] = []; + launcher.onSessionInitialized(session => sessionIds.push(session.sessionId)); + + const response = await launcher.handleRequest(postInit()); + const sessionId = response.headers.get('mcp-session-id')!; + expect(sessionIds).to.include(sessionId); + }); + + it('Route mismatch (POST /other when launcher route is /mcp) → 404', async () => { + const response = await launcher.handleRequest( + new Request('http://test/other', { method: 'POST', body: '{}', headers: { 'content-type': 'application/json' } }) + ); + expect(response.status).to.equal(404); + }); + + it('Unsupported method (PUT /mcp) → 405 with Allow header', async () => { + const response = await launcher.handleRequest(new Request('http://test/mcp', { method: 'PUT' })); + expect(response.status).to.equal(405); + expect(response.headers.get('Allow')).to.equal('POST, GET, DELETE'); + }); +}); + +describe('AbstractMcpServerLauncher · initializeServer lifecycle', () => { + it('Idempotent initializeServer: second call reuses existing config without re-binding', async () => { + const launcher = buildLauncher(); + let bindCount = 0; + const original = (launcher as unknown as { bindTransport: () => Promise }).bindTransport.bind(launcher); + (launcher as unknown as { bindTransport: () => Promise }).bindTransport = async () => { + bindCount += 1; + return original(); + }; + + await initLauncher(launcher); + await initLauncher(launcher); + expect(bindCount).to.equal(1); + launcher.dispose(); + }); + + it('dispose() closes sessions and resets state; a subsequent initializeServer boots cleanly', async () => { + const launcher = buildLauncher(); + await initLauncher(launcher); + const firstResponse = await launcher.handleRequest(postInit()); + expect(firstResponse.headers.get('mcp-session-id')).to.be.a('string'); + await readSseFirstData(firstResponse).catch(() => undefined); + + launcher.dispose(); + + // Re-init from the same instance — proves dispose() cleared the singleton state. + await initLauncher(launcher, { route: '/mcp', name: 'spec-2' }); + const secondResponse = await launcher.handleRequest(postInit()); + expect(secondResponse.status).to.equal(200); + expect(secondResponse.headers.get('mcp-session-id')).to.be.a('string'); + launcher.dispose(); + }); + + it('honors the custom `route` from initialize params', async () => { + const launcher = buildLauncher(); + await initLauncher(launcher, { route: '/custom' }); + + const wrongRoute = await launcher.handleRequest(postInit()); + expect(wrongRoute.status).to.equal(404); + + const correctRoute = await launcher.handleRequest( + new Request('http://test/custom', { + method: 'POST', + headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' }, + body: JSON.stringify(INIT_BODY) + }) + ); + expect(correctRoute.status).to.equal(200); + launcher.dispose(); + }); + + it('attaches mcpServer.url to the returned InitializeResult when bindTransport reports a URL', async () => { + const launcher = buildLauncher(); + // Override the test subclass's `bindTransport` to report a synthetic URL. + (launcher as unknown as { bindTransport: () => Promise }).bindTransport = async () => ({ + url: 'http://announced.example/mcp' + }); + const result = await initLauncher(launcher); + expect(McpInitializeResult.is(result)).to.equal(true); + expect(McpInitializeResult.getServer(result)?.url).to.equal('http://announced.example/mcp'); + launcher.dispose(); + }); +}); diff --git a/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.ts b/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.ts new file mode 100644 index 0000000..4395c34 --- /dev/null +++ b/packages/server-mcp/src/common/server/abstract-mcp-server-launcher.ts @@ -0,0 +1,524 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { McpServerOptions as McpServerOptionsType } from '@eclipse-glsp/protocol'; +import { + ClientSessionListener, + ClientSessionManager, + Disposable, + DisposableCollection, + Emitter, + GLSPServer, + GLSPServerInitializer, + GLSPServerListener, + InitializeParameters, + InitializeResult, + Logger, + McpInitializeParameters, + McpInitializeResult, + McpServerConfiguration, + McpServerInitOptions +} from '@eclipse-glsp/server'; +import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'; +import { + ServerCapabilities, + SUPPORTED_PROTOCOL_VERSIONS, + SetLevelRequestSchema, + isInitializeRequest +} from '@modelcontextprotocol/sdk/types.js'; +import { inject, injectable, multiInject, optional } from 'inversify'; +import { version as packageVersion } from '../../../package.json'; +import { GLSPMcpServer, GLSPMcpServerFactory } from './glsp-mcp-server'; +import { LruEventStore } from './lru-event-store'; +import { McpDiagramHandlerDispatcher } from './mcp-diagram-handler-dispatcher'; +import { McpLogLevelRegistry } from './mcp-log-level-registry'; +import { McpServerDefaults, McpServerOptions } from './mcp-options'; +import { McpPromptHandler } from './mcp-prompt-handler'; +import { McpResourceHandler } from './mcp-resource-handler'; +import { McpSession, McpSessionId, WithSessionId } from './mcp-session'; +import { McpToolHandler } from './mcp-tool-handler'; + +/** + * Server version reported in MCP `initialize` handshake responses (the SDK's `serverInfo.version` + * field). Sourced from the package's own `package.json` so adopters and clients can tell builds + * apart without the server author having to remember to bump a literal. + */ +export const SERVER_VERSION: string = packageVersion; + +/** + * Launcher's internal handoff shape: everything from the public {@link McpServerConfiguration} + * with all fields resolved, plus `host`. `host` is deliberately *not* in the public protocol's + * init schema — it lives on `McpServerDeployOptions` (deploy-only) rather than + * `McpServerInitOptions` (init-controllable). The launcher reads it from the adopter-supplied + * defaults via `McpServerOptions.values.host`. + */ +export type FullMcpServerConfiguration = Omit, 'options'> & { + host: string; + options: McpServerOptionsType; +}; + +/** + * Where this launcher's transport can be reached. Node binders populate `url` (loopback HTTP + * announcement). The web binder returns `{}` because the adopter wires the handler into its + * own listener and may not have a routable URL. + */ +export interface TransportEndpoint { + url?: string; + headers?: Record; +} + +/** + * Per-call options accepted by {@link AbstractMcpServerLauncher.handleRequest}. Adopters wrap + * their own auth middleware around the handler and forward parsed bearer/JWT/etc. info here. + */ +export interface McpRequestHandlerOptions { + /** Authenticated principal info — passed through to the SDK so tool handlers can read it via `extra.authInfo`. */ + authInfo?: AuthInfo; +} + +/** + * Defense-in-depth filter for the init-side options payload. The static type already rules out + * deploy-only fields (`host`, `allowedHosts`, `allowedOrigins`, `acknowledgedNoAuth`) on + * `McpServerConfiguration.options`, but the wire payload is JSON, so a malformed or malicious + * client could smuggle extra keys. Destructure-based pick drops anything outside the allowed set + * so deploy-only fields are sourced *only* from adopter defaults. + * + * **Update this allowlist when adding a field to `McpServerInitOptions`** — the destructure + * below is the single source of truth for which init-side fields cross the wire. + * + * Exported for regression-test access only; not part of the public package surface. + */ +export function pickInitOptions(options: McpServerInitOptions): McpServerInitOptions { + const { dataMode, agentPersona, eventStoreLimit } = options; + const picked: McpServerInitOptions = {}; + if (dataMode !== undefined) picked.dataMode = dataMode; + if (agentPersona !== undefined) picked.agentPersona = agentPersona; + if (eventStoreLimit !== undefined) picked.eventStoreLimit = eventStoreLimit; + return picked; +} + +/** + * JSON-RPC 2.0 § 5 mandates `null` for error responses where the request id cannot be determined + * (e.g., parse errors, batch-level rejection, missing session id). Centralized so the unavoidable + * `null` literal lives behind one eslint exception instead of many. + */ +// eslint-disable-next-line no-null/no-null +const JSON_RPC_NULL_ID = null; + +/** + * Portable, target-neutral MCP server launcher. Owns the per-MCP-session + * {@link WebStandardStreamableHTTPServerTransport} map, the per-session {@link GLSPMcpServer} + * registry, handler registration, and the Fetch-shaped `(Request) => Promise` entry + * point. Concrete subclasses bind a listener (or no listener) in {@link bindTransport}. + * + * **Session state is single-process and in-memory.** Multi-isolate/Durable-Objects adopters + * need to override the session storage hook in a follow-up. + * + * **Authentication** is the adopter's responsibility on non-Node targets — wrap the handler + * with whatever middleware the deployment requires (bearer, mTLS at proxy, Cloudflare Access, + * etc.). Node binders run the {@link assertLoopbackOrAcknowledged} guard since they bind the + * socket themselves. + */ +@injectable() +export abstract class AbstractMcpServerLauncher implements GLSPServerInitializer, GLSPServerListener, Disposable { + @inject(Logger) protected logger: Logger; + + @inject(McpServerOptions) protected mcpOptions: McpServerOptions; + + @inject(McpServerDefaults) protected mcpDefaults: McpServerDefaults; + + @inject(GLSPMcpServerFactory) protected glspMcpServerFactory: GLSPMcpServerFactory; + + @inject(McpDiagramHandlerDispatcher) protected dispatcher: McpDiagramHandlerDispatcher; + + @inject(McpLogLevelRegistry) protected logLevelRegistry: McpLogLevelRegistry; + + @inject(ClientSessionManager) protected clientSessionManager: ClientSessionManager; + + @multiInject(McpToolHandler) @optional() protected toolHandlers: McpToolHandler[] = []; + + @multiInject(McpResourceHandler) @optional() protected resourceHandlers: McpResourceHandler[] = []; + + @multiInject(McpPromptHandler) @optional() protected promptHandlers: McpPromptHandler[] = []; + + protected toDispose = new DisposableCollection(); + protected serverUrl: string | undefined; + protected serverConfig: FullMcpServerConfiguration | undefined; + + /** Per-MCP-session transport — populated on session-init, cleared on session-close. */ + protected readonly sessions = new Map(); + + /** Per-MCP-session GLSPMcpServer registry — populated on session-init, cleared on session-close. */ + protected readonly sessionServers = new Map(); + + protected onSessionInitializedEmitter = new Emitter(); + readonly onSessionInitialized = this.onSessionInitializedEmitter.event; + protected onSessionClosedEmitter = new Emitter(); + readonly onSessionClosed = this.onSessionClosedEmitter.event; + + async initializeServer(server: GLSPServer, params: InitializeParameters, result: InitializeResult): Promise { + const mcpServerParam = McpInitializeParameters.getServerConfig(params); + if (!mcpServerParam) { + return result; + } + + // Idempotent: subsequent client sessions of the same GLSP server reuse the existing + // MCP server. Only the first call binds the listener. + if (this.serverConfig) { + if (this.serverUrl) { + return McpInitializeResult.attachServer(result, { name: this.serverConfig.name, url: this.serverUrl }); + } + return result; + } + + const { port = 0, route = '/mcp', name = 'glsp', options = {} } = mcpServerParam; + const mergedOptions = { ...this.mcpDefaults, ...pickInitOptions(options) }; + this.mcpOptions.values = mergedOptions; + const host = mergedOptions.host ?? '127.0.0.1'; + const mcpServerConfig: FullMcpServerConfiguration = { port, host, route, name, options: mergedOptions }; + + this.dispatcher.harvest(); + this.installResourceListChangedNotifier(); + + const endpoint = await this.bindTransport(mcpServerConfig); + this.serverConfig = mcpServerConfig; + this.serverUrl = endpoint.url; + this.logger.info( + `MCP server '${mcpServerConfig.name}' is ready to accept new client requests on: ${this.serverUrl ?? '(no network endpoint)'}` + ); + + if (endpoint.url) { + return McpInitializeResult.attachServer(result, { + name: mcpServerConfig.name, + url: endpoint.url, + headers: endpoint.headers + }); + } + return result; + } + + serverShutDown(_server: GLSPServer): void { + this.dispose(); + } + + dispose(): void { + // Close transports first so in-flight SSE responses are signalled cleanly. The HTTP + // listener (registered on `toDispose`) is torn down only after the transport closes + // settle — closing it earlier would cut the SSE socket mid-flush. + const closing = Array.from(this.sessions.values()).map(transport => + transport.close().catch(err => this.logger.warn(`Error closing MCP session ${transport.sessionId}: ${err}`)) + ); + this.sessions.clear(); + this.sessionServers.forEach(glspMcpServer => glspMcpServer.dispose()); + this.sessionServers.clear(); + Promise.allSettled(closing).then(() => { + this.toDispose.dispose(); + this.toDispose.clear(); + }); + this.serverUrl = undefined; + this.serverConfig = undefined; + this.dispatcher.reset(); + } + + /** + * Fetch-shaped request entry point. Dispatches POST/GET/DELETE against `config.route`, + * routing to the right per-session SDK transport. Validates the `MCP-Protocol-Version` + * header and the spec-mandated session-id rules before handing off to the SDK. + */ + async handleRequest(req: Request, options?: McpRequestHandlerOptions): Promise { + const url = new URL(req.url); + const route = this.serverConfig?.route ?? '/mcp'; + if (url.pathname !== route) { + return new Response('Not Found', { status: 404 }); + } + if (req.method !== 'POST' && req.method !== 'GET' && req.method !== 'DELETE') { + return new Response('Method Not Allowed', { status: 405, headers: { Allow: 'POST, GET, DELETE' } }); + } + const hostError = this.validateHostHeader(req); + if (hostError) { + return hostError; + } + + let parsedBody: unknown | undefined; + if (req.method === 'POST') { + // `req.clone()` is mandatory: the body stream can only be read once; the SDK reads the original. + parsedBody = await req + .clone() + .json() + .catch(() => undefined); + } + + const isInit = req.method === 'POST' && isInitializeRequest(parsedBody); + const versionError = this.validateProtocolVersionHeader(req, isInit); + if (versionError) { + return versionError; + } + + const transport = this.resolveTransport(req, isInit); + if (transport instanceof Response) { + return transport; + } + try { + return await transport.handleRequest(req, { parsedBody, authInfo: options?.authInfo }); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error('Error handling MCP request:', err); + return this.jsonRpcErrorResponse(500, -32603, `Internal server error: ${message}`); + } + } + + /** Bound `(req) => Promise` for adopters plugging the handler into any Fetch-shaped listener. */ + getRequestHandler(): (req: Request, options?: McpRequestHandlerOptions) => Promise { + return (req, options) => this.handleRequest(req, options); + } + + /** Concrete subclasses bind their transport (or no-op for runtimes where the adopter owns the listener). */ + protected abstract bindTransport(config: FullMcpServerConfiguration): Promise; + + /** + * Resolve the right transport for an incoming request. Returns a {@link Response} for the + * spec-mandated 400/404 rejections so the caller can early-return. + */ + protected resolveTransport(req: Request, isInit: boolean): WebStandardStreamableHTTPServerTransport | Response { + const sessionId = req.headers.get('mcp-session-id') ?? undefined; + if (isInit && !sessionId) { + return this.createTransport(); + } + if (!sessionId) { + // MCP Streamable HTTP § Session Management #2: non-initialize request without session id → 400. + return this.jsonRpcErrorResponse(400, -32000, 'Bad Request: No valid session ID provided'); + } + const existing = this.sessions.get(sessionId); + if (!existing) { + // § Session Management #3: unknown or terminated session id → 404. + return this.jsonRpcErrorResponse(404, -32001, 'Session not found'); + } + return existing; + } + + /** + * Validate the `MCP-Protocol-Version` header per the Streamable HTTP transport spec. + * Initialize POSTs negotiate the version in the body — the header isn't expected there. + * For every other request: absent → pass through (server defaults to `2025-03-26`); + * present-but-unsupported → respond `400` with a JSON-RPC error envelope. + */ + protected validateProtocolVersionHeader(req: Request, isInit: boolean): Response | undefined { + if (isInit) { + return undefined; + } + const version = req.headers.get('mcp-protocol-version') ?? undefined; + if (version === undefined) { + return undefined; + } + if (!SUPPORTED_PROTOCOL_VERSIONS.includes(version)) { + return this.jsonRpcErrorResponse( + 400, + -32000, + `Unsupported MCP-Protocol-Version: '${version}'. Supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')}.` + ); + } + return undefined; + } + + /** Port-agnostic Host validation — `WebStandardStreamableHTTPServerTransport` does exact-string match incl. port. */ + protected validateHostHeader(req: Request): Response | undefined { + const allowedHosts = this.mcpOptions.values.allowedHosts; + if (!allowedHosts || allowedHosts.length === 0) { + return undefined; + } + const hostHeader = req.headers.get('host'); + if (!hostHeader) { + return this.jsonRpcErrorResponse(403, -32000, 'Missing Host header'); + } + let hostname: string; + try { + hostname = new URL(`http://${hostHeader}`).hostname; + } catch { + return this.jsonRpcErrorResponse(403, -32000, `Invalid Host header: ${hostHeader}`); + } + if (!allowedHosts.includes(hostname)) { + return this.jsonRpcErrorResponse(403, -32000, `Invalid Host: ${hostname}`); + } + return undefined; + } + + protected createTransport(): WebStandardStreamableHTTPServerTransport { + const allowedOrigins = this.mcpOptions.values.allowedOrigins; + const transport = new WebStandardStreamableHTTPServerTransport({ + // `globalThis.crypto` is available on Node 18+, Bun, Deno, Cloudflare Workers, browsers. + // Avoid `node:crypto` so this file compiles cleanly into a browser bundle. + sessionIdGenerator: () => globalThis.crypto.randomUUID(), + eventStore: new LruEventStore(this.mcpOptions.values.eventStoreLimit, this.logger), + // Host validation lives on `validateHostHeader`. + allowedOrigins, + enableDnsRebindingProtection: (allowedOrigins?.length ?? 0) > 0, + onsessioninitialized: sessionId => { + this.logger.info(`MCP session initialized with ID: ${sessionId}`); + this.sessions.set(sessionId, transport); + if (this.serverConfig) { + this.attachServerToSession(transport, this.serverConfig); + } + this.onSessionInitializedEmitter.fire(transport as WithSessionId); + }, + onsessionclosed: sessionId => this.closeSession(sessionId) + }); + transport.onclose = () => this.closeSession(transport.sessionId); + transport.onerror = err => this.logger.error(`MCP transport error (session ${transport.sessionId ?? ''}):`, err); + return transport; + } + + protected attachServerToSession(transport: WebStandardStreamableHTTPServerTransport, config: FullMcpServerConfiguration): void { + const sessionId = transport.sessionId; + if (!sessionId) { + return; + } + const glspMcpServer = this.createGlspMcpServer(config); + this.sessionServers.set(sessionId, glspMcpServer); + this.registerLogLevelHandler(glspMcpServer, sessionId); + glspMcpServer.connect(transport as McpSession); + } + + protected closeSession(sessionId: string | undefined): void { + if (!sessionId) { + return; + } + const existed = this.sessions.delete(sessionId); + const glspMcpServer = this.sessionServers.get(sessionId); + if (glspMcpServer) { + this.sessionServers.delete(sessionId); + this.logLevelRegistry.clear(sessionId); + glspMcpServer.dispose(); + } + if (existed) { + this.logger.info(`MCP session closed: ${sessionId}`); + this.onSessionClosedEmitter.fire(sessionId); + } + } + + /** + * Fire `notifications/resources/list_changed` to every connected MCP client when a GLSP + * session opens or closes — diagram-scope resources aggregate across GLSP sessions, so the + * visible list mutates with that lifecycle. No-op when no diagram-scope resources are bound. + */ + protected installResourceListChangedNotifier(): void { + if (!this.dispatcher.hasDiagramResources()) { + return; + } + const listener: ClientSessionListener = { + sessionCreated: () => this.broadcastResourceListChanged(), + sessionDisposed: () => this.broadcastResourceListChanged() + }; + this.clientSessionManager.addListener(listener); + this.toDispose.push(Disposable.create(() => this.clientSessionManager.removeListener(listener))); + } + + /** Best-effort fan-out — failures on individual MCP sessions (e.g. transport mid-close) are swallowed. */ + protected broadcastResourceListChanged(): void { + for (const glspMcpServer of this.sessionServers.values()) { + glspMcpServer + .getRawServer() + .server.sendResourceListChanged() + .catch(err => this.logger.debug('sendResourceListChanged failed:', err)); + } + } + + /** Register `logging/setLevel` so a connected MCP client can adjust its message severity threshold. */ + protected registerLogLevelHandler(glspMcpServer: GLSPMcpServer, sessionId: string): void { + glspMcpServer.getRawServer().server.setRequestHandler(SetLevelRequestSchema, async request => { + this.logLevelRegistry.setLevel(sessionId, request.params.level); + return {}; + }); + } + + protected createGlspMcpServer({ name, options }: FullMcpServerConfiguration): GLSPMcpServer { + const resourcesAsResources = options.dataMode === 'resources'; + const server = new McpServer( + { name, version: SERVER_VERSION }, + { + capabilities: this.buildCapabilities(resourcesAsResources), + instructions: options.agentPersona + } + ); + const glspMcpServer = this.glspMcpServerFactory(server, options); + this.registerHandlers(glspMcpServer, resourcesAsResources); + return glspMcpServer; + } + + /** + * Build the MCP capabilities map from what is actually bound. Only declare a key when at + * least one handler contributes — declaring a capability the SDK never registers a handler + * for produces `-32601 Method not found` on `/list`. Resources surfaced as tools + * (`dataMode === 'tools'`) count toward `tools`, not `resources`. + */ + protected buildCapabilities(resourcesAsResources: boolean): ServerCapabilities { + const hasStaticTools = this.toolHandlers.length > 0; + const hasStaticPrompts = this.promptHandlers.length > 0; + const hasStaticResources = this.resourceHandlers.length > 0; + const hasDiagramTools = this.dispatcher.hasDiagramTools(); + const hasDiagramPrompts = this.dispatcher.hasDiagramPrompts(); + const hasDiagramResources = this.dispatcher.hasDiagramResources(); + const anyResources = hasStaticResources || hasDiagramResources; + + const hasTools = hasStaticTools || hasDiagramTools || (!resourcesAsResources && anyResources); + const hasPrompts = hasStaticPrompts || hasDiagramPrompts; + const hasResources = resourcesAsResources && anyResources; + + return { + logging: {}, + ...(hasTools ? { tools: { listChanged: false } } : {}), + ...(hasResources ? { resources: { listChanged: hasDiagramResources } } : {}), + ...(hasPrompts ? { prompts: { listChanged: false } } : {}) + }; + } + + protected registerHandlers(glspMcpServer: GLSPMcpServer, resourcesAsResources: boolean): void { + this.toolHandlers.forEach(handler => handler.registerTool(glspMcpServer)); + this.promptHandlers.forEach(handler => handler.registerPrompt(glspMcpServer)); + if (resourcesAsResources) { + this.resourceHandlers.forEach(handler => handler.registerResource(glspMcpServer)); + } else { + this.resourceHandlers.forEach(handler => handler.registerToolAlternative?.(glspMcpServer)); + } + + this.dispatcher.registerAll(glspMcpServer, resourcesAsResources); + this.validatePromptToolReferences(glspMcpServer); + } + + /** + * Warn when a server-scope prompt references a tool that isn't registered on this MCP session + * — catches adopters who unbind a tool a shipped prompt mentions via `${OtherHandler.NAME}`. + */ + protected validatePromptToolReferences(glspMcpServer: GLSPMcpServer): void { + for (const handler of this.promptHandlers) { + const missing = handler.referencedToolNames().filter(name => !glspMcpServer.hasTool(name)); + if (missing.length > 0) { + this.logger.warn( + `Prompt '${handler.name}' references unbound tool(s): ${missing.join(', ')}. ` + + 'The prompt will still register but its text points at tools the LLM cannot invoke.' + ); + } + } + } + + protected jsonRpcErrorResponse(status: number, code: number, message: string): Response { + return new Response(JSON.stringify({ jsonrpc: '2.0', error: { code, message }, id: JSON_RPC_NULL_ID }), { + status, + headers: { 'Content-Type': 'application/json' } + }); + } +} diff --git a/packages/server-mcp/src/server/glsp-mcp-server.spec.ts b/packages/server-mcp/src/common/server/glsp-mcp-server.spec.ts similarity index 100% rename from packages/server-mcp/src/server/glsp-mcp-server.spec.ts rename to packages/server-mcp/src/common/server/glsp-mcp-server.spec.ts diff --git a/packages/server-mcp/src/server/glsp-mcp-server.ts b/packages/server-mcp/src/common/server/glsp-mcp-server.ts similarity index 95% rename from packages/server-mcp/src/server/glsp-mcp-server.ts rename to packages/server-mcp/src/common/server/glsp-mcp-server.ts index 2f67e96..acac27e 100644 --- a/packages/server-mcp/src/server/glsp-mcp-server.ts +++ b/packages/server-mcp/src/common/server/glsp-mcp-server.ts @@ -90,7 +90,7 @@ export class DefaultGLSPMcpServer implements GLSPMcpServer { protected readonly tools = new Map(); protected readonly resources = new Map(); protected readonly prompts = new Map(); - protected keepAliveTimer?: NodeJS.Timeout; + protected keepAliveTimer?: ReturnType; readonly registerTool: McpServer['registerTool']; readonly registerResource: McpServer['registerResource']; @@ -152,7 +152,12 @@ export class DefaultGLSPMcpServer implements GLSPMcpServer { }), KEEP_ALIVE_INTERVAL_MS ); - this.keepAliveTimer.unref(); + // `unref` lives on Node's Timeout but not on the browser's numeric handle; guard so the + // call is a no-op when the runtime doesn't ship it. + const maybeUnref = (this.keepAliveTimer as unknown as { unref?: () => void }).unref; + if (typeof maybeUnref === 'function') { + maybeUnref.call(this.keepAliveTimer); + } } isConnected(): boolean { diff --git a/packages/server-mcp/src/server/lru-event-store.spec.ts b/packages/server-mcp/src/common/server/lru-event-store.spec.ts similarity index 100% rename from packages/server-mcp/src/server/lru-event-store.spec.ts rename to packages/server-mcp/src/common/server/lru-event-store.spec.ts diff --git a/packages/server-mcp/src/server/lru-event-store.ts b/packages/server-mcp/src/common/server/lru-event-store.ts similarity index 97% rename from packages/server-mcp/src/server/lru-event-store.ts rename to packages/server-mcp/src/common/server/lru-event-store.ts index d999970..fb530db 100644 --- a/packages/server-mcp/src/server/lru-event-store.ts +++ b/packages/server-mcp/src/common/server/lru-event-store.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { Logger } from '@eclipse-glsp/server'; -import { EventId, EventStore, StreamId } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import type { EventId, EventStore, StreamId } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; /** diff --git a/packages/server-mcp/src/server/mcp-diagram-handler-dispatcher.spec.ts b/packages/server-mcp/src/common/server/mcp-diagram-handler-dispatcher.spec.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-diagram-handler-dispatcher.spec.ts rename to packages/server-mcp/src/common/server/mcp-diagram-handler-dispatcher.spec.ts diff --git a/packages/server-mcp/src/server/mcp-diagram-handler-dispatcher.ts b/packages/server-mcp/src/common/server/mcp-diagram-handler-dispatcher.ts similarity index 95% rename from packages/server-mcp/src/server/mcp-diagram-handler-dispatcher.ts rename to packages/server-mcp/src/common/server/mcp-diagram-handler-dispatcher.ts index 3bf61df..a3d4bca 100644 --- a/packages/server-mcp/src/server/mcp-diagram-handler-dispatcher.ts +++ b/packages/server-mcp/src/common/server/mcp-diagram-handler-dispatcher.ts @@ -25,7 +25,7 @@ import { import { CompleteResourceTemplateCallback, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js'; import { CallToolResult, GetPromptResult, ListResourcesResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; -import { Container, ContainerModule, inject, injectable, interfaces } from 'inversify'; +import { Container, ContainerModule, inject, injectable, interfaces, optional } from 'inversify'; import { GLSPMcpServer } from './glsp-mcp-server'; import { McpDiagramPromptHandlerRegistry } from './mcp-diagram-prompt-handler-registry'; import { McpDiagramResourceHandlerRegistry } from './mcp-diagram-resource-handler-registry'; @@ -33,7 +33,7 @@ import { McpDiagramToolHandlerRegistry } from './mcp-diagram-tool-handler-regist import { McpMissingParamError, McpSessionNotFoundError, McpToolError, runWithToolErrorEnvelope } from './mcp-handler-shared'; import { McpDiagramScopedInput } from './mcp-input-schemas'; import { AbstractMcpDiagramPromptHandler, McpDiagramPromptHandlerConstructor } from './mcp-prompt-handler'; -import { mcpRequestContext } from './mcp-request-context'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; import { AbstractMcpDiagramResourceHandler, McpDiagramResourceHandlerConstructor, toParams } from './mcp-resource-handler'; import { BaseMcpDiagramToolHandler, McpDiagramToolHandlerConstructor } from './mcp-tool-handler'; @@ -53,7 +53,7 @@ export interface DiagramTypeCatalog { /** * Owns diagram-scope handler discovery, SDK registration, and per-MCP-call dispatch routing. - * Extracted from {@link McpServerLauncher} so adopters can `rebind(McpDiagramHandlerDispatcher)` + * Extracted from {@link AbstractMcpServerLauncher} so adopters can `rebind(McpDiagramHandlerDispatcher)` * to a custom implementation without subclassing the entire launcher lifecycle. * * Responsibilities: @@ -84,6 +84,10 @@ export const McpDiagramHandlerDispatcher = Symbol('McpDiagramHandlerDispatcher') @injectable() export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDispatcher { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + @inject(InjectionContainer) protected serverContainer: Container; @inject(DiagramModules) protected diagramModules: Map; @inject(ClientSessionManager) protected clientSessionManager: ClientSessionManager; @@ -181,7 +185,7 @@ export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDisp } seen.set(probe.name, fingerprint); glspMcpServer.registerTool(probe.name, probe.toRegistrationConfig(), (params, extra) => - mcpRequestContext.run(extra, () => this.dispatchDiagramTool(probe.name, params)) + this.requestContext.run(extra, () => this.dispatchDiagramTool(probe.name, params)) ); } } @@ -220,12 +224,12 @@ export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDisp if (typeof probe.uri === 'string') { const uri = probe.uri; glspMcpServer.registerResource(name, uri, config, (_uri, extra) => - mcpRequestContext.run(extra, () => this.dispatchStaticDiagramRead(name, uri)) + this.requestContext.run(extra, () => this.dispatchStaticDiagramRead(name, uri)) ); } else { const template = this.buildAggregatingResourceTemplate(probe.uri.template, name); glspMcpServer.registerResource(name, template, config, (uri, params, extra) => - mcpRequestContext.run(extra, () => this.dispatchTemplatedDiagramRead(name, uri, params)) + this.requestContext.run(extra, () => this.dispatchTemplatedDiagramRead(name, uri, params)) ); } } @@ -247,7 +251,7 @@ export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDisp inputSchema: inputSchema.strict(), outputSchema: probe.toolAlternativeOutputSchema }, - (params, extra) => mcpRequestContext.run(extra, () => this.dispatchDiagramResourceAsTool(name, params)) + (params, extra) => this.requestContext.run(extra, () => this.dispatchDiagramResourceAsTool(name, params)) ); } @@ -262,7 +266,7 @@ export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDisp seenNames.add(probe.name); // Prompt errors propagate as JSON-RPC errors per spec — no `runWithToolErrorEnvelope` wrap. glspMcpServer.registerPrompt(probe.name, probe.toRegistrationConfig(), (args, extra) => - mcpRequestContext.run(extra, () => this.dispatchDiagramPrompt(probe.name, args)) + this.requestContext.run(extra, () => this.dispatchDiagramPrompt(probe.name, args)) ); } } @@ -276,7 +280,7 @@ export class DefaultMcpDiagramHandlerDispatcher implements McpDiagramHandlerDisp */ protected buildAggregatingResourceTemplate(uriTemplate: string, name: string): ResourceTemplate { return new ResourceTemplate(uriTemplate, { - list: extra => mcpRequestContext.run(extra, () => this.aggregateList(name)), + list: extra => this.requestContext.run(extra, () => this.aggregateList(name)), complete: this.buildAggregatedCompleters(name) }); } diff --git a/packages/server-mcp/src/server/mcp-diagram-prompt-handler-registry.ts b/packages/server-mcp/src/common/server/mcp-diagram-prompt-handler-registry.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-diagram-prompt-handler-registry.ts rename to packages/server-mcp/src/common/server/mcp-diagram-prompt-handler-registry.ts diff --git a/packages/server-mcp/src/server/mcp-diagram-resource-handler-registry.ts b/packages/server-mcp/src/common/server/mcp-diagram-resource-handler-registry.ts similarity index 89% rename from packages/server-mcp/src/server/mcp-diagram-resource-handler-registry.ts rename to packages/server-mcp/src/common/server/mcp-diagram-resource-handler-registry.ts index 7aebb0a..6bc0278 100644 --- a/packages/server-mcp/src/server/mcp-diagram-resource-handler-registry.ts +++ b/packages/server-mcp/src/common/server/mcp-diagram-resource-handler-registry.ts @@ -63,7 +63,10 @@ export class McpDiagramResourceHandlerRegistryInitializer implements ClientSessi } if (!handler.canRegister()) { if (!isProbe) { - this.logger.warn(`Skipping MCP diagram resource handler '${handler.name}': canRegister() returned false.`); + // Debug-level: returning false is the designed-for outcome when the connecting + // client doesn't speak the action the handler relies on (e.g. RequestExportAction + // for diagram-png / diagram-svg). Not actionable for operators. + this.logger.debug(`Skipping MCP diagram resource handler '${handler.name}': canRegister() returned false.`); } continue; } diff --git a/packages/server-mcp/src/server/mcp-diagram-tool-handler-registry.ts b/packages/server-mcp/src/common/server/mcp-diagram-tool-handler-registry.ts similarity index 93% rename from packages/server-mcp/src/server/mcp-diagram-tool-handler-registry.ts rename to packages/server-mcp/src/common/server/mcp-diagram-tool-handler-registry.ts index ae42746..7c55ed1 100644 --- a/packages/server-mcp/src/server/mcp-diagram-tool-handler-registry.ts +++ b/packages/server-mcp/src/common/server/mcp-diagram-tool-handler-registry.ts @@ -87,7 +87,9 @@ export class McpDiagramToolHandlerRegistryInitializer implements ClientSessionIn } if (!handler.canRegister()) { if (!isProbe) { - this.logger.warn(`Skipping MCP diagram tool handler '${handler.name}': canRegister() returned false.`); + // Debug-level: returning false is the designed-for outcome when the connecting + // client doesn't speak the action the handler relies on. Not actionable for operators. + this.logger.debug(`Skipping MCP diagram tool handler '${handler.name}': canRegister() returned false.`); } continue; } diff --git a/packages/server-mcp/src/server/mcp-handler-shared.spec.ts b/packages/server-mcp/src/common/server/mcp-handler-shared.spec.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-handler-shared.spec.ts rename to packages/server-mcp/src/common/server/mcp-handler-shared.spec.ts diff --git a/packages/server-mcp/src/server/mcp-handler-shared.ts b/packages/server-mcp/src/common/server/mcp-handler-shared.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-handler-shared.ts rename to packages/server-mcp/src/common/server/mcp-handler-shared.ts diff --git a/packages/server-mcp/src/server/mcp-id-alias-service.spec.ts b/packages/server-mcp/src/common/server/mcp-id-alias-service.spec.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-id-alias-service.spec.ts rename to packages/server-mcp/src/common/server/mcp-id-alias-service.spec.ts diff --git a/packages/server-mcp/src/server/mcp-id-alias-service.ts b/packages/server-mcp/src/common/server/mcp-id-alias-service.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-id-alias-service.ts rename to packages/server-mcp/src/common/server/mcp-id-alias-service.ts diff --git a/packages/server-mcp/src/server/mcp-input-schemas.ts b/packages/server-mcp/src/common/server/mcp-input-schemas.ts similarity index 86% rename from packages/server-mcp/src/server/mcp-input-schemas.ts rename to packages/server-mcp/src/common/server/mcp-input-schemas.ts index 7081915..9e2a328 100644 --- a/packages/server-mcp/src/server/mcp-input-schemas.ts +++ b/packages/server-mcp/src/common/server/mcp-input-schemas.ts @@ -73,6 +73,19 @@ export const ElementIdentitySchema = z.object({ /** Inferred shape of {@link ElementIdentitySchema} — see its docstring for usage. */ export type ElementIdentity = z.infer; +/** + * Shared output field for mutating tools — the number of underlying GLSP operations a single + * call dispatched. Adopters chain `undo` / `redo` against this count to roll back or replay a + * full user action (a single MCP call may produce several commands, e.g. create-with-label). + * Always non-negative; `0` when nothing was dispatched (e.g. all inputs hit a pre-dispatch + * validation error). + */ +export const dispatchedCommands = z + .number() + .int() + .nonnegative() + .describe('Number of underlying GLSP operations dispatched. Useful when chaining `undo` / `redo`.'); + /** * Inferred shape of {@link McpDiagramScopedInputSchema} — `{ sessionId: string }`. Any * adopter input schema that extends `McpDiagramScopedInputSchema` infers a type structurally diff --git a/packages/server-mcp/src/server/mcp-label-provider.ts b/packages/server-mcp/src/common/server/mcp-label-provider.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-label-provider.ts rename to packages/server-mcp/src/common/server/mcp-label-provider.ts diff --git a/packages/server-mcp/src/server/mcp-log-level-registry.spec.ts b/packages/server-mcp/src/common/server/mcp-log-level-registry.spec.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-log-level-registry.spec.ts rename to packages/server-mcp/src/common/server/mcp-log-level-registry.spec.ts diff --git a/packages/server-mcp/src/server/mcp-log-level-registry.ts b/packages/server-mcp/src/common/server/mcp-log-level-registry.ts similarity index 99% rename from packages/server-mcp/src/server/mcp-log-level-registry.ts rename to packages/server-mcp/src/common/server/mcp-log-level-registry.ts index 32f21a2..c2970fa 100644 --- a/packages/server-mcp/src/server/mcp-log-level-registry.ts +++ b/packages/server-mcp/src/common/server/mcp-log-level-registry.ts @@ -21,7 +21,7 @@ export const McpLogLevelRegistry = Symbol('McpLogLevelRegistry'); /** * Per-MCP-session minimum-severity threshold for `notifications/message`. Updated by the - * server's `logging/setLevel` request handler (registered in {@link McpServerLauncher} on + * server's `logging/setLevel` request handler (registered in {@link AbstractMcpServerLauncher} on * session-init); read by {@link McpLogger} to gate message delivery. * * Bound as a server-scope singleton: one registry shared across MCP sessions, keyed by diff --git a/packages/server-mcp/src/server/mcp-logger.spec.ts b/packages/server-mcp/src/common/server/mcp-logger.spec.ts similarity index 86% rename from packages/server-mcp/src/server/mcp-logger.spec.ts rename to packages/server-mcp/src/common/server/mcp-logger.spec.ts index f1d566f..1d7f997 100644 --- a/packages/server-mcp/src/server/mcp-logger.spec.ts +++ b/packages/server-mcp/src/common/server/mcp-logger.spec.ts @@ -20,7 +20,8 @@ import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from './mcp-log-level-registry'; import { McpLogger } from './mcp-logger'; -import { McpRequestExtra, mcpRequestContext } from './mcp-request-context'; +import { NodeMcpRequestContext } from '../../node/server/node-mcp-request-context'; +import { McpRequestContext, McpRequestExtra } from './mcp-request-context'; interface RecordedLog { level: string; @@ -43,18 +44,25 @@ class RecordingLogger extends NullLogger { } } -function buildLogger(): { logger: McpLogger; glspLogger: RecordingLogger; levelRegistry: DefaultMcpLogLevelRegistry } { +function buildLogger(): { + logger: McpLogger; + glspLogger: RecordingLogger; + levelRegistry: DefaultMcpLogLevelRegistry; + requestContext: McpRequestContext; +} { const container = new Container(); const glspLogger = new RecordingLogger(); const levelRegistry = new DefaultMcpLogLevelRegistry(); + const requestContext = new NodeMcpRequestContext(); container.load( new ContainerModule(bind => { bind(Logger).toConstantValue(glspLogger); bind(McpLogLevelRegistry).toConstantValue(levelRegistry); + bind(McpRequestContext).toConstantValue(requestContext); bind(McpLogger).toSelf().inSingletonScope(); }) ); - return { logger: container.get(McpLogger), glspLogger, levelRegistry }; + return { logger: container.get(McpLogger), glspLogger, levelRegistry, requestContext }; } /** @@ -92,12 +100,12 @@ describe('McpLogger', () => { }); }); - describe('inside an mcpRequestContext.run frame', () => { + describe('inside an requestContext.run frame', () => { it('emits notifications/message to the bound MCP client AND the GLSP Logger', async () => { - const { logger, glspLogger } = buildLogger(); + const { logger, glspLogger, requestContext } = buildLogger(); const { extra, sent } = buildExtra(); - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { logger.info('one'); logger.warn('two'); logger.error('three'); @@ -122,14 +130,14 @@ describe('McpLogger', () => { }); it('swallows transport failures so a broken MCP send never breaks the producing tool', async () => { - const { logger, glspLogger } = buildLogger(); + const { logger, glspLogger, requestContext } = buildLogger(); const failingExtra = { sendNotification: async () => { throw new Error('transport closed'); } } as unknown as McpRequestExtra; - await mcpRequestContext.run(failingExtra, async () => { + await requestContext.run(failingExtra, async () => { expect(() => logger.error('still works')).to.not.throw(); await new Promise(resolve => setImmediate(resolve)); }); @@ -141,17 +149,17 @@ describe('McpLogger', () => { describe('concurrent request contexts', () => { it('keeps each request frame isolated via AsyncLocalStorage', async () => { - const { logger } = buildLogger(); + const { logger, requestContext } = buildLogger(); const { extra: extraA, sent: sentA } = buildExtra(); const { extra: extraB, sent: sentB } = buildExtra(); await Promise.all([ - mcpRequestContext.run(extraA, async () => { + requestContext.run(extraA, async () => { logger.info('A'); await new Promise(resolve => setImmediate(resolve)); logger.info('A-after-yield'); }), - mcpRequestContext.run(extraB, async () => { + requestContext.run(extraB, async () => { logger.info('B'); await new Promise(resolve => setImmediate(resolve)); logger.info('B-after-yield'); @@ -165,12 +173,12 @@ describe('McpLogger', () => { describe('logging/setLevel threshold gate (G4)', () => { it('drops messages below the per-session threshold; the GLSP-side log still fires', async () => { - const { logger, glspLogger, levelRegistry } = buildLogger(); + const { logger, glspLogger, levelRegistry, requestContext } = buildLogger(); const { extra, sent } = buildExtra('session-X'); // Client opted down to "warning" — info and debug must be dropped on the MCP side. levelRegistry.setLevel('session-X', 'warning'); - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { logger.info('chatty'); logger.debug('verbose'); logger.warn('important'); @@ -185,19 +193,19 @@ describe('McpLogger', () => { }); it('isolates thresholds across sessions (different setLevel per session id)', async () => { - const { logger, levelRegistry } = buildLogger(); + const { logger, levelRegistry, requestContext } = buildLogger(); const { extra: extraA, sent: sentA } = buildExtra('session-A'); const { extra: extraB, sent: sentB } = buildExtra('session-B'); levelRegistry.setLevel('session-A', 'error'); levelRegistry.setLevel('session-B', 'debug'); await Promise.all([ - mcpRequestContext.run(extraA, async () => { + requestContext.run(extraA, async () => { logger.info('A-info'); logger.error('A-error'); await new Promise(resolve => setImmediate(resolve)); }), - mcpRequestContext.run(extraB, async () => { + requestContext.run(extraB, async () => { logger.info('B-info'); logger.error('B-error'); await new Promise(resolve => setImmediate(resolve)); @@ -209,11 +217,11 @@ describe('McpLogger', () => { }); it('default threshold (no setLevel sent) lets every level through (preserves prior behavior)', async () => { - const { logger } = buildLogger(); + const { logger, requestContext } = buildLogger(); const { extra, sent } = buildExtra('session-default'); // No setLevel call → default threshold = 'debug' → everything emitted. - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { logger.debug('d'); logger.info('i'); logger.warn('w'); diff --git a/packages/server-mcp/src/server/mcp-logger.ts b/packages/server-mcp/src/common/server/mcp-logger.ts similarity index 89% rename from packages/server-mcp/src/server/mcp-logger.ts rename to packages/server-mcp/src/common/server/mcp-logger.ts index 62bc0b0..9cbbb18 100644 --- a/packages/server-mcp/src/server/mcp-logger.ts +++ b/packages/server-mcp/src/common/server/mcp-logger.ts @@ -16,16 +16,16 @@ import { Logger } from '@eclipse-glsp/server'; import type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, optional } from 'inversify'; import { McpLogLevelRegistry, passesLogThreshold } from './mcp-log-level-registry'; -import { mcpRequestContext } from './mcp-request-context'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; /** * Logger that writes to BOTH the GLSP-side server log and the connected MCP client. * * Mirrors the {@link Logger} shape so handlers can drop-in switch from `@inject(Logger)` to * `@inject(McpLogger)`. The server-side route always fires; the MCP-side route fires only when - * a call is made from inside an MCP request callback (tracked via {@link mcpRequestContext}). + * a call is made from inside an MCP request callback (tracked via {@link McpRequestContext}). * * We deliberately do NOT auto-forward arbitrary GLSP `Logger.info` calls to MCP clients — * that would leak unrelated server-wide log lines into every connected LLM. Adopters opt in @@ -38,13 +38,17 @@ import { mcpRequestContext } from './mcp-request-context'; * - `debug` → `debug` * * Shared across MCP clients on the same GLSP session; per-client routing is handled by the - * active `mcpRequestContext` frame, and the per-MCP-session `logging/setLevel` threshold is + * active `McpRequestContext` frame, and the per-MCP-session `logging/setLevel` threshold is * stored in {@link McpLogLevelRegistry}. * * @experimental */ @injectable() export class McpLogger { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + @inject(Logger) protected glspLogger: Logger; @inject(McpLogLevelRegistry) protected levelRegistry: McpLogLevelRegistry; @@ -78,7 +82,7 @@ export class McpLogger { * Failures to deliver are swallowed — a broken transport must not break the producing tool. */ protected notify(level: LoggingLevel, data: string): void { - const extra = mcpRequestContext.getStore(); + const extra = this.requestContext.getStore(); if (!extra) { return; } diff --git a/packages/server-mcp/src/server/mcp-mime-types.ts b/packages/server-mcp/src/common/server/mcp-mime-types.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-mime-types.ts rename to packages/server-mcp/src/common/server/mcp-mime-types.ts diff --git a/packages/server-mcp/src/server/mcp-options.ts b/packages/server-mcp/src/common/server/mcp-options.ts similarity index 96% rename from packages/server-mcp/src/server/mcp-options.ts rename to packages/server-mcp/src/common/server/mcp-options.ts index 7663b93..b1eb96b 100644 --- a/packages/server-mcp/src/server/mcp-options.ts +++ b/packages/server-mcp/src/common/server/mcp-options.ts @@ -37,7 +37,7 @@ export class McpServerOptions { /** * DI binding identifier for adopter-provided default options. Supplied via the * {@link AbstractMcpServerModule} constructor and merged with init-time options by - * `McpServerLauncher.initializeServer` — init-time wins per field. + * `AbstractMcpServerLauncher.initializeServer` — init-time wins per field. */ export const McpServerDefaults = Symbol('McpServerDefaults'); export type McpServerDefaults = McpServerOptionsType; diff --git a/packages/server-mcp/src/server/mcp-progress-reporter.spec.ts b/packages/server-mcp/src/common/server/mcp-progress-reporter.spec.ts similarity index 76% rename from packages/server-mcp/src/server/mcp-progress-reporter.spec.ts rename to packages/server-mcp/src/common/server/mcp-progress-reporter.spec.ts index 755ac7d..cfa55ce 100644 --- a/packages/server-mcp/src/server/mcp-progress-reporter.spec.ts +++ b/packages/server-mcp/src/common/server/mcp-progress-reporter.spec.ts @@ -17,7 +17,9 @@ import { ServerNotification } from '@modelcontextprotocol/sdk/types.js'; import { expect } from 'chai'; import { McpProgressReporter } from './mcp-progress-reporter'; -import { McpRequestExtra, mcpRequestContext } from './mcp-request-context'; +import { Container } from 'inversify'; +import { NodeMcpRequestContext } from '../../node/server/node-mcp-request-context'; +import { McpRequestContext, McpRequestExtra } from './mcp-request-context'; function buildExtra(progressToken?: string | number): { extra: McpRequestExtra; sent: ServerNotification[] } { const sent: ServerNotification[] = []; @@ -30,12 +32,20 @@ function buildExtra(progressToken?: string | number): { extra: McpRequestExtra; return { extra, sent }; } +function buildReporter(): { reporter: McpProgressReporter; requestContext: McpRequestContext } { + const container = new Container(); + const requestContext = new NodeMcpRequestContext(); + container.bind(McpRequestContext).toConstantValue(requestContext); + container.bind(McpProgressReporter).toSelf().inSingletonScope(); + return { reporter: container.get(McpProgressReporter), requestContext }; +} + describe('McpProgressReporter', () => { it('no-ops inside a request that has no progressToken (client did not opt in)', async () => { - const reporter = new McpProgressReporter(); + const { reporter, requestContext } = buildReporter(); const { extra, sent } = buildExtra(undefined); - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { await reporter.emit({ progress: 0, message: 'starting' }); }); @@ -43,10 +53,10 @@ describe('McpProgressReporter', () => { }); it('emits notifications/progress when the request carries a progressToken', async () => { - const reporter = new McpProgressReporter(); + const { reporter, requestContext } = buildReporter(); const { extra, sent } = buildExtra('tok-42'); - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { await reporter.emit({ progress: 0, message: 'starting' }); await reporter.emit({ progress: 1, total: 3, message: 'step 1/3' }); }); @@ -63,10 +73,10 @@ describe('McpProgressReporter', () => { }); it('accepts numeric progress tokens', async () => { - const reporter = new McpProgressReporter(); + const { reporter, requestContext } = buildReporter(); const { extra, sent } = buildExtra(7); - await mcpRequestContext.run(extra, async () => { + await requestContext.run(extra, async () => { await reporter.emit({ progress: 0 }); }); @@ -75,7 +85,7 @@ describe('McpProgressReporter', () => { }); it('swallows transport failures so a broken send never breaks the producing tool', async () => { - const reporter = new McpProgressReporter(); + const { reporter, requestContext } = buildReporter(); const failingExtra = { sendNotification: async () => { throw new Error('transport closed'); @@ -83,7 +93,7 @@ describe('McpProgressReporter', () => { _meta: { progressToken: 'tok-1' } } as unknown as McpRequestExtra; - await mcpRequestContext.run(failingExtra, async () => { + await requestContext.run(failingExtra, async () => { // Must complete without throwing. If we re-threw, every PNG export would hard-fail // when the client's SSE stream blipped — the opposite of progress reporting being // a UX nicety. diff --git a/packages/server-mcp/src/server/mcp-progress-reporter.ts b/packages/server-mcp/src/common/server/mcp-progress-reporter.ts similarity index 86% rename from packages/server-mcp/src/server/mcp-progress-reporter.ts rename to packages/server-mcp/src/common/server/mcp-progress-reporter.ts index ec2953f..de47252 100644 --- a/packages/server-mcp/src/server/mcp-progress-reporter.ts +++ b/packages/server-mcp/src/common/server/mcp-progress-reporter.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { mcpRequestContext } from './mcp-request-context'; +import { inject, injectable, optional } from 'inversify'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; /** * Per-call shape of a `notifications/progress` emission. Mirrors the SDK's @@ -33,7 +33,7 @@ export interface McpProgressBeat { /** * Emits `notifications/progress` to the connected MCP client when the active request carries a - * `progressToken` in its `_meta`. Built on the same {@link mcpRequestContext} as + * `progressToken` in its `_meta`. Built on the same {@link McpRequestContext} as * {@link McpLogger}; handlers don't need to thread `extra` through their own signatures. * * Behaviour: @@ -43,14 +43,18 @@ export interface McpProgressBeat { * - Failures to deliver are swallowed: a broken transport must not break the producing tool. * * Shared across MCP clients on the same GLSP session; per-client routing is handled by the - * active `mcpRequestContext` frame. + * active `McpRequestContext` frame. * * @experimental */ @injectable() export class McpProgressReporter { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + async emit(beat: McpProgressBeat): Promise { - const extra = mcpRequestContext.getStore(); + const extra = this.requestContext.getStore(); const progressToken = extra?._meta?.progressToken; if (extra === undefined || progressToken === undefined) { return; diff --git a/packages/server-mcp/src/server/mcp-prompt-handler.ts b/packages/server-mcp/src/common/server/mcp-prompt-handler.ts similarity index 93% rename from packages/server-mcp/src/server/mcp-prompt-handler.ts rename to packages/server-mcp/src/common/server/mcp-prompt-handler.ts index b5911f2..9606fe2 100644 --- a/packages/server-mcp/src/server/mcp-prompt-handler.ts +++ b/packages/server-mcp/src/common/server/mcp-prompt-handler.ts @@ -15,13 +15,13 @@ ********************************************************************************/ import { ClientId, Logger, MaybePromise, ModelState } from '@eclipse-glsp/server'; -import { inject, injectable, interfaces } from 'inversify'; +import { inject, injectable, interfaces, optional } from 'inversify'; import { ZodObject, ZodRawShape } from 'zod/v4'; import { GLSPMcpServer } from './glsp-mcp-server'; import { McpPromptResult, McpToolError, extractErrorMessage } from './mcp-handler-shared'; import { McpIdAliasService } from './mcp-id-alias-service'; import { McpDiagramScopedInput } from './mcp-input-schemas'; -import { mcpRequestContext } from './mcp-request-context'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; /** * Multi-binding key for **server-scope** prompt handlers — singletons that don't target a @@ -45,6 +45,10 @@ export const McpPromptHandler = Symbol('McpPromptHandler'); /** Shared infrastructure for both server- and diagram-scope prompt handlers. */ @injectable() abstract class BaseMcpPromptHandler { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + @inject(Logger) protected logger: Logger; /** @@ -113,7 +117,7 @@ export abstract class AbstractMcpPromptHandler> exte registerPrompt(server: GLSPMcpServer): void { server.registerPrompt(this.name, this.toRegistrationConfig(), async (args, extra) => - mcpRequestContext.run(extra, () => this.execute(() => this.createResult(args as T))) + this.requestContext.run(extra, () => this.execute(() => this.createResult(args as T))) ); } } @@ -141,7 +145,7 @@ export abstract class AbstractMcpDiagramPromptHandler< protected abstract createResult(args: T): MaybePromise; /** - * Public dispatch entry point invoked by {@link McpServerLauncher}'s SDK callback. + * Public dispatch entry point invoked by {@link AbstractMcpServerLauncher}'s SDK callback. */ handle(args: T): Promise { return this.execute(() => this.createResult(args)); diff --git a/packages/server-mcp/src/server/mcp-request-context.ts b/packages/server-mcp/src/common/server/mcp-request-context.ts similarity index 50% rename from packages/server-mcp/src/server/mcp-request-context.ts rename to packages/server-mcp/src/common/server/mcp-request-context.ts index 401800a..cb994b2 100644 --- a/packages/server-mcp/src/server/mcp-request-context.ts +++ b/packages/server-mcp/src/common/server/mcp-request-context.ts @@ -16,24 +16,28 @@ import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'; -import { AsyncLocalStorage } from 'node:async_hooks'; export type McpRequestExtra = RequestHandlerExtra; +export const McpRequestContext = Symbol('McpRequestContext'); + /** - * Module-level `AsyncLocalStorage` carrying the SDK's per-request `extra` (notification sender, - * progress token, request id, session id) for the duration of a tool/resource/prompt callback. - * - * The handler bases (tool / resource / prompt) wrap each registered SDK callback in - * `mcpRequestContext.run(extra, () => …)`. Anything inside the handler — and any await chain - * branching off it — can read the active context via {@link mcpRequestContext.getStore}. - * - * This lets {@link McpLogger} forward logs to the MCP client without every handler having to - * thread `extra` through its own signature, and lets future progress-emission code (P1f / PNG - * export) reach the same channel from inside `requestUntil` chains. - * - * Concurrent requests on the same MCP session each get their own AsyncLocalStorage frame — - * no cross-talk. Code that runs OUTSIDE a request (init contributions, background timers) - * sees `undefined` from `getStore()`. + * Carries the SDK's per-request `extra` (notification sender, progress token, request id, + * session id) for the duration of a tool/resource/prompt callback so consumers like the MCP + * logger and progress reporter can reach the client without threading `extra` through their + * own signatures. */ -export const mcpRequestContext = new AsyncLocalStorage(); +export interface McpRequestContext { + run(extra: McpRequestExtra, callback: () => R): R; + getStore(): McpRequestExtra | undefined; +} + +/** No-op fallback used when no implementation is bound (e.g. in unit tests). */ +export class NoopMcpRequestContext implements McpRequestContext { + run(_extra: McpRequestExtra, callback: () => R): R { + return callback(); + } + getStore(): McpRequestExtra | undefined { + return undefined; + } +} diff --git a/packages/server-mcp/src/server/mcp-resource-handler.ts b/packages/server-mcp/src/common/server/mcp-resource-handler.ts similarity index 95% rename from packages/server-mcp/src/server/mcp-resource-handler.ts rename to packages/server-mcp/src/common/server/mcp-resource-handler.ts index 37ba93a..52e0839 100644 --- a/packages/server-mcp/src/server/mcp-resource-handler.ts +++ b/packages/server-mcp/src/common/server/mcp-resource-handler.ts @@ -18,7 +18,7 @@ import { ActionDispatcher, ClientId, Logger, MaybePromise, ModelState, RequestAc import { CompleteResourceTemplateCallback, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js'; import { Annotations, ListResourcesResult, Role } from '@modelcontextprotocol/sdk/types.js'; -import { inject, injectable, interfaces } from 'inversify'; +import { inject, injectable, interfaces, optional } from 'inversify'; import { ZodObject, ZodRawShape } from 'zod/v4'; import { GLSPMcpServer } from './glsp-mcp-server'; import { @@ -33,7 +33,7 @@ import { import { McpIdAliasService } from './mcp-id-alias-service'; import { McpDiagramScopedInput } from './mcp-input-schemas'; import { McpMimeType } from './mcp-mime-types'; -import { mcpRequestContext } from './mcp-request-context'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; /** * Multi-binding key for **server-scope** resource handlers — singletons that don't target a @@ -55,6 +55,10 @@ export type McpResourceUri = string | { template: string }; /** Shared infrastructure for both server- and diagram-scope resource handlers. */ @injectable() abstract class BaseMcpResourceHandler { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + @inject(Logger) protected logger: Logger; /** @@ -184,7 +188,7 @@ abstract class BaseMcpResourceHandler { /** Builds the SDK `ResourceTemplate` for templated URIs (server-scope path). */ protected buildResourceTemplate(template: string): ResourceTemplate { return new ResourceTemplate(template, { - list: this.list ? async extra => mcpRequestContext.run(extra, () => this.list!()) : undefined, + list: this.list ? async extra => this.requestContext.run(extra, () => this.list!()) : undefined, complete: this.complete?.() }); } @@ -217,11 +221,11 @@ export abstract class AbstractMcpResourceHandler> ex if (typeof this.uri === 'string') { const uri = this.uri; server.registerResource(this.name, uri, config, async (_uri, extra) => - mcpRequestContext.run(extra, async () => this.toResourceResult(uri, await this.execute(() => this.createResult({} as T)))) + this.requestContext.run(extra, async () => this.toResourceResult(uri, await this.execute(() => this.createResult({} as T)))) ); } else { server.registerResource(this.name, this.buildResourceTemplate(this.uri.template), config, async (uri, params, extra) => - mcpRequestContext.run(extra, async () => + this.requestContext.run(extra, async () => this.toResourceResult(uri.toString(), await this.execute(() => this.createResult(toParams(params) as T))) ) ); @@ -250,7 +254,7 @@ export abstract class AbstractMcpResourceHandler> ex outputSchema: this.toolAlternativeOutputSchema }, async (params, extra) => - mcpRequestContext.run(extra, async () => this.toToolResult(await this.execute(() => this.createResult(params as T)))) + this.requestContext.run(extra, async () => this.toToolResult(await this.execute(() => this.createResult(params as T)))) ); } } @@ -330,7 +334,7 @@ export abstract class AbstractMcpDiagramResourceHandler< } /** - * Public dispatch entry point invoked by {@link McpServerLauncher}'s SDK callback for + * Public dispatch entry point invoked by {@link AbstractMcpServerLauncher}'s SDK callback for * resource reads. The launcher passes the URI it received from the SDK plus the URI-template * variable values normalized into a flat record. */ diff --git a/packages/server-mcp/src/server/mcp-session.ts b/packages/server-mcp/src/common/server/mcp-session.ts similarity index 100% rename from packages/server-mcp/src/server/mcp-session.ts rename to packages/server-mcp/src/common/server/mcp-session.ts diff --git a/packages/server-mcp/src/server/mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/server/mcp-tool-handler.spec.ts similarity index 97% rename from packages/server-mcp/src/server/mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/server/mcp-tool-handler.spec.ts index 3a1cbc4..0656fa8 100644 --- a/packages/server-mcp/src/server/mcp-tool-handler.spec.ts +++ b/packages/server-mcp/src/common/server/mcp-tool-handler.spec.ts @@ -106,13 +106,13 @@ const matrix: Array<{ name: 'create-nodes', Constructor: CreateNodesMcpToolHandler, schema: CreateNodesOutputSchema, - sample: { createdNodes: [{ id: 'n1', elementTypeId: 'node:foo' }], errors: [], warnings: [] } + sample: { createdNodes: [{ id: 'n1', elementTypeId: 'node:foo' }], dispatchedCommands: 1, errors: [], warnings: [] } }, { name: 'create-edges', Constructor: CreateEdgesMcpToolHandler, schema: CreateEdgesOutputSchema, - sample: { createdEdges: [{ id: 'e1', elementTypeId: 'edge' }], errors: [] } + sample: { createdEdges: [{ id: 'e1', elementTypeId: 'edge' }], dispatchedCommands: 1, errors: [] } }, { name: 'modify-nodes', @@ -130,11 +130,11 @@ const matrix: Array<{ name: 'delete-elements', Constructor: DeleteElementsMcpToolHandler, schema: DeleteElementsOutputSchema, - sample: { deletedElements: [{ id: 'n1', elementTypeId: 'node:foo' }], deletedCount: 3 } + sample: { deletedElements: [{ id: 'n1', elementTypeId: 'node:foo' }], deletedCount: 3, dispatchedCommands: 1 } }, { name: 'undo', Constructor: UndoMcpToolHandler, schema: UndoOutputSchema, sample: { commandsUndone: 2 } }, { name: 'redo', Constructor: RedoMcpToolHandler, schema: RedoOutputSchema, sample: { commandsRedone: 2 } }, - { name: 'layout', Constructor: LayoutMcpToolHandler, schema: LayoutOutputSchema, sample: { applied: true } }, + { name: 'layout', Constructor: LayoutMcpToolHandler, schema: LayoutOutputSchema, sample: { applied: true, dispatchedCommands: 1 } }, { name: 'validate-diagram', Constructor: ValidateDiagramMcpToolHandler, diff --git a/packages/server-mcp/src/server/mcp-tool-handler.ts b/packages/server-mcp/src/common/server/mcp-tool-handler.ts similarity index 97% rename from packages/server-mcp/src/server/mcp-tool-handler.ts rename to packages/server-mcp/src/common/server/mcp-tool-handler.ts index 49f2f6c..851e208 100644 --- a/packages/server-mcp/src/server/mcp-tool-handler.ts +++ b/packages/server-mcp/src/common/server/mcp-tool-handler.ts @@ -25,7 +25,7 @@ import { ResponseAction } from '@eclipse-glsp/server'; import { ToolAnnotations } from '@modelcontextprotocol/sdk/types'; -import { inject, injectable, interfaces } from 'inversify'; +import { inject, injectable, interfaces, optional } from 'inversify'; import { ZodObject, ZodRawShape } from 'zod/v4'; import { GLSPMcpServer } from './glsp-mcp-server'; import { @@ -44,7 +44,7 @@ import { import { McpIdAliasService } from './mcp-id-alias-service'; import { ElementIdentity, McpDiagramScopedInput } from './mcp-input-schemas'; import { McpLabelProvider } from './mcp-label-provider'; -import { mcpRequestContext } from './mcp-request-context'; +import { McpRequestContext, NoopMcpRequestContext } from './mcp-request-context'; /** * Multi-binding key for **server-scope** tool handlers — singletons that don't target a @@ -75,6 +75,10 @@ export const McpToolHandler = Symbol('McpToolHandler'); */ @injectable() export abstract class BaseMcpToolHandler { + @inject(McpRequestContext) + @optional() + protected requestContext: McpRequestContext = new NoopMcpRequestContext(); + @inject(Logger) protected logger: Logger; /** @@ -222,7 +226,7 @@ export abstract class AbstractMcpToolHandler> extend registerTool(server: GLSPMcpServer): void { server.registerTool(this.name, this.toRegistrationConfig(), async (params, extra) => - mcpRequestContext.run(extra, () => this.execute(() => this.createResult(params as T))) + this.requestContext.run(extra, () => this.execute(() => this.createResult(params as T))) ); } } @@ -253,7 +257,7 @@ export abstract class BaseMcpDiagramToolHandler; /** - * Public dispatch entry point invoked by {@link McpServerLauncher}'s registered SDK + * Public dispatch entry point invoked by {@link AbstractMcpServerLauncher}'s registered SDK * callback. Each sibling sets its own policy — {@link AbstractMcpDiagramToolHandler} * passes through; {@link OperationMcpDiagramToolHandler} enforces the readonly gate. * Adopters don't call this directly. diff --git a/packages/server-mcp/src/tools/handlers/count-elements-mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/tools/handlers/count-elements-mcp-tool-handler.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/count-elements-mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/tools/handlers/count-elements-mcp-tool-handler.spec.ts diff --git a/packages/server-mcp/src/tools/handlers/count-elements-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/count-elements-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/count-elements-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/count-elements-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/create-edges-mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/tools/handlers/create-edges-mcp-tool-handler.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/create-edges-mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/tools/handlers/create-edges-mcp-tool-handler.spec.ts diff --git a/packages/server-mcp/src/tools/handlers/create-edges-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/create-edges-mcp-tool-handler.ts similarity index 97% rename from packages/server-mcp/src/tools/handlers/create-edges-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/create-edges-mcp-tool-handler.ts index 6c3f024..7e37cfe 100644 --- a/packages/server-mcp/src/tools/handlers/create-edges-mcp-tool-handler.ts +++ b/packages/server-mcp/src/common/tools/handlers/create-edges-mcp-tool-handler.ts @@ -18,7 +18,13 @@ import { ChangeRoutingPointsOperation, CreateEdgeOperation, DiagramConfiguration import { inject, injectable, optional } from 'inversify'; import * as z from 'zod/v4'; import { McpToolResult } from '../../server/mcp-handler-shared'; -import { ElementIdentity, ElementIdentitySchema, McpDiagramScopedInputSchema, position } from '../../server/mcp-input-schemas'; +import { + ElementIdentity, + ElementIdentitySchema, + McpDiagramScopedInputSchema, + dispatchedCommands, + position +} from '../../server/mcp-input-schemas'; import { OperationMcpDiagramToolHandler } from '../../server/mcp-tool-handler'; import { formatNoticeList } from '../../util/mcp-util'; @@ -53,6 +59,7 @@ export const CreateEdgesValidationResultSchema = z.object({ export const CreateEdgesOutputSchema = z.object({ createdEdges: z.array(ElementIdentitySchema).describe('Identity of each edge successfully created. Empty in `dryRun` mode.'), + dispatchedCommands, errors: z.array(z.string()).describe('Per-input failure messages; absent or empty when every input succeeded.'), validationResults: z .array(CreateEdgesValidationResultSchema) @@ -159,7 +166,7 @@ export class CreateEdgesMcpToolHandler extends OperationMcpDiagramToolHandler; export const LayoutOutputSchema = z.object({ - applied: z.boolean().describe('Always true on success — surfaced for parity with other operations.') + applied: z.boolean().describe('Always true on success — surfaced for parity with other operations.'), + dispatchedCommands }); /** Not registered by default: requires an adopter-supplied `LayoutEngine` to bind, which only some GLSP servers ship. */ @@ -53,6 +54,6 @@ export class LayoutMcpToolHandler extends OperationMcpDiagramToolHandler { await this.actionDispatcher.dispatch(LayoutOperation.create()); - return this.success('Automatic layout applied', { applied: true }); + return this.success('Automatic layout applied', { applied: true, dispatchedCommands: 1 }); } } diff --git a/packages/server-mcp/src/tools/handlers/modify-edges-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/modify-edges-mcp-tool-handler.ts similarity index 96% rename from packages/server-mcp/src/tools/handlers/modify-edges-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/modify-edges-mcp-tool-handler.ts index d4562b7..8bf38ea 100644 --- a/packages/server-mcp/src/tools/handlers/modify-edges-mcp-tool-handler.ts +++ b/packages/server-mcp/src/common/tools/handlers/modify-edges-mcp-tool-handler.ts @@ -18,7 +18,13 @@ import { ChangeRoutingPointsOperation, GEdge, ReconnectEdgeOperation } from '@ec import { injectable } from 'inversify'; import * as z from 'zod/v4'; import { McpToolError, McpToolResult } from '../../server/mcp-handler-shared'; -import { ElementIdentitySchema, McpDiagramScopedInputSchema, elementId as elementIdSchema, position } from '../../server/mcp-input-schemas'; +import { + ElementIdentitySchema, + McpDiagramScopedInputSchema, + dispatchedCommands, + elementId as elementIdSchema, + position +} from '../../server/mcp-input-schemas'; import { OperationMcpDiagramToolHandler } from '../../server/mcp-tool-handler'; import { formatNoticeList } from '../../util/mcp-util'; @@ -46,7 +52,7 @@ export type ModifyEdgesInput = z.infer; export const ModifyEdgesOutputSchema = z.object({ modifiedEdges: z.array(ElementIdentitySchema).describe('Identity of each edge whose change request was dispatched.'), - dispatchedCommands: z.number().int().describe('Number of underlying GLSP operations dispatched.'), + dispatchedCommands, errors: z.array(z.string()).describe('Per-input failure messages; absent or empty when every input succeeded.') }); diff --git a/packages/server-mcp/src/tools/handlers/modify-nodes-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/modify-nodes-mcp-tool-handler.ts similarity index 96% rename from packages/server-mcp/src/tools/handlers/modify-nodes-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/modify-nodes-mcp-tool-handler.ts index e2fe387..85f4864 100644 --- a/packages/server-mcp/src/tools/handlers/modify-nodes-mcp-tool-handler.ts +++ b/packages/server-mcp/src/common/tools/handlers/modify-nodes-mcp-tool-handler.ts @@ -18,7 +18,13 @@ import { ApplyLabelEditOperation, ChangeBoundsOperation, GEdge, GShapeElement } import { injectable } from 'inversify'; import * as z from 'zod/v4'; import { McpToolError, McpToolResult } from '../../server/mcp-handler-shared'; -import { ElementIdentitySchema, McpDiagramScopedInputSchema, elementId, position } from '../../server/mcp-input-schemas'; +import { + ElementIdentitySchema, + McpDiagramScopedInputSchema, + dispatchedCommands, + elementId, + position +} from '../../server/mcp-input-schemas'; import { OperationMcpDiagramToolHandler } from '../../server/mcp-tool-handler'; import { formatNoticeList } from '../../util/mcp-util'; @@ -48,7 +54,7 @@ export const ModifyNodesOutputSchema = z.object({ modifiedNodes: z .array(ElementIdentitySchema) .describe('Identity of each node whose change request was dispatched (post-modification labels).'), - dispatchedCommands: z.number().int().describe('Number of underlying GLSP operations dispatched (a single change may yield several).'), + dispatchedCommands, warnings: z .array(z.string()) .describe( diff --git a/packages/server-mcp/src/tools/handlers/query-elements-mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/tools/handlers/query-elements-mcp-tool-handler.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/query-elements-mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/tools/handlers/query-elements-mcp-tool-handler.spec.ts diff --git a/packages/server-mcp/src/tools/handlers/query-elements-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/query-elements-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/query-elements-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/query-elements-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/redo-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/redo-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/redo-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/redo-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/save-model-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/save-model-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/save-model-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/save-model-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/session-info-mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/tools/handlers/session-info-mcp-tool-handler.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/session-info-mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/tools/handlers/session-info-mcp-tool-handler.spec.ts diff --git a/packages/server-mcp/src/tools/handlers/session-info-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/session-info-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/session-info-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/session-info-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/set-selection-mcp-tool-handler.spec.ts b/packages/server-mcp/src/common/tools/handlers/set-selection-mcp-tool-handler.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/set-selection-mcp-tool-handler.spec.ts rename to packages/server-mcp/src/common/tools/handlers/set-selection-mcp-tool-handler.spec.ts diff --git a/packages/server-mcp/src/tools/handlers/set-selection-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/set-selection-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/set-selection-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/set-selection-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/set-view-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/set-view-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/set-view-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/set-view-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/undo-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/undo-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/undo-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/undo-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/handlers/validate-diagram-mcp-tool-handler.ts b/packages/server-mcp/src/common/tools/handlers/validate-diagram-mcp-tool-handler.ts similarity index 100% rename from packages/server-mcp/src/tools/handlers/validate-diagram-mcp-tool-handler.ts rename to packages/server-mcp/src/common/tools/handlers/validate-diagram-mcp-tool-handler.ts diff --git a/packages/server-mcp/src/tools/tool-annotations.spec.ts b/packages/server-mcp/src/common/tools/tool-annotations.spec.ts similarity index 100% rename from packages/server-mcp/src/tools/tool-annotations.spec.ts rename to packages/server-mcp/src/common/tools/tool-annotations.spec.ts diff --git a/packages/server-mcp/src/util/markdown-util.ts b/packages/server-mcp/src/common/util/markdown-util.ts similarity index 100% rename from packages/server-mcp/src/util/markdown-util.ts rename to packages/server-mcp/src/common/util/markdown-util.ts diff --git a/packages/server-mcp/src/util/mcp-util.ts b/packages/server-mcp/src/common/util/mcp-util.ts similarity index 100% rename from packages/server-mcp/src/util/mcp-util.ts rename to packages/server-mcp/src/common/util/mcp-util.ts diff --git a/packages/server-mcp/src/node/index.ts b/packages/server-mcp/src/node/index.ts new file mode 100644 index 0000000..6103438 --- /dev/null +++ b/packages/server-mcp/src/node/index.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from './modules/node-mcp-server-module'; +export * from './reexport'; +export * from './server/node-mcp-request-context'; +export * from './server/node-mcp-server-launcher'; diff --git a/packages/server-mcp/src/node/modules/node-mcp-server-module.ts b/packages/server-mcp/src/node/modules/node-mcp-server-module.ts new file mode 100644 index 0000000..401714c --- /dev/null +++ b/packages/server-mcp/src/node/modules/node-mcp-server-module.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { BindingTarget } from '@eclipse-glsp/server'; +import { AbstractMcpServerModule, DEFAULT_MCP_OPTIONS } from '../../common/modules/abstract-mcp-server-module'; +import { AbstractMcpServerLauncher } from '../../common/server/abstract-mcp-server-launcher'; +import { McpServerDefaults } from '../../common/server/mcp-options'; +import { McpRequestContext } from '../../common/server/mcp-request-context'; +import { NodeMcpServerLauncher } from '../server/node-mcp-server-launcher'; +import { NodeMcpRequestContext } from '../server/node-mcp-request-context'; + +/** + * Default {@link AbstractMcpServerModule} entry point for Node hosts. Ships GLSP-default option + * values (see {@link DEFAULT_OPTIONS}) on top of the abstract module's hook defaults, and binds + * the Node-flavored {@link NodeMcpServerLauncher} + {@link NodeMcpRequestContext}. Adopter-provided + * overrides via the constructor merge on top. + * + * @experimental The MCP integration is under active development. Option names, schema shapes, + * and handler contracts MAY change in minor releases until the feature graduates from + * experimental status. + */ +export class NodeMcpServerModule extends AbstractMcpServerModule { + static readonly DEFAULT_OPTIONS: McpServerDefaults = { + ...DEFAULT_MCP_OPTIONS, + host: '127.0.0.1', + allowedHosts: ['127.0.0.1', 'localhost'] + // `allowedOrigins` deliberately undefined: accept absent Origin (typical for desktop-IDE + // MCP clients) and rely on Host validation to gate DNS-rebinding. Adopters whose + // deployment is browser-fronted set this explicitly to their frontend's origin. + }; + + constructor(overrides: McpServerDefaults = {}) { + super({ ...NodeMcpServerModule.DEFAULT_OPTIONS, ...overrides }); + } + + protected override bindMcpServerLauncher(): BindingTarget { + return NodeMcpServerLauncher; + } + + protected override bindMcpRequestContext(): BindingTarget { + return NodeMcpRequestContext; + } +} diff --git a/packages/server-mcp/src/node/reexport.ts b/packages/server-mcp/src/node/reexport.ts new file mode 100644 index 0000000..be944aa --- /dev/null +++ b/packages/server-mcp/src/node/reexport.ts @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from '../common'; diff --git a/packages/server-mcp/src/node/server/mcp-http-transport-e2e.spec.ts b/packages/server-mcp/src/node/server/mcp-http-transport-e2e.spec.ts new file mode 100644 index 0000000..18cfe6b --- /dev/null +++ b/packages/server-mcp/src/node/server/mcp-http-transport-e2e.spec.ts @@ -0,0 +1,297 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ClientSessionManager, InitializeParameters, InitializeResult, Logger, NullLogger } from '@eclipse-glsp/server'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { expect } from 'chai'; +import { Container, ContainerModule } from 'inversify'; +import * as z from 'zod/v4'; +import { FullMcpServerConfiguration } from '../../common/server/abstract-mcp-server-launcher'; +import { DefaultGLSPMcpServer, GLSPMcpServerFactory } from '../../common/server/glsp-mcp-server'; +import { McpDiagramHandlerDispatcher } from '../../common/server/mcp-diagram-handler-dispatcher'; +import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from '../../common/server/mcp-log-level-registry'; +import { McpServerDefaults, McpServerOptions } from '../../common/server/mcp-options'; +import { NodeMcpServerLauncher } from './node-mcp-server-launcher'; +import { rawHttpRequest } from './raw-http.spec'; + +class StubDispatcher implements McpDiagramHandlerDispatcher { + harvest(): void { + /* no-op */ + } + reset(): void { + /* no-op */ + } + hasDiagramTools(): boolean { + return false; + } + hasDiagramResources(): boolean { + return false; + } + hasDiagramPrompts(): boolean { + return false; + } + registerAll(): void { + /* no-op */ + } +} + +class StubClientSessionManager { + addListener(): boolean { + return true; + } + removeListener(): boolean { + return true; + } + getSessions(): unknown[] { + return []; + } + getSession(): unknown { + return undefined; + } + getSessionsByType(): unknown[] { + return []; + } +} + +/** + * Test subclass that registers a single `echo` tool on every fresh GLSP MCP server. We use this + * to drive a real MCP SDK client through a full handshake without depending on the workflow + * server's diagram handler stack. + */ +class EchoLauncher extends NodeMcpServerLauncher { + protected override createGlspMcpServer(config: FullMcpServerConfiguration): DefaultGLSPMcpServer { + const server = super.createGlspMcpServer(config) as DefaultGLSPMcpServer; + server.registerTool( + 'echo', + { description: 'Returns the supplied message verbatim.', inputSchema: { message: z.string() } }, + async ({ message }) => ({ content: [{ type: 'text', text: message }] }) + ); + return server; + } +} + +function buildLauncher(optionValues: McpServerOptions['values'] = {}): EchoLauncher { + const container = new Container(); + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new NullLogger()); + const opts = new McpServerOptions(); + opts.values = optionValues; + bind(McpServerOptions).toConstantValue(opts); + bind(McpServerDefaults).toConstantValue(optionValues); + bind(McpDiagramHandlerDispatcher).toConstantValue(new StubDispatcher()); + bind(McpLogLevelRegistry).to(DefaultMcpLogLevelRegistry).inSingletonScope(); + bind(ClientSessionManager).toConstantValue(new StubClientSessionManager()); + const factory: GLSPMcpServerFactory = (mcpServer, options) => new DefaultGLSPMcpServer(mcpServer, options, new NullLogger()); + bind(GLSPMcpServerFactory).toConstantValue(factory); + bind(EchoLauncher).toSelf().inSingletonScope(); + }) + ); + return container.get(EchoLauncher); +} + +async function startLauncher( + launcher: EchoLauncher, + options: { port?: number; host?: string; route?: string; allowedHosts?: string[]; allowedOrigins?: string[] } = {} +): Promise { + const params: InitializeParameters = { + applicationId: 'spec-app', + clientSessionId: 'spec-session', + protocolVersion: '1.0.0', + args: {}, + mcpServer: { + port: options.port ?? 0, + route: options.route ?? '/mcp', + name: 'spec', + options: {} + } + } as unknown as InitializeParameters; + // Inject the deploy-only fields directly on the merged options holder. + launcher['mcpDefaults'] = { + host: options.host ?? '127.0.0.1', + allowedHosts: options.allowedHosts, + allowedOrigins: options.allowedOrigins + }; + const baseResult: InitializeResult = { protocolVersion: '1.0.0', serverActions: {} } as unknown as InitializeResult; + const result = await launcher.initializeServer({} as never, params, baseResult); + const url = (result as unknown as { mcpServer?: { url?: string } }).mcpServer?.url; + if (!url) throw new Error('launcher did not announce a URL'); + return url; +} + +/** + * End-to-end smoke test: boot the launcher (which spins up the Hono-on-Node listener), + * connect a real SDK MCP client over Streamable HTTP, and exercise a tool round-trip (`echo`). + */ +describe('NodeMcpServerLauncher (e2e — real MCP SDK client over HTTP)', () => { + let launcher: EchoLauncher | undefined; + let client: Client | undefined; + + async function safeClose(target: Client | undefined): Promise { + if (!target) return; + try { + await target.close(); + } catch { + /* ignore — best effort */ + } + } + + afterEach(async () => { + await safeClose(client); + client = undefined; + launcher?.dispose(); + launcher = undefined; + }); + + it('round-trips tools/list and tools/call against a registered echo tool', async () => { + launcher = buildLauncher(); + const url = await startLauncher(launcher); + expect(url, 'launcher must announce a URL after initializeServer').to.be.a('string'); + + client = new Client({ name: 'wn-058-test-client', version: '1.0.0' }); + await client.connect(new StreamableHTTPClientTransport(new URL(url))); + + const tools = await client.listTools(); + expect(tools.tools.map(tool => tool.name)).to.include('echo'); + + const result = await client.callTool({ name: 'echo', arguments: { message: 'hello GLSP' } }); + expect(result.isError).to.not.equal(true); + expect(result.content).to.have.lengthOf(1); + const [block] = result.content as Array<{ type: string; text?: string }>; + expect(block.type).to.equal('text'); + expect(block.text).to.equal('hello GLSP'); + }); + + it('DELETE /mcp terminates the session; subsequent POSTs with the same id return 404 (§ #5)', async () => { + launcher = buildLauncher(); + const url = await startLauncher(launcher); + + client = new Client({ name: 'delete-smoke-test', version: '1.0.0' }); + const clientTransport = new StreamableHTTPClientTransport(new URL(url)); + await client.connect(clientTransport); + const sessionId = clientTransport.sessionId; + expect(sessionId, 'SDK transport should expose the minted session id after initialize').to.be.a('string'); + + const port = Number(new URL(url).port); + + const deleteRes = await rawHttpRequest(port, 'DELETE', { 'mcp-session-id': sessionId! }); + expect(deleteRes.status, 'DELETE should succeed for an active session').to.be.lessThan(300); + + const followUp = await rawHttpRequest( + port, + 'POST', + { 'mcp-session-id': sessionId! }, + { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } + ); + expect(followUp.status).to.equal(404); + const payload = JSON.parse(followUp.body); + expect(payload.error.code).to.equal(-32001); + }); +}); + +describe('NodeMcpServerLauncher (raw HTTP wire validation)', () => { + let launcher: EchoLauncher | undefined; + + afterEach(() => { + launcher?.dispose(); + launcher = undefined; + }); + + async function startWith(values: McpServerOptions['values']): Promise { + launcher = buildLauncher(values); + const url = await startLauncher(launcher, { allowedHosts: values.allowedHosts, allowedOrigins: values.allowedOrigins }); + return Number(new URL(url).port); + } + + it('binds an HTTP listener on a resolvable loopback port', async () => { + launcher = buildLauncher(); + const url = await startLauncher(launcher); + expect(url).to.match(/^http:\/\/(127\.0\.0\.1|localhost):\d+\/mcp$/); + }); + + it('rejects a non-initialize POST without an Mcp-Session-Id header with 400 (§ #2)', async () => { + const port = await startWith({}); + const res = await rawHttpRequest(port, 'POST', {}, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); + expect(res.status).to.equal(400); + const payload = JSON.parse(res.body); + expect(payload.error.code).to.equal(-32000); + expect(payload.error.message).to.match(/No valid session ID/); + }); + + it('rejects a POST with an unknown Mcp-Session-Id with 404 (§ #3)', async () => { + const port = await startWith({}); + const res = await rawHttpRequest( + port, + 'POST', + { 'mcp-session-id': 'no-such-session' }, + { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } + ); + expect(res.status).to.equal(404); + const payload = JSON.parse(res.body); + expect(payload.error.code).to.equal(-32001); + }); + + it('rejects a non-initialize POST whose MCP-Protocol-Version is unsupported with HTTP 400', async () => { + const port = await startWith({}); + const res = await rawHttpRequest( + port, + 'POST', + { 'mcp-session-id': 'doesnt-matter', 'mcp-protocol-version': '1999-01-01' }, + { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } + ); + expect(res.status).to.equal(400); + const payload = JSON.parse(res.body); + expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/); + }); + + it('rejects requests with a Host header outside the allowlist', async () => { + const port = await startWith({ allowedHosts: ['127.0.0.1', 'localhost'] }); + // POST init body so we go past session-id validation into the SDK transport's host check. + const res = await rawHttpRequest( + port, + 'POST', + { Host: 'evil.example' }, + { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'x', version: '1' } } + } + ); + expect(res.status).to.equal(403); + }); + + it('rejects start when the requested port is already in use with an actionable error', async () => { + const firstLauncher = buildLauncher(); + const url = await startLauncher(firstLauncher); + const taken = Number(new URL(url).port); + const secondLauncher = buildLauncher(); + let error: Error | undefined; + try { + await startLauncher(secondLauncher, { port: taken }); + } catch (err: unknown) { + error = err as Error; + } finally { + firstLauncher.dispose(); + secondLauncher.dispose(); + } + expect(error, 'expected initializeServer to reject').to.not.equal(undefined); + expect(error!.message).to.match(/127\.0\.0\.1:\d+/); + expect(error!.message).to.include('mcpServer.port'); + expect(error!.message).to.match(/already in use/); + }); +}); diff --git a/packages/server-mcp/src/node/server/node-mcp-request-context.ts b/packages/server-mcp/src/node/server/node-mcp-request-context.ts new file mode 100644 index 0000000..82bef66 --- /dev/null +++ b/packages/server-mcp/src/node/server/node-mcp-request-context.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { AsyncLocalStorage } from 'async_hooks'; +import { injectable } from 'inversify'; +import { McpRequestContext, McpRequestExtra } from '../../common/server/mcp-request-context'; + +/** {@link McpRequestContext} backed by native `AsyncLocalStorage`; frame survives across `await`. */ +@injectable() +export class NodeMcpRequestContext implements McpRequestContext { + protected storage = new AsyncLocalStorage(); + + run(extra: McpRequestExtra, callback: () => R): R { + return this.storage.run(extra, callback); + } + + getStore(): McpRequestExtra | undefined { + return this.storage.getStore(); + } +} diff --git a/packages/server-mcp/src/server/mcp-server-launcher.spec.ts b/packages/server-mcp/src/node/server/node-mcp-server-launcher.spec.ts similarity index 86% rename from packages/server-mcp/src/server/mcp-server-launcher.spec.ts rename to packages/server-mcp/src/node/server/node-mcp-server-launcher.spec.ts index 6c21d2e..473645a 100644 --- a/packages/server-mcp/src/server/mcp-server-launcher.spec.ts +++ b/packages/server-mcp/src/node/server/node-mcp-server-launcher.spec.ts @@ -16,10 +16,11 @@ import { McpServerInitOptions } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; -import { version as packageVersion } from '../../package.json'; -import { McpServerLauncher, SERVER_VERSION, assertLoopbackOrAcknowledged, isLoopbackHost, pickInitOptions } from './mcp-server-launcher'; +import { version as packageVersion } from '../../../package.json'; +import { SERVER_VERSION, pickInitOptions } from '../../common/server/abstract-mcp-server-launcher'; +import { NodeMcpServerLauncher, assertLoopbackOrAcknowledged, isLoopbackHost } from './node-mcp-server-launcher'; -describe('McpServerLauncher · SERVER_VERSION', () => { +describe('NodeMcpServerLauncher · SERVER_VERSION', () => { it('matches the package.json version (no stale literal)', () => { // Regression guard: the launcher used to hard-code '1.0.0'. Pull from package.json so // adopters and MCP clients can tell builds apart via the `serverInfo.version` handshake @@ -28,7 +29,7 @@ describe('McpServerLauncher · SERVER_VERSION', () => { }); }); -describe('McpServerLauncher · buildCapabilities', () => { +describe('NodeMcpServerLauncher · buildCapabilities', () => { /** * Sidestep DI: build a stub whose shape matches the fields `buildCapabilities` reads, then * invoke the prototype method against it. The method is protected, so we cast through. @@ -54,7 +55,7 @@ describe('McpServerLauncher · buildCapabilities', () => { hasDiagramResources: () => args.hasDiagramResources ?? false } }; - const proto = McpServerLauncher.prototype as unknown as { + const proto = NodeMcpServerLauncher.prototype as unknown as { buildCapabilities(this: typeof stub, resourcesAsResources: boolean): Record; }; return proto.buildCapabilities.call(stub, resourcesAsResources); @@ -97,7 +98,7 @@ describe('McpServerLauncher · buildCapabilities', () => { }); }); -describe('McpServerLauncher · isLoopbackHost', () => { +describe('NodeMcpServerLauncher · isLoopbackHost', () => { it('treats 127.0.0.0/8, localhost, and ::1 as loopback', () => { expect(isLoopbackHost('127.0.0.1')).to.equal(true); expect(isLoopbackHost('127.55.0.1')).to.equal(true); @@ -114,7 +115,7 @@ describe('McpServerLauncher · isLoopbackHost', () => { }); }); -describe('McpServerLauncher · assertLoopbackOrAcknowledged (auth footgun)', () => { +describe('NodeMcpServerLauncher · assertLoopbackOrAcknowledged (auth footgun)', () => { it('passes silently for a loopback bind without acknowledgement', () => { expect(() => assertLoopbackOrAcknowledged('127.0.0.1', undefined)).to.not.throw(); expect(() => assertLoopbackOrAcknowledged('localhost', undefined)).to.not.throw(); @@ -139,7 +140,17 @@ describe('McpServerLauncher · assertLoopbackOrAcknowledged (auth footgun)', () }); }); -describe('McpServerLauncher · pickInitOptions (deploy/init split — defense-in-depth)', () => { +// Compile-time exhaustiveness check — fails the build if `McpServerInitOptions` grows or +// shrinks while the `pickInitOptions` destructure doesn't follow. +type _Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; +function assertPickInitKeysExhaustive( + _: _Equal +): void { + /* type-only */ +} +assertPickInitKeysExhaustive(true); + +describe('NodeMcpServerLauncher · pickInitOptions (deploy/init split — defense-in-depth)', () => { it('passes through every allowed init-side field unchanged', () => { const picked = pickInitOptions({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 }); expect(picked).to.deep.equal({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 }); diff --git a/packages/server-mcp/src/node/server/node-mcp-server-launcher.ts b/packages/server-mcp/src/node/server/node-mcp-server-launcher.ts new file mode 100644 index 0000000..69df23f --- /dev/null +++ b/packages/server-mcp/src/node/server/node-mcp-server-launcher.ts @@ -0,0 +1,130 @@ +/******************************************************************************** + * Copyright (c) 2025-2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Disposable } from '@eclipse-glsp/server'; +import { serve } from '@hono/node-server'; +import { injectable } from 'inversify'; +import * as http from 'http'; +import { AddressInfo } from 'net'; +import { AbstractMcpServerLauncher, FullMcpServerConfiguration, TransportEndpoint } from '../../common/server/abstract-mcp-server-launcher'; + +// Re-export the subclass-extension types that adopters need when subclassing the launcher. +export { FullMcpServerConfiguration, TransportEndpoint } from '../../common/server/abstract-mcp-server-launcher'; + +/** + * Stdout tag used to announce the started MCP server so IDE integrations can pick up the URL + * automatically. The full line is `MCP_SERVER_READY_MSG + JSON.stringify({name, url, route})`. + */ +export const MCP_SERVER_READY_MSG = '[GLSP-MCP-Server]:Ready. '; + +/** + * Returns true iff `host` is a loopback bind: `localhost`, `::1`, or any IPv4 in + * `127.0.0.0/8`. Any other value (`0.0.0.0`, `::`, LAN/public addresses) is non-loopback. + * Used by {@link assertLoopbackOrAcknowledged} for the auth-footgun runtime check. + */ +export function isLoopbackHost(host: string): boolean { + return host === 'localhost' || host === '::1' || /^127\./.test(host); +} + +/** + * Refuse to bind on a non-loopback host unless the operator has acknowledged that traffic is + * authenticated externally (reverse proxy, mTLS, ACL). The MCP server has no built-in auth. + * Exported for regression tests only; not part of the public surface. + */ +export function assertLoopbackOrAcknowledged(host: string, acknowledgedNoAuth: boolean | undefined): void { + if (isLoopbackHost(host) || acknowledgedNoAuth === true) { + return; + } + throw new Error( + `Refusing to bind MCP server to non-loopback host '${host}' without authentication. ` + + 'The MCP server has no built-in auth; binding to a non-loopback interface exposes an ' + + 'unauthenticated MCP endpoint to the network. If this is intentional (e.g., the endpoint ' + + 'is fronted by a reverse proxy, mTLS, or a network ACL that authenticates traffic), set ' + + '`acknowledgedNoAuth: true` on the McpServerDefaults you pass to the server module.' + ); +} + +/** + * Boots the embedded MCP HTTP server when a GLSP `initialize` call carries an `mcpServer` + * configuration. Runs in-process via the {@link GLSPServerInitializer} lifecycle. The portable + * per-session multiplexer + handler dispatch lives on {@link AbstractMcpServerLauncher}; this + * subclass only binds the Node HTTP listener via `@hono/node-server`, runs the loopback-auth + * guard, and announces the URL via {@link MCP_SERVER_READY_MSG}. + */ +@injectable() +export class NodeMcpServerLauncher extends AbstractMcpServerLauncher { + protected async bindTransport(config: FullMcpServerConfiguration): Promise { + // Auth-footgun guard: refuse non-loopback bind unless the operator opted in via + // `acknowledgedNoAuth`. Runs BEFORE the listener binds so a careless `host: '0.0.0.0'` + // doesn't get a chance to expose an unauthenticated endpoint. + assertLoopbackOrAcknowledged(config.host, config.options.acknowledgedNoAuth); + + const handler = this.getRequestHandler(); + const server = await this.startListener(handler, config); + // Disable the per-request timeout so long-lived SSE GET streams aren't killed during + // chat idle periods. Node's default 5-minute `requestTimeout` (>=18.1) treats SSE as + // a single in-progress request and tears the socket whenever no events flow for ≥5 min. + server.requestTimeout = 0; + + const addressInfo = this.resolveAddress(server); + const url = this.toServerUrl(addressInfo, config.route); + this.toDispose.push(Disposable.create(() => server.close())); + + // stdout ready-marker for parent processes to discover the URL. Uses `console.log` + // (not the GLSP logger) so adopter logger config can never hide it. + console.log(MCP_SERVER_READY_MSG + JSON.stringify({ name: config.name, url, route: config.route })); + return { url }; + } + + /** + * Start the Hono-on-Node listener, returning the underlying Node `http.Server` once it is + * listening. Translates `EADDRINUSE` into an actionable error naming the offending port + * and the override path adopters reach for first (`mcpServer.port` in `initialize`). + */ + protected startListener(handler: (req: Request) => Promise, config: FullMcpServerConfiguration): Promise { + return new Promise((resolve, reject) => { + const server = serve({ fetch: handler, port: config.port, hostname: config.host }) as http.Server; + server.once('error', err => { + const nodeErr = err as NodeJS.ErrnoException; + if (nodeErr.code === 'EADDRINUSE') { + const portLabel = config.port === 0 ? 'requested address' : `${config.host}:${config.port}`; + reject( + new Error( + `MCP server cannot bind ${portLabel}: address already in use. ` + + 'Pass a different `mcpServer.port` in the GLSP `initialize` call, or omit the port to get a random one.' + ) + ); + return; + } + reject(err); + }); + server.once('listening', () => resolve(server)); + }); + } + + protected resolveAddress(server: http.Server): AddressInfo { + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error(`MCP server unexpectedly bound to ${String(address)} — expected an AddressInfo.`); + } + return address; + } + + protected toServerUrl({ address, family, port }: AddressInfo, route: string, protocol = 'http'): string { + const host = address === '::' || address === '0.0.0.0' ? 'localhost' : family === 'IPv6' ? `[${address}]` : address; + return `${protocol}://${host}:${port}${route}`; + } +} diff --git a/packages/server-mcp/src/server/raw-http.spec.ts b/packages/server-mcp/src/node/server/raw-http.spec.ts similarity index 100% rename from packages/server-mcp/src/server/raw-http.spec.ts rename to packages/server-mcp/src/node/server/raw-http.spec.ts diff --git a/packages/server-mcp/src/server/mcp-http-transport-e2e.spec.ts b/packages/server-mcp/src/server/mcp-http-transport-e2e.spec.ts deleted file mode 100644 index 9ec2659..0000000 --- a/packages/server-mcp/src/server/mcp-http-transport-e2e.spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Logger, NullLogger } from '@eclipse-glsp/server'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { expect } from 'chai'; -import { Container, ContainerModule } from 'inversify'; -import * as z from 'zod/v4'; -import { McpHttpTransport } from './mcp-http-transport'; -import { McpServerOptions } from './mcp-options'; -import { rawHttpRequest } from './raw-http.spec'; - -function buildTransport(): McpHttpTransport { - const container = new Container(); - container.load( - new ContainerModule(bind => { - bind(Logger).toConstantValue(new NullLogger()); - const opts = new McpServerOptions(); - opts.values = {}; - bind(McpServerOptions).toConstantValue(opts); - bind(McpHttpTransport).toSelf().inSingletonScope(); - }) - ); - return container.get(McpHttpTransport); -} - -/** - * WN-058 — End-to-end smoke test that boots {@link McpHttpTransport}, - * connects a real SDK MCP client over Streamable HTTP, and exercises a tool - * round-trip (`echo`). Validates that the transport implements the protocol - * conformantly without depending on the GLSP handler stack. - */ -describe('McpHttpTransport (WN-058 e2e — real MCP SDK client over HTTP)', () => { - let httpServer: McpHttpTransport | undefined; - let client: Client | undefined; - - /** - * Idempotent client close: a test that already tore the SDK client down (e.g. after a - * spec-mandated DELETE on the same session) leaves the local `client` ref pointing at a - * closed instance. `Client.close()` is safe to call twice on the SDK side, but we still - * swallow any throw so afterEach is robust under partial-progress failures too. - */ - async function safeClose(target: Client | undefined): Promise { - if (!target) return; - try { - await target.close(); - } catch { - /* ignore — best effort */ - } - } - - afterEach(async () => { - await safeClose(client); - client = undefined; - httpServer?.dispose(); - httpServer = undefined; - }); - - it('round-trips tools/list and tools/call against a registered echo tool', async () => { - httpServer = buildTransport(); - - // Wire one fresh `McpServer` per accepted session, register an `echo` tool, - // and let the server attach to the transport. Mirrors what `McpServerLauncher` - // does; kept inline here so the test exercises only the transport path. - httpServer.onSessionInitialized(session => { - const mcpServer = new McpServer({ name: 'glsp-test', version: '1.0.0' }, { capabilities: {} }); - mcpServer.registerTool( - 'echo', - { description: 'Returns the supplied message verbatim.', inputSchema: { message: z.string() } }, - async ({ message }) => ({ content: [{ type: 'text', text: message }] }) - ); - mcpServer.connect(session); - }); - - const endpoint = await httpServer.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'glsp-test', - options: { dataMode: 'tools' } - }); - expect(endpoint.url, 'transport must report a URL after start()').to.be.a('string'); - - client = new Client({ name: 'wn-058-test-client', version: '1.0.0' }); - await client.connect(new StreamableHTTPClientTransport(new URL(endpoint.url!))); - - const tools = await client.listTools(); - expect(tools.tools.map(tool => tool.name)).to.include('echo'); - - const result = await client.callTool({ name: 'echo', arguments: { message: 'hello GLSP' } }); - expect(result.isError).to.not.equal(true); - expect(result.content).to.have.lengthOf(1); - const [block] = result.content as Array<{ type: string; text?: string }>; - expect(block.type).to.equal('text'); - expect(block.text).to.equal('hello GLSP'); - }); - - it('DELETE /mcp terminates the session; subsequent POSTs with the same id return 404 (§ #5)', async () => { - httpServer = buildTransport(); - httpServer.onSessionInitialized(session => { - const mcpServer = new McpServer({ name: 'glsp-test', version: '1.0.0' }, { capabilities: {} }); - mcpServer.connect(session); - }); - const endpoint = await httpServer.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'glsp-test', - options: { dataMode: 'tools' } - }); - - client = new Client({ name: 'delete-smoke-test', version: '1.0.0' }); - const clientTransport = new StreamableHTTPClientTransport(new URL(endpoint.url!)); - await client.connect(clientTransport); - const sessionId = clientTransport.sessionId; - expect(sessionId, 'SDK transport should expose the minted session id after initialize').to.be.a('string'); - - const { port } = await httpServer.getAddress(); - - // 1. Spec § Session Management #5 — DELETE terminates the session. - const deleteRes = await rawHttpRequest(port, 'DELETE', { 'mcp-session-id': sessionId! }); - expect(deleteRes.status, 'DELETE should succeed for an active session').to.be.lessThan(300); - - // 2. After termination, the same id MUST be rejected with 404 (§ #3) — proves the - // session was actually removed, not just acked. - const followUp = await rawHttpRequest( - port, - 'POST', - { 'mcp-session-id': sessionId! }, - { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } - ); - expect(followUp.status).to.equal(404); - const payload = JSON.parse(followUp.body); - expect(payload.error.code).to.equal(-32001); - }); -}); diff --git a/packages/server-mcp/src/server/mcp-http-transport.spec.ts b/packages/server-mcp/src/server/mcp-http-transport.spec.ts deleted file mode 100644 index 4311003..0000000 --- a/packages/server-mcp/src/server/mcp-http-transport.spec.ts +++ /dev/null @@ -1,385 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2026 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Logger, NullLogger } from '@eclipse-glsp/server'; -import { expect } from 'chai'; -import { Container, ContainerModule } from 'inversify'; -import { McpHttpTransport } from './mcp-http-transport'; -import { McpServerOptions } from './mcp-options'; -import { rawHttpRequest } from './raw-http.spec'; - -/** - * Builds a transport instance with a configurable `McpServerOptions` binding so tests can - * exercise the SDK-level host allowlist (forwarded via `createMcpExpressApp`) and our own - * Origin allowlist (installed in `configureExpressApp`). - */ -function buildTransport(optionValues: McpServerOptions['values'] = {}): McpHttpTransport { - const container = new Container(); - container.load( - new ContainerModule(bind => { - bind(Logger).toConstantValue(new NullLogger()); - const opts = new McpServerOptions(); - opts.values = optionValues; - bind(McpServerOptions).toConstantValue(opts); - bind(McpHttpTransport).toSelf().inSingletonScope(); - }) - ); - return container.get(McpHttpTransport); -} - -describe('McpHttpTransport (startup smoke test)', () => { - let httpServer: McpHttpTransport | undefined; - - afterEach(() => { - // Always tear down so a failing assertion does not leak the listening socket. - httpServer?.dispose(); - httpServer = undefined; - }); - - it('binds an HTTP server on a resolvable port and 127.0.0.1 host', async () => { - httpServer = buildTransport(); - - const endpoint = await httpServer.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'test', - options: { dataMode: 'tools' } - }); - - expect(endpoint.url).to.match(/^http:\/\/127\.0\.0\.1:\d+\/mcp$/); - - const address = await httpServer.getAddress(); - expect(address.address).to.equal('127.0.0.1'); - expect(address.port).to.be.a('number'); - expect(address.port).to.be.greaterThan(0); - }); - - it('exposes the underlying http.Server and Express app after start', async () => { - httpServer = buildTransport(); - - await httpServer.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'test', - options: { dataMode: 'tools' } - }); - - expect(httpServer.app).to.not.be.undefined; - expect(httpServer.server).to.not.be.undefined; - expect(httpServer.server!.listening).to.equal(true); - }); - - it('closes the underlying http.Server on dispose()', async () => { - const local = buildTransport(); - - await local.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'test', - options: { dataMode: 'tools' } - }); - const underlying = local.server!; - expect(underlying.listening).to.equal(true); - - local.dispose(); - - // `Server.close()` is asynchronous — wait for the actual close event. - await new Promise(resolve => { - if (!underlying.listening) { - resolve(); - return; - } - underlying.once('close', () => resolve()); - }); - expect(underlying.listening).to.equal(false); - }); - - it('rejects start() with an actionable error when the requested port is already in use', async () => { - // First transport: bind a random port so we know exactly which port is taken. - const first = buildTransport(); - await first.start({ - port: 0, - host: '127.0.0.1', - route: '/mcp', - name: 'first', - options: { dataMode: 'tools' } - }); - const taken = (await first.getAddress()).port; - - // Second transport: try to bind the same port → EADDRINUSE. - const second = buildTransport(); - try { - await expectStartToReject(second, taken); - } finally { - first.dispose(); - second.dispose(); - } - }); - - async function expectStartToReject(transport: McpHttpTransport, takenPort: number): Promise { - let error: Error | undefined; - try { - await transport.start({ - port: takenPort, - host: '127.0.0.1', - route: '/mcp', - name: 'second', - options: { dataMode: 'tools' } - }); - } catch (err: unknown) { - error = err as Error; - } - expect(error, 'expected start() to reject').to.not.equal(undefined); - // The actionable hint must name the offending host:port AND point at the override path. - expect(error!.message).to.include(`127.0.0.1:${takenPort}`); - expect(error!.message).to.include('mcpServer.port'); - expect(error!.message).to.match(/already in use/); - } -}); - -describe('McpHttpTransport (Origin/Host validation — DNS-rebinding mitigation)', () => { - let httpServer: McpHttpTransport | undefined; - - afterEach(() => { - httpServer?.dispose(); - httpServer = undefined; - }); - - it('rejects requests with a Host header outside the allowlist', async () => { - httpServer = buildTransport({ allowedHosts: ['127.0.0.1', 'localhost'] }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - const port = (await httpServer.getAddress()).port; - - const res = await rawHttpRequest(port, 'POST', { Host: 'evil.example' }, {}); - - expect(res.status).to.equal(403); - expect(res.body).to.match(/Host/); - }); - - it('accepts requests whose Host header matches the allowlist', async () => { - httpServer = buildTransport({ allowedHosts: ['127.0.0.1', 'localhost'] }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - const port = (await httpServer.getAddress()).port; - - // No `mcp-session-id` header on a non-init body → session-id gate rejects with 400 - // (not the middleware's 403). What matters here is that we got *past* the middleware. - const res = await rawHttpRequest(port, 'POST', { Host: `127.0.0.1:${port}` }, {}); - - expect(res.status).to.not.equal(403); - }); - - it('rejects requests whose Origin header is outside an explicit allowlist', async () => { - httpServer = buildTransport({ - allowedHosts: ['127.0.0.1', 'localhost'], - allowedOrigins: ['https://my-frontend.example'] - }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - const port = (await httpServer.getAddress()).port; - - const res = await rawHttpRequest(port, 'POST', { Host: '127.0.0.1', Origin: 'https://evil.example' }, {}); - - expect(res.status).to.equal(403); - expect(res.body).to.match(/Origin/); - }); - - it('skips Origin checks when allowedOrigins is undefined (desktop-IDE default)', async () => { - httpServer = buildTransport({ allowedHosts: ['127.0.0.1', 'localhost'] }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - const port = (await httpServer.getAddress()).port; - - // With allowedOrigins undefined, ANY Origin (or none) is allowed past the middleware. - const res = await rawHttpRequest(port, 'POST', { Host: '127.0.0.1', Origin: 'https://anything.example' }, {}); - - expect(res.status).to.not.equal(403); - }); -}); - -describe('McpHttpTransport (session-id validation per MCP Streamable HTTP § Session Management)', () => { - let httpServer: McpHttpTransport | undefined; - - afterEach(() => { - httpServer?.dispose(); - httpServer = undefined; - }); - - async function startTransport(): Promise { - httpServer = buildTransport({ allowedHosts: ['127.0.0.1', 'localhost'] }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - return (await httpServer.getAddress()).port; - } - - it('rejects a non-initialize POST without an Mcp-Session-Id header with 400 (§ #2)', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest(port, 'POST', {}, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.jsonrpc).to.equal('2.0'); - // eslint-disable-next-line no-null/no-null -- JSON-RPC 2.0 § 5 mandates `null` for unattributable error responses. - expect(payload.id).to.equal(null); - expect(payload.error.code).to.equal(-32000); - expect(payload.error.message).to.match(/No valid session ID/); - }); - - it('rejects a POST with an unknown Mcp-Session-Id with 404 (§ #3)', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest( - port, - 'POST', - { 'mcp-session-id': 'no-such-session' }, - { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } - ); - - expect(res.status).to.equal(404); - const payload = JSON.parse(res.body); - expect(payload.jsonrpc).to.equal('2.0'); - // eslint-disable-next-line no-null/no-null -- JSON-RPC 2.0 § 5 mandates `null` for unattributable error responses. - expect(payload.id).to.equal(null); - expect(payload.error.code).to.equal(-32001); - expect(payload.error.message).to.match(/Session not found/i); - }); - - it('rejects an initialize POST that carries an unknown Mcp-Session-Id with 404 (§ #3 — must not silently mint)', async () => { - const port = await startTransport(); - - const initBody = { - jsonrpc: '2.0', - id: 1, - method: 'initialize', - params: { - protocolVersion: '2025-06-18', - capabilities: {}, - clientInfo: { name: 'test', version: '0.0.1' } - } - }; - - const res = await rawHttpRequest(port, 'POST', { 'mcp-session-id': 'stale-session' }, initBody); - - expect(res.status).to.equal(404); - const payload = JSON.parse(res.body); - expect(payload.error.code).to.equal(-32001); - }); - - it('rejects a GET (SSE stream) with an unknown Mcp-Session-Id with the JSON-RPC envelope', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest(port, 'GET', { 'mcp-session-id': 'no-such-session' }); - - expect(res.status).to.equal(404); - const payload = JSON.parse(res.body); - expect(payload.error.code).to.equal(-32001); - // eslint-disable-next-line no-null/no-null -- JSON-RPC 2.0 § 5 mandates `null` for unattributable error responses. - expect(payload.id).to.equal(null); - }); - - it('rejects a DELETE without an Mcp-Session-Id header with the JSON-RPC envelope', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest(port, 'DELETE', {}); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.error.code).to.equal(-32000); - // eslint-disable-next-line no-null/no-null -- JSON-RPC 2.0 § 5 mandates `null` for unattributable error responses. - expect(payload.id).to.equal(null); - }); - - // Happy path (initialize POST without session id ⇒ new session) is exercised end-to-end - // by `mcp-http-transport-e2e.spec.ts` against a real SDK client; not duplicated here - // because asserting it inline requires wiring up an `McpServer` to actually respond. -}); - -describe('McpHttpTransport (MCP-Protocol-Version header validation)', () => { - let httpServer: McpHttpTransport | undefined; - - afterEach(() => { - httpServer?.dispose(); - httpServer = undefined; - }); - - async function startTransport(): Promise { - httpServer = buildTransport({ allowedHosts: ['127.0.0.1', 'localhost'] }); - await httpServer.start({ port: 0, host: '127.0.0.1', route: '/mcp', name: 'test', options: {} }); - return (await httpServer.getAddress()).port; - } - - it('rejects a non-initialize POST whose MCP-Protocol-Version is unsupported with HTTP 400', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest( - port, - 'POST', - { 'mcp-session-id': 'doesnt-matter', 'mcp-protocol-version': '1999-01-01' }, - { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } - ); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.jsonrpc).to.equal('2.0'); - // eslint-disable-next-line no-null/no-null -- JSON-RPC 2.0 § 5 mandates `null` for unattributable error responses. - expect(payload.id).to.equal(null); - expect(payload.error.code).to.equal(-32000); - expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/); - expect(payload.error.message).to.match(/1999-01-01/); - expect(payload.error.message).to.match(/Supported versions/); - }); - - it('rejects a GET whose MCP-Protocol-Version is unsupported with HTTP 400 (header validated before session lookup)', async () => { - const port = await startTransport(); - - const res = await rawHttpRequest(port, 'GET', { 'mcp-session-id': 'any', 'mcp-protocol-version': 'bogus' }); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/); - }); - - it('passes a non-initialize POST through when the MCP-Protocol-Version header is absent (spec defaults to 2025-03-26)', async () => { - // Without the header, the protocol-version middleware must let the request through to - // the next layer (which then enforces session-id rules). Asserting we hit the 400 - // session-id error — not the 400 protocol-version error — proves the middleware - // didn't short-circuit. - const port = await startTransport(); - - const res = await rawHttpRequest(port, 'POST', {}, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.error.message).to.match(/No valid session ID/); - }); - - it('passes a non-initialize POST through when the MCP-Protocol-Version is one of the supported versions', async () => { - // Header is supported → middleware passes → we hit the 400 session-id check, not the - // 400 protocol-version check. - const port = await startTransport(); - - const res = await rawHttpRequest( - port, - 'POST', - { 'mcp-protocol-version': '2025-06-18' }, - { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} } - ); - - expect(res.status).to.equal(400); - const payload = JSON.parse(res.body); - expect(payload.error.message).to.match(/No valid session ID/); - }); -}); diff --git a/packages/server-mcp/src/server/mcp-http-transport.ts b/packages/server-mcp/src/server/mcp-http-transport.ts deleted file mode 100644 index b7980dd..0000000 --- a/packages/server-mcp/src/server/mcp-http-transport.ts +++ /dev/null @@ -1,368 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025-2026 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Deferred, Disposable, Emitter, Logger } from '@eclipse-glsp/server'; -import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { SUPPORTED_PROTOCOL_VERSIONS, isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; -import type { Express } from 'express'; -import * as express from 'express'; -import * as http from 'http'; -import { inject, injectable } from 'inversify'; -import { AddressInfo } from 'net'; -import { randomUUID } from 'node:crypto'; -import { LruEventStore } from './lru-event-store'; -import { McpServerOptions } from './mcp-options'; -import type { FullMcpServerConfiguration } from './mcp-server-launcher'; -import { McpSession, McpSessionId, WithSessionId } from './mcp-session'; - -/** - * Where this transport can be reached. Network transports populate `url`; - * future in-process or stdio transports would leave it undefined. - */ -export interface TransportEndpoint { - url?: string; - headers?: Record; -} - -@injectable() -export class McpHttpTransport implements Disposable { - protected _app?: Express; - protected _server?: http.Server; - protected _addressInfo = new Deferred(); - - protected sessions = new Map(); - protected onSessionInitializedEmitter = new Emitter(); - onSessionInitialized = this.onSessionInitializedEmitter.event; - protected onSessionClosedEmitter = new Emitter(); - onSessionClosed = this.onSessionClosedEmitter.event; - - @inject(McpServerOptions) protected serverOptions: McpServerOptions; - - constructor(@inject(Logger) protected logger: Logger) {} - - get app(): Express | undefined { - return this._app; - } - - get server(): http.Server | undefined { - return this._server; - } - - getAddress(): Promise { - return this._addressInfo.promise; - } - - async start(config: FullMcpServerConfiguration): Promise { - const { route, host, port } = config; - // `createMcpExpressApp` gives us (a) a base Express app, (b) `express.json()` body - // parsing — load-bearing; without it `req.body` is undefined and `isInitializeRequest` - // can't tell init from non-init — and (c) DNS-rebinding host-header validation for - // the configured allowlist. We forward our `allowedHosts` so the SDK's validator and - // any explicit policy share one source of truth. - this._app = createMcpExpressApp({ host, allowedHosts: this.serverOptions.values.allowedHosts }); - // Allow subclasses to install Express middleware (auth, CORS, rate-limiting, - // request logging) before the MCP routes are registered. Default: origin allowlist. - this.configureExpressApp(this._app); - // MCP-Protocol-Version validation runs after subclass middleware so adopter-installed - // gates (auth, CORS) get first cut, but before the SDK route handlers so an unsupported - // header rejects with HTTP 400 cleanly per spec. - this._app.use(route, this.validateProtocolVersionHeader.bind(this)); - this._app.post(route, this.handlePostRequest.bind(this)); - this._app.get(route, this.handleGetRequest.bind(this)); - this._app.delete(route, this.handleDeleteRequest.bind(this)); - this._server = this._app.listen(port, host); - // Disable the per-request timeout so long-lived SSE GET streams aren't killed during - // chat idle periods. From Node's perspective an SSE response is a single in-progress - // request that lasts as long as the client stays connected, so the default 5-minute - // `requestTimeout` (Node 18.1+) terminates the socket whenever no events flow for - // ≥5 min — the client surfaces this as `TypeError: terminated`. We rely on the MCP - // session-id handshake + `onclose` to detect gone clients. - this._server.requestTimeout = 0; - this._server.on('listening', () => this.listening()); - // Pre-listen errors (typically `EADDRINUSE`) fire on the http.Server. Without a - // listener the deferred address never resolves and `start()` hangs; with it we - // surface an actionable message naming the offending port + the override path. - this._server.on('error', err => this.handleListenError(err, host, port)); - const addressInfo = await this.getAddress(); - return { url: this.toServerUrl(addressInfo, route) }; - } - - /** - * Translate a pre-listen failure into an actionable error and reject the address-info - * deferred so `start()` propagates it to the caller. `EADDRINUSE` gets a tailored hint - * about overriding via `mcpServer.port`; other codes pass through unchanged. - */ - protected handleListenError(err: NodeJS.ErrnoException, host: string, port: number): void { - if (err.code === 'EADDRINUSE') { - const portLabel = port === 0 ? 'requested address' : `${host}:${port}`; - this._addressInfo.reject( - new Error( - `MCP server cannot bind ${portLabel}: address already in use. ` + - 'Pass a different `mcpServer.port` in the GLSP `initialize` call, or omit the port to get a random one.' - ) - ); - return; - } - this._addressInfo.reject(err); - } - - /** - * Hook for subclasses to register middleware on the Express app before the MCP routes - * are mounted. Called once during {@link start}, after the app is created and before - * `POST` / `GET` / `DELETE` handlers are added. - * - * Default behavior: install an Origin allowlist if one is configured. Host-header - * validation is already wired by the SDK's `createMcpExpressApp` (using the same - * `allowedHosts` we forward in {@link start}); we don't duplicate it here. Subclasses - * that override SHOULD `super.configureExpressApp(app)` to keep the origin gate in place; - * pre-existing security middleware can run before or after by calling super at the - * appropriate point. - */ - protected configureExpressApp(app: Express): void { - const allowedOrigins = this.serverOptions.values.allowedOrigins; - if (!allowedOrigins) { - return; - } - app.use((req, res, next) => { - const origin = req.headers.origin; - if (origin && !allowedOrigins.includes(origin)) { - res.status(403).json({ error: `Forbidden: Origin '${origin}' not allowed` }); - return; - } - next(); - }); - } - - /** - * Validate the `MCP-Protocol-Version` header per the Streamable HTTP transport spec. - * Initialize POSTs negotiate the version in the body — the header isn't expected there. - * For every other request: absent header → pass through (the spec mandates the server - * default to `2025-03-26`); present-but-unsupported → respond `400` with a JSON-RPC error - * envelope so the client knows which versions to retry with. - */ - protected validateProtocolVersionHeader(req: express.Request, res: express.Response, next: express.NextFunction): void { - if (req.method === 'POST' && isInitializeRequest(req.body)) { - return next(); - } - const headerValue = req.headers['mcp-protocol-version']; - const version = Array.isArray(headerValue) ? headerValue[0] : headerValue; - if (version === undefined) { - return next(); - } - if (!SUPPORTED_PROTOCOL_VERSIONS.includes(version)) { - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: - `Unsupported MCP-Protocol-Version: '${version}'. ` + - `Supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')}.` - }, - id: JSON_RPC_NULL_ID - }); - return; - } - next(); - } - - protected toServerUrl({ address, family, port }: AddressInfo, route: string, protocol = 'http'): string { - const host = address === '::' || address === '0.0.0.0' ? 'localhost' : family === 'IPv6' ? `[${address}]` : address; - return `${protocol}://${host}:${port}${route}`; - } - - protected listening(): void { - const addressInfo = this.server?.address(); - if (!addressInfo) { - this.logger.error('Could not resolve MCP Server address info. Shutting down.'); - this._server?.close(); - return; - } else if (typeof addressInfo === 'string') { - this.logger.error(`MCP Server is unexpectedly listening to pipe or domain socket "${addressInfo}". Shutting down.`); - this._server?.close(); - return; - } - this._addressInfo.resolve(addressInfo); - } - - protected async handlePostRequest(req: express.Request, res: express.Response): Promise { - const client = this.getOrCreateClient(req, res); - if (!client) { - return; - } - this.logger.debug(`Handling POST request for session ${client.sessionId}`); - try { - await client.handleRequest(req, res, req.body); - } catch (err: unknown) { - this.logger.error('Error handling MCP request:', err); - if (!res.headersSent) { - res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: JSON_RPC_NULL_ID }); - } - } - } - - /** - * Handle GET requests for SSE streams (using built-in support from StreamableHTTP) - */ - protected async handleGetRequest(req: express.Request, res: express.Response): Promise { - const client = this.getClient(req, res); - if (!client) { - return; - } - - // Check for Last-Event-ID header for resumability - const lastEventId = req.headers['last-event-id'] as string | undefined; - if (lastEventId) { - this.logger.info(`Client reconnecting with Last-Event-ID: ${lastEventId}`); - } else { - this.logger.info(`Establishing new SSE stream for session ${client.sessionId}`); - } - await client.handleRequest(req, res); - } - - /** - * Handle DELETE requests for session termination (according to MCP spec). - */ - protected async handleDeleteRequest(req: express.Request, res: express.Response): Promise { - const client = this.getClient(req, res); - if (!client) { - return; - } - - this.logger.info(`Received session termination request for session ${client.sessionId}`); - try { - // SDK transport closes the session as part of handleRequest. - await client.handleRequest(req, res); - } catch (err: unknown) { - this.logger.error('Error handling session termination:', err); - if (!res.headersSent) { - res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: JSON_RPC_NULL_ID }); - } - } - } - - protected getOrCreateClient(req: express.Request, res: express.Response): StreamableHTTPServerTransport | undefined { - // A brand-new session is born on an initialize POST that doesn't assert a session id. - // Every other case falls through to `getClient`, which enforces the spec-mandated - // 400/404 errors — including the case where an initialize POST carries an unknown - // session id (§ #3 — must not silently mint a replacement). - if (!getSessionIdHeader(req) && isInitializeRequest(req.body)) { - return this.createClient(); - } - return this.getClient(req, res); - } - - protected getClient(req: express.Request, res: express.Response): StreamableHTTPServerTransport | undefined { - const sessionId = getSessionIdHeader(req); - if (!sessionId) { - // MCP Streamable HTTP § Session Management #2: a non-initialize request without - // a session id MUST be rejected with HTTP 400. - res.status(400).json({ - jsonrpc: '2.0', - error: { code: -32000, message: 'Bad Request: No valid session ID provided' }, - id: JSON_RPC_NULL_ID - }); - return undefined; - } - const client = this.sessions.get(sessionId); - if (!client) { - // MCP Streamable HTTP § Session Management #3: requests bearing an unknown or - // terminated session id MUST be answered with HTTP 404 so the client knows to - // re-initialize. - res.status(404).json({ - jsonrpc: '2.0', - error: { code: -32001, message: 'Session not found' }, - id: JSON_RPC_NULL_ID - }); - return undefined; - } - return client; - } - - protected createClient(): StreamableHTTPServerTransport { - const client = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - // Bounded LRU store so resumability via `Last-Event-ID` works without leaking - // memory in long-running deployments. Cap configurable via `eventStoreLimit`. - eventStore: new LruEventStore(this.serverOptions.values.eventStoreLimit, this.logger), - onsessioninitialized: sessionId => { - // Store the transport by session ID when session is initialized - // This avoids race conditions where requests might come in before the session is stored - this.logger.info(`Session initialized with ID: ${sessionId}`); - this.sessions.set(sessionId, client); - this.onSessionInitializedEmitter.fire(client as WithSessionId); - } - }); - client.onclose = () => this.closeClient(client.sessionId); - // Surface transport errors to the GLSP logger. SDK 1.27.1 routes previously-swallowed - // errors here; without an explicit handler they go undiagnosed. - client.onerror = err => this.logger.error(`MCP transport error (session ${client.sessionId ?? ''}):`, err); - return client; - } - - protected closeClient(sessionId?: string): void { - if (!sessionId) { - return; - } - const client = this.sessions.get(sessionId); - if (client) { - this.sessions.delete(sessionId); - client.close(); - this.logger.info(`Closed and removed client with session ID ${sessionId}`); - this.onSessionClosedEmitter.fire(sessionId); - } - } - - dispose(): void { - // Close session transports first so their SSE responses end cleanly. `http.Server.close()` - // only stops accepting new connections — existing sockets stay open until they drain — so - // closing the server first would leave streams hanging until the per-session `client.close()` - // catches up. - // `Transport.close()` is async (returns Promise) but `Disposable.dispose()` is sync, - // so we attach a catch handler to keep stray rejections out of the unhandled-rejection log. - Array.from(this.sessions.values()).forEach(client => - client.close().catch(err => this.logger.warn(`Error closing MCP session ${client.sessionId}: ${err}`)) - ); - this.sessions.clear(); - this._server?.close(); - // Reset transient state so a subsequent `start()` call boots cleanly. Required because - // the transport is bound `inSingletonScope()` — without the reset, dispose-then-restart - // (e.g., GLSP server shutdown followed by a fresh `initializeServer`) would reuse the - // dead `_addressInfo` deferred and the closed Express app. - this._app = undefined; - this._server = undefined; - this._addressInfo = new Deferred(); - this.logger.info('Server shutdown complete'); - } -} - -/** - * Read the `mcp-session-id` header. Node's `IncomingHttpHeaders` types unknown headers as - * `string | string[] | undefined`; if a misbehaving client sends the header twice we pick - * the first value rather than coercing the array to `"a,b"` and silently failing the lookup. - */ -function getSessionIdHeader(req: express.Request): string | undefined { - const value = req.headers['mcp-session-id']; - return Array.isArray(value) ? value[0] : value; -} - -/** - * JSON-RPC 2.0 § 5 mandates `null` for error responses where the request id cannot be - * determined (e.g., parse errors, batch-level rejection, missing session id). Centralised so - * the unavoidable `null` literal lives behind one eslint exception instead of many. - */ -// eslint-disable-next-line no-null/no-null -const JSON_RPC_NULL_ID = null; diff --git a/packages/server-mcp/src/server/mcp-server-launcher.ts b/packages/server-mcp/src/server/mcp-server-launcher.ts deleted file mode 100644 index ff42390..0000000 --- a/packages/server-mcp/src/server/mcp-server-launcher.ts +++ /dev/null @@ -1,369 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025-2026 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { - ClientSessionListener, - ClientSessionManager, - Disposable, - DisposableCollection, - GLSPServer, - GLSPServerInitializer, - GLSPServerListener, - InitializeParameters, - InitializeResult, - Logger, - McpInitializeParameters, - McpInitializeResult, - McpServerConfiguration, - McpServerInitOptions -} from '@eclipse-glsp/server'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { ServerCapabilities, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { inject, injectable, multiInject, optional } from 'inversify'; -import { version as packageVersion } from '../../package.json'; -import { GLSPMcpServer, GLSPMcpServerFactory } from './glsp-mcp-server'; -import { McpDiagramHandlerDispatcher } from './mcp-diagram-handler-dispatcher'; -import { McpHttpTransport } from './mcp-http-transport'; -import { McpLogLevelRegistry } from './mcp-log-level-registry'; -import { McpServerDefaults, McpServerOptions } from './mcp-options'; -import { McpPromptHandler } from './mcp-prompt-handler'; -import { McpResourceHandler } from './mcp-resource-handler'; -import { McpSession } from './mcp-session'; -import { McpToolHandler } from './mcp-tool-handler'; - -/** - * Stdout tag used to announce the started MCP server so IDE integrations can pick up the URL - * automatically. The full line is `MCP_SERVER_READY_MSG + JSON.stringify({name, url, route})`, - * mirroring how the GLSP server itself announces its port via `START_UP_COMPLETE_MSG`. - */ -export const MCP_SERVER_READY_MSG = '[GLSP-MCP-Server]:Ready. '; - -/** - * Server version reported in MCP `initialize` handshake responses (the SDK's `serverInfo.version` - * field). Sourced from the package's own `package.json` so adopters and clients can tell builds - * apart without the server author having to remember to bump a literal. - */ -export const SERVER_VERSION: string = packageVersion; - -/** - * Launcher's internal handoff shape: everything from the public {@link McpServerConfiguration} - * with all fields resolved, plus `host`. `host` is deliberately *not* in the public protocol's - * init schema — it lives on `McpServerDeployOptions` (deploy-only) rather than - * `McpServerInitOptions` (init-controllable). The launcher reads it from the adopter-supplied - * defaults via `McpServerOptions.values.host` (whose ship default lives in - * `DefaultMcpServerModule.DEFAULT_OPTIONS`). The init/deploy split limits blast radius: - * MCP clients can negotiate behavioral fields like `port` over the wire, but security-sensitive - * fields like the bind interface are settable only by the adopter at process start. - */ -export type FullMcpServerConfiguration = Required & { host: string }; - -/** - * Defense-in-depth filter for the init-side options payload. The static type already rules - * out deploy-only fields (`host`, `allowedHosts`, `allowedOrigins`, `acknowledgedNoAuth`) on - * `McpServerConfiguration.options`, but the wire payload is JSON, so a malformed or - * malicious client could smuggle extra keys. Destructure-based pick drops anything outside - * the allowed set so deploy-only fields are sourced *only* from adopter defaults. - * - * **Update this allowlist when adding a field to `McpServerInitOptions`** — the destructure - * below is the single source of truth for which init-side fields cross the wire. - * - * Exported for regression-test access only; not part of the public package surface. - */ -export function pickInitOptions(options: McpServerInitOptions): McpServerInitOptions { - const { dataMode, agentPersona, eventStoreLimit } = options; - const picked: McpServerInitOptions = {}; - if (dataMode !== undefined) picked.dataMode = dataMode; - if (agentPersona !== undefined) picked.agentPersona = agentPersona; - if (eventStoreLimit !== undefined) picked.eventStoreLimit = eventStoreLimit; - return picked; -} - -/** - * Returns true iff `host` is a loopback bind: `localhost`, `::1`, or any IPv4 in - * `127.0.0.0/8`. Any other value (`0.0.0.0`, `::`, LAN/public addresses) is non-loopback. - * Used by {@link assertLoopbackOrAcknowledged} for the auth-footgun runtime check. - */ -export function isLoopbackHost(host: string): boolean { - return host === 'localhost' || host === '::1' || /^127\./.test(host); -} - -/** - * Refuse to bind on a non-loopback host unless the operator has acknowledged that traffic is - * authenticated externally (reverse proxy, mTLS, ACL). The MCP server has no built-in auth. - * Exported for regression tests only; not part of the public surface. - */ -export function assertLoopbackOrAcknowledged(host: string, acknowledgedNoAuth: boolean | undefined): void { - if (isLoopbackHost(host) || acknowledgedNoAuth === true) { - return; - } - throw new Error( - `Refusing to bind MCP server to non-loopback host '${host}' without authentication. ` + - 'The MCP server has no built-in auth; binding to a non-loopback interface exposes an ' + - 'unauthenticated MCP endpoint to the network. If this is intentional (e.g., the endpoint ' + - 'is fronted by a reverse proxy, mTLS, or a network ACL that authenticates traffic), set ' + - '`acknowledgedNoAuth: true` on the McpServerDefaults you pass to the server module.' - ); -} - -/** - * Boots the embedded MCP HTTP server when a GLSP `initialize` call carries an `mcpServer` - * configuration. Runs in-process via the {@link GLSPServerInitializer} lifecycle — not a - * separate process runner. Diagram-scope handler discovery and dispatch are delegated to - * {@link McpDiagramHandlerDispatcher}. - */ -@injectable() -export class McpServerLauncher implements GLSPServerInitializer, GLSPServerListener, Disposable { - @inject(Logger) protected logger: Logger; - - @inject(McpServerOptions) protected mcpOptions: McpServerOptions; - - @inject(McpServerDefaults) protected mcpDefaults: McpServerDefaults; - - @inject(McpHttpTransport) protected transport: McpHttpTransport; - - @inject(GLSPMcpServerFactory) protected glspMcpServerFactory: GLSPMcpServerFactory; - - @inject(McpDiagramHandlerDispatcher) protected dispatcher: McpDiagramHandlerDispatcher; - - @inject(McpLogLevelRegistry) protected logLevelRegistry: McpLogLevelRegistry; - - @inject(ClientSessionManager) protected clientSessionManager: ClientSessionManager; - - @multiInject(McpToolHandler) @optional() protected toolHandlers: McpToolHandler[] = []; - - @multiInject(McpResourceHandler) @optional() protected resourceHandlers: McpResourceHandler[] = []; - - @multiInject(McpPromptHandler) @optional() protected promptHandlers: McpPromptHandler[] = []; - - protected toDispose = new DisposableCollection(); - protected serverUrl: string | undefined; - protected serverConfig: FullMcpServerConfiguration | undefined; - - /** Per-MCP-session GLSPMcpServer registry — populated on session-init, cleared on session-close. */ - protected readonly sessionServers = new Map(); - - async initializeServer(server: GLSPServer, params: InitializeParameters, result: InitializeResult): Promise { - const mcpServerParam = McpInitializeParameters.getServerConfig(params); - if (!mcpServerParam) { - return result; - } - - // Idempotent: subsequent client sessions of the same GLSP server reuse the existing - // MCP HTTP server. Only the first call starts it. - if (this.serverUrl && this.serverConfig) { - return McpInitializeResult.attachServer(result, { name: this.serverConfig.name, url: this.serverUrl }); - } - - // Port defaults to 0 (random); the resolved URL is announced via the stdout marker - // below. `host` is intentionally NOT in the init-time schema — it comes from the - // server module's adopter defaults (no DNS-rebinding foot-gun via the LLM path). - const { port = 0, route = '/mcp', name = 'glsp', options = {} } = mcpServerParam; - // Init-time options win per field, but only fields in the init allowlist — - // `pickInitOptions` strips any wire-smuggled deploy-only keys before merge. - const mergedOptions = { ...this.mcpDefaults, ...pickInitOptions(options) }; - this.mcpOptions.values = mergedOptions; - const host = mergedOptions.host ?? '127.0.0.1'; - // Auth-footgun guard: refuse non-loopback bind unless the operator opted in via - // `acknowledgedNoAuth`. Runs BEFORE the transport binds the socket so a careless - // `host: '0.0.0.0'` doesn't get a chance to expose an unauthenticated endpoint. - assertLoopbackOrAcknowledged(host, mergedOptions.acknowledgedNoAuth); - const mcpServerConfig: FullMcpServerConfiguration = { port, host, route, name, options: mergedOptions }; - - this.dispatcher.harvest(); - - // Capture the per-init subscription disposables so a dispose-then-restart cycle - // (transport is `inSingletonScope()`) doesn't accumulate stale listeners. - this.toDispose.push(this.transport.onSessionInitialized(client => this.onSessionInitialized(client, mcpServerConfig))); - this.toDispose.push(this.transport.onSessionClosed(sessionId => this.onSessionClosed(sessionId))); - this.toDispose.push(this.transport); - this.installResourceListChangedNotifier(); - - const endpoint = await this.transport.start(mcpServerConfig); - this.serverUrl = endpoint.url; - this.serverConfig = mcpServerConfig; - this.logger.info( - `MCP server '${mcpServerConfig.name}' is ready to accept new client requests on: ${this.serverUrl ?? '(no network endpoint)'}` - ); - - // stdout ready-marker for parent processes to discover the URL. Uses `console.log` - // (not the GLSP logger) so adopter logger config can never hide it. - console.log(MCP_SERVER_READY_MSG + JSON.stringify({ name: mcpServerConfig.name, url: this.serverUrl, route })); - if (endpoint.url) { - return McpInitializeResult.attachServer(result, { - name: mcpServerConfig.name, - url: endpoint.url, - headers: endpoint.headers - }); - } - return result; - } - - protected onSessionInitialized(client: McpSession, config: FullMcpServerConfiguration): void { - this.logger.info(`MCP session initialized with ID: ${client.sessionId}`); - const glspMcpServer = this.createGlspMcpServer(config); - this.sessionServers.set(client.sessionId, glspMcpServer); - this.registerLogLevelHandler(glspMcpServer, client.sessionId); - // server assumes control of the connection - glspMcpServer.connect(client); - } - - protected onSessionClosed(sessionId: string): void { - const glspMcpServer = this.sessionServers.get(sessionId); - if (glspMcpServer) { - this.sessionServers.delete(sessionId); - this.logLevelRegistry.clear(sessionId); - // The transport already closes the client end; close the SDK server end too. - glspMcpServer.dispose(); - this.logger.info(`MCP session closed: ${sessionId}`); - } - } - - /** - * Fire `notifications/resources/list_changed` to every connected MCP client when a GLSP - * session opens or closes — diagram-scope resources aggregate across GLSP sessions, so the - * visible list mutates with that lifecycle. No-op when no diagram-scope resources are bound. - */ - protected installResourceListChangedNotifier(): void { - if (!this.dispatcher.hasDiagramResources()) { - return; - } - const listener: ClientSessionListener = { - sessionCreated: () => this.broadcastResourceListChanged(), - sessionDisposed: () => this.broadcastResourceListChanged() - }; - this.clientSessionManager.addListener(listener); - this.toDispose.push(Disposable.create(() => this.clientSessionManager.removeListener(listener))); - } - - /** Best-effort fan-out — failures on individual MCP sessions (e.g. transport mid-close) are swallowed. */ - protected broadcastResourceListChanged(): void { - for (const glspMcpServer of this.sessionServers.values()) { - glspMcpServer - .getRawServer() - .server.sendResourceListChanged() - .catch(err => this.logger.debug('sendResourceListChanged failed:', err)); - } - } - - /** Register `logging/setLevel` so a connected MCP client can adjust its message severity threshold. */ - protected registerLogLevelHandler(glspMcpServer: GLSPMcpServer, sessionId: string): void { - glspMcpServer.getRawServer().server.setRequestHandler(SetLevelRequestSchema, async request => { - this.logLevelRegistry.setLevel(sessionId, request.params.level); - return {}; - }); - } - - protected createGlspMcpServer({ name, options }: FullMcpServerConfiguration): GLSPMcpServer { - const resourcesAsResources = options.dataMode === 'resources'; - const server = new McpServer( - { name, version: SERVER_VERSION }, - { - capabilities: this.buildCapabilities(resourcesAsResources), - instructions: options.agentPersona - } - ); - const glspMcpServer = this.glspMcpServerFactory(server, options); - this.registerHandlers(glspMcpServer, resourcesAsResources); - return glspMcpServer; - } - - /** - * Build the MCP capabilities map from what is actually bound. Only declare a key when at - * least one handler contributes — declaring a capability the SDK never registers a handler - * for produces `-32601 Method not found` on `/list`. Resources surfaced as tools - * (`dataMode === 'tools'`) count toward `tools`, not `resources`. - */ - protected buildCapabilities(resourcesAsResources: boolean): ServerCapabilities { - const hasStaticTools = this.toolHandlers.length > 0; - const hasStaticPrompts = this.promptHandlers.length > 0; - const hasStaticResources = this.resourceHandlers.length > 0; - const hasDiagramTools = this.dispatcher.hasDiagramTools(); - const hasDiagramPrompts = this.dispatcher.hasDiagramPrompts(); - const hasDiagramResources = this.dispatcher.hasDiagramResources(); - const anyResources = hasStaticResources || hasDiagramResources; - - const hasTools = hasStaticTools || hasDiagramTools || (!resourcesAsResources && anyResources); - const hasPrompts = hasStaticPrompts || hasDiagramPrompts; - const hasResources = resourcesAsResources && anyResources; - - return { - logging: {}, - ...(hasTools ? { tools: { listChanged: false } } : {}), - // `resources.listChanged: true` iff the catalog contains diagram-scope resources — - // those aggregate across open GLSP sessions, so the visible list mutates with - // session add/remove. Server-scope-only catalogs are static, so the flag stays - // honest at `false` (the SDK reads it; clients refetch only when notified). - ...(hasResources ? { resources: { listChanged: hasDiagramResources } } : {}), - ...(hasPrompts ? { prompts: { listChanged: false } } : {}) - }; - } - - /** - * Registers tool/resource/prompt handlers against the per-MCP-session GLSP MCP server. Two - * sources flow into the catalog: - * - * 1. **Server-scope handlers**: singletons bound under `McpToolHandler` / - * `McpResourceHandler` / `McpPromptHandler`. Registered via their `register*(server)` - * methods — they're already-instantiated objects that close over their own state. - * - * 2. **Diagram-scope handlers**: registered by {@link McpDiagramHandlerDispatcher}, which - * walks the catalogs harvested at server start and dispatches each registered SDK - * callback by `params.sessionId` → per-GLSP-session container → registry lookup. - */ - protected registerHandlers(glspMcpServer: GLSPMcpServer, resourcesAsResources: boolean): void { - this.toolHandlers.forEach(handler => handler.registerTool(glspMcpServer)); - this.promptHandlers.forEach(handler => handler.registerPrompt(glspMcpServer)); - if (resourcesAsResources) { - this.resourceHandlers.forEach(handler => handler.registerResource(glspMcpServer)); - } else { - this.resourceHandlers.forEach(handler => handler.registerToolAlternative?.(glspMcpServer)); - } - - this.dispatcher.registerAll(glspMcpServer, resourcesAsResources); - this.validatePromptToolReferences(glspMcpServer); - } - - /** - * Warn when a server-scope prompt's {@link AbstractMcpPromptHandler.referencedToolNames} - * contains a name not registered on this MCP session — catches adopters who unbind a tool - * a shipped prompt references via `${OtherHandler.NAME}`. - */ - protected validatePromptToolReferences(glspMcpServer: GLSPMcpServer): void { - for (const handler of this.promptHandlers) { - const missing = handler.referencedToolNames().filter(name => !glspMcpServer.hasTool(name)); - if (missing.length > 0) { - this.logger.warn( - `Prompt '${handler.name}' references unbound tool(s): ${missing.join(', ')}. ` + - 'The prompt will still register but its text points at tools the LLM cannot invoke.' - ); - } - } - } - - serverShutDown(server: GLSPServer): void { - this.dispose(); - } - - dispose(): void { - this.sessionServers.forEach(glspMcpServer => glspMcpServer.dispose()); - this.sessionServers.clear(); - this.toDispose.dispose(); - this.toDispose.clear(); - this.serverUrl = undefined; - this.serverConfig = undefined; - this.dispatcher.reset(); - } -} From 541db91cf47f8f165be586704f9569b72e2bcb46 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 22 May 2026 15:33:27 +0200 Subject: [PATCH 528/566] GLSP-1675: Avoid unnecessary await for sync action handlers (#505) - Only await handler results that are actually promises - Sync handler results are used directly, preserving sprotty's original microtask timing for the common case Also: - Fix eslint deprecation warnings Fixes https://github.com/eclipse-glsp/glsp/issues/1675 --- packages/client/src/base/action-dispatcher.ts | 4 +++- packages/client/src/features/export/export-modules.ts | 1 + .../src/features/export/export-svg-action-handler.ts | 3 ++- .../client/src/features/export/glsp-svg-exporter.ts | 1 + .../protocol/src/action-protocol/model-saving.spec.ts | 1 + packages/protocol/src/action-protocol/model-saving.ts | 1 + packages/protocol/src/utils/type-util.ts | 11 +++++++++++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index f6e7bb6..b2f5ca5 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -22,6 +22,7 @@ import { EMPTY_ROOT, GModelRoot, IActionDispatcher, + MaybePromise, RejectAction, RequestAction, ResponseAction, @@ -183,7 +184,8 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo this.logger.log(this, 'Handle', action); const handlerResults: Promise[] = []; for (const handler of handlers) { - const result = await handler.handle(action); + const maybeResult = handler.handle(action); + const result = MaybePromise.isPromise(maybeResult) ? await maybeResult : maybeResult; if (Action.is(result)) { handlerResults.push(this.dispatch(result)); } else if (result !== undefined) { diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 7206ca7..02f45fd 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-deprecated */ import { bindAsService, configureActionHandler, diff --git a/packages/client/src/features/export/export-svg-action-handler.ts b/packages/client/src/features/export/export-svg-action-handler.ts index 3d65547..7f8c6e1 100644 --- a/packages/client/src/features/export/export-svg-action-handler.ts +++ b/packages/client/src/features/export/export-svg-action-handler.ts @@ -13,9 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-deprecated */ +import { ExportSvgAction, IActionHandler } from '@eclipse-glsp/sprotty'; import { saveAs } from 'file-saver'; import { injectable } from 'inversify'; -import { ExportSvgAction, IActionHandler } from '@eclipse-glsp/sprotty'; /** * The default handler for {@link ExportSvgAction}s. This generic handler can be used in diff --git a/packages/client/src/features/export/glsp-svg-exporter.ts b/packages/client/src/features/export/glsp-svg-exporter.ts index 1226e01..641e138 100644 --- a/packages/client/src/features/export/glsp-svg-exporter.ts +++ b/packages/client/src/features/export/glsp-svg-exporter.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-deprecated */ import { Action, ExportSvgAction, diff --git a/packages/protocol/src/action-protocol/model-saving.spec.ts b/packages/protocol/src/action-protocol/model-saving.spec.ts index b6b9ade..cf7e47e 100644 --- a/packages/protocol/src/action-protocol/model-saving.spec.ts +++ b/packages/protocol/src/action-protocol/model-saving.spec.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-deprecated */ import { expect } from 'chai'; import { diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index eed834f..47fc67c 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/* eslint-disable @typescript-eslint/no-deprecated */ import * as sprotty from 'sprotty-protocol/lib/actions'; import { ProposalString, hasBooleanProp, hasStringProp } from '../utils/type-util'; import { Action, RequestAction, ResponseAction } from './base-protocol'; diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index 1bc8772..8cc1b0a 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -67,6 +67,17 @@ export type PropertiesOfType = Pick>; */ export type MaybePromise = T | PromiseLike; +export namespace MaybePromise { + /** + * Type guard to check whether a given value is a {@link PromiseLike}. + * @param value The value to check. + * @returns `true` if the value is a `PromiseLike`. + */ + export function isPromise(value: MaybePromise): value is PromiseLike { + return AnyObject.is(value) && hasFunctionProp(value, 'then'); + } +} + /** * Utility type to describe typeguard functions. */ From 9d202035617da9f8031ddc224a6afc8162e78b57 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 27 May 2026 11:57:10 +0200 Subject: [PATCH 529/566] GLSP-1656: Extends repo commands for playwright testing (#1678) - Add `glsp repo run - svg { - width: 100%; - height: 100%; - } - - + + + + + + -

+
+ + diff --git a/examples/workflow-standalone/css/app.css b/examples/workflow-standalone/css/app.css new file mode 100644 index 0000000..c83739a --- /dev/null +++ b/examples/workflow-standalone/css/app.css @@ -0,0 +1,633 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* + * Application frame: shell, card, title bar, theme switcher, canvas, footer. + * Single CSS entry point — imports the theme layer and the diagram styling. + */ + +@import './themes.css'; +@import './diagram.css'; + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* + * Respect the user's reduced-motion preference for the diagram surface. Besides being an + * accessibility nicety, this keeps the canvas and its elements geometrically stable immediately + * after rendering/interaction -> improves E2E testing reliability + */ +@media (prefers-reduced-motion: reduce) { + .app-card { + animation: none !important; + } + + .sprotty-graph *, + .sprotty-resize-handle, + .sprotty-routing-handle { + transition: none !important; + } +} + +html, +body { + width: 100%; + height: 100%; + overflow: hidden; + font-family: var(--font-body); + color: var(--text-1); + background: var(--app-bg); + -webkit-font-smoothing: antialiased; + transition: + background 0.4s ease, + color 0.4s ease; +} + +/* --- App Shell + Atmosphere --- */ + +.app-shell { + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + padding: 14px; +} + +.app-shell::before { + content: ''; + position: absolute; + inset: 0; + background: var(--atmosphere); + transition: background 0.4s ease; + pointer-events: none; +} + +.app-card { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + max-width: 1600px; + max-height: 920px; + border-radius: 14px; + overflow: hidden; + background: var(--card-bg); + border: 1px solid var(--card-border); + box-shadow: var(--card-shadow); + transition: + background 0.4s ease, + border-color 0.4s ease, + box-shadow 0.4s ease; + /* `backwards` not `both`: a lingering transform would offset position:fixed overlays */ + animation: card-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards; +} + +@keyframes card-in { + from { + opacity: 0; + transform: translateY(8px) scale(0.992); + } + to { + opacity: 1; + transform: none; + } +} + +/* --- Window Resize Handles --- */ + +/* Overlay sibling of the card; only the handles themselves capture pointer events. */ +.window-resize-layer { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 40; +} + +.window-resize-handle { + position: absolute; + pointer-events: auto; + touch-action: none; +} + +/* Corners sit above the edge handles so their diagonal cursor wins at overlaps. */ +.window-resize-handle--nw, +.window-resize-handle--ne, +.window-resize-handle--sw, +.window-resize-handle--se { + z-index: 1; +} + +/* --- Card Header (Title Bar) --- */ + +.card-header { + display: flex; + align-items: center; + gap: 14px; + padding: 13px 22px; + border-bottom: 1px solid var(--hairline); + flex-shrink: 0; + position: relative; + /* keep the header (and its theme dropdown) above the diagram tool palette */ + z-index: 30; +} + +.app-logo svg { + display: block; + height: 30px; + width: auto; +} + +.app-logo svg path { + fill: var(--logo); + transition: fill 0.4s ease; +} + +.app-divider { + width: 1px; + height: 20px; + background: var(--hairline); +} + +.app-title { + font-family: var(--font-display); + font-size: 16px; + font-weight: 600; + color: var(--text-1); + letter-spacing: -0.01em; +} + +.app-badge { + font-family: var(--font-mono); + font-size: 9.5px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--accent); + background: var(--accent-soft); + padding: 3px 7px; + border-radius: 4px; +} + +.app-spacer { + flex: 1; +} + +.app-links { + display: flex; + align-items: center; + gap: 2px; +} + +.app-link { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 12px; + font-weight: 500; + color: var(--text-3); + text-decoration: none; + padding: 5px 9px; + border-radius: 6px; + transition: + color 0.15s, + background 0.15s; +} + +.app-link:hover { + color: var(--text-2); + background: var(--surface-hover); +} + +.app-link svg { + width: 13px; + height: 13px; + flex-shrink: 0; +} + +/* --- Title Bar Menu Bar --- */ + +.app-menubar { + display: flex; + align-items: center; + gap: 2px; +} + +.menubar-item { + font-family: inherit; + font-size: 13px; + font-weight: 500; + color: var(--text-2); + background: transparent; + border: none; + padding: 5px 10px; + border-radius: 6px; + cursor: pointer; + transition: + color 0.15s, + background 0.15s; +} + +.menubar-item:hover, +.menubar-item.active { + color: var(--text-1); + background: var(--surface-active); +} + +/* --- Title Bar Toolbar --- */ + +.title-bar-toolbar { + display: flex; + align-items: center; + gap: 2px; + margin-right: 6px; +} + +.toolbar-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-3); + cursor: pointer; + transition: + color 0.15s, + background 0.15s; +} + +.toolbar-btn:hover { + color: var(--text-1); + background: var(--surface-hover); +} + +.toolbar-btn:active { + background: var(--surface-active); +} + +.toolbar-btn svg { + width: 14px; + height: 14px; +} + +.dirty-indicator { + width: 7px; + height: 7px; + border-radius: 50%; + background: transparent; + margin-right: 4px; + transition: + background 0.2s, + box-shadow 0.2s; +} + +.dirty-indicator.active { + background: var(--warning); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 22%, transparent); +} + +/* --- Theme Switcher --- */ + +.theme-switcher { + position: relative; +} + +.theme-trigger { + display: inline-flex; + align-items: center; + gap: 8px; + font-family: inherit; + font-size: 12px; + font-weight: 500; + color: var(--text-2); + background: var(--surface-sunken); + border: 1px solid var(--hairline); + padding: 5px 8px 5px 7px; + border-radius: 8px; + cursor: pointer; + transition: + color 0.15s, + background 0.15s, + border-color 0.15s; +} + +.theme-trigger:hover { + color: var(--text-1); + background: var(--surface-hover); + border-color: var(--overlay-border); +} + +.theme-swatch { + width: 16px; + height: 16px; + border-radius: 5px; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); + flex-shrink: 0; +} + +.theme-trigger .theme-mode-glyph { + width: 13px; + height: 13px; + color: var(--text-3); +} + +.theme-trigger .theme-caret { + width: 9px; + height: 9px; + color: var(--text-3); + transition: transform 0.2s ease; +} + +.theme-switcher.open .theme-caret { + transform: rotate(180deg); +} + +.theme-menu { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 250px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 12px; + box-shadow: var(--overlay-shadow); + padding: 6px; + z-index: 100; + opacity: 0; + transform: translateY(-6px) scale(0.98); + transform-origin: top right; + pointer-events: none; + transition: + opacity 0.16s ease, + transform 0.16s ease; +} + +.theme-switcher.open .theme-menu { + opacity: 1; + transform: none; + pointer-events: auto; +} + +.theme-menu-label { + font-size: 9.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-3); + padding: 6px 8px 5px; +} + +/* Light / dark segmented toggle */ +.mode-toggle { + display: flex; + gap: 4px; + padding: 2px; + margin: 0 4px 4px; + background: var(--surface-sunken); + border-radius: 9px; +} + +.mode-btn { + flex: 1; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + font-family: inherit; + font-size: 12px; + font-weight: 600; + color: var(--text-3); + background: transparent; + border: none; + padding: 6px 0; + border-radius: 7px; + cursor: pointer; + transition: + color 0.13s, + background 0.13s, + box-shadow 0.13s; +} + +.mode-btn svg { + width: 14px; + height: 14px; +} + +.mode-btn:hover { + color: var(--text-1); +} + +.mode-btn.active { + color: var(--accent); + background: var(--card-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); +} + +.theme-option { + display: flex; + align-items: center; + gap: 11px; + width: 100%; + text-align: left; + font-family: inherit; + background: transparent; + border: none; + border-radius: 9px; + padding: 8px; + cursor: pointer; + color: var(--text-1); + transition: background 0.13s; +} + +.theme-option:hover { + background: var(--surface-hover); +} + +.theme-option-swatch { + width: 34px; + height: 34px; + border-radius: 8px; + flex-shrink: 0; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); + position: relative; +} + +.theme-option-text { + display: flex; + flex-direction: column; + gap: 1px; + min-width: 0; +} + +.theme-option-name { + font-size: 13px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.theme-option-desc { + font-size: 11px; + color: var(--text-3); +} + +.theme-option-check { + margin-left: auto; + width: 16px; + height: 16px; + color: var(--accent); + opacity: 0; + flex-shrink: 0; +} + +.theme-option.selected .theme-option-check { + opacity: 1; +} + +.theme-option.selected .theme-option-name { + color: var(--accent); +} + +/* --- Diagram Canvas --- */ + +.card-canvas { + flex: 1; + position: relative; + background: var(--canvas-bg); + min-height: 0; + transition: background 0.4s ease; + /* own stacking context so the tool palette stays below the header dropdown */ + z-index: 1; +} + +#sprotty { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +#sprotty svg { + width: 100%; + height: 100%; +} + +/* --- Card Footer (Shortcuts) --- */ + +.card-footer { + flex-shrink: 0; + border-top: 1px solid var(--hairline); +} + +.shortcuts-toggle { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 22px; + font-size: 12px; + font-weight: 500; + color: var(--text-3); + cursor: pointer; + user-select: none; +} + +.shortcuts-toggle:hover { + color: var(--text-2); +} + +.shortcuts-chevron { + display: inline-block; + font-size: 9px; + transition: transform 0.2s ease; +} + +.shortcuts-chevron.open { + transform: rotate(90deg); +} + +.shortcuts-panel { + max-height: 0; + overflow: hidden; + transition: max-height 0.25s ease; +} + +.shortcuts-panel.open { + max-height: 320px; + overflow-y: auto; +} + +.shortcuts-inner { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 4px 28px; + padding: 0 22px 14px; +} + +.shortcuts-section { + display: flex; + flex-direction: column; + gap: 1px; +} + +.shortcuts-section-title { + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-3); + padding-bottom: 2px; +} + +.shortcut-item { + display: flex; + align-items: center; + gap: 7px; + font-size: 11px; + color: var(--text-2); + line-height: 1.6; +} + +.shortcut-item kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + padding: 2px 5px; + font-family: var(--font-mono); + font-size: 9px; + font-weight: 500; + color: var(--text-2); + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: 4px; + white-space: nowrap; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; +} diff --git a/examples/workflow-standalone/css/command-palette.css b/examples/workflow-standalone/css/command-palette.css index 6eec88c..273ffa6 100644 --- a/examples/workflow-standalone/css/command-palette.css +++ b/examples/workflow-standalone/css/command-palette.css @@ -39,7 +39,7 @@ .command-palette.validation.error input, .command-palette.validation.error input:focus { - color: var(--glsp-error-foregroundd); + color: var(--glsp-error-foreground); outline-color: var(--glsp-error-foreground); } @@ -58,3 +58,59 @@ background-color: var(--glsp-warning-foreground); color: black; } + +/* --- Container, input & suggestions --- */ + +.command-palette { + font-family: var(--font-body); + border-radius: 10px; + box-shadow: var(--overlay-shadow); + overflow: hidden; +} + +.command-palette input { + font-family: var(--font-body); + font-size: 14px; + padding: 10px 14px; + border: none; + border-bottom: 1px solid var(--hairline); + outline: none; + color: var(--text-1); + background: var(--surface-solid); +} + +.command-palette input::placeholder { + color: var(--text-3); +} + +.command-palette-suggestions { + background: var(--surface-solid); + border: none; + border-radius: 0 0 10px 10px; + box-shadow: none; + font-size: 13px; + color: var(--text-2); +} + +.command-palette-suggestions > div { + padding: 6px 14px; +} + +.command-palette-suggestions .group { + background: var(--surface-sunken); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-2); + padding: 6px 14px; +} + +.command-palette-suggestions > div:hover:not(.group) { + background: var(--surface-hover); +} + +.command-palette-suggestions > div.selected { + background: var(--accent-soft); + color: var(--accent); +} diff --git a/examples/workflow-standalone/css/context-menu.css b/examples/workflow-standalone/css/context-menu.css new file mode 100644 index 0000000..c67b9ad --- /dev/null +++ b/examples/workflow-standalone/css/context-menu.css @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.glsp-context-menu-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} + +.glsp-context-menu { + position: fixed; + min-width: 160px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 8px; + padding: 4px 0; + box-shadow: var(--overlay-shadow); + font-family: var(--font-body); + font-size: 13px; + z-index: 1001; +} + +.glsp-context-menu-item { + display: flex; + align-items: center; + padding: 6px 12px; + cursor: pointer; + color: var(--text-1); + user-select: none; + position: relative; +} + +.glsp-context-menu-item:hover { + background: var(--surface-hover); +} + +.glsp-context-menu-item.disabled { + color: var(--text-3); + cursor: default; + pointer-events: none; +} + +.glsp-context-menu-indicator { + width: 16px; + font-size: 11px; + text-align: center; + flex-shrink: 0; + color: var(--text-2); +} + +.glsp-context-menu-label { + flex: 1; +} + +.glsp-context-menu-chevron { + font-size: 10px; + color: var(--text-3); + margin-left: 8px; +} + +.glsp-context-menu-separator { + height: 1px; + margin: 4px 0; + background: var(--hairline); +} + +/* Submenus */ +.glsp-context-menu-submenu { + display: none; + position: absolute; + left: 100%; + top: -4px; +} + +.glsp-context-menu-item.has-submenu:hover > .glsp-context-menu-submenu { + display: block; +} diff --git a/examples/workflow-standalone/css/diagram.css b/examples/workflow-standalone/css/diagram.css index 9ac2d76..a27ebed 100644 --- a/examples/workflow-standalone/css/diagram.css +++ b/examples/workflow-standalone/css/diagram.css @@ -13,70 +13,260 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -:root { - --glsp-info-foreground: blue; + +/* + * Diagram/SVG styling. Colors resolve to theme tokens from themes.css; this only + * re-skins the base @eclipse-glsp/client and workflow-glsp styles. Overlay + * components live in the sibling files imported below (kept first for cascade order). + */ + +@import './tool-palette.css'; +@import './command-palette.css'; +@import './context-menu.css'; +@import './shortcuts.css'; +@import './feedback.css'; + +.sprotty svg { + border: none; +} + +.sprotty-graph { + font-family: var(--font-body); +} + +.sprotty-text { + font-family: var(--font-body); } .sprotty-graph, .grid-background { - background: rgb(179, 196, 202); + background: var(--canvas-bg); +} + +.grid-background .sprotty-graph, +.grid-background.sprotty-graph { + /* feed the themed grid line color into the base grid.css variable */ + --grid-color: var(--grid-line); } +/* --- Nodes & Edges --- */ + .sprotty-node { - fill: #cdc; - stroke: rgb(0, 0, 0); + fill: var(--node-fill); + stroke: var(--node-stroke); + /* No SVG `filter` on nodes: under the viewport zoom transform a `drop-shadow` + filter gets clipped to its rectangular region and, worse, the browser leaves a + displaced copy of the filtered node (an offset "ghost" outline) on zoom/pan. + Depth/feedback is done with crisp strokes instead, which scale cleanly. */ + filter: none; + transition: + stroke 0.15s ease, + stroke-width 0.15s ease, + opacity 0.15s ease; } .sprotty-edge { - stroke: black; + stroke: var(--edge); + stroke-linecap: round; + stroke-linejoin: round; } .sprotty-edge.arrow { - fill: black; + fill: var(--edge); } .sprotty-edge.selected { - stroke: #844; + stroke: var(--selection); } .sprotty-edge.selected > .arrow { - fill: #844; - stroke: #844; + fill: var(--selection); + stroke: var(--selection); } .forkOrJoin > .sprotty-node { - fill: black; + fill: var(--node-fork); } .forkOrJoin > .sprotty-node.selected { - stroke: rgb(87, 87, 214); + stroke: var(--selection); } polygon.sprotty-node { - stroke: black; + fill: var(--node-poly-fill); + stroke: var(--node-poly-stroke); + stroke-width: 1.25; } +/* --- Selection & Hover Feedback --- */ + +/* crisp stroke outlines (no SVG filters) so feedback stays sharp at any zoom level */ .sprotty-node.selected { - stroke: rgb(87, 87, 214); + stroke: var(--selection); + stroke-width: 2.5px; +} + +.sprotty-node.mouseover:not(.selected) { + /* dim on hover, matching the base client / master behavior; no SVG filter so nothing + ghosts or clips under the zoom transform (the opacity transition eases it in) */ + opacity: 60%; +} + +.sprotty-edge.mouseover:not(.selected) { + stroke: var(--accent); + opacity: 85%; +} + +.sprotty-edge.mouseover:not(.selected) > .arrow { + fill: var(--accent); + stroke: var(--accent); +} + +/* --- Resize & Routing Handles --- */ + +.sprotty-resize-handle, +.sprotty-edge > .sprotty-routing-handle { + fill: var(--handle); + stroke: var(--handle-ring); + stroke-width: 1.5px; + transition: r 0.12s ease; +} + +.sprotty-resize-handle.selected, +.sprotty-resize-handle.active, +.sprotty-edge > .sprotty-routing-handle.selected { + fill: var(--handle); + r: 6px; +} + +.sprotty-resize-handle.movement-not-allowed, +.sprotty-resize-handle.resize-not-allowed { + fill: var(--glsp-error-foreground); + stroke: var(--handle-ring); +} + +.sprotty-edge > .sprotty-routing-handle.mouseover { + stroke: var(--accent); + stroke-width: 1.5; +} + +.sprotty-node.marquee { + fill: var(--accent); + opacity: 0.16; +} + +/* --- Diagram Labels --- */ + +.sprotty-graph .sprotty-label:not(.heading) { + fill: var(--diagram-label); +} + +/* --- Workflow Node Theming (re-skins workflow-glsp node fills) --- */ + +.task.automated > .sprotty-node { + fill: var(--node-automated); } +.task.manual > .sprotty-node { + fill: var(--node-manual); +} + +.category > .sprotty-node { + fill: var(--node-category); +} + +.category .category > .sprotty-node { + fill: var(--node-category-nested); + stroke: var(--node-category-nested-stroke); + stroke-width: 1; +} + +.task .sprotty-label, +.category .sprotty-label { + fill: var(--node-label); +} + +.task .sprotty-label.heading, +.category .sprotty-label.heading { + fill: color-mix(in srgb, var(--node-label) 82%, transparent); +} + +.icon path { + fill: color-mix(in srgb, var(--node-label) 95%, transparent); +} + +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-low); +} + +.sprotty-edge.weighted.low:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-low); +} + +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-medium); +} + +.sprotty-edge.weighted:not(.selected, .navigable-element, .search-highlighted) .arrow, +.sprotty-edge.weighted.medium:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-medium); +} + +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted), +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted) .arrow { + stroke: var(--edge-high); +} + +.sprotty-edge.weighted.high:not(.selected, .navigable-element, .search-highlighted) .arrow { + fill: var(--edge-high); +} + +/* --- Projection / Minimap Bars --- */ + .bordered-projection-bar { - border-color: #a1a1a1; + border-color: var(--hairline); } .sprotty-viewport { border-width: 1px; - border-color: #555555; + border-color: var(--node-stroke); } .sprotty-projection-bar.horizontal.bordered-projection-bar { - height: 15px; + height: 12px; } .sprotty-projection-bar.vertical.bordered-projection-bar { - width: 15px; + width: 12px; } .projection-scroll-bar { - background-color: #555555; + background-color: var(--text-3); + border-radius: 3px; + opacity: 0.6; +} + +.glsp-projection { + background-color: var(--text-3); + border-radius: 2px; + opacity: 0.7; +} + +.glsp-projection.sprotty-issue.sprotty-error { + background-color: var(--glsp-error-foreground); + opacity: 1; +} + +.glsp-projection.sprotty-issue.sprotty-warning { + background-color: var(--glsp-warning-foreground); + opacity: 1; +} + +.glsp-projection.sprotty-issue.sprotty-info { + background-color: var(--glsp-info-foreground); + opacity: 1; } diff --git a/examples/workflow-standalone/css/feedback.css b/examples/workflow-standalone/css/feedback.css new file mode 100644 index 0000000..8e52a37 --- /dev/null +++ b/examples/workflow-standalone/css/feedback.css @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Transient feedback UI: toasts, autocomplete palette, sprotty popups. */ + +/* --- Toast --- */ + +.toast { + left: 20px; + bottom: 20px; +} + +.toast-container { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + color: var(--text-1); + border: 1px solid var(--overlay-border); + border-radius: 8px; + padding: 8px 14px; + font-size: 12px; + box-shadow: var(--overlay-shadow); +} + +/* --- Autocomplete Palette --- */ + +.autocomplete-palette { + font-family: var(--font-body); +} + +.autocomplete-palette input { + font-family: var(--font-body); + font-size: 14px; + padding: 10px 14px; + border: 1px solid var(--overlay-border); + border-radius: 10px; + outline: none; + color: var(--text-1); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + box-shadow: var(--overlay-shadow); +} + +.autocomplete-palette input:focus { + border-color: var(--accent); + box-shadow: + var(--overlay-shadow), + 0 0 0 2px var(--accent-glow); +} + +/* --- Sprotty Popup (Tooltip) --- */ + +.sprotty-popup { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 8px; + box-shadow: var(--overlay-shadow); + color: var(--text-2); + font-size: 12px; + padding: 8px 12px; +} diff --git a/examples/workflow-standalone/css/shortcuts.css b/examples/workflow-standalone/css/shortcuts.css new file mode 100644 index 0000000..5bdf7cb --- /dev/null +++ b/examples/workflow-standalone/css/shortcuts.css @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Keyboard grid + keyboard-shortcuts overlay. */ + +/* --- Keyboard Grid Overlay --- */ + +.grid-item { + border-color: var(--overlay-border); +} + +.grid-item-number { + background: var(--surface-solid); + border: 1.5px solid var(--overlay-border); + color: var(--text-2); + font-family: var(--font-mono); + font-weight: 600; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + justify-content: center; +} + +/* --- Keyboard Shortcuts Overlay --- */ + +.keyboard-shortcuts-menu { + font-family: var(--font-body); + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-radius: 12px; + box-shadow: var(--overlay-shadow); + color: var(--text-1); + font-size: 13px; + line-height: 1.5; +} + +.keyboard-shortcuts-menu h3 { + font-size: 14px; + font-weight: 700; + color: var(--text-1); + padding: 12px 16px; + margin: 0; + border-bottom: 1px solid var(--hairline); +} + +.keyboard-shortcuts-container { + padding: 8px 16px 16px; + color: var(--text-2); + font-size: 13px; +} + +.menu-header { + background: var(--surface-sunken); + padding: 6px 16px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-2); + border-bottom: 1px solid var(--hairline); +} + +.shortcut-entry-container { + padding: 3px 0; + color: var(--text-2); + font-size: 13px; +} + +.column-title { + font-weight: 700; + color: var(--text-1); + font-size: 13px; + padding-top: 8px; +} + +.keyboard-shortcuts-menu kbd { + background: var(--surface-sunken); + border: 1px solid var(--overlay-border); + border-radius: 4px; + color: var(--text-2); + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + padding: 2px 6px; + box-shadow: none; + text-shadow: none; +} + +#key-shortcut-close-btn { + top: 10px; + right: 10px; + background: transparent; + color: var(--text-3); + font-size: 18px; + border-radius: 6px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +#key-shortcut-close-btn:hover { + background: var(--surface-hover); + color: var(--text-2); +} + +.shortcut-table thead th { + border-bottom: 1px solid var(--overlay-border); + font-weight: 600; + color: var(--text-2); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; +} diff --git a/examples/workflow-standalone/css/themes.css b/examples/workflow-standalone/css/themes.css new file mode 100644 index 0000000..217cc92 --- /dev/null +++ b/examples/workflow-standalone/css/themes.css @@ -0,0 +1,261 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* + * Theme layer: all tokens, keyed on data-mode (light/dark) and data-theme (5 families). + * Each family's --selection sits outside its node palette so selected outlines stay visible. + */ + +/* ---------- Cross-theme constants ---------- */ + +:root { + /* Single external font (Outfit) for the whole app, shared across all themes; mono and the + fallbacks stay on system fonts so no other web fonts are loaded. */ + --font-body: 'Outfit', system-ui, 'Segoe UI', sans-serif; + --font-display: var(--font-body); + --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --node-label: #ffffff; + --handle: var(--selection); + --handle-ring: var(--card-bg); + --danger: #e11d48; + --warning: #f59e0b; + --info: var(--accent); +} + +/* ---------- Mode neutrals ---------- */ + +:root, +[data-mode='light'] { + color-scheme: light; + + /* neutral surfaces carry a few percent of the family accent for a cohesive tint */ + --app-bg: color-mix(in srgb, var(--accent) 7%, #e9edf2); + --card-bg: color-mix(in srgb, var(--accent) 3%, #ffffff); + --card-border: rgba(15, 23, 42, 0.07); + --card-shadow: 0 1px 3px rgba(2, 6, 23, 0.08), 0 18px 44px -18px rgba(2, 6, 23, 0.22); + --hairline: rgba(15, 23, 42, 0.07); + + --canvas-bg: color-mix(in srgb, var(--accent) 5%, #f5f7fa); + --grid-line: rgba(15, 23, 42, 0.055); + + --surface: rgba(255, 255, 255, 0.86); + --surface-solid: color-mix(in srgb, var(--accent) 3%, #ffffff); + --surface-sunken: rgba(15, 23, 42, 0.04); + --surface-hover: rgba(15, 23, 42, 0.05); + --surface-active: rgba(15, 23, 42, 0.08); + --overlay-border: rgba(15, 23, 42, 0.09); + --overlay-shadow: 0 8px 32px rgba(2, 6, 23, 0.12), 0 2px 6px rgba(2, 6, 23, 0.05); + --blur: 16px; + + --text-1: #0f172a; + --text-2: #475569; + --text-3: #94a3b8; + --text-4: #b4bdca; + + --node-fill: #e2e8f0; + --node-stroke: #cbd5e1; + --node-poly-fill: #cbd5e1; + --node-poly-stroke: #94a3b8; + --node-fork: #334155; + --diagram-label: #1e293b; + --edge: #64748b; +} + +[data-mode='dark'] { + color-scheme: dark; + + /* tinted a touch more for a richer colored-dark feel */ + --app-bg: color-mix(in srgb, var(--accent) 10%, #0a0d13); + --card-bg: color-mix(in srgb, var(--accent) 7%, #11151e); + --card-border: rgba(255, 255, 255, 0.08); + --card-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 22px 50px -18px rgba(0, 0, 0, 0.7); + --hairline: rgba(255, 255, 255, 0.07); + + --canvas-bg: color-mix(in srgb, var(--accent) 8%, #0c1017); + --grid-line: rgba(255, 255, 255, 0.045); + + --surface: rgba(24, 30, 41, 0.84); + --surface-solid: color-mix(in srgb, var(--accent) 8%, #181e29); + --surface-sunken: rgba(255, 255, 255, 0.045); + --surface-hover: rgba(255, 255, 255, 0.07); + --surface-active: rgba(255, 255, 255, 0.11); + --overlay-border: rgba(255, 255, 255, 0.1); + --overlay-shadow: 0 12px 40px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.4); + --blur: 18px; + + --text-1: #e7eaf0; + --text-2: #9aa4b3; + --text-3: #6b7585; + --text-4: #4d5664; + + --node-fill: #232b39; + --node-stroke: #344052; + --node-poly-fill: #2a3344; + --node-poly-stroke: #3d4a5e; + --node-fork: #8a93a3; + --diagram-label: #cbd5e1; + --edge: #6b7585; +} + +/* ---------- Family identity ---------- */ + +:root, +[data-theme='tide'] { + --accent: #0ea5b7; + --accent-strong: #0b8c9c; + --accent-soft: rgba(14, 165, 183, 0.12); + --accent-on: #ffffff; + --accent-glow: rgba(14, 165, 183, 0.34); + --logo: #0ea5b7; + --atmosphere: radial-gradient(900px 620px at 10% -5%, rgba(14, 165, 183, 0.14), transparent 60%), + radial-gradient(820px 600px at 102% 104%, rgba(84, 112, 184, 0.13), transparent 55%); + + --node-automated: #0d9aac; + --node-manual: #f0795a; + --node-category: #5470b8; + --node-category-nested: #6f8fce; + --node-category-nested-stroke: #3f5fa0; + --edge-low: #9db4dc; + --edge-medium: #5570b5; + --edge-high: #3a58a8; + + --selection: #7c5cff; + --selection-glow: rgba(124, 92, 255, 0.45); +} + +[data-theme='graphite'] { + --accent: #3b9eff; + --accent-strong: #5cb0ff; + --accent-soft: rgba(59, 158, 255, 0.16); + --accent-on: #051321; + --accent-glow: rgba(59, 158, 255, 0.45); + --logo: #3b9eff; + --atmosphere: radial-gradient(1000px 720px at 0% -8%, rgba(59, 158, 255, 0.14), transparent 60%), + radial-gradient(900px 700px at 104% 106%, rgba(47, 159, 176, 0.1), transparent 55%); + + --node-automated: #3187d4; + --node-manual: #2f9fb0; + --node-category: #5b82c5; + --node-category-nested: #6f95d6; + --node-category-nested-stroke: #3f63a0; + --edge-low: #8fa6cf; + --edge-medium: #5a78bd; + --edge-high: #3d5fb0; + + --selection: #fb5fa0; + --selection-glow: rgba(251, 95, 160, 0.45); +} + +[data-theme='ember'] { + --accent: #c2410c; + --accent-strong: #9a3412; + --accent-soft: rgba(194, 65, 12, 0.12); + --accent-on: #fff7ed; + --accent-glow: rgba(194, 65, 12, 0.34); + --logo: #c2410c; + --atmosphere: radial-gradient(900px 620px at 8% -5%, rgba(194, 65, 12, 0.14), transparent 60%), + radial-gradient(820px 600px at 104% 106%, rgba(202, 138, 4, 0.16), transparent 55%); + + --node-automated: #c2410c; + --node-manual: #4f7d6e; + --node-category: #a16207; + --node-category-nested: #bd7e1f; + --node-category-nested-stroke: #825009; + --edge-low: #c2b08f; + --edge-medium: #a3814a; + --edge-high: #c2410c; + + --selection: #0ea5e9; + --selection-glow: rgba(14, 165, 233, 0.45); +} + +[data-theme='orchid'] { + --accent: #d946ef; + --accent-strong: #e879f9; + --accent-soft: rgba(217, 70, 239, 0.18); + --accent-on: #11061a; + --accent-glow: rgba(217, 70, 239, 0.5); + --logo: #d946ef; + --atmosphere: radial-gradient(1000px 720px at 14% -8%, rgba(217, 70, 239, 0.18), transparent 60%), + radial-gradient(900px 700px at 104% 108%, rgba(34, 206, 192, 0.13), transparent 55%); + + --node-automated: #d437e8; + --node-manual: #22cec0; + --node-category: #7c7cf0; + --node-category-nested: #9a9af6; + --node-category-nested-stroke: #5b5be0; + --edge-low: #7c6f9e; + --edge-medium: #a855f7; + --edge-high: #d946ef; + + --selection: #f5b313; + --selection-glow: rgba(245, 179, 19, 0.5); +} + +[data-theme='verdant'] { + --accent: #10b981; + --accent-strong: #0e9f6e; + --accent-soft: rgba(16, 185, 129, 0.14); + --accent-on: #04231a; + --accent-glow: rgba(16, 185, 129, 0.4); + --logo: #10b981; + --atmosphere: radial-gradient(940px 660px at 10% -6%, rgba(16, 185, 129, 0.14), transparent 60%), + radial-gradient(840px 620px at 104% 106%, rgba(44, 122, 158, 0.13), transparent 55%); + + --node-automated: #0e9f6e; + --node-manual: #d08700; + --node-category: #2c7a9e; + --node-category-nested: #3b93b8; + --node-category-nested-stroke: #236781; + --edge-low: #8fb8a6; + --edge-medium: #3f8f6e; + --edge-high: #0e7a52; + + --selection: #8b5cf6; + --selection-glow: rgba(139, 92, 246, 0.46); +} + +/* ---------- GLSP semantic-color bridge ---------- */ + +:root { + --glsp-error-foreground: var(--danger, #e11d48); + --glsp-warning-foreground: var(--warning, #f59e0b); + --glsp-info-foreground: var(--info, #0ea5b7); + --glsp-navigation-highlight: var(--accent-glow, rgba(14, 165, 183, 0.25)); + --glsp-search-highlight: var(--accent, #0ea5b7); +} + +/* ---------- Theme-switcher swatch gradients ---------- */ +.theme-swatch[data-theme='tide'], +.theme-option[data-theme='tide'] .theme-option-swatch { + background: linear-gradient(135deg, #0ea5b7 0%, #5470b8 55%, #f0795a 100%); +} +.theme-swatch[data-theme='graphite'], +.theme-option[data-theme='graphite'] .theme-option-swatch { + background: linear-gradient(135deg, #3b9eff 0%, #2f9fb0 55%, #fb5fa0 100%); +} +.theme-swatch[data-theme='ember'], +.theme-option[data-theme='ember'] .theme-option-swatch { + background: linear-gradient(135deg, #c2410c 0%, #a16207 55%, #4f7d6e 100%); +} +.theme-swatch[data-theme='orchid'], +.theme-option[data-theme='orchid'] .theme-option-swatch { + background: linear-gradient(135deg, #d946ef 0%, #7c7cf0 55%, #22cec0 100%); +} +.theme-swatch[data-theme='verdant'], +.theme-option[data-theme='verdant'] .theme-option-swatch { + background: linear-gradient(135deg, #10b981 0%, #2c7a9e 55%, #d08700 100%); +} diff --git a/examples/workflow-standalone/css/tool-palette.css b/examples/workflow-standalone/css/tool-palette.css new file mode 100644 index 0000000..2d5ee8a --- /dev/null +++ b/examples/workflow-standalone/css/tool-palette.css @@ -0,0 +1,203 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* Tool palette re-skin. */ + +.tool-palette { + font-family: var(--font-body); + background: transparent; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border: none; + border-radius: 12px; + box-shadow: var(--overlay-shadow); + color: var(--text-1); + font-size: 13px; + right: 40px; + top: 32px; +} + +.tool-palette.collapsible-palette { + overflow-x: visible; + overflow-y: clip; +} + +.palette-header { + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-bottom: 1px solid var(--hairline); + border-radius: 12px 12px 0 0; + padding: 8px 10px; +} + +.header-icon { + color: var(--text-2); + font-weight: 600; + font-size: 12px; +} + +.header-tools { + gap: 2px; +} + +.header-tools i { + border: 1px solid transparent; + border-radius: 6px; + padding: 5px; + margin-right: 1px; + color: var(--text-2); +} + +.header-tools i:hover { + background: var(--surface-hover); + color: var(--text-1); +} + +.header-tools .clicked { + background: var(--accent-soft); + border: 1px solid transparent; + color: var(--accent); +} + +.palette-body { + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + border-top: none; + border-radius: 0 0 12px 12px; + padding: 4px 0; +} + +.tool-group { + background: transparent; +} + +.group-header { + background: var(--surface-sunken); + color: var(--text-2); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.04em; + padding: 6px 12px; + border-bottom: 1px solid var(--hairline); + margin-top: 2px; +} + +.group-header:hover { + background: var(--surface-hover); +} + +.group-header i { + padding: 0.2em; + color: var(--text-2); + font-size: 12px; +} + +.tool-button { + background: transparent; + padding: 6px 12px 6px 24px; + border-radius: 6px; + margin: 1px 4px; + color: var(--text-1); + font-size: 13px; + position: relative; +} + +.tool-button i { + color: var(--text-2); + margin-right: 8px; + display: inline-flex; + align-items: center; +} + +.tool-button:hover { + background: var(--surface-hover); +} + +.tool-button.clicked { + background: var(--accent-soft); + color: var(--accent); +} + +.tool-button.clicked i { + color: var(--accent); +} + +.search-input { + background: var(--surface-sunken); + border: 1px solid var(--overlay-border); + border-radius: 6px; + padding: 6px 8px; + font-family: inherit; + color: var(--text-1); + font-size: 13px; +} + +.search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-glow); +} + +.minimize-palette-button .codicon::before { + font-size: 18px; +} + +.accessibility-tool-palette .tool-button .key-shortcut, +.accessibility-tool-palette .header-tools .key-shortcut { + background: var(--surface-solid); + border: 1.5px solid var(--overlay-border); + color: var(--text-2); + font-family: var(--font-mono); + font-weight: 600; + font-size: 0.7rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.accessibility-show-shortcuts:focus-within .header-tools .key-shortcut, +.accessibility-show-shortcuts:focus-within .tool-button .key-shortcut { + display: flex; + align-items: center; + justify-content: center; +} + +.minimize-palette-button { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: var(--surface); + backdrop-filter: blur(var(--blur)); + -webkit-backdrop-filter: blur(var(--blur)); + border: 1px solid var(--overlay-border); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); + color: var(--text-2); + cursor: pointer; +} + +.minimize-palette-button:hover { + color: var(--text-1); + background: var(--surface-hover); +} + +.minimize-palette-button .codicon::before { + font-size: 16px; +} diff --git a/examples/workflow-standalone/esbuild.js b/examples/workflow-standalone/esbuild.js new file mode 100644 index 0000000..5b5b622 --- /dev/null +++ b/examples/workflow-standalone/esbuild.js @@ -0,0 +1,136 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +// @ts-check +const { spawn } = require('child_process'); +const esbuild = require('esbuild'); +const fs = require('fs'); +const path = require('path'); + +const appRoot = path.resolve(__dirname, 'app'); +const serverDir = path.resolve(__dirname, 'server'); + +const args = process.argv.slice(2); +const isBrowser = args.includes('--browser'); +const isWatch = args.includes('--watch'); // dev: rebuild + live-reload +const isServe = args.includes('--serve'); // start: serve the built bundle, no watch/live-reload +const isMcp = args.includes('--mcp'); +const noOpen = args.includes('--no-open'); + +// full-page live reload: subscribe to esbuild's change stream (served on the dev port). Over file:// +// there is no EventSource endpoint, so the guard turns this into a harmless no-op for production builds. +const liveReloadBanner = { + js: ";(() => { if (typeof EventSource !== 'undefined') { new EventSource('/esbuild').addEventListener('change', () => location.reload()); } })();" +}; + +/** + * Reports the build progress and surfaces errors/warnings in a format that + * VS Code's `$esbuild-watch` problem matcher can pick up. + * @type {import('esbuild').Plugin} + */ +const esbuildProblemMatcherPlugin = { + name: 'esbuild-problem-matcher', + setup(build) { + build.onStart(() => { + console.log(`${isWatch ? '[watch] ' : ''}build started`); + }); + build.onEnd(result => { + result.errors.forEach(({ text, location }) => { + console.error(`✘ [ERROR] ${text}`); + if (location) { + console.error(` ${location.file}:${location.line}:${location.column}:`); + } + }); + console.log(`${isWatch ? '[watch] ' : ''}build finished`); + }); + } +}; + +// replaces CopyWebpackPlugin: the browser entry loads the worker via the stable 'wf-glsp-server-webworker.js' name +function copyWebWorker() { + const source = path.resolve(serverDir, 'wf-glsp-server-web.js'); + const target = path.resolve(appRoot, 'wf-glsp-server-webworker.js'); + fs.copyFileSync(source, target); + if (fs.existsSync(source + '.map')) { + fs.copyFileSync(source + '.map', target + '.map'); + } +} + +// mirror webpack's DefinePlugin; only injected for the node/websocket entry +const nodeDefine = { + GLSP_SERVER_HOST: JSON.stringify(process.env.GLSP_SERVER_HOST || 'localhost'), + GLSP_SERVER_PORT: JSON.stringify(process.env.GLSP_SERVER_PORT || '8081'), + GLSP_MCP_SERVER_PORT: JSON.stringify(process.env.GLSP_MCP_SERVER_PORT || '64577'), + GLSP_SOURCE_URI: JSON.stringify(path.resolve(appRoot, 'example1.wf')) +}; + +/** @type {import('esbuild').BuildOptions} */ +const buildOptions = { + entryPoints: [path.resolve(__dirname, 'src', isBrowser ? 'browser/app.ts' : 'node/app.ts')], + outdir: appRoot, + entryNames: 'bundle', // -> app/bundle.js + app/bundle.css + assetNames: '[name]-[hash]', // -> app/codicon-.ttf, referenced relatively from bundle.css + bundle: true, + sourcemap: true, + format: 'iife', // diagram.html loads bundle.js via a classic
+
+
+ +
+ Workflow Editor + Demo +
+ +
+ + + +
+ + +
+ + +
+ +
+
+
+ +
+
+
+ +
+
+ + Keyboard Shortcuts +
+
+
+
+ Navigate + Scroll Zoom in / out + Middle Drag Pan canvas + Arrow keys Move viewport / element + Ctrl Shift F Fit to screen + Ctrl Shift C Center selected + N Global navigation mode + Alt N Local navigation mode +
+
+ Zoom + + - Zoom viewport / element + Ctrl 0 Reset viewport + Ctrl + Zoom in via grid +
+
+ Edit + Ctrl Z Undo + Ctrl Y Redo + Del Delete + Ctrl A Select all + Esc Deselect all + Ctrl S Save + Ctrl Shift E Export SVG +
+
+ Resize (selected) + Alt A Enter resize mode + + - Grow / shrink + Ctrl 0 Reset size + Esc Exit resize mode +
+
+ Tools & Accessibility + Alt P Focus palette + Alt G Focus diagram + Alt H Show shortcut help + Ctrl Space Command palette + Ctrl F Search elements + Ctrl . Ctrl , Next / prev marker + 1 Select tool + 2 Eraser + 3 Marquee + 4 Validate + 5 Search +
+
+
+
+
+

(key: string, guard?: (object: unknown) => object is P): P | undefined { + const result = this.properties.get(key); + if (!guard) { + return result as P; + } + return guard(result) ? result : undefined; + } + + clear(key: string): void { + this.properties.delete(key); + } + + get isReadonly(): boolean { + return this.editMode === EditMode.READONLY; + } + + public get root(): GModelRoot { + return this._root; + } + + public set root(root: GModelRoot) { + this.index.indexRoot(root); + this._root = root; + } +} diff --git a/packages/server-node/src/features/model/model-submission-handler.ts b/packages/server-node/src/features/model/model-submission-handler.ts new file mode 100644 index 0000000..aa856f9 --- /dev/null +++ b/packages/server-node/src/features/model/model-submission-handler.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelRootSchema } from '@eclipse-glsp/graph'; +import { Action, RequestBoundsAction, SetDirtyStateAction, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; +import { LayoutEngine } from '../../layout/layout-engine'; +import { GModelFactory } from './gmodel-factory'; +import { GModelSerializer } from './gmodel-serializer'; +import { ModelState } from './model-state'; + +@injectable() +export class ModelSubmissionHandler { + @inject(DiagramConfiguration) + protected diagramConfiguration: DiagramConfiguration; + + @inject(GModelSerializer) + protected serializer: GModelSerializer; + + @inject(GModelFactory) + protected modelFactory: GModelFactory; + + @inject(ModelState) + protected modelState: ModelState; + + @inject(LayoutEngine) + @optional() + protected layoutEngine: LayoutEngine; + + /** + * Returns a list of actions to update the client-side model, based on the specified modelState. + * + * These actions are not processed by this {@link ModelSubmissionHandler}, but should be either manually dispatched + * to the `ActionDispatcher`, or simply returned as the result of an `ActionHandler.execute()` method. + * + * @param reason The optional reason that caused the model update. + * @returns A list of actions to be processed in order to submit the model. + */ + submitModel(reason?: string): Action[] { + this.modelFactory.createModel(); + this.modelState.root.revision = (this.modelState.root.revision ?? 0) + 1; + const root = this.serializeGModel(); + + if (this.diagramConfiguration.needsClientLayout) { + return [new RequestBoundsAction(root), new SetDirtyStateAction(this.modelState.isDirty, reason)]; + } + return [new SetModelAction(root)]; + } + + /** + * Returns a list of actions to directly update the client-side model without any server- or client-side layouting. + * + * Typically `ActionHandler`s don't invoke this method but use {@link submitModel()} + * instead, as this is only used to eventually submit the model on the client directly after all layouting is already + * performed before. The only foreseen caller of this method is `ComputedBoundsActionHandler`. + * + * These actions are not processed by this {@link ModelSubmissionHandler}, but should be either manually dispatched + * to the `ActionDispatcher`, or simply returned as the result of an + * `ActionHandler.execute()` method. + * + * @param reason The optional reason that caused the model update. + * @returns A list of actions to be processed in order to submit the model. + */ + submitModelDirectly(reason?: string): Action[] { + const root = this.serializeGModel(); + + if (this.diagramConfiguration.layoutKind === ServerLayoutKind.AUTOMATIC && this.layoutEngine) { + this.layoutEngine.layout(); + } + const result: Action[] = []; + result.push(root.revision === 0 ? new SetModelAction(root) : new UpdateModelAction(root, this.diagramConfiguration.animatedUpdate)); + if (!this.diagramConfiguration.needsClientLayout) { + result.push(new SetDirtyStateAction(this.modelState.isDirty, reason)); + } + return result; + } + + protected serializeGModel(): GModelRootSchema { + return this.serializer.createSchema(this.modelState.root); + } +} diff --git a/packages/server-node/src/features/model/request-model-action-handler.ts b/packages/server-node/src/features/model/request-model-action-handler.ts new file mode 100644 index 0000000..dd2608d --- /dev/null +++ b/packages/server-node/src/features/model/request-model-action-handler.ts @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GGraph } from '@eclipse-glsp/graph'; +import { Action, GLSPServerStatusAction, isRequestModelAction, RequestModelAction, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionDispatcher } from '../../actions/action-dispatcher'; +import { ActionHandler } from '../../actions/action-handler'; +import { Logger } from '../../utils/logger'; +import { ModelSourceLoader } from './model-source-loader'; +import { ModelState } from './model-state'; +import { ModelSubmissionHandler } from './model-submission-handler'; + +export const EMPTY_ROOT = GGraph.builder().id('empty'); + +@injectable() +export class RequestModelActionHandler implements ActionHandler { + actionKinds = [RequestModelAction.KIND]; + + @inject(Logger) + private logger: Logger; + + @inject(ModelSourceLoader) + protected modelSourceLoader: ModelSourceLoader; + + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + + @inject(ModelState) + protected modelState: ModelState; + + @inject(ModelSubmissionHandler) + protected submissionHandler: ModelSubmissionHandler; + + async execute(action: Action): Promise { + this.logger.debug('Execute RequestModelAction:', action); + if (isRequestModelAction(action)) { + this.modelState.setAll(action.options ?? {}); + + this.notifyClient('Model loading in progress'); + await this.modelSourceLoader.loadSourceModel(action); + this.notifyClient(); + } + return this.submissionHandler.submitModel(); + } + + protected notifyClient(message?: string): void { + const severity = message ? 'INFO' : 'NONE'; + this.actionDispatcher.dispatchAll( + new GLSPServerStatusAction(severity, message ?? ''), + new ServerMessageAction(severity, message ?? '') + ); + } +} diff --git a/packages/server-node/src/features/navigation/json-opener-options.ts b/packages/server-node/src/features/navigation/json-opener-options.ts new file mode 100644 index 0000000..aaa4404 --- /dev/null +++ b/packages/server-node/src/features/navigation/json-opener-options.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export class JsonOpenerOptions { + selection: TextSelection; + + constructor(selection?: TextSelection) { + if (selection) { + this.selection = selection; + } + } + + public toString(): string { + return `JsonOpenerOptions [selection=${this.selection}]`; + } + + public toJson(): string { + return JsonOpenerOptions.toJson(this); + } + + public static toJson(options: JsonOpenerOptions): string { + return JSON.stringify(options); + } + + public static fromJson(options: string): JsonOpenerOptions | undefined { + const parsed = JSON.parse(options); + return parsed instanceof JsonOpenerOptions ? parsed : undefined; + } +} + +export class TextSelection { + start: LinePosition; + end: LinePosition; + + constructor(start: LinePosition, end: LinePosition) { + this.start = start; + this.end = end; + } + + public toString(): string { + return `TextSelection [start=${this.start}, end=${this.end}]`; + } +} + +export class LinePosition { + line: number; + character: number; + + constructor(line: number, character: number) { + this.line = line; + this.character = character; + } + + public toString(): string { + return `LinePosition [line=${this.line}, character=${this.character}]`; + } +} diff --git a/packages/server-node/src/features/navigation/navigation-target-provider-registry.ts b/packages/server-node/src/features/navigation/navigation-target-provider-registry.ts new file mode 100644 index 0000000..b82eda2 --- /dev/null +++ b/packages/server-node/src/features/navigation/navigation-target-provider-registry.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { multiInject, optional } from 'inversify'; +import { NavigationTargetProviders } from '../../di/service-identifiers'; +import { Registry } from '../../utils/registry'; +import { NavigationTargetProvider } from './navigation-target-provider'; + +export const NavigationTargetProviderRegistry = Symbol('NavigationTargetProviderRegistry'); + +/** + * This registry keeps track of registered {@link NavigationTargetProvider} for a certain target types. + */ +export interface NavigationTargetProviderRegistry extends Registry {} + +export class DefaultNavigationTargetProviderRegistry + extends Registry + implements NavigationTargetProviderRegistry +{ + constructor(@multiInject(NavigationTargetProviders) @optional() navigationTargetProviders: NavigationTargetProvider[] = []) { + super(); + navigationTargetProviders.forEach(provider => this.register(provider.targetTypeId, provider)); + } +} diff --git a/packages/server-node/src/features/navigation/navigation-target-provider.ts b/packages/server-node/src/features/navigation/navigation-target-provider.ts new file mode 100644 index 0000000..b2f0f4c --- /dev/null +++ b/packages/server-node/src/features/navigation/navigation-target-provider.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; + +export const NavigationTargetProvider = Symbol('NavigationTargetProviders'); + +/** + * This provider retrieves navigation targets for its target type from a given {@link EditorContext}. + */ +export interface NavigationTargetProvider { + /** + * Specifies the navigation targets for the given target type. + * + * If the `args` of a returned {@link NavigationTarget} contain a + * {@link NavigationTarget.ELEMENT_IDS_SEPARATOR}, GLSP diagram clients should navigate to + * the model elements with the specified ID within the current diagram. Multiple element + * IDs can be concatenated with {@link NavigationTarget.ELEMENT_IDS_SEPARATOR}. + * + * If the `args` of a returned {@link NavigationTarget} contain a property + * `JSON_OPENER_OPTIONS` and its uri is outside of the current diagram, the string + * value of the `JSON_OPENER_OPTIONS` property will be parsed as JSON + * and merged into the opener options by the Theia integration of the GLSP client. + * This allows GLSP servers to pass additional opener options, such as a selection, etc. + * Other clients (non-Theia clients) should behave the same way. + * + * @param editorContext The editor context + * @returns the list of navigation targets + */ + getTargets(editorContext: EditorContext): NavigationTarget[]; + + /** + * Returns the targetTypeId of the provider. + * + * @returns The targetTypeId of the provider. + */ + targetTypeId: string; +} diff --git a/packages/server-node/src/features/navigation/navigation-target-resolution.ts b/packages/server-node/src/features/navigation/navigation-target-resolution.ts new file mode 100644 index 0000000..9f6db08 --- /dev/null +++ b/packages/server-node/src/features/navigation/navigation-target-resolution.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; + +export class NavigationTargetResolution { + static readonly EMPTY = new NavigationTargetResolution([]); + + constructor(readonly elementIds: string[], readonly args?: Args) {} +} diff --git a/packages/server-node/src/features/navigation/navigation-target-resolver.ts b/packages/server-node/src/features/navigation/navigation-target-resolver.ts new file mode 100644 index 0000000..436d927 --- /dev/null +++ b/packages/server-node/src/features/navigation/navigation-target-resolver.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args, MaybePromise, NavigationTarget } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { NavigationTargetResolution } from './navigation-target-resolution'; + +@injectable() +export abstract class NavigationTargetResolver { + readonly INFO = 'info'; + readonly WARNING = 'warning'; + readonly ERROR = 'error'; + + abstract resolve(navigationTarget: NavigationTarget): MaybePromise; + + createArgs(): Args { + return {}; + } + + createArgsWithInfo(message: string): Args { + const args = this.createArgs(); + this.addInfo(message, args); + return args; + } + + createArgsWithWarning(message: string): Args { + const args = this.createArgs(); + this.addWarning(message, args); + return args; + } + + createArgsWithError(message: string): Args { + const args = this.createArgs(); + this.addError(message, args); + return args; + } + + addInfo(message: string, args: Args): void { + args[this.INFO] = message; + } + + addWarning(message: string, args: Args): void { + args[this.WARNING] = message; + } + + addError(message: string, args: Args): void { + args[this.ERROR] = message; + } +} diff --git a/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts b/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts new file mode 100644 index 0000000..889b32c --- /dev/null +++ b/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MaybePromise, NavigationTarget, RequestNavigationTargetsAction, SetNavigationTargetsAction } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { inject } from 'inversify/lib/annotation/inject'; +import { ActionHandler } from '../../actions/action-handler'; +import { NavigationTargetProviderRegistry } from './navigation-target-provider-registry'; + +@injectable() +export class RequestNavigationTargetsActionHandler implements ActionHandler { + actionKinds = [RequestNavigationTargetsAction.KIND]; + + @inject(NavigationTargetProviderRegistry) + protected navigationTargetProviderRegistry: NavigationTargetProviderRegistry; + + execute(action: RequestNavigationTargetsAction): MaybePromise { + const editorContext = action.editorContext; + const allTargets: NavigationTarget[] = []; + const provider = this.navigationTargetProviderRegistry.get(action.targetTypeId); + if (provider) { + const targets = provider.getTargets(editorContext); + targets.forEach(target => allTargets.push(target)); + } + return [new SetNavigationTargetsAction(allTargets, action.requestId, editorContext.args)]; + } +} diff --git a/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts b/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts new file mode 100644 index 0000000..e928b6f --- /dev/null +++ b/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, ResolveNavigationTargetAction, SetResolvedNavigationTargetAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ActionHandler } from '../../actions/action-handler'; +import { NavigationTargetResolver } from './navigation-target-resolver'; +import { Logger } from '../../utils/logger'; + +@injectable() +export class ResolveNavigationTargetsActionHandler implements ActionHandler { + actionKinds = [ResolveNavigationTargetAction.KIND]; + + @inject(Logger) + protected logger: Logger; + + @inject(NavigationTargetResolver) + @optional() + protected readonly navigationTargetResolver: NavigationTargetResolver; + + async execute(action: ResolveNavigationTargetAction): Promise { + if (!this.navigationTargetResolver) { + this.logger.warn('Could not resolve navigation target. No implementation for: NavigationTargetResolver has been bound'); + return []; + } + const target = action.navigationTarget; + const resolution = await this.navigationTargetResolver.resolve(target); + return [new SetResolvedNavigationTargetAction(resolution.elementIds, resolution.args)]; + } +} diff --git a/packages/server-node/src/features/popup/popup-model-factory.ts b/packages/server-node/src/features/popup/popup-model-factory.ts new file mode 100644 index 0000000..52417e7 --- /dev/null +++ b/packages/server-node/src/features/popup/popup-model-factory.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement, GModelRoot } from '@eclipse-glsp/graph'; +import { RequestPopupModelAction } from '@eclipse-glsp/protocol'; + +export const PopupModelFactory = Symbol('PopupModelFactory'); + +/** + * A PopupModelFactory creates a popup model for the given element. + * A popup model is a {@link GHtmlRoot} that is self contained and not part of the diagram model. + * In contrast to diagram elements popup models typically don't contain dynamically rendered {@link GModelElement}s and + * use{@link GPreRenderedElement}s that contain server-side computed html or svg code instead. + */ +export interface PopupModelFactory { + /** + * Returns an {@link GModelRoot} for a given {@link GModelElement} triggered by a + * {@link RequestPopupModelAction}. + * + * @param element The element to create the popup model for. + * @param action The {@link RequestPopupModelAction} that triggered this popup model creation. + * @returns An optional {@link GHtmlRoot} for a given {@link GModelElement}. + */ + createPopupModel(element: GModelElement, action: RequestPopupModelAction): GModelRoot | undefined; +} diff --git a/packages/server-node/src/features/popup/request-popup-model-action-handler.ts b/packages/server-node/src/features/popup/request-popup-model-action-handler.ts new file mode 100644 index 0000000..501d339 --- /dev/null +++ b/packages/server-node/src/features/popup/request-popup-model-action-handler.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, MaybePromise, RequestPopupModelAction, SetPopupModelAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ActionHandler } from '../../actions/action-handler'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { Logger } from '../../utils/logger'; +import { GModelSerializer } from '../model/gmodel-serializer'; +import { ModelState } from '../model/model-state'; +import { PopupModelFactory } from './popup-model-factory'; + +@injectable() +export class RequestPopupModelActionHandler implements ActionHandler { + @inject(Logger) + protected logger: Logger; + + @inject(PopupModelFactory) + @optional() + protected popupModelFactory?: PopupModelFactory; + + @inject(ModelState) + protected modelState: ModelState; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + actionKinds = [RequestPopupModelAction.KIND]; + + execute(action: RequestPopupModelAction): MaybePromise { + if (this.popupModelFactory) { + const hoverElement = this.modelState.index.find(action.elementId); + if (hoverElement) { + const popupModel = this.popupModelFactory.createPopupModel(hoverElement, action); + if (popupModel) { + const modelSchema = this.modelSerializer.createSchema(popupModel); + return [new SetPopupModelAction(modelSchema)]; + } + return []; + } + throw new GLSPServerError(`Could not process 'RequestPopupModelAction'. Hover element with id ${action.elementId} not found`); + } + this.logger.warn('Could not process `RequestPopupModelAction`. No `PopupModelFactory` is bound'); + return []; + } + + priority?: number | undefined; +} diff --git a/packages/server-node/src/features/validation/model-validator.ts b/packages/server-node/src/features/validation/model-validator.ts new file mode 100644 index 0000000..ad7f5b5 --- /dev/null +++ b/packages/server-node/src/features/validation/model-validator.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GModelElement } from '@eclipse-glsp/graph'; +import { Marker, MaybePromise } from '@eclipse-glsp/protocol'; + +export const ModelValidator = Symbol('ModelValidator'); + +/** + * Validates a list of {@link GModelElement}s based on a set of validation rules and returns corresponding issue + * {@link Marker}s. + * An issue marker is a serializable description of the validation violation that can be visualized by the GLSP client. + */ +export interface ModelValidator { + /** + * Validates the given list of {@link GModelElement}s and returns a list of {@link Marker}s. + * + * @param elements The list of {@link GModelElement} to validate. + * @returns A list of {@link Marker}s for the validated {@link GModelElement}s. + */ + validate(elements: GModelElement[]): MaybePromise; +} diff --git a/packages/server-node/src/features/validation/request-markers-handler.ts b/packages/server-node/src/features/validation/request-markers-handler.ts new file mode 100644 index 0000000..879a59b --- /dev/null +++ b/packages/server-node/src/features/validation/request-markers-handler.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, Marker, RequestMarkersAction, SetMarkersAction } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { GModelState } from '../../base-impl/gmodel-state'; +import { ActionHandler } from '../../actions/action-handler'; +import { ModelValidator } from './model-validator'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { GGraph } from '@eclipse-glsp/graph'; + +@injectable() +export class RequestMarkersHandler implements ActionHandler { + actionKinds = [RequestMarkersAction.KIND]; + + @inject(ModelValidator) @optional() validator: ModelValidator; + @inject(GModelState) modelState: GModelState; + + async execute(action: RequestMarkersAction, ...args: unknown[]): Promise { + let elementIDs = action.elementsIDs; + if (!this.validator) { + throw new GLSPServerError('Cannot compute markers! No implementation for ModelValidator has been bound'); + } + + if (!elementIDs || elementIDs.length === 0 || (elementIDs.length === 1 && elementIDs[0] === 'EMPTY')) { + elementIDs = [this.modelState.root.id]; + } + + let markers: Marker[] = []; + const currentModelIndex = this.modelState.index; + for (const elementID of elementIDs) { + const modelElement = currentModelIndex.findByClass(elementID, GGraph); + if (modelElement) { + markers = markers.concat(await this.validator.validate([modelElement])); + } + } + return [new SetMarkersAction(markers)]; + } +} diff --git a/packages/server-node/src/index.ts b/packages/server-node/src/index.ts new file mode 100644 index 0000000..fd28246 --- /dev/null +++ b/packages/server-node/src/index.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export * from '@eclipse-glsp/graph'; +export * from '@eclipse-glsp/protocol'; +export * from './actions/action-dispatcher'; +export * from './actions/action-handler'; +export * from './actions/action-handler-registry'; +export * from './actions/client-action-handler'; +export * from './actions/global-action-provider'; +export * from './base-impl'; +export * from './base-impl/computed-bounds-action-handler'; +export * from './base-impl/change-bounds-operation-handler'; +export * from './command/command'; +export * from './command/command-stack'; +export * from './di/app-module'; +export * from './di/client-session-module'; +export * from './di/diagram-module'; +export * from './di/glsp-module'; +export * from './di/multi-binding'; +export * from './di/server-module'; +export * from './di/service-identifiers'; +export * from './diagram/diagram-configuration'; +export * from './diagram/request-type-hints-action-handler'; +export * from './features/clipboard/request-clipboard-data-action-handler'; +export * from './features/contextactions/command-palette-action-provider'; +export * from './features/contextactions/context-actions-provider'; +export * from './features/contextactions/context-actions-provider-registry'; +export * from './features/contextactions/context-menu-item-provider'; +export * from './features/contextactions/request-context-actions-handler'; +export * from './features/contextactions/tool-palette-item-provider'; +export * from './features/directediting/apply-label-edit-operation-handler'; +export * from './features/directediting/context-edit-validator'; +export * from './features/directediting/context-edit-validator-registry'; +export * from './features/directediting/label-edit-validator'; +export * from './features/directediting/request-edit-validation-handler'; +export * from './features/model/gmodel-factory'; +export * from './features/model/gmodel-index'; +export * from './features/model/gmodel-serializer'; +export * from './features/popup/popup-model-factory'; +export * from './features/popup/request-popup-model-action-handler'; +export * from './features/model/model-source-loader'; +export * from './features/model/model-state'; +export * from './features/model/model-submission-handler'; +export * from './features/model/request-model-action-handler'; +export * from './features/validation/model-validator'; +export * from './features/validation/request-markers-handler'; +export * from './launch/cli-parser'; +export * from './launch/glsp-server-launcher'; +export * from './launch/socket-cli-parser'; +export * from './launch/socket-server-launcher'; +export * from './base-impl/delete-operation-handler'; +export * from './operations/compound-operation-handler'; +export * from './operations/create-operation-handler'; +export * from './operations/cut-operation-handler'; +export * from './operations/operation-action-handler'; +export * from './operations/operation-handler'; +export * from './operations/operation-handler-registry'; +export * from './operations/paste-operation-handler'; +export * from './protocol/glsp-client-proxy'; +export * from './protocol/glsp-server'; +export * from './protocol/glsp-server-listener'; +export * from './session/client-session'; +export * from './session/client-session-factory'; +export * from './session/client-session-listener'; +export * from './session/client-session-manager'; +export * from './utils/args-util'; +export * from './utils/disposable'; +export * from './utils/glsp-server-error'; +export * from './utils/logger'; +export * from './utils/maybe-promise'; +export * from './utils/promise-queue'; +export * from './utils/registry'; +export * from './utils/winston-logger'; diff --git a/packages/server-node/src/launch/cli-parser.spec.ts b/packages/server-node/src/launch/cli-parser.spec.ts new file mode 100644 index 0000000..a72bdc1 --- /dev/null +++ b/packages/server-node/src/launch/cli-parser.spec.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as path from 'path'; +import { LogLevel } from '../utils/logger'; +import { createCliParser, defaultLaunchOptions } from './cli-parser'; +import { expect } from 'chai'; + +describe('test createCliParser', () => { + const parser = createCliParser(); + + parser.command.exitOverride(); + const argv = ['/usr/bin/node', 'test']; + + it('parse - no args', () => { + const options = parser.parse(argv); + expect(options).to.deep.equal(defaultLaunchOptions); + }); + + it('parse - invalid log dir', () => { + expect(() => parser.parse([...argv, '--logDir', 'invalid.Path'])).to.throw; + }); + + it('parse - valid log dir', () => { + const logDir = path.resolve('../customLog'); + const result = parser.parse([...argv, '--logDir', '../customLog/']); + expect(result.logDir, logDir); + }); + + it('parse - invalid logLevel', () => { + expect(() => parser.parse([...argv, '--logLevel', 'someRandomLevel'])).to.throw; + }); + + it('parse - valid logLevel', () => { + const result = parser.parse([...argv, '--logLevel', 'error']); + expect(result.logLevel).to.be.equal(LogLevel.error); + }); + + it('parse- no args with custom default options', () => { + const options = { + logDir: '.myLogDir', + logLevel: LogLevel.debug, + consoleLog: false, + fileLog: true + }; + const result = createCliParser(options).parse(argv); + expect(result).to.deep.equal(options); + }); +}); diff --git a/packages/server-node/src/launch/cli-parser.ts b/packages/server-node/src/launch/cli-parser.ts new file mode 100644 index 0000000..b8249e9 --- /dev/null +++ b/packages/server-node/src/launch/cli-parser.ts @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as cmd from 'commander'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { asLogLevel, LogLevel } from '../utils/logger'; + +export interface LaunchOptions { + logLevel: LogLevel; + logDir?: string; + fileLog: boolean; + consoleLog: boolean; +} + +export interface CliParser { + command: cmd.Command; + parse(argv?: string[]): O; +} + +export const defaultLaunchOptions: Required = { + logLevel: LogLevel.info, + logDir: '.config', + consoleLog: true, + fileLog: false +}; + +export function createCliParser(options: LaunchOptions = defaultLaunchOptions): CliParser { + const command = new cmd.Command() + .version('0.9.0') + .description('GLSP server') + .showHelpAfterError(true) + .name('Launch a GLSP server') + .option('-l , --logLevel ', `Set the log level. [default='${options.logLevel}']`, processLogLevel, options.logLevel) + .option( + '-d , --logDir ', + `Set the directory for log files (when file logging is enabled) [default=${options.logDir}]`, + processLogDir, + options.logDir + ) + .addHelpText('afterAll', '\n Copyright (c) 2022 Eclipse GLSP'); + + return { command, parse: argv => parse(command, options as Partial, argv) }; +} + +export function parse(command: cmd.Command, defaultOptions: Partial, argv?: string[]): T { + command.parse(argv); + return { ...defaultOptions, ...command.opts() }; +} + +export function processLogLevel(value: string): LogLevel { + const level = asLogLevel(value); + if (!level) { + throw new cmd.InvalidArgumentError("Argument has to be 'none'|'error'|'warn'|'info'|'debug'!"); + } + return level; +} + +export function processLogDir(value: string): string { + const logDir = path.resolve(value); + if (path.extname(logDir).length !== 0 || !isDirectory(path.dirname(logDir))) { + throw new cmd.InvalidArgumentError('Argument is not a valid directory!'); + } + + return logDir; +} + +export function isDirectory(value: string): boolean { + try { + return fs.statSync(value).isDirectory(); + } catch (error) { + return false; + } +} diff --git a/packages/server-node/src/launch/glsp-server-launcher.ts b/packages/server-node/src/launch/glsp-server-launcher.ts new file mode 100644 index 0000000..3de2aaf --- /dev/null +++ b/packages/server-node/src/launch/glsp-server-launcher.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MaybePromise } from '@eclipse-glsp/protocol'; +import { Container, ContainerModule, inject, injectable, optional } from 'inversify'; +import { ServerModule } from '../di/server-module'; +import { InjectionContainer } from '../di/service-identifiers'; +import { Logger } from '../utils/logger'; + +@injectable() +export abstract class GLSPServerLauncher { + @inject(Logger) protected logger: Logger; + + protected _modules: ContainerModule[] = []; + protected running: boolean; + + @inject(InjectionContainer) @optional() protected parentContainer?: Container; + + configure(serverModule: ServerModule, ...additionalModules: ContainerModule[]): void { + this._modules.push(serverModule, ...additionalModules); + } + + createContainer(): Container { + const container = this.parentContainer ? this.parentContainer.createChild() : new Container(); + container.load(...this._modules); + return container; + } + + start(startParams: T): MaybePromise { + if (!this.running) { + const result = this.run(startParams); + this.running = false; + return result; + } + this.logger.warn('Could not start launcher. Launcher is already running!'); + } + + protected abstract run(startParams: T): MaybePromise; + + shutdown(): MaybePromise { + if (this.running) { + const result = this.stop(); + this.running = false; + return result; + } + this.logger.warn('Could not stop launcher. Launcher is not running!'); + } + + protected abstract stop(): MaybePromise; + + get modules(): ContainerModule[] { + if (!this._modules) { + throw new Error('The GLSPServerLauncher has not been configured yet'); + } + return this._modules; + } +} diff --git a/packages/server-node/src/launch/socket-cli-parser.spec.ts b/packages/server-node/src/launch/socket-cli-parser.spec.ts new file mode 100644 index 0000000..96a02bc --- /dev/null +++ b/packages/server-node/src/launch/socket-cli-parser.spec.ts @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LogLevel } from '../utils/logger'; +import { createSocketCliParser, defaultSocketLaunchOptions, SocketLaunchOptions } from './socket-cli-parser'; +import { expect } from 'chai'; + +describe('test createCliParser', () => { + const parser = createSocketCliParser(); + + parser.command.exitOverride(); + const argv = ['/usr/bin/node', 'test']; + + it('parse - no args', () => { + const options = parser.parse(argv); + expect(options).to.deep.equal(defaultSocketLaunchOptions); + }); + + it('parse - invalid port (below lower range)', () => { + expect(() => parser.parse([...argv, '--port', '-1'])).to.throw; + }); + + it('parse - invalid port (below upper range)', () => { + expect(() => parser.parse([...argv, '--port', '65536'])).to.throw; + }); + + it('parse - valid port', () => { + const port = 3000; + const result = parser.parse([...argv, '--port', '3000']); + expect(result.port).to.equal(port); + }); + + it('parse - --no-consoleLog', () => { + const result = parser.parse([...argv, '--no-consoleLog']); + expect(result.consoleLog).false; + }); + + it('parse - --fileLog', () => { + const result = parser.parse([...argv, '--fileLog']); + expect(result.fileLog).true; + }); + + it('parse - custom host name', () => { + const host = 'docker.internal'; + const result = parser.parse([...argv, '--host', host]); + expect(result.host, host); + }); + + it('parse- no args with custom default options', () => { + const options: SocketLaunchOptions = { + logDir: '.myLogDir', + logLevel: LogLevel.debug, + consoleLog: true, + fileLog: true, + host: 'myHost', + port: 3000 + }; + const result = createSocketCliParser(options).parse(argv); + expect(result).to.deep.equal(options); + }); +}); diff --git a/packages/server-node/src/launch/socket-cli-parser.ts b/packages/server-node/src/launch/socket-cli-parser.ts new file mode 100644 index 0000000..ca302a1 --- /dev/null +++ b/packages/server-node/src/launch/socket-cli-parser.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as cmd from 'commander'; +import { CliParser, createCliParser, defaultLaunchOptions, LaunchOptions } from './cli-parser'; + +export interface SocketLaunchOptions extends LaunchOptions { + port: number; + host: string; +} + +export const defaultSocketLaunchOptions: Required = { + ...defaultLaunchOptions, + port: 5007, + host: 'localhost' +}; + +export function createSocketCliParser( + defaultOptions: SocketLaunchOptions = defaultSocketLaunchOptions +): CliParser { + const parser = createCliParser(defaultOptions); + parser.command + .option('-p , --port ', `Set server port [default= ${defaultOptions.port}]`, processPort, defaultOptions.port) + .option('--host ', `Set host name [default= ${defaultOptions.host}`, defaultOptions.host) + .option('--no-consoleLog', 'Disable console logging') + .option('--fileLog', 'Enable file logging', defaultOptions.fileLog); + return parser; +} + +export function processPort(value: string): number { + const port = Number.parseInt(value, 10); + if (isNaN(port)) { + throw new cmd.InvalidArgumentError('Port is not a number!'); + } + if (port < 0 || port > 65535) { + throw new cmd.InvalidArgumentError('Port has to be between in range (0,65535)!'); + } + + return port; +} diff --git a/packages/server-node/src/launch/socket-server-launcher.ts b/packages/server-node/src/launch/socket-server-launcher.ts new file mode 100644 index 0000000..ab9e50c --- /dev/null +++ b/packages/server-node/src/launch/socket-server-launcher.ts @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, inject, injectable } from 'inversify'; +import * as net from 'net'; +import * as jsonrpc from 'vscode-jsonrpc'; +import { GLSPServer, JsonRpcGLSPServer } from '../protocol/glsp-server'; +import { Logger } from '../utils/logger'; +import { GLSPServerLauncher } from './glsp-server-launcher'; +const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed'; + +@injectable() +export class SocketServerLauncher extends GLSPServerLauncher { + @inject(Logger) protected logger: Logger; + + protected currentConnections: jsonrpc.MessageConnection[] = []; + protected startupCompleteMessage = START_UP_COMPLETE_MSG; + + run(opts: net.TcpSocketConnectOpts): Promise { + const netServer = net.createServer(socket => this.createClientConnection(socket)); + netServer.listen(opts.port, opts.host); + this.logger.info(`The GLSP server is ready to accept new client requests on port: ${opts.port}`); + // Print a message to the output stream that indicates that the start is completed. + // This indicates to the client that the sever process is ready (in an embedded scenario). + console.log(this.startupCompleteMessage); + netServer.on('error', () => this.shutdown()); + return new Promise((resolve, reject) => { + netServer.on('close', () => resolve(undefined)); + netServer.on('error', error => reject(error)); + }); + } + + protected async createClientConnection(socket: net.Socket): Promise { + const container = this.createContainer(); + const connection = this.createConnection(socket); + this.currentConnections.push(connection); + const glspServer = container.get(JsonRpcGLSPServer); + glspServer.connect(connection); + this.logger.info(`Starting GLSP server connection for client: '${socket.localAddress}'`); + connection.listen(); + connection.onDispose(() => this.disposeClientConnection(container, glspServer)); + socket.on('close', () => this.disposeClientConnection(container, glspServer)); + return new Promise((resolve, rejects) => { + connection.onClose(() => resolve(undefined)); + connection.onError(error => rejects(error)); + }); + } + + protected disposeClientConnection(container: Container, glspServer: GLSPServer): void { + glspServer.shutdown(); + container.unbindAll(); + } + + protected createConnection(socket: net.Socket): jsonrpc.MessageConnection { + return jsonrpc.createMessageConnection(new jsonrpc.SocketMessageReader(socket), new jsonrpc.SocketMessageWriter(socket), console); + } + + stop(): void { + this.logger.info('Shutdown SocketServerLauncher'); + this.currentConnections.forEach(connection => connection.dispose()); + } +} diff --git a/packages/server-node/src/layout/layout-engine.ts b/packages/server-node/src/layout/layout-engine.ts new file mode 100644 index 0000000..ee6dff2 --- /dev/null +++ b/packages/server-node/src/layout/layout-engine.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * A layout engine is able to compute layout information for a model. + */ +export interface LayoutEngine { + /* + * Compute a layout for the model state and modify the model accordingly. + */ + layout(): void; +} + +export const LayoutEngine = Symbol('LayoutEngine'); diff --git a/packages/server-node/src/operations/compound-operation-handler.ts b/packages/server-node/src/operations/compound-operation-handler.ts new file mode 100644 index 0000000..1d4aeb5 --- /dev/null +++ b/packages/server-node/src/operations/compound-operation-handler.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CompoundOperation, Operation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { OperationActionHandler } from './operation-action-handler'; +import { OperationHandler } from './operation-handler'; +import { OperationHandlerRegistry } from './operation-handler-registry'; + +@injectable() +export class CompoundOperationHandler implements OperationHandler { + operationType = CompoundOperation.KIND; + + @inject(OperationHandlerRegistry) protected operationHandlerRegistry: OperationHandlerRegistry; + + execute(operation: CompoundOperation): void { + operation.operationList.forEach(nestedOperation => this.executeNestedOperation(nestedOperation)); + } + + executeNestedOperation(operation: Operation): void { + const operationHandler = OperationActionHandler.getOperationHandler(operation, this.operationHandlerRegistry); + if (operationHandler) { + operationHandler.execute(operation); + } + } +} diff --git a/packages/server-node/src/operations/create-operation-handler.ts b/packages/server-node/src/operations/create-operation-handler.ts new file mode 100644 index 0000000..d23b195 --- /dev/null +++ b/packages/server-node/src/operations/create-operation-handler.ts @@ -0,0 +1,146 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, GGraph, GModelElement, GNode, GPort, isGBoundsAware } from '@eclipse-glsp/graph'; +import { + Args, + CreateEdgeOperation, + CreateNodeOperation, + CreateOperation, + Operation, + Point, + TriggerEdgeCreationAction, + TriggerElementCreationAction, + TriggerNodeCreationAction +} from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { GModelState } from '../base-impl/gmodel-state'; +import { absoluteToRelative } from '../utils/geometry-util'; +import { OperationHandler } from './operation-handler'; + +/** + * A special {@link OperationHandler} that is responsible for the handling of {@link CreateOperation}s. Depending on its + * operation type the triggered actions are {@link TriggerNodeCreationAction} or {@link TriggerEdgeCreationAction}s. + */ +export abstract class CreateOperationHandler implements OperationHandler { + abstract operationType: string; + abstract readonly label: string; + abstract elementTypeIds: string[]; + + abstract execute(operation: Operation): void; + + /** + * Returns a list of {@link TriggerElementCreationAction}s for registered element types. + * + * @returns A list of {@link TriggerElementCreationAction}s. + */ + getTriggerActions(): TriggerElementCreationAction[] { + if (this.operationType === CreateNodeOperation.KIND) { + return this.elementTypeIds.map(typeId => new TriggerNodeCreationAction(typeId)); + } else if (this.operationType === CreateEdgeOperation.KIND) { + return this.elementTypeIds.map(typeId => new TriggerEdgeCreationAction(typeId)); + } + return []; + } + + handles(operation: CreateOperation): boolean { + return this.elementTypeIds.includes(operation.elementTypeId); + } +} + +@injectable() +export abstract class CreateNodeOperationHandler extends CreateOperationHandler { + @inject(GModelState) + protected modelState: GModelState; + + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + + get operationType(): string { + return CreateNodeOperation.KIND; + } + + abstract elementTypeIds: string[]; + + execute(operation: CreateNodeOperation): void { + const container = this.getContainer(operation) ?? this.modelState.root; + + const absoluteLocation = this.getLocation(operation); + const relativeLocation = this.getRelativeLocation(absoluteLocation, container); + const element = this.createNode(relativeLocation, operation.args); + if (element) { + container.children.push(element); + element.parent = container; + } + // TODO: Dispatch Select Action after next update + } + + getContainer(operation: CreateNodeOperation): GModelElement | undefined { + const index = this.modelState.index; + return operation.containerId ? index.get(operation.containerId) : undefined; + } + + getLocation(operation: CreateNodeOperation): Point | undefined { + return operation.location; + } + + getRelativeLocation(absoluteLocation: Point | undefined, container: GModelElement | undefined): Point | undefined { + if (absoluteLocation && container) { + const allowNegativeCoordinates = container instanceof GGraph; + if (isGBoundsAware(container)) { + const relativePosition = absoluteToRelative(absoluteLocation, container); + const relativeLocation = allowNegativeCoordinates + ? relativePosition + : { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) }; + return relativeLocation; + } + } + return undefined; + } + + abstract createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined; +} + +@injectable() +export abstract class CreateEdgeOperationHandler extends CreateOperationHandler { + @inject(GModelState) + protected modelState: GModelState; + + get operationType(): string { + return CreateEdgeOperation.KIND; + } + + execute(operation: CreateEdgeOperation): void { + const index = this.modelState.index; + + const source = index.find(operation.sourceElementId, element => element instanceof GNode || element instanceof GPort); + const target = index.find(operation.targetElementId, element => element instanceof GNode || element instanceof GPort); + + if (!source || !target) { + throw new Error( + `Invalid source or target for source ID ${operation.sourceElementId} and target ID ${operation.targetElementId}` + ); + } + + const connection = this.createEdge(source, target, this.modelState); + if (connection) { + const currentModel = this.modelState.root; + (currentModel as GModelElement).children.push(connection); + } + } + + abstract createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined; +} diff --git a/packages/server-node/src/operations/cut-operation-handler.ts b/packages/server-node/src/operations/cut-operation-handler.ts new file mode 100644 index 0000000..ee33c76 --- /dev/null +++ b/packages/server-node/src/operations/cut-operation-handler.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CutOperation, DeleteElementOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { OperationHandler } from '../operations/operation-handler'; + +@injectable() +export class CutOperationHandler implements OperationHandler { + operationType = CutOperation.KIND; + + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + + execute(operation: CutOperation): void { + const cutableElementIds = this.getElementToCut(operation); + if (cutableElementIds.length > 0) { + this.actionDispatcher.dispatch(new DeleteElementOperation(cutableElementIds)); + } + } + + protected getElementToCut(cutOperation: CutOperation): string[] { + return cutOperation.editorContext.selectedElementIds; + } +} diff --git a/packages/server-node/src/operations/operation-action-handler.ts b/packages/server-node/src/operations/operation-action-handler.ts new file mode 100644 index 0000000..4a8b01d --- /dev/null +++ b/packages/server-node/src/operations/operation-action-handler.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, isCreateOperation, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionHandler } from '../actions/action-handler'; +import { GModelState } from '../base-impl/gmodel-state'; +import { Operations } from '../di/service-identifiers'; +import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; +import { OperationHandler } from './operation-handler'; +import { OperationHandlerRegistry } from './operation-handler-registry'; + +@injectable() +export class OperationActionHandler implements ActionHandler { + @inject(OperationHandlerRegistry) protected operationHandlerRegistry: OperationHandlerRegistry; + @inject(ModelSubmissionHandler) protected modelSubmissionHandler: ModelSubmissionHandler; + @inject(GModelState) protected modelState: GModelState; + + constructor(@inject(Operations) readonly actionKinds: string[]) {} + + execute(action: Action): Action[] { + if (this.handles(action)) { + if (this.modelState.isReadonly) { + return [new ServerMessageAction('WARNING', `Server is in readonly-mode! Could not execute operation: ${action.kind}`)]; + } + const operationHandler = OperationActionHandler.getOperationHandler(action, this.operationHandlerRegistry); + if (operationHandler) { + return this.executeHandler(action, operationHandler); + } + } + return []; + } + + executeHandler(operation: Operation, handler: OperationHandler): Action[] { + // TODO: Create GModelRecordingCommand; + handler.execute(operation); + this.modelState.index.indexRoot(this.modelState.root); + // TODO: this.modelState.execute(command) + return this.modelSubmissionHandler.submitModel(); // TODO: Add SetDirtyStateAction.Reason.Operation + } + + handles(action: Action): boolean { + if (this.actionKinds.includes(action.kind)) { + return true; + } + return false; + } + + static getOperationHandler(operation: Operation, registry: OperationHandlerRegistry): OperationHandler | undefined { + return isCreateOperation(operation) ? registry.get(`${operation.kind}_${operation.elementTypeId}`) : registry.get(operation.kind); + } +} diff --git a/packages/server-node/src/operations/operation-handler-registry.spec.ts b/packages/server-node/src/operations/operation-handler-registry.spec.ts new file mode 100644 index 0000000..dea5442 --- /dev/null +++ b/packages/server-node/src/operations/operation-handler-registry.spec.ts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { CompoundOperation, CreateEdgeOperation, CreateNodeOperation } from '@eclipse-glsp/protocol'; +import * as mock from '../test/mock-util'; +import { CompoundOperationHandler } from './compound-operation-handler'; +import { OperationHandlerRegistry } from './operation-handler-registry'; +import { expect } from 'chai'; + +describe('Test OperationHandlerRegistry', () => { + const stubANode = new mock.StubCreateNodeOperationHandler('ANode'); + const stubBNode = new mock.StubCreateNodeOperationHandler('BNode'); + const stubAEdge = new mock.StubCreateEdgeOperationHandler('AEdge'); + const stubBEdge = new mock.StubCreateEdgeOperationHandler('BEdge'); + const operationHandlerRegistry = new OperationHandlerRegistry(); + + it('register OperationActionHandler', () => { + operationHandlerRegistry.registerHandler(new CompoundOperationHandler()); + expect(operationHandlerRegistry.keys()).to.have.length(1); + expect(operationHandlerRegistry.keys()).to.contain(CompoundOperation.KIND); + expect(operationHandlerRegistry.get(CompoundOperation.KIND)).instanceOf(CompoundOperationHandler); + }); + + it('register CreateOperationActionHandlers', () => { + operationHandlerRegistry.registerHandler(stubANode); + operationHandlerRegistry.registerHandler(stubBNode); + operationHandlerRegistry.registerHandler(stubAEdge); + operationHandlerRegistry.registerHandler(stubBEdge); + expect(operationHandlerRegistry.keys()).to.contain(`${CreateNodeOperation.KIND}_ANode`); + expect(operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)).to.be.equal(stubANode); + expect(operationHandlerRegistry.keys()).to.contain(`${CreateNodeOperation.KIND}_BNode`); + expect(operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_BNode`)).to.be.equal(stubBNode); + expect(operationHandlerRegistry.keys()).to.contain(`${CreateEdgeOperation.KIND}_AEdge`); + expect(operationHandlerRegistry.get(`${CreateEdgeOperation.KIND}_AEdge`)).to.be.equal(stubAEdge); + expect(operationHandlerRegistry.keys()).to.contain(`${CreateEdgeOperation.KIND}_BEdge`); + expect(operationHandlerRegistry.get(`${CreateEdgeOperation.KIND}_BEdge`)).to.be.equal(stubBEdge); + }); +}); diff --git a/packages/server-node/src/operations/operation-handler-registry.ts b/packages/server-node/src/operations/operation-handler-registry.ts new file mode 100644 index 0000000..1639ee8 --- /dev/null +++ b/packages/server-node/src/operations/operation-handler-registry.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { Registry } from '../utils/registry'; +import { CreateOperationHandler } from './create-operation-handler'; +import { OperationHandler, OperationHandlerConstructor, OperationHandlerFactory } from './operation-handler'; + +@injectable() +export class OperationHandlerRegistry extends Registry { + registerHandler(handler: OperationHandler): boolean { + if (handler instanceof CreateOperationHandler) { + handler.elementTypeIds.forEach(typeId => this.register(`${handler.operationType}_${typeId}`, handler)); + return true; + } else { + return this.register(handler.operationType, handler); + } + } +} + +@injectable() +export class OperationHandlerRegistryInitializer implements ClientSessionInitializer { + @inject(OperationHandlerFactory) + protected factory: OperationHandlerFactory; + + @inject(OperationHandlerConstructor) + @optional() + protected handlerConstructors: OperationHandlerConstructor[] = []; + + @inject(OperationHandlerRegistry) + protected registry: OperationHandlerRegistry; + + initialize(args?: Args): void { + const handlers = this.handlerConstructors.map(constructor => this.factory(constructor)); + handlers.forEach(handler => this.registry.registerHandler(handler)); + } +} diff --git a/packages/server-node/src/operations/operation-handler.ts b/packages/server-node/src/operations/operation-handler.ts new file mode 100644 index 0000000..d6b309e --- /dev/null +++ b/packages/server-node/src/operations/operation-handler.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Operation } from '@eclipse-glsp/protocol'; +import { interfaces } from 'inversify'; + +export const OperationHandler = Symbol('OperationHandler'); + +/** + * An operation handler can execute {@link Operation}s of a certain type (subclass). + * The operation handler processes the operation in the {@link OperationHandler.execute()} method. The result + * of the execution is an update of the `GModelState` state. + * This update is reversible (undo) and can be reapplied (redo). For basic diagram languages these updates are typically + * applied directly on the `GModelState` using EMF `Command`s and the + * `GModelState.execute()` method. For more complex diagram languages the + * GModel state might be updated indirectly and the operation handler manipulates a custom model representation. + * + * The `OperationActionHandler` is responsible for retrieving all available (valid) operation handlers for an + * operation that is dispatched via `ActionDispatcher`. + */ +export interface OperationHandler { + /** + * Returns the operation type that can be handled by this operation handler. + * + * @returns the operation type that can be handled. + */ + readonly operationType: string; + + readonly label?: string; + + /** + * Executes the operation handler for the given {@link Operation}. + * + * @param operation The operation that should be executed. + */ + execute(operation: Operation): void; +} + +export const OperationHandlerConstructor = Symbol('OperationHandlerConstructor'); + +export type OperationHandlerConstructor = interfaces.Newable; + +export const OperationHandlerFactory = Symbol('OperationHandlerFactory'); + +export type OperationHandlerFactory = (constructor: OperationHandlerConstructor) => OperationHandler; diff --git a/packages/server-node/src/operations/paste-operation-handler.ts b/packages/server-node/src/operations/paste-operation-handler.ts new file mode 100644 index 0000000..bfc31cb --- /dev/null +++ b/packages/server-node/src/operations/paste-operation-handler.ts @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GBoundsAware, GModelElement, isGBoundsAware } from '@eclipse-glsp/graph'; +import { ORIGIN_POINT, PasteOperation, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GModelState } from '../base-impl/gmodel-state'; +import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { OperationHandler } from '../operations/operation-handler'; +import { shift } from '../utils/geometry-util'; +import * as uuid from 'uuid'; + +@injectable() +export class PasteOperationHandler implements OperationHandler { + operationType = PasteOperation.KIND; + + protected readonly DEFAULT_OFFSET = 20; + + @inject(GModelState) + protected modelState: GModelState; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + execute(operation: PasteOperation): void { + const schemas: SModelElementSchema[] = JSON.parse(operation.clipboardData['application/json']); + const elements = schemas.map(schema => this.modelSerializer.createElement(schema, this.modelState.root)); + shift(elements, this.computeOffset(elements, operation.editorContext.lastMousePosition)); + const idMap = this.reassignIds(elements); + this.filterElements(elements, idMap); + this.rewireEdges(elements, idMap); + + this.modelState.root.children = this.modelState.root.children.concat(elements); + } + + protected computeOffset(elements: GModelElement[], lastMousePosition?: Point): Point { + const offset = { x: this.DEFAULT_OFFSET, y: this.DEFAULT_OFFSET }; + if (lastMousePosition) { + const referenceElement = this.getReferenceElementForPasteOffset(elements); + if (referenceElement) { + const position = referenceElement.position ?? ORIGIN_POINT; + offset.x = lastMousePosition.x - position.x; + offset.y = lastMousePosition.y - position.y; + } + } + return offset; + } + + protected getReferenceElementForPasteOffset(elements: GModelElement[]): GBoundsAware | undefined { + let minY = Number.MAX_VALUE; + for (const element of elements) { + if (isGBoundsAware(element)) { + const position = element.position ?? ORIGIN_POINT; + if (minY > position.y) { + minY = position.y; + return element; + } + } + } + return undefined; + } + + protected reassignIds(elements: GModelElement[]): Map { + const idMap = new Map(); + elements.forEach(element => { + const newId = uuid.v4(); + idMap.set(element.id, newId); + element.id = newId; + const childMap = this.reassignIds(element.children); + childMap.forEach((value, key) => { + idMap.set(key, value); + }); + }); + return idMap; + } + + protected filterElements(elements: GModelElement[], idMap: Map): void { + elements.filter(e => !this.shouldExcludeElement(e, idMap)); + } + + protected shouldExcludeElement(element: GModelElement, idMap: Map): boolean { + return false; + } + + protected rewireEdges(elements: GModelElement[], idMap: Map): void { + const edges = this.modelState.index.getAllEdges(); + edges.forEach(edge => { + const sourceId = idMap.get(edge.sourceId); + if (sourceId) { + edge.sourceId = sourceId; + } + const targetId = idMap.get(edge.targetId); + if (targetId) { + edge.targetId = targetId; + } + }); + } +} + +export type TypeGuard = (object: unknown) => object is T; + +export function filterByType(elements: GModelElement[], guard: TypeGuard): T[] { + return elements.filter(element => guard(element)) as T[]; +} diff --git a/packages/server-node/src/operations/reconnect-edge-operation-handler.ts b/packages/server-node/src/operations/reconnect-edge-operation-handler.ts new file mode 100644 index 0000000..bc9293a --- /dev/null +++ b/packages/server-node/src/operations/reconnect-edge-operation-handler.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ReconnectEdgeOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { OperationHandler } from '../operations/operation-handler'; +import { GModelState } from '../base-impl/gmodel-state'; +import { GEdge, GNode, GPort } from '@eclipse-glsp/graph'; + +@injectable() +export class ReconnectEdgeOperationHandler implements OperationHandler { + operationType = ReconnectEdgeOperation.KIND; + + @inject(GModelState) + protected readonly modelState: GModelState; + + execute(operation: ReconnectEdgeOperation): void { + if (!operation.edgeElementId || !operation.sourceElementId || !operation.targetElementId) { + throw new GLSPServerError('Incomplete reconnect connection action'); + } + + const index = this.modelState.index; + + const edge = index.findByClass(operation.edgeElementId, GEdge); + const source = index.findByClass(operation.sourceElementId, GNode || GPort); + const target = index.findByClass(operation.targetElementId, GNode || GPort); + + if (!edge) { + throw new Error(`Invalid edge: edge ID ${operation.edgeElementId}`); + } + if (!source) { + throw new Error(`Invalid source: source ID ${operation.sourceElementId}`); + } + if (!target) { + throw new Error(`Invalid target: target ID ${operation.targetElementId}`); + } + + edge.sourceId = source.id; + edge.targetId = target.id; + edge.routingPoints = []; + } +} diff --git a/packages/server-node/src/protocol/glsp-client-proxy.ts b/packages/server-node/src/protocol/glsp-client-proxy.ts new file mode 100644 index 0000000..8eabf63 --- /dev/null +++ b/packages/server-node/src/protocol/glsp-client-proxy.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ActionMessage, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import * as jsonrpc from 'vscode-jsonrpc'; +import { Logger } from '../utils/logger'; + +export const GLSPClientProxyFactory = Symbol('GLSPClientProxyFactory'); + +export type GLSPClientProxyFactory = (connection: jsonrpc.MessageConnection) => GLSPClientProxy; + +export const GLSPClientProxy = Symbol('GLSPClientProxy'); + +/** + * Json-rpc client proxy interface to hide the underlying json-rpc logic. + */ +export interface GLSPClientProxy { + /** + * A `process` notification is sent from the server to server to the client when the client should handle i.e. + * process a specific {@link ActionMessage}. Any communication that is performed between initialization and shutdown + * is handled by sending action messages, either from the client to the server or from the server to the client. This + * is the core part of the Graphical Language Server Protocol. + * + * @param message The {@link ActionMessage} that should be processed. + */ + process(message: ActionMessage): void; +} + +export const JsonRpcGLSPClientProxy = Symbol('JsonRpcGLSPClientProxy'); + +export interface JsonRpcGLSPClientProxy extends GLSPClientProxy { + connect(connection: jsonrpc.MessageConnection): void; +} + +@injectable() +export class DefaultGLSPClientProxy implements JsonRpcGLSPClientProxy { + @inject(Logger) + private logger: Logger; + + protected connection: jsonrpc.MessageConnection; + + connect(connection: jsonrpc.MessageConnection): void { + if (!this.connection) { + this.connection = connection; + } + } + + process(message: ActionMessage): void { + this.logger.debug(`Send action '${message.action.kind}' to client '${message.clientId}'`); + this.connection.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message); + } +} diff --git a/packages/server-node/src/protocol/glsp-server-listener.ts b/packages/server-node/src/protocol/glsp-server-listener.ts new file mode 100644 index 0000000..0815da9 --- /dev/null +++ b/packages/server-node/src/protocol/glsp-server-listener.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GLSPServer } from './glsp-server'; + +export const GLSPServerListener = Symbol('GLSPServerListener'); + +/** + * A listener to track the connection status of {@link GLSPClient}s (i.e. client applications). + * Gets notified when a new GLSP client connects or disconnects. + */ +export interface GLSPServerListener { + /** + * Triggered after a GLSPServer has been initialized via the {@link GLSPServer.initialize()} + * method. + * + * @param server The GLSPServer which has been initialized. + */ + serverInitialized?(server: GLSPServer): void; + + /** + * Triggered after the {@link GLSPServer.shutdown()} method has been invoked. + * + * @param glspServer The glspServer which has been shut down. + */ + serverShutDown?(server: GLSPServer): void; +} diff --git a/packages/server-node/src/protocol/glsp-server.spec.ts b/packages/server-node/src/protocol/glsp-server.spec.ts new file mode 100644 index 0000000..d18e753 --- /dev/null +++ b/packages/server-node/src/protocol/glsp-server.spec.ts @@ -0,0 +1,140 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '@eclipse-glsp/protocol'; +import { Container, ContainerModule } from 'inversify'; +import { GlobalActionProvider } from '../actions/global-action-provider'; +import { ClientSessionManager } from '../session/client-session-manager'; +import * as mock from '../test/mock-util'; +import { Logger } from '../utils/logger'; +import { GLSPClientProxy, JsonRpcGLSPClientProxy } from './glsp-client-proxy'; +import { DefaultGLSPServer } from './glsp-server'; +import { GLSPServerListener } from './glsp-server-listener'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; + +describe('test DefaultGLSPServer', () => { + const container = new Container(); + const clientSessionId = 'myClientSession'; + const diagramType = 'myDiagram'; + const applicationId = 'Test'; + const protocolVersion = '0.9.0'; + const serverActionKinds = new Map(); + serverActionKinds.set(diagramType, ['A1', 'A2']); + const sessionManager = new mock.StubClientSessionManager(); + const spy_sessionManager_getOrCreate = sinon.spy(sessionManager, 'getOrCreateClientSession'); + const spy_sessionManager_dispose = sinon.spy(sessionManager, 'disposeClientSession'); + const listener1 = new mock.StubGLSPServerListener(); + const spy_listener1_initialize = sinon.spy(listener1, 'serverInitialized'); + const spy_listener1_shutdown = sinon.spy(listener1, 'serverShutDown'); + const listener2 = new mock.StubGLSPServerListener(); + const spy_listener2_initialize = sinon.spy(listener2, 'serverInitialized'); + const spy_listener2_shutdown = sinon.spy(listener2, 'serverShutDown'); + + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(GLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); + bind(JsonRpcGLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); + bind(ClientSessionManager).toConstantValue(sessionManager); + bind(GlobalActionProvider).toConstantValue({ clientActionKinds: new Map(), serverActionKinds }); + bind(GLSPServerListener).toConstantValue(listener1); + }) + ); + + const glspServer = container.resolve(DefaultGLSPServer); + + beforeEach(() => { + spy_sessionManager_getOrCreate.restore(); + spy_sessionManager_dispose.restore(); + spy_listener1_initialize.restore(); + spy_listener1_shutdown.restore(); + spy_listener2_initialize.restore(); + spy_listener2_shutdown.restore(); + }); + + it('Test calls before server initialization (should throw errors)', async () => { + expect(async () => glspServer.initializeClientSession({ clientSessionId: 'id', diagramType: 'type' })).to.throw; + expect(async () => glspServer.disposeClientSession({ clientSessionId: 'id' })).to.throw; + expect(async () => glspServer.process({ clientId: 'id', action: { kind: 'action' } })).to.throw; + }); + + it('addListener - add existing listener', () => { + expect(glspServer.addListener(listener1)).false; + }); + + it('addListener - add new listener', () => { + expect(glspServer.addListener(listener2)).true; + }); + + it('removeListener - remove non-existing listener', () => { + expect(glspServer.removeListener({})).false; + }); + + it('removeListener - remove existing listener', () => { + expect(glspServer.removeListener(listener2)).true; + }); + + it('initialize - with wrong protocol version', async () => { + const initializeParameters: InitializeParameters = { applicationId, protocolVersion: 'abc' }; + await expect(async () => glspServer.initialize(initializeParameters)).to.throw; + }); + + it('initialize - with correct parameters', async () => { + const initializeParameters: InitializeParameters = { applicationId, protocolVersion }; + const result = await glspServer.initialize(initializeParameters); + expect(result.protocolVersion).to.be.equal(protocolVersion); + expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + expect(spy_listener1_initialize.calledWith(glspServer)); + expect(spy_listener2_initialize.notCalled); + }); + + it('initialize - subsequent call with same parameters', async () => { + const initializeParameters: InitializeParameters = { applicationId, protocolVersion }; + const result = await glspServer.initialize(initializeParameters); + expect(result.protocolVersion).to.be.equal(protocolVersion); + expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + expect(result.serverActions[diagramType]).to.be.equal(serverActionKinds.get(diagramType)); + }); + + it('initialize - subsequent call with other parameters', async () => { + const initializeParameters = { applicationId: 'someOtherApp', protocolVersion: 'AnotherProtocolVersion' }; + await expect(async () => glspServer.initialize(initializeParameters)).to.throw; + }); + + it('initialize client session', async () => { + const initializeClientSessionParameters: InitializeClientSessionParameters = { + clientSessionId, + diagramType + }; + await glspServer.initializeClientSession(initializeClientSessionParameters); + expect(spy_sessionManager_getOrCreate.calledWith(initializeClientSessionParameters)); + }); + + it('dispose client session', async () => { + const disposeClientSessionParameters: DisposeClientSessionParameters = { + clientSessionId + }; + await glspServer.disposeClientSession(disposeClientSessionParameters); + expect(spy_sessionManager_dispose.calledWith(clientSessionId)); + }); + + it('shutdown server', async () => { + glspServer.shutdown(); + expect(spy_listener1_shutdown.calledWith(glspServer)); + expect(spy_listener2_shutdown.notCalled); + }); +}); diff --git a/packages/server-node/src/protocol/glsp-server.ts b/packages/server-node/src/protocol/glsp-server.ts new file mode 100644 index 0000000..23c03b3 --- /dev/null +++ b/packages/server-node/src/protocol/glsp-server.ts @@ -0,0 +1,313 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { + ActionMessage, + Args, + DisposeClientSessionParameters, + distinctAdd, + InitializeClientSessionParameters, + InitializeParameters, + InitializeResult, + remove, + ServerActions, + ServerMessageAction +} from '@eclipse-glsp/protocol'; +import { JsonrpcGLSPClient, MaybePromise } from '@eclipse-glsp/protocol/lib/jsonrpc/glsp-jsonrpc-client'; +import { inject, injectable, multiInject, optional } from 'inversify'; +import * as jsonrpc from 'vscode-jsonrpc'; +import { MessageConnection } from 'vscode-jsonrpc'; +import { GlobalActionProvider } from '../actions/global-action-provider'; +import { ClientSession } from '../session/client-session'; +import { ClientSessionManager } from '../session/client-session-manager'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { Logger } from '../utils/logger'; +import { JsonRpcGLSPClientProxy } from './glsp-client-proxy'; +import { GLSPServerListener } from './glsp-server-listener'; + +export const GLSPServer = Symbol('GLSPServer'); + +/** + * Interface for implementations of a server component using json-rpc for client-server communication. + * Based on the specification of the Graphical Language Server Protocol: + * https://github.com/eclipse-glsp/glsp/blob/master/PROTOCOL.md + */ +export interface GLSPServer { + /** + * + * The `initialize` request has to be the first request from the client to the server. Until the server has responded + * with an {@link InitializeResult} no other request or notification can be handled and is expected to throw an + * error. A client is uniquely identified by an `applicationId` and has to specify on which `protocolVersion` it is + * based on. In addition, custom arguments can be provided in the `args` map to allow for custom initialization + * behavior on the server. + * + * After successfully initialization all {@link GLSPServerListener}s are notified via the + * {@link GLSPServerListener.serverInitialized} method. + * + * @param params the {@link InitializeParameters}. + * @returns A promise of the {@link InitializeResult} . + * + * @throws {@link Error} Subsequent initialize requests return the {@link InitializeResult} of the initial request + * if the given application id and protocol version are matching, otherwise the promise rejects with an error. + * + */ + initialize(params: InitializeParameters): Promise; + + /** + * The `initializeClientSession` request is sent to the server whenever a new graphical representation (diagram) is + * created. Each individual diagram on the client side counts as one session and has to provide a unique + * `clientSessionId` and its `diagramType`. In addition, custom arguments can be provided in the `args` map to allow + * for custom initialization behavior on the server. Subsequent `initializeClientSession` requests for the same + * client id and diagram type are expected to resolve successfully but don't have an actual effect because the + * corresponding client session is already initialized. + * + * @param params the {@link InitializeClientSessionParameters}. + * @returns A promise that completes when the initialization was successful. + */ + initializeClientSession(params: InitializeClientSessionParameters): Promise; + + /** + * The 'DisposeClientSession' request is sent to the server when a graphical representation (diagram) is no longer + * needed, e.g. the tab containing the diagram widget has been closed. The session is identified by its unique + * `clientSessionId`. In addition, custom arguments can be provided in the `args` map to allow for custom dispose + * behavior on the server. + * + * @param params the {@link DisposeClientSessionParameters}. + * @returns A `void` promise that completes if the disposal was successful. + * + */ + disposeClientSession(params: DisposeClientSessionParameters): Promise; + + /** + * A `process` notification is sent from the client to server when the server should handle i.e. process a specific + * {@link ActionMessage}. Any communication that is performed between initialization and shutdown is handled by + * sending action messages, either from the client to the server or from the server to the client. This is the core + * part of the Graphical Language Server Protocol. + * + * @param message The {@link ActionMessage} that should be processed. + */ + process(message: ActionMessage): void; + + /** + * The `shutdown` notification is sent from the client to the server if the client disconnects from the server (e.g. + * the client application has been closed). + * This gives the server a chance to clean up and dispose any resources dedicated to the client and its sessions. + * All {@link GLSPServerListener}s are notified via the {@link GLSPServerListener.serverShutDown} method. + * Afterwards the server instance is considered to be disposed and can no longer be used for handling requests. + * + */ + shutdown(): void; + + /** + * Register a new {@link GLSPServerListener}. + * + * @param listener The listener that should be registered. + * @returns `true` if the listener was registered successfully, `false` otherwise (e.g. listener is already + * registered). + */ + addListener(listener: GLSPServerListener): boolean; + + /** + * Unregister a {@link GLSPServerListener}. + * + * @param listener The listener that should be removed + * @returns 'true' if the listener was unregistered successfully, `false` otherwise (e.g. listener is was not + * registered in the first place). + */ + removeListener(listener: GLSPServerListener): boolean; + + /** + * get a {@link ClientSession}. + * + * @param sessionId The id of the session to get + * @returns The either the ClientSession or undefined, if no ClientSession was found for the given id. + */ + getClientSession(sessionId: string): ClientSession | undefined; +} + +export const JsonRpcGLSPServer = Symbol('JsonRpcGLSPServer'); + +export interface JsonRpcGLSPServer extends GLSPServer { + connect(connection: jsonrpc.MessageConnection): void; +} + +@injectable() +export class DefaultGLSPServer implements JsonRpcGLSPServer { + public static readonly PROTOCOL_VERSION = '0.9.0'; + + @inject(Logger) + protected logger: Logger; + + @inject(ClientSessionManager) + protected sessionManager: ClientSessionManager; + + @inject(GlobalActionProvider) + protected actionProvider: GlobalActionProvider; + + @inject(JsonRpcGLSPClientProxy) + protected glspClient: JsonRpcGLSPClientProxy; + + protected initializeResult?: InitializeResult; + + protected applicationId: string; + + protected clientSessions: Map; + protected serverListeners: GLSPServerListener[] = []; + + constructor(@multiInject(GLSPServerListener) @optional() serverListeners: GLSPServerListener[] = []) { + this.clientSessions = new Map(); + serverListeners.forEach(listener => this.addListener(listener)); + } + + protected setupJsonRpc(connection: MessageConnection): void { + connection.onRequest(JsonrpcGLSPClient.InitializeRequest.method, (params: InitializeParameters) => this.initialize(params)); + connection.onRequest(JsonrpcGLSPClient.InitializeClientSessionRequest, (params: InitializeClientSessionParameters) => + this.initializeClientSession(params) + ); + connection.onRequest(JsonrpcGLSPClient.DisposeClientSessionRequest, (params: DisposeClientSessionParameters) => + this.disposeClientSession(params) + ); + connection.onNotification(JsonrpcGLSPClient.ActionMessageNotification, message => this.process(message)); + connection.onNotification(JsonrpcGLSPClient.ShutdownNotification, () => this.shutdown()); + } + + protected validateProtocolVersion(params: InitializeParameters): void { + if (params.protocolVersion !== DefaultGLSPServer.PROTOCOL_VERSION) { + throw new Error( + // eslint-disable-next-line max-len + `Protocol version mismatch! The client protocol version ${params.protocolVersion} is not compatible with the server protocol version ${DefaultGLSPServer.PROTOCOL_VERSION}!` + ); + } + } + + protected validateServerInitialized(): void { + if (!this.isInitialized()) { + throw new Error('This GLSP server has not been initialized.'); + } + } + + public async initialize(params: InitializeParameters): Promise { + this.logger.info(`Initializing server with: applicationId: ${params.applicationId}, protocolVersion: ${params.protocolVersion}`); + this.validateProtocolVersion(params); + + if (this.isInitialized()) { + if (params.applicationId !== this.applicationId) { + throw new Error(`Could not initialize GLSP server for application ${params.applicationId}. + Server has already been initialized for different application with id ${this.applicationId}`); + } + if (this.initializeResult) { + return this.initializeResult; + } + } + + this.applicationId = params.applicationId; + const serverActions: ServerActions = {}; + + this.actionProvider.serverActionKinds.forEach((kinds, diagramType) => (serverActions[diagramType] = kinds)); + + let result = { protocolVersion: DefaultGLSPServer.PROTOCOL_VERSION, serverActions }; + + result = await this.handleInitializeArgs(result, params.args); + this.getListenersToNotify('serverInitialized').forEach((listener: GLSPServerListener) => listener.serverInitialized!(this)); + this.initializeResult = result; + return result; + } + + protected handleInitializeArgs(result: InitializeResult, args: Args | undefined): MaybePromise { + return result; + } + + public async initializeClientSession(params: InitializeClientSessionParameters): Promise { + this.logger.info( + `Initializing client session with: clientSessionId: '${params.clientSessionId}', diagramType: '${params.diagramType}'` + ); + this.validateServerInitialized(); + + const session = this.sessionManager.getOrCreateClientSession(params); + this.clientSessions.set(params.clientSessionId, session); + return this.handleInitializeClientSessionArgs(params.args); + } + + protected handleInitializeClientSessionArgs(args: Args | undefined): MaybePromise { + return; + } + + public async disposeClientSession(params: DisposeClientSessionParameters): Promise { + this.logger.debug('Dispose client session with:', params); + this.validateServerInitialized(); + if (this.sessionManager.disposeClientSession(params.clientSessionId)) { + this.clientSessions.delete(params.clientSessionId); + return this.handleDisposeClientSessionArgs(params.args); + } + return; + } + + protected async handleDisposeClientSessionArgs(args: Args | undefined): Promise { + return; + } + + process(message: ActionMessage): void { + this.validateServerInitialized(); + this.logger.info(`process [action=${message.action.kind}, clientId=${message.clientId}]`); + const clientSessionId = message.clientId; + const clientSession = this.clientSessions.get(clientSessionId); + if (!clientSession) { + throw new Error(`No client session has been initialized for client id: ${clientSessionId}`); + } + + clientSession.actionDispatcher.dispatch(message.action).catch(error => this.handleProcessError(message, error)); + } + + protected handleProcessError(message: ActionMessage, reason: any): void | PromiseLike { + const errorMsg = `Could not process action: '${message.action.kind}`; + this.logger.error(errorMsg, reason); + if (reason instanceof GLSPServerError) { + const errorAction = new ServerMessageAction('ERROR', errorMsg, reason.cause?.toString()); + this.glspClient.process({ clientId: message.clientId, action: errorAction }); + } + } + + getClientSession(sessionId: string): ClientSession | undefined { + return this.clientSessions.get(sessionId); + } + + public shutdown(): void { + this.logger.info('Shutdown GLSP Server'); + this.getListenersToNotify('serverShutDown').forEach(listener => listener.serverShutDown!(this)); + this.clientSessions.clear(); + this.initializeResult = undefined; + } + + public connect(connection: jsonrpc.MessageConnection): void { + this.setupJsonRpc(connection); + this.glspClient.connect(connection); + } + + protected isInitialized(): boolean { + return this.initializeResult !== undefined; + } + + addListener(listener: GLSPServerListener): boolean { + return distinctAdd(this.serverListeners, listener); + } + + removeListener(listener: GLSPServerListener): boolean { + return remove(this.serverListeners, listener); + } + + protected getListenersToNotify(method: keyof GLSPServerListener): GLSPServerListener[] { + return this.serverListeners.filter(listener => listener[method]); + } +} diff --git a/packages/server-node/src/session/client-session-factory.spec.ts b/packages/server-node/src/session/client-session-factory.spec.ts new file mode 100644 index 0000000..b76aa85 --- /dev/null +++ b/packages/server-node/src/session/client-session-factory.spec.ts @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; +import * as mock from '../test/mock-util'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { Logger } from '../utils/logger'; +import { DefaultClientSessionFactory } from './client-session-factory'; +import { ClientSessionInitializer } from './client-session-initializer'; +import { expect } from 'chai'; + +describe('test DefaultClientSessionFactory', () => { + const clientSessionId = 'myClientId'; + const diagramType = 'myDiagramType'; + const container = new Container(); + const diagramModule = new ContainerModule(bind => { + bind(ActionDispatcher).toConstantValue(new mock.StubActionDispatcher()); + bind(ClientSessionInitializer).toConstantValue(new mock.StubClientSessionInitializer()); + }); + + const diagramModules = new Map(); + diagramModules.set(diagramType, [diagramModule]); + + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(InjectionContainer).toConstantValue(container); + bind(DiagramModules).toConstantValue(diagramModules); + bind(GLSPClientProxy).toConstantValue(new mock.StubGLSPClientProxy()); + }) + ); + const factory = container.resolve(DefaultClientSessionFactory); + + it('create - new client session', () => { + const session = factory.create({ clientSessionId, diagramType }); + expect(session.id).to.be.equal(clientSessionId); + expect(session.diagramType).to.be.equal(diagramType); + expect(session.container.parent).to.be.equal(container); + expect(session.actionDispatcher instanceof mock.StubActionDispatcher).true; + }); + + it('create - unknown diagram type', () => { + expect(() => factory.create({ clientSessionId, diagramType: 'unknown-type' })).to.throw(GLSPServerError); + }); +}); diff --git a/packages/server-node/src/session/client-session-factory.ts b/packages/server-node/src/session/client-session-factory.ts new file mode 100644 index 0000000..a60b932 --- /dev/null +++ b/packages/server-node/src/session/client-session-factory.ts @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { InitializeClientSessionParameters } from '@eclipse-glsp/protocol'; +import { Container, ContainerModule, inject, injectable } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { createClientSessionModule } from '../di/client-session-module'; +import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; +import { GLSPClientProxy } from '../protocol/glsp-client-proxy'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { ClientSession, DefaultClientSession } from './client-session'; +import { ClientSessionInitializer } from './client-session-initializer'; + +export const ClientSessionFactory = Symbol('ClientSessionFactory'); + +/** + * Handles the construction of new {@link ClientSession}. A client session factory has to know + * how to derive the client session specific injector and its entrypoint (i.e. the {@link ActionDispatcher} + * from a given client session id and a given diagram type. + */ +export interface ClientSessionFactory { + /** + * Create a new {@link ClientSession} based on the given client session id and diagram type. + * + * @param clientSessionId The client session id. + * @param diagramType The diagram type. + * @returns A new instance of {@link ClientSession} that correlates to the given input parameters. + */ + create(params: InitializeClientSessionParameters): ClientSession; +} + +@injectable() +export class DefaultClientSessionFactory implements ClientSessionFactory { + @inject(InjectionContainer) + protected serverContainer: Container; + + @inject(DiagramModules) + protected diagramModules: Map; + + @inject(GLSPClientProxy) + protected glspClient: GLSPClientProxy; + + create(params: InitializeClientSessionParameters): ClientSession { + const { clientSessionId: id, diagramType, args } = params; + const diagramModules = this.diagramModules.get(diagramType); + if (!diagramModules || diagramModules.length === 0) { + throw new GLSPServerError(`Could not retrieve diagram module configuration for diagram type: '${diagramType}''`); + } + + const sessionModule = createClientSessionModule(id, this.glspClient); + const sessionContainer = this.serverContainer.createChild(); + sessionContainer.load(...diagramModules, sessionModule); + const initializers = sessionContainer.getAll(ClientSessionInitializer); + initializers.forEach(service => service.initialize(args)); + const actionDispatcher = sessionContainer.get(ActionDispatcher); + return new DefaultClientSession(id, diagramType, actionDispatcher, sessionContainer); + } +} diff --git a/packages/server-node/src/session/client-session-initializer.ts b/packages/server-node/src/session/client-session-initializer.ts new file mode 100644 index 0000000..c35a77c --- /dev/null +++ b/packages/server-node/src/session/client-session-initializer.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; + +export const ClientSessionInitializer = Symbol('ClientSessionInitializer'); + +/** + * Client session initializers are services the are used to configure the client session container after its initialization. + * The {@link ClientSessionFactory} is expected to retrieve and invoke all client session listeners from the container in the + * {@link ClientSessionFactory.create} method. + */ +export interface ClientSessionInitializer { + initialize(args?: Args): void; +} diff --git a/packages/server-node/src/session/client-session-listener.ts b/packages/server-node/src/session/client-session-listener.ts new file mode 100644 index 0000000..9521c59 --- /dev/null +++ b/packages/server-node/src/session/client-session-listener.ts @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ClientSession } from './client-session'; + +/** + * A listener that gets notified on certain client session lifecycle events.
+ * Life cycle events: + * * Session creation + * * Session disposal + * + * The scope of relevant client session ids can be restricted when registering the listener with + * `ClientSessionManager.addListener()` + */ +export interface ClientSessionListener { + /** + * Is invoked after a new {@link ClientSession} has been created by the `ClientSessionManager`. + * + * @param clientSession The newly created client session. + */ + sessionCreated?(clientSession: ClientSession): void; + + /** + * Is invoked after a {@link ClientSession} has been disposed by the `ClientSessionManager`. + * + * @param clientSession The client session that was disposed. + */ + sessionDisposed?(clientSession: ClientSession): void; +} diff --git a/packages/server-node/src/session/client-session-manager.spec.ts b/packages/server-node/src/session/client-session-manager.spec.ts new file mode 100644 index 0000000..75b3651 --- /dev/null +++ b/packages/server-node/src/session/client-session-manager.spec.ts @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container, ContainerModule } from 'inversify'; +import * as mock from '../test/mock-util'; +import { Logger } from '../utils/logger'; +import { ClientSessionFactory } from './client-session-factory'; +import { DefaultClientSessionManager } from './client-session-manager'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +describe('test DefaultClientSessionManager', () => { + const testSession = mock.createClientSession('myId', 'myDiagram'); + const testSessionListener = new mock.StubClientSessionListener(); + + const sessionFactory = new mock.StubClientSessionFactory(); + sinon.stub(sessionFactory, 'create').returns(testSession); + + const container = new Container(); + container.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(ClientSessionFactory).toConstantValue(sessionFactory); + }) + ); + const sessionManager = container.resolve(DefaultClientSessionManager); + + it('add listener', () => { + expect(sessionManager.addListener(testSessionListener, testSession.id)).true; + }); + + it('add create client session', () => { + // Mock setup + const listener_create = sinon.spy(testSessionListener, 'sessionCreated'); + // Test execution + const createdSession = sessionManager.getOrCreateClientSession({ + clientSessionId: testSession.id, + diagramType: testSession.diagramType + }); + expect(createdSession).to.not.be.undefined; + expect(createdSession.id).to.be.equal(testSession.id); + expect(createdSession.diagramType).to.be.equal(testSession.diagramType); + + const retrievedSession = sessionManager.getSession(testSession.id); + expect(retrievedSession).to.not.be.undefined; + expect(retrievedSession).to.be.equal(createdSession); + expect(listener_create.calledWith(testSession)); + }); + + it('get sessions by type', () => { + const clientSessions = sessionManager.getSessionsByType(testSession.diagramType); + expect(clientSessions.length).to.be.equal(1); + expect(clientSessions[0]).to.be.equal(testSession); + }); + + it('get sessions by type that does no exist', async () => { + const clientSessions = sessionManager.getSessionsByType('wrongType'); + expect(clientSessions.length).to.be.equal(0); + }); + + it('dispose client session', () => { + // Mock setup + const listener_dispose = sinon.spy(testSessionListener, 'sessionDisposed'); + // Test execution + expect(sessionManager.disposeClientSession(testSession.id)).to.be.equal(true); + const session = sessionManager.getSession(testSession.id); + expect(session).to.be.undefined; + expect(listener_dispose.calledWith(testSession)); + }); +}); diff --git a/packages/server-node/src/session/client-session-manager.ts b/packages/server-node/src/session/client-session-manager.ts new file mode 100644 index 0000000..42645f2 --- /dev/null +++ b/packages/server-node/src/session/client-session-manager.ts @@ -0,0 +1,187 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { InitializeClientSessionParameters, remove } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { GLSPServer } from '../protocol/glsp-server'; +import { GLSPServerListener } from '../protocol/glsp-server-listener'; +import { Disposable } from '../utils/disposable'; +import { GLSPServerError } from '../utils/glsp-server-error'; +import { ClientSession } from './client-session'; +import { ClientSessionFactory } from './client-session-factory'; +import { ClientSessionListener } from './client-session-listener'; + +export const ClientSessionManager = Symbol('ClientSessionManager'); + +/** + * The central component that manages the lifecycle of client sessions. + */ +export interface ClientSessionManager { + /** + * Retries an existing (or created a new) {@link ClientSession} for the given id and diagram type. + * If a new session has been created all {@link ClientSessionListener}s are notified via the + * {@link ClientSessionListener.sessionCreated} method. + * + * @param clientSessionId The client session id (i.e. clientId). + * @param diagramType The diagram type. + * @returns The existing or newly constructed {@link ClientSession}. + * + * @throws {@link GLSPServerError} if another session with matching client id but different diagram type already exists. + */ + getOrCreateClientSession(params: InitializeClientSessionParameters): ClientSession; + + /** + * Retrieve an existing (i.e. currently active) {@link ClientSession} for the given client session id. + * + * @param clientSessionId The client session id. + * @returns The client session for the given id o `undefined` if no session is present with the given id. + */ + getSession(clientSessionId: string): ClientSession | undefined; + + /** + * Return all currently active {@link ClientSession}s for the given diagram type. + * + * @param diagramType The diagram type. + * @returns An array of all currently active {@link ClientSession}s. + */ + getSessionsByType(diagramType: string): Array; + + /** + * Dispose the active client session with the given id. This marks the end of the lifecylce of a client session. + * After successfully disposal all {@link ClientSessionListener}s are notified via the + * {@link ClientSessionListener.sessionDisposed} method. + * + * @param clientSessionId The id of the client session which should be disposed. + * @returns `true` if a session with the given id was active and successfully disposed, `false` otherwise. + */ + disposeClientSession(clientSessionId: string): boolean; + + /** + * Register a new {@link ClientSessionListener}. Optionally the scope of the listener can be restricted to a set + * of client session ids. If no client session ids are passed, the listener will be registered globally and trigger + * for all client sessions. + * + * @param listener The listener that should be registered. + * @param clientSessionIds Scope of client ids + * @returns `true` if the listener was registered successfully, `false` otherwise + */ + addListener(listener: ClientSessionListener, ...clientSessionIds: string[]): boolean; + + /** + * Unregister a given {@link ClientSessionListener} from this client session manager. + * + * @param listener The listener that should be removed. + * @returns `false` if the listener is not registered, `true` if it was successfully unregistered. + */ + removeListener(listener: ClientSessionListener): boolean; + + /** + * Unregister all {@link ClientSessionListener} that haven been registered for the given scope of client session ids. + * If not client session ids are passed all listeners will be removed. + * + * @param clientSessionIds Scope (i.e. set) of client session ids. + */ + removeListeners(...clientSessionIds: string[]): void; +} + +const ALL_CLIENT_IDS_KEY = '*'; + +@injectable() +export class DefaultClientSessionManager extends Disposable implements ClientSessionManager, GLSPServerListener { + @inject(ClientSessionFactory) sessionFactory: ClientSessionFactory; + + protected clientSessions: Map = new Map(); + protected listeners: Map = new Map(); + + protected getListenersToNotify(clientSession: ClientSession, method: keyof ClientSessionListener): ClientSessionListener[] { + const globalListeners = this.listeners.get(ALL_CLIENT_IDS_KEY)?.filter(listener => listener[method]) ?? []; + const sessionListeners = this.listeners.get(clientSession.id)?.filter(listener => listener[method]) ?? []; + return [...globalListeners, ...sessionListeners]; + } + + getOrCreateClientSession(params: InitializeClientSessionParameters): ClientSession { + const { clientSessionId, diagramType } = params; + const session = this.clientSessions.get(clientSessionId); + if (session) { + if (session.diagramType !== diagramType) { + throw new GLSPServerError(`Could not initialize new session for diagram type '${diagramType}' + Another session with the same id for the diagram type ${session.diagramType} already exists`); + } + return session; + } + + const newSession = this.sessionFactory.create(params); + this.clientSessions.set(clientSessionId, newSession); + this.getListenersToNotify(newSession, 'sessionCreated').forEach(listener => listener.sessionCreated!(newSession)); + return newSession; + } + + getSession(clientSessionId: string): ClientSession | undefined { + return this.clientSessions.get(clientSessionId); + } + + getSessionsByType(diagramType: string): ClientSession[] { + return Array.from(this.clientSessions.values()).filter(session => session.diagramType === diagramType); + } + + disposeClientSession(clientSessionId: string): boolean { + const session = this.clientSessions.get(clientSessionId); + if (session) { + session.dispose(); + this.getListenersToNotify(session, 'sessionDisposed').forEach(listener => listener.sessionDisposed!(session)); + this.clientSessions.delete(clientSessionId); + this.listeners.delete(clientSessionId); + return true; + } + return false; + } + + addListener(listener: ClientSessionListener, ...clientSessionIds: string[]): boolean { + if (clientSessionIds.length === 0) { + this.addSingleListener(ALL_CLIENT_IDS_KEY, listener); + } + return clientSessionIds.map(clientId => this.addSingleListener(clientId, listener)).every(added => added); + } + + protected addSingleListener(clientSessionId: string, listener: ClientSessionListener): boolean { + const listeners = this.listeners.get(clientSessionId) ?? []; + listeners.push(listener); + this.listeners.set(clientSessionId, listeners); + return true; + } + + removeListener(listener: ClientSessionListener): boolean { + return Array.from(this.listeners.values()) + .map(listeners => remove(listeners, listener)) + .every(removed => removed); + } + + removeListeners(...clientSessionIds: string[]): void { + if (clientSessionIds.length === 0) { + this.listeners.clear(); + } else { + clientSessionIds.forEach(id => this.listeners.delete(id)); + } + } + + serverShutDown(server: GLSPServer): void { + this.dispose(); + } + + protected doDispose(): void { + Array.from(this.clientSessions.keys()).forEach(id => this.disposeClientSession(id)); + this.listeners.clear(); + } +} diff --git a/packages/server-node/src/session/client-session.ts b/packages/server-node/src/session/client-session.ts new file mode 100644 index 0000000..0487afa --- /dev/null +++ b/packages/server-node/src/session/client-session.ts @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Container } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { Disposable, IDisposable, isIDisposable } from '../utils/disposable'; + +/** + * Stores the core information that the `GLSPServer` needs to know about a client session. + * When handling session specific requests (i.e. `ActionMessage`s the server retrieves the corresponding + * client session from the `ClientSessionManager` and delegates the action message to its + * {@link ActionDispatcher}; + */ +export interface ClientSession extends IDisposable { + /** + * The id of the client session. + */ + readonly id: string; + + /** + * The diagram type of the client session. + */ + readonly diagramType: string; + + /** + * The action dispatcher of this diagram type. The action dispatcher is typically created by the session + * specific injector and is basically the entrypoint to the session specific injection context. + */ + readonly actionDispatcher: ActionDispatcher; + + /** + * The session specific {@link Container}. + */ + readonly container: Container; +} + +export class DefaultClientSession extends Disposable implements ClientSession { + constructor( + readonly id: string, + readonly diagramType: string, + readonly actionDispatcher: ActionDispatcher, + readonly container: Container + ) { + super(); + } + + protected doDispose(): void { + if (isIDisposable(this.actionDispatcher)) { + this.actionDispatcher.dispose(); + } + } +} diff --git a/packages/server-node/src/test/integration-test.spec.ts b/packages/server-node/src/test/integration-test.spec.ts new file mode 100644 index 0000000..6e93b05 --- /dev/null +++ b/packages/server-node/src/test/integration-test.spec.ts @@ -0,0 +1,163 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { getDefaultMapping, GNode } from '@eclipse-glsp/graph'; +import { + Args, + CompoundOperation, + CreateNodeOperation, + InitializeResult, + isRequestBoundsAction, + Point, + RequestModelAction +} from '@eclipse-glsp/protocol'; +import { Container, ContainerModule, injectable, interfaces } from 'inversify'; +import * as path from 'path'; +import { GModelDiagramModule } from '../base-impl/gmodel-diagram-module'; +import { InstanceMultiBinding } from '../di/multi-binding'; +import { ServerModule } from '../di/server-module'; +import { InjectionContainer } from '../di/service-identifiers'; +import { DiagramConfiguration } from '../diagram/diagram-configuration'; +import { SocketServerLauncher } from '../launch/socket-server-launcher'; +import { CompoundOperationHandler } from '../operations/compound-operation-handler'; +import { CreateNodeOperationHandler } from '../operations/create-operation-handler'; +import { OperationHandlerConstructor } from '../operations/operation-handler'; +import { OperationHandlerRegistry } from '../operations/operation-handler-registry'; +import { GLSPClientProxy, JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; +import { DefaultGLSPServer, GLSPServer } from '../protocol/glsp-server'; +import { Logger } from '../utils/logger'; +import * as mock from './mock-util'; +const applicationId = 'testApp'; +const protocolVersion = '0.9.0'; +const diagramType = 'testDiagram'; +const clientId = 'session1'; +const sourceUri = path.resolve(__dirname, 'minimal.json'); +import * as sinon from 'sinon'; +import { expect } from 'chai'; + +class TestDiagramModule extends GModelDiagramModule { + diagramType = diagramType; + + protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + super.configure(bind, unbind, isBound, rebind); + bind(DiagramConfiguration).toConstantValue( + new (class extends mock.StubDiagramConfiguration { + typeMapping = getDefaultMapping(); + })() + ); + } + + configureOperationHandlers(binding: InstanceMultiBinding): void { + super.configureOperationHandlers(binding); + binding.add(CreateANodeOperationHandler); + } +} + +@injectable() +class CreateANodeOperationHandler extends CreateNodeOperationHandler { + label = 'ANode'; + elementTypeIds = ['ANode']; + + createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode { + return new GNode(); + } +} + +let serverContainer: Container; +const clientProxy: GLSPClientProxy = new mock.StubGLSPClientProxy(); +const spy_client_process = sinon.stub(clientProxy, 'process'); +let glspServer: GLSPServer; +let operationHandlerRegistry: OperationHandlerRegistry; + +describe('Integration tests for a glsp server created by SocketServerLauncher', () => { + const parentContainer = new Container(); + parentContainer.load( + new ContainerModule(bind => { + bind(Logger).toConstantValue(new mock.StubLogger()); + bind(InjectionContainer).toConstantValue(parentContainer); + }) + ); + + const rebindModule = new ContainerModule((bind, unbind, isBound, rebind) => { + rebind(GLSPClientProxy).toConstantValue(clientProxy); + rebind(JsonRpcGLSPClientProxy).toConstantValue(clientProxy); + }); + const serverModule = new ServerModule().configureDiagramModule(new TestDiagramModule()); + const serverLauncher = parentContainer.resolve(SocketServerLauncher); + serverLauncher.configure(serverModule, rebindModule); + serverContainer = serverLauncher.createContainer(); + glspServer = serverContainer.resolve(DefaultGLSPServer); + + let initializeResult: InitializeResult; + + it('process- should fail (server not initialized)', () => { + expect(() => + glspServer.process({ + clientId, + action: new RequestModelAction({ + ['sourceUri']: sourceUri + }) + }) + ).to.throw; + }); + + it('initialize - should return valid initializeResult', async () => { + initializeResult = await glspServer.initialize({ applicationId, protocolVersion }); + expect(initializeResult.protocolVersion).to.be.equal(protocolVersion); + const serverActions = initializeResult.serverActions[diagramType]; + expect(serverActions).to.not.be.undefined; + expect(serverActions.includes(RequestModelAction.KIND)).true; + }); + + it('initialize - subsequent call should return same result', async () => { + const newResult = await glspServer.initialize({ applicationId, protocolVersion }); + expect(newResult).to.be.equals(initializeResult); + }); + + it('initializeClientSession - should complete successfully', async () => { + await glspServer.initializeClientSession({ clientSessionId: clientId, diagramType }); + const clientSession = glspServer.getClientSession(clientId)!; + operationHandlerRegistry = clientSession.container.get(OperationHandlerRegistry); + }); + + it('initializeClientSession - subsequent call should complete successfully', async () => { + await glspServer.initializeClientSession({ clientSessionId: clientId, diagramType }); + }); + + it('process - RequestModelAction should send RequestBoundsAction to client', async () => { + glspServer.process({ clientId, action: new RequestModelAction({ ['sourceUri']: sourceUri }) }); + // Action handling is done async. We have to add a delay to ensure that we receive all responses + await mock.delay(200); + const result = spy_client_process.getCalls().map(call => call.args); + expect(spy_client_process.calledOnce); + expect(result[result.length - 1][0].clientId).to.be.equals(clientId); + expect(isRequestBoundsAction(result[result.length - 2][0].action)).true; + }); + + it('check OperationHandlerRegistry for defined operations', () => { + expect(operationHandlerRegistry.keys()).to.contain(`${CreateNodeOperation.KIND}_ANode`); + expect(operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)).instanceOf(CreateANodeOperationHandler); + expect(operationHandlerRegistry.keys()).to.contain(CompoundOperation.KIND); + expect(operationHandlerRegistry.get(CompoundOperation.KIND)).instanceOf(CompoundOperationHandler); + }); + + it('correct handler is triggered on process', () => { + const handler = operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)!; + const spy_operationHandler_execute = sinon.stub(handler, 'execute'); + + glspServer.process({ clientId, action: new CreateNodeOperation('ANode') }); + expect(spy_operationHandler_execute.calledOnce); + }); +}); diff --git a/packages/server-node/src/test/minimal.json b/packages/server-node/src/test/minimal.json new file mode 100644 index 0000000..8551758 --- /dev/null +++ b/packages/server-node/src/test/minimal.json @@ -0,0 +1,32 @@ +{ + "id": "8e03a1d0-e5f7-4a79-916f-741e93cc0d10", + "children": [ + { + "id": "811d39c9-e3f6-404e-8306-79c10f0a3e6b", + "cssClasses": ["minimal-node"], + "type": "node", + "position": { + "x": 175.00570678710938, + "y": 68.0227279663086 + }, + "size": { + "width": 40.0, + "height": 20.0 + } + }, + { + "id": "36c79c6a-1d71-411c-ad5a-7051145f9b78", + "cssClasses": ["minimal-node"], + "type": "node", + "position": { + "x": 265.0057067871094, + "y": 141.0227279663086 + }, + "size": { + "width": 40.0, + "height": 20.0 + } + } + ], + "type": "graph" +} diff --git a/packages/server-node/src/test/mock-util.ts b/packages/server-node/src/test/mock-util.ts new file mode 100644 index 0000000..6950bd1 --- /dev/null +++ b/packages/server-node/src/test/mock-util.ts @@ -0,0 +1,253 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +// Stub-implementation classes used for unit testing + +import { GEdge, GModelElement, GModelElementConstructor, GNode } from '@eclipse-glsp/graph'; +import { + Action, + ActionMessage, + Args, + EdgeTypeHint, + InitializeClientSessionParameters, + MaybeArray, + Point, + RequestEditValidationAction, + ShapeTypeHint, + ValidationStatus +} from '@eclipse-glsp/protocol'; +import { Container } from 'inversify'; +import { MessageConnection } from 'vscode-jsonrpc'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { ActionHandler, ActionHandlerFactory } from '../actions/action-handler'; +import { GModelState } from '../base-impl'; +import { DiagramConfiguration, ServerLayoutKind } from '../diagram/diagram-configuration'; +import { ContextEditValidator } from '../features/directediting/context-edit-validator'; +import { LabelEditValidator } from '../features/directediting/label-edit-validator'; +import { CreateEdgeOperationHandler, CreateNodeOperationHandler } from '../operations/create-operation-handler'; +import { JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; +import { GLSPServer } from '../protocol/glsp-server'; +import { GLSPServerListener } from '../protocol/glsp-server-listener'; +import { ClientSession } from '../session/client-session'; +import { ClientSessionFactory } from '../session/client-session-factory'; +import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { ClientSessionListener } from '../session/client-session-listener'; +import { ClientSessionManager } from '../session/client-session-manager'; +import { Logger, LogLevel } from '../utils/logger'; + +export async function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export function createClientSession( + id: string, + diagramType: string, + container = new Container(), + actionDispatcher = new StubActionDispatcher() +): ClientSession { + return { + id, + diagramType, + container, + actionDispatcher, + dispose: () => { + // + }, + isDisposed: false + }; +} + +export class StubActionHandler implements ActionHandler { + constructor(public actionKinds: string[]) {} + + execute(action: Action): Action[] { + return []; + } +} + +export class StubCreateNodeOperationHandler extends CreateNodeOperationHandler { + elementTypeIds: string[]; + + constructor(readonly label: string) { + super(); + this.elementTypeIds = [label]; + } + + createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode { + return new GNode(); + } +} + +export class StubCreateEdgeOperationHandler extends CreateEdgeOperationHandler { + elementTypeIds: string[]; + + constructor(readonly label: string) { + super(); + this.elementTypeIds = [label]; + } + + createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + return undefined; + } +} + +export class StubActionDispatcher implements ActionDispatcher { + dispatch(action: Action): Promise { + return Promise.resolve(); + } + + dispatchAll(...actions: MaybeArray[]): Promise { + return Promise.resolve(); + } +} + +export class StubClientSessionFactory implements ClientSessionFactory { + create(params: InitializeClientSessionParameters): ClientSession { + const { clientSessionId, diagramType } = params; + return createClientSession(clientSessionId, diagramType); + } +} + +export class StubClientSessionManager implements ClientSessionManager { + getOrCreateClientSession(params: InitializeClientSessionParameters): ClientSession { + const { clientSessionId, diagramType } = params; + return createClientSession(clientSessionId, diagramType); + } + + getSession(clientSessionId: string): ClientSession | undefined { + return undefined; + } + + getSessionsByType(diagramType: string): ClientSession[] { + return []; + } + + disposeClientSession(clientSessionId: string): boolean { + return true; + } + + addListener(listener: ClientSessionListener, ...clientSessionIds: string[]): boolean { + return true; + } + + removeListener(listener: ClientSessionListener): boolean { + return true; + } + + removeListeners(...clientSessionIds: string[]): void { + return undefined; + } +} + +export class StubLogger extends Logger { + logLevel = LogLevel.none; + caller = undefined; + + info(message: string, ...params: any[]): void { + // no-op + } + + warn(message: string, ...params: any[]): void { + // no-op + } + + error(message: string, ...params: any[]): void { + // no-op + } + + debug(message: string, ...params: any[]): void { + // no-op + } +} + +export class StubClientSessionListener implements ClientSessionListener { + sessionCreated(clientSession: ClientSession): void { + // no-op + } + + sessionDisposed(clientSession: ClientSession): void { + // no-op + } +} + +export class StubGLSPClientProxy implements JsonRpcGLSPClientProxy { + connect(connection: MessageConnection): void { + // no-op + } + + process(message: ActionMessage): void { + // no-op + } +} + +export class StubGLSPServerListener implements GLSPServerListener { + serverInitialized(server: GLSPServer): void { + // no-op + } + + serverShutDown(server: GLSPServer): void { + // no-op + } +} + +export class StubDiagramConfiguration implements DiagramConfiguration { + typeMapping = new Map(); + + shapeTypeHints: ShapeTypeHint[] = []; + + edgeTypeHints: EdgeTypeHint[] = []; + + layoutKind = ServerLayoutKind.NONE; + + needsClientLayout = true; + + animatedUpdate = true; +} + +export class TestLabelEditValidator extends LabelEditValidator { + validate(label: string, element: GModelElement): ValidationStatus { + if (label === 'error') { + return { severity: ValidationStatus.Severity.ERROR, message: 'error' }; + } + if (label === 'warning') { + return { severity: ValidationStatus.Severity.WARNING, message: 'warning' }; + } + return { severity: ValidationStatus.Severity.OK, message: 'ok' }; + } +} + +export class TestContextEditValidator implements ContextEditValidator { + get contextId(): string { + return 'test'; + } + + validate(action: RequestEditValidationAction): ValidationStatus { + if (action.text === 'error') { + return { severity: ValidationStatus.Severity.ERROR, message: 'error' }; + } + if (action.text === 'warning') { + return { severity: ValidationStatus.Severity.WARNING, message: 'warning' }; + } + return { severity: ValidationStatus.Severity.OK, message: 'ok' }; + } +} + +export const stubActionHandlerFactory: ActionHandlerFactory = constructor => new constructor(); + +export class StubClientSessionInitializer implements ClientSessionInitializer { + initialize(args?: Args): void { + // no-op + } +} diff --git a/packages/server-node/src/utils/args-util.ts b/packages/server-node/src/utils/args-util.ts new file mode 100644 index 0000000..0532111 --- /dev/null +++ b/packages/server-node/src/utils/args-util.ts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Args } from '@eclipse-glsp/protocol'; + +export namespace ArgsUtil { + export const KEY_EDGE_PADDING = 'edgePadding'; + export const KEY_RADIUS_TOP_LEFT = 'radiusTopLeft'; + export const KEY_RADIUS_TOP_RIGHT = 'radiusTopRight'; + export const KEY_RADIUS_BOTTOM_RIGHT = 'radiusBottomRight'; + export const KEY_RADIUS_BOTTOM_LEFT = 'radiusBottomLeft'; + + export function getEdgePadding(args: Args): number | undefined { + return getNumber(args, KEY_EDGE_PADDING); + } + + export function cornerRadius(radius: number): Args { + return { + [KEY_RADIUS_TOP_RIGHT]: radius, + [KEY_RADIUS_BOTTOM_LEFT]: radius, + [KEY_RADIUS_BOTTOM_RIGHT]: radius, + [KEY_RADIUS_TOP_RIGHT]: radius + }; + } + + export function get(args: Args = {}, key: string): string | undefined { + const value = args[key]; + return value && typeof value === 'string' ? value : undefined; + } + + export function getNumber(args: Args = {}, key: string): number | undefined { + const value = args[key]; + return value && typeof value === 'number' ? value : undefined; + } + + export function getBoolean(args: Args = {}, key: string): boolean { + const value = args[key]; + return value && typeof value === 'boolean' ? value : false; + } +} diff --git a/packages/server-node/src/utils/client-options-util.ts b/packages/server-node/src/utils/client-options-util.ts new file mode 100644 index 0000000..3b637a6 --- /dev/null +++ b/packages/server-node/src/utils/client-options-util.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export class ClientOptionsUtil { + private static FILE_PREFIX = 'file://'; + + public static adaptUri(uri: string): string { + return uri.replace(this.FILE_PREFIX, ''); + } +} diff --git a/packages/server-node/src/utils/di-utilts.ts b/packages/server-node/src/utils/di-utilts.ts new file mode 100644 index 0000000..bec6c27 --- /dev/null +++ b/packages/server-node/src/utils/di-utilts.ts @@ -0,0 +1,19 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export function isInjectable(constr: new (...args: unknown[]) => unknown): boolean { + return Reflect.getMetadata('inversify:paramtypes', constr) !== undefined; +} diff --git a/packages/server-node/src/utils/disposable.spec.ts b/packages/server-node/src/utils/disposable.spec.ts new file mode 100644 index 0000000..4d9eab3 --- /dev/null +++ b/packages/server-node/src/utils/disposable.spec.ts @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Disposable } from './disposable'; +import { expect } from 'chai'; + +describe('test Disposable', () => { + let doDisposeCalled: boolean; + const disposable = new (class extends Disposable { + protected doDispose(): void { + doDisposeCalled = true; + } + })(); + + beforeEach(() => (doDisposeCalled = false)); + + it('dispose - should invoke doDispose', () => { + expect(disposable.isDisposed).false; + disposable.dispose(); + expect(doDisposeCalled).true; + expect(disposable.isDisposed).true; + }); + + it('dispose- already disposed, should not invoke doDispose', () => { + expect(disposable.isDisposed).true; + disposable.dispose(); + expect(doDisposeCalled).false; + }); +}); diff --git a/packages/server-node/src/utils/disposable.ts b/packages/server-node/src/utils/disposable.ts new file mode 100644 index 0000000..31cee7e --- /dev/null +++ b/packages/server-node/src/utils/disposable.ts @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; + +/** + * Interface for objects that can or need to be disposed properly. + */ +export interface IDisposable { + /** + * Disposes the current object by releasing any underlying resources or cleaning up any registrations. + */ + dispose(): void; + + /** + * Returns true if this object is considered disposed. + */ + readonly isDisposed: boolean; +} + +export function isIDisposable(object: any): object is IDisposable { + return ( + object !== undefined && + typeof object === 'object' && + 'dispose' in object && + typeof object['dispose'] === 'function' && + 'isDisposed' in object && + typeof object['isDisposed'] === 'boolean' + ); +} + +@injectable() +export class Disposable implements IDisposable { + private disposed = false; + + dispose(): void { + if (!this.disposed) { + this.doDispose(); + this.disposed = true; + } + } + + protected doDispose(): void { + // do nothing. + } + + get isDisposed(): boolean { + return this.disposed; + } + + static create(runnable: () => void): IDisposable { + const disposable = new Disposable(); + disposable['doDispose'] = runnable; + return disposable; + } +} diff --git a/packages/server-node/src/utils/geometry-util.ts b/packages/server-node/src/utils/geometry-util.ts new file mode 100644 index 0000000..75810ca --- /dev/null +++ b/packages/server-node/src/utils/geometry-util.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GBoundsAware, GModelElement, GModelRoot, isGBoundsAware } from '@eclipse-glsp/graph'; +import { ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; + +/** + * Convert a point in absolute coordinates to a point relative to the specified GBoundsAware. + * Note: this method only works if the specified {@link GBoundsAware} is part of a + * hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement + * {@link GBoundsAware}, this method will throw an exception. + * + * @param absolutePoint + * @param modelElement + * @returns + * A new point, relative to the coordinates space of the specified {@link GBoundsAware} + * @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy + */ +export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point { + let parentElement; + if (!(modelElement instanceof GModelRoot)) { + parentElement = modelElement.parent; + } + + let relativeToParent: Point; + if (!parentElement) { + relativeToParent = { x: absolutePoint.x, y: absolutePoint.y }; + } else { + if (!isGBoundsAware(parentElement)) { + throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`); + } + relativeToParent = absoluteToRelative(absolutePoint, parentElement); + } + + const x = modelElement.position?.x ?? 0; + const y = modelElement.position?.y ?? 0; + + return { x: relativeToParent.x - x, y: relativeToParent.y - y }; +} + +export function shift(elements: GModelElement[], offset: Point): void { + elements + .filter(element => isGBoundsAware(element)) + .map(element => element as unknown as GBoundsAware) + .forEach(gBoundsAware => { + const position = gBoundsAware.position ?? ORIGIN_POINT; + gBoundsAware.position = { x: position.x + offset.x, y: position.y + offset.y }; + }); +} diff --git a/packages/server-node/src/utils/glsp-server-error.ts b/packages/server-node/src/utils/glsp-server-error.ts new file mode 100644 index 0000000..2ee8194 --- /dev/null +++ b/packages/server-node/src/utils/glsp-server-error.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export class GLSPServerError extends Error { + constructor(message: string, readonly cause?: Record) { + super(message); + } +} + +export function getOrThrow(optional: undefined | T, errorMsg: string): T { + if (optional !== undefined) { + return optional; + } + throw new GLSPServerError(errorMsg); +} diff --git a/packages/server-node/src/utils/logger.ts b/packages/server-node/src/utils/logger.ts new file mode 100644 index 0000000..44d3aff --- /dev/null +++ b/packages/server-node/src/utils/logger.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; + +@injectable() +export abstract class Logger { + abstract logLevel: LogLevel; + + abstract caller?: string; + + abstract info(message: string, ...params: any[]): void; + + abstract warn(message: string, ...params: any[]): void; + + abstract error(message: string, ...params: any[]): void; + + abstract debug(message: string, ...params: any[]): void; +} + +export const LoggerFactory = Symbol('LoggerFactory'); + +export type LoggerFactory = (caller: string) => Logger; + +export enum LogLevel { + none = 0, + error = 1, + warn = 2, + info = 3, + debug = 4 +} + +export function asLogLevel(level: string | number): LogLevel | undefined { + let levelKey = level; + if (typeof levelKey === 'string') { + levelKey = levelKey.toLowerCase(); + } + + if (levelKey in LogLevel) { + return (LogLevel as any)[level]; + } + return undefined; +} diff --git a/packages/server-node/src/utils/maybe-promise.ts b/packages/server-node/src/utils/maybe-promise.ts new file mode 100644 index 0000000..fc3a20b --- /dev/null +++ b/packages/server-node/src/utils/maybe-promise.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MaybePromise } from '@eclipse-glsp/protocol'; + +export function resolveAndCatch(maybe: () => MaybePromise, errorHandler: (reason: any) => void): void { + resolve(maybe, errorHandler).catch(errorHandler); +} + +async function resolve(maybe: () => MaybePromise, errorHandler: (reason: any) => void): Promise { + try { + await maybe(); + } catch (error) { + errorHandler(error); + } +} diff --git a/packages/server-node/src/utils/promise-queue.spec.ts b/packages/server-node/src/utils/promise-queue.spec.ts new file mode 100644 index 0000000..5450e09 --- /dev/null +++ b/packages/server-node/src/utils/promise-queue.spec.ts @@ -0,0 +1,138 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { delay } from '../test/mock-util'; +import { PromiseQueue } from './promise-queue'; +import { expect } from 'chai'; + +// Helper types and functions that are needed for test setup + +/** + * Helper class to inspect the state of promise during its resolve() execution. + */ +class PromiseState { + private _started = false; + private _stopped = false; + onStartRunnable?: () => void; + onStopRunnable?: () => void; + + start(): void { + this._started = true; + if (this.onStartRunnable) { + this.onStartRunnable(); + } + } + + stop(): void { + this._stopped = true; + if (this.onStopRunnable) { + this.onStopRunnable(); + } + } + + onStart(runnable: () => void): void { + this.onStartRunnable = runnable; + } + + onStop(runnable: () => void): void { + this.onStopRunnable = runnable; + } + + get started(): boolean { + return this._started; + } + + get stopped(): boolean { + return this._stopped; + } +} + +interface TestPromise { + state: PromiseState; + promise: () => Promise; +} + +function newTestPromise(resolveTime: number): TestPromise { + const state = new PromiseState(); + const promise = async (): Promise => { + state.start(); + await delay(resolveTime); + state.stop(); + }; + return { state, promise }; +} + +let queue = new PromiseQueue(); + +// Test execution +describe('test PromiseQueue', () => { + beforeEach(() => { + queue = new PromiseQueue(); + }); + it('enqueue - one element', async () => { + const { state, promise } = newTestPromise(100); + state.onStart(() => { + expect(queue.isBusy).true; + }); + const queEnd = queue.enqueue(promise); + expect(queue.isEmpty).true; + await queEnd; + }); + + it('enqueue - two elements', async () => { + const p1 = newTestPromise(100); + p1.state.onStop(() => expect(p2.state.started).false); + + const p2 = newTestPromise(100); + + p2.state.onStart(() => { + expect(queue.isEmpty).true; + expect(p1.state.stopped).true; + }); + + queue.enqueue(p1.promise); + const queEnd = queue.enqueue(p2.promise); + expect(queue.size).to.be.equal(1); + await queEnd; + }); + + it('enqueue - three elements (first promise in queue has longest resolve time)', async () => { + const p1 = newTestPromise(300); + p1.state.onStop(() => { + expect(p2.state.started).false; + expect(p3.state.started).false; + }); + + const p2 = newTestPromise(200); + p2.state.onStart(() => { + expect(queue.size).to.be.equal(1); + expect(p1.state.stopped).true; + expect(p3.state.started).false; + }); + + const p3 = newTestPromise(100); + p3.state.onStart(() => { + expect(queue.isEmpty).true; + expect(p2.state.stopped).true; + }); + + queue.enqueue(p1.promise); + queue.enqueue(p2.promise); + expect(queue.size).to.be.equal(1); + const queueEnd = queue.enqueue(p3.promise); + expect(queue.size).to.be.equal(2); + await queueEnd; + }); +}); diff --git a/packages/server-node/src/utils/promise-queue.ts b/packages/server-node/src/utils/promise-queue.ts new file mode 100644 index 0000000..6f8fd11 --- /dev/null +++ b/packages/server-node/src/utils/promise-queue.ts @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/** + * Description for promises that are put into a queue and + * resolved once dequeued. + */ +export interface PromiseQueueElement { + promise: () => Promise; + resolve: (value: T) => void; + reject: (reason?: any) => void; +} + +/** + * A data structure that enables controlled,sequential resolving + * of promises. Promises that are put in this queue are processed one by one. + * i.e. After the first promise in the queue is resolved, it will be removed from the queue and the resolving of the + * the next promise (if present) will start. The queue can only resolve one promise at a given time. + */ +export class PromiseQueue { + protected queue: PromiseQueueElement[] = []; + protected busy = false; + + /** + * Add a {@link PromiseQueueElement} to the queue + * @param promise The element that should be queued + * @param atStart if `true` the element is added to the start of queue, otherwise it's added at queue end. + * @returns A promise of the queued promise that resolves once dequeued. + */ + enqueue(promise: () => Promise): Promise { + return new Promise((resolve, reject) => { + const element = { + promise, + resolve, + reject + }; + this.queue.push(element); + this.dequeue(); + }); + } + + protected dequeue(): boolean { + if (this.busy) { + return false; + } + + const element = this.queue.shift(); + if (!element) { + return false; + } + + try { + this.busy = true; + this.resolveQueuedPromise(element); + } catch (error) { + element.reject(error); + this.busy = false; + this.dequeue(); + } + return true; + } + + protected async resolveQueuedPromise(element: PromiseQueueElement): Promise { + try { + const value = await element.promise(); + return element.resolve(value); + } catch (error) { + element.reject(error); + } finally { + this.busy = false; + this.dequeue(); + } + } + + get isBusy(): boolean { + return this.busy; + } + + get size(): number { + return this.queue.length; + } + + get isEmpty(): boolean { + return this.queue.length === 0; + } + + clear(): void { + this.queue = []; + } +} diff --git a/packages/server-node/src/utils/registry.spec.ts b/packages/server-node/src/utils/registry.spec.ts new file mode 100644 index 0000000..5fb4672 --- /dev/null +++ b/packages/server-node/src/utils/registry.spec.ts @@ -0,0 +1,223 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MultiRegistry, Registry } from './registry'; +import { expect } from 'chai'; + +describe('Test Registry', () => { + let registry: Registry; + + beforeEach(() => { + registry = new Registry(); + }); + + it('register - with new key-value pair', () => { + const key = 'key'; + const value = 'value'; + expect(registry.register(key, value)).true; + expect(registry.get(key), value); + }); + + it('register - with key-value pair with a already registered key', () => { + // Setup + const key = 'key'; + const value = 'value'; + expect(registry.register(key, value)).true; + // Test execution + expect(registry.register(key, 'newValue')).false; + expect(registry.get(key), value); + }); + + it('deregister - with registered key', () => { + // Setup + const key = 'key'; + const value = 'value'; + expect(registry.register(key, value)).true; + // Test execution + expect(registry.deregister(key)).true; + expect(registry.get(key), undefined); + }); + + it('deregister - with unregistered key', () => { + expect(registry.deregister('unregisteredKey')).false; + }); + + it('haskey - with registered key', () => { + // Setup + const key = 'key'; + const value = 'value'; + expect(registry.register(key, value)).true; + // Test execution + expect(registry.hasKey(key)).true; + }); + + it('haskey - with unregistered key', () => { + expect(registry.deregister('unregisteredKey')).false; + }); + + it('get - with registered key', () => { + // Setup + const key = 'key'; + const value = 'value'; + expect(registry.register(key, value)).true; + // Test execution + expect(registry.get(key), value); + }); + + it('get - with unregistered key', () => { + expect(registry.get('unregisteredKey'), undefined); + }); + + it('getAll - should return three values', () => { + // Setup + const e1 = { key: 'key1', value: 'value1' }; + const e2 = { key: 'key2', value: 'value2' }; + const e3 = { key: 'key3', value: 'value3' }; + registry.register(e1.key, e1.value); + registry.register(e2.key, e2.value); + registry.register(e3.key, e3.value); + // Test execution + const result = registry.getAll(); + expect(result).to.have.length(3); + expect(result.includes(e1.value)).true; + expect(result.includes(e2.value)).true; + expect(result.includes(e3.value)).true; + }); + + it('keys - should return three keys', () => { + // Setup + const e1 = { key: 'key1', value: 'value1' }; + const e2 = { key: 'key2', value: 'value2' }; + const e3 = { key: 'key3', value: 'value3' }; + registry.register(e1.key, e1.value); + registry.register(e2.key, e2.value); + registry.register(e3.key, e3.value); + // Test execution + const result = registry.keys(); + expect(result).to.have.length(3); + expect(result.includes(e1.key)).true; + expect(result.includes(e2.key)).true; + expect(result.includes(e3.key)).true; + }); +}); + +describe('Test MapMultiRegistry', () => { + let multiRegistry: MultiRegistry; + + beforeEach(() => { + multiRegistry = new MultiRegistry(); + }); + + it('register - with new key-value pair', () => { + // Setup + const e1 = { key: 'key', value: 'value' }; + const e2 = { key: e1.key, value: 'value2' }; + + multiRegistry.register(e1.key, e1.value); + multiRegistry.register(e2.key, e2.value); + + const resultValue = multiRegistry.get(e1.key); + expect(resultValue).to.not.be.undefined; + expect(resultValue!.length).to.be.equal(2); + expect(resultValue!.includes(e1.value)).true; + expect(resultValue!.includes(e2.value)).true; + }); + + it('deregister - with registered key', () => { + // Setup + const key = 'key'; + const existingValue = ['value', 'value2']; + multiRegistry.register(key, existingValue[0]); + multiRegistry.register(key, existingValue[1]); + // Test execution + expect(multiRegistry.deregister(key, existingValue[1])).true; + const result = multiRegistry.get(key); + expect(result.length).to.be.equal(1); + expect(result[0], existingValue[0]); + }); + + it('deregister - with unregistered key', () => { + expect(multiRegistry.deregister('unregisteredKey', 'someValue')).false; + }); + + it('deregisterAll - with registered key', () => { + // Setup + const key = 'key'; + const existingValue = ['value', 'value2']; + multiRegistry.register(key, existingValue[0]); + multiRegistry.register(key, existingValue[1]); + expect(multiRegistry.deregisterAll(key)).true; + expect(multiRegistry.get(key)).to.have.length(0); + }); + + it('deregisterAll - with unregistered key', () => { + expect(multiRegistry.deregisterAll('unregisteredKey')).false; + }); + + it('haskey - with registered key', () => { + // Setup + const key = 'key'; + const existingValue = 'value'; + multiRegistry.register(key, existingValue); + // Test execution + expect(multiRegistry.hasKey(key)).true; + }); + + it('haskey - with unregistered key', () => { + expect(multiRegistry.hasKey('unregisteredKey')).false; + }); + + it('get - with registered key', () => { + // Setup + const key = 'key'; + const existingValue = 'value'; + multiRegistry.register(key, existingValue); + // Test execution + expect(multiRegistry.get(key).length).to.be.equal(1); + expect(multiRegistry.get(key).includes(existingValue)).true; + }); + + it('get - with unregistered key', () => { + expect(multiRegistry.get('unregisteredKey')).to.have.length(0); + }); + + it('getAll', () => { + // Setup + const e1 = { key: 'key1', value: ['value1', 'value2'] }; + const e2 = { key: 'key2', value: ['value3'] }; + multiRegistry.register(e1.key, e1.value[0]); + multiRegistry.register(e1.key, e1.value[1]); + multiRegistry.register(e2.key, e2.value[0]); + // Test execution + const result = multiRegistry.getAll(); + expect(result).to.have.length(3); + expect(e1.value.every(v => result.includes(v))).true; + expect(e2.value.every(v => result.includes(v))).true; + }); + + it('keys', () => { + // Setup + const e1 = { key: 'key1', value: ['value1', 'value2'] }; + const e2 = { key: 'key2', value: ['value3'] }; + multiRegistry.register(e1.key, e1.value[0]); + multiRegistry.register(e1.key, e1.value[1]); + multiRegistry.register(e2.key, e2.value[0]); + // Test execution + const result = multiRegistry.keys(); + expect(result.length).to.be.equal(2); + expect(result.includes(e1.key)).true; + expect(result.includes(e2.key)).true; + }); +}); diff --git a/packages/server-node/src/utils/registry.ts b/packages/server-node/src/utils/registry.ts new file mode 100644 index 0000000..2b9a7d0 --- /dev/null +++ b/packages/server-node/src/utils/registry.ts @@ -0,0 +1,153 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { remove } from '@eclipse-glsp/protocol/lib/utils/array-util'; +import { injectable } from 'inversify'; + +/** + * A registry manages a set of key-value pairs and provides query functionality. In GLSP this is typically used to + * provide a convenience API for working with multi-injected instances e.g. action handlers or + * operation handlers. + * + * @typeParam K Key type + * @param V Value type + */ + +@injectable() +export class Registry { + protected elements: Map = new Map(); + /** + * Registers a new key-value pair. + * + * @param key The key object. + * @param element The value object. + * @returns `true` if the pair was registered successfully, `false` if another pair with the same key is already + * registered. + */ + register(key: K, instance: V): boolean { + if (!this.hasKey(key)) { + this.elements.set(key, instance); + return true; + } + return false; + } + + /** + * Removes the value with the given key from the registry. + * + * @param key The key of the value which should be removed. + * @returns `true` if the value was removed successfully, `false` if no value was registered for the given key. + */ + deregister(key: K): boolean { + return this.elements.delete(key); + } + + /** + * Queries the registry to check whether a value for the given key is registered. + * + * @param key The key which should be checked. + * @returns `true` if a key-value pair is registered for the given key, `false` otherwise. + */ + hasKey(key: K): boolean { + return this.elements.has(key); + } + + /** + * Retrieve the value for the given key. + * + * @param key The key whose value should be retrieved. + * @returns The registered value or `undefined`. Is `undefined` if no value was registered for the given key. + */ + get(key: K): V | undefined { + return this.elements.get(key); + } + + /** + * Retrieve all registered values from the registry. + * + * @returns An array of all registered keys. + */ + + getAll(): V[] { + return [...new Set(this.elements.values())]; + } + + /** + * Retrieve all registered keys from the registry. + * + * @returns An array of all registered keys. + */ + keys(): K[] { + return [...new Set(this.elements.keys())]; + } +} + +/** + * A multi registry is used to manage a set of key-value pairs. The main difference to {@link Registry} is that + * a multi registry doesn't enforce a 1-1 relation between key and value(s). + * One key can be associated with multiple values. + * + * + * @typeParam K Type of the key + * @param V Type of the values + */ +@injectable() +export class MultiRegistry { + protected elements: Map = new Map(); + + register(key: K, instance: V): void { + const instances = this.elements.get(key); + if (instances) { + instances.push(instance); + } else { + this.elements.set(key, [instance]); + } + } + + deregister(key: K, element: V): boolean { + const instances = this.elements.get(key); + if (instances) { + return remove(instances, element); + } + return false; + } + + deregisterAll(key: K): boolean { + return this.elements.delete(key); + } + + get(key: K): V[] { + const existingInstances = this.elements.get(key); + if (existingInstances) { + return [...new Set(existingInstances)]; + } else { + return []; + } + } + + hasKey(key: K): boolean { + return this.elements.has(key); + } + + getAll(): V[] { + const values: V[] = []; + Array.from(this.elements.values()).forEach(instances => values.push(...instances)); + return [...new Set(values)]; + } + + keys(): K[] { + return [...new Set(this.elements.keys())]; + } +} diff --git a/packages/server-node/src/utils/winston-logger.ts b/packages/server-node/src/utils/winston-logger.ts new file mode 100644 index 0000000..fac2085 --- /dev/null +++ b/packages/server-node/src/utils/winston-logger.ts @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as winston from 'winston'; +import { asLogLevel, Logger, LogLevel } from './logger'; + +export class WinstonLogger extends Logger { + constructor(protected baseLogger: winston.Logger, public caller?: string) { + super(); + } + + info(message: string, ...params: any): void { + if (this.logLevel !== LogLevel.none) { + this.baseLogger.info(this.combinedMessage(message, ...params)); + } + } + + warn(message: string, ...params: any[]): void { + if (this.logLevel !== LogLevel.none) { + this.baseLogger.warn(this.combinedMessage(message, ...params)); + } + } + + error(message: string, ...params: any[]): void { + if (this.logLevel !== LogLevel.none) { + this.baseLogger.error(this.combinedMessage(message, ...params)); + } + } + + debug(message: string, ...params: any[]): void { + if (this.logLevel !== LogLevel.none) { + this.baseLogger.debug(this.combinedMessage(message, ...params)); + } + } + + get logLevel(): LogLevel { + return asLogLevel(this.baseLogger.level) || LogLevel.none; + } + + set logLevel(level: LogLevel) { + this.baseLogger.level = LogLevel[level]; + } + + combinedMessage(message: string, ...params: any[]): string { + const caller = this.caller ? `[${this.caller}]` : ''; + const additional = this.logAdditionals(...params); + return `${caller} ${message} ${additional}`; + } + + logAdditionals(...params: any[]): string { + if (!params || params.length === 0) { + return ''; + } + return params.map(param => this.toString(param)).join(',\n'); + } + + toString(param: unknown): string { + if (param instanceof Error) { + return `${param.message} + ${param.stack || ''}`; + } + return JSON.stringify(param, undefined, 4); + } +} diff --git a/packages/server-node/tsconfig.json b/packages/server-node/tsconfig.json new file mode 100644 index 0000000..920ba34 --- /dev/null +++ b/packages/server-node/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": "src", + "types": ["node", "reflect-metadata"] + }, + "include": ["src"], + "exclude": ["**/*.spec.ts"] +} From 2cc11c12413cea27114ca8cf342c64a8ef2f3566 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 18 Jan 2022 00:11:54 +0100 Subject: [PATCH 076/566] Update Metadata & Setup Jenkinsbuild --- examples/workflow-server/package.json | 2 +- examples/workflow-server/tsconfig.json | 2 +- packages/graph/package.json | 10 +--------- packages/graph/tsconfig.json | 2 +- packages/server-node/package.json | 10 ++-------- packages/server-node/tsconfig.json | 2 +- 6 files changed, 7 insertions(+), 21 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 2a518cb..98e6bbf 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -49,7 +49,7 @@ "clean": "rimraf lib", "build": "yarn run clean && tsc", "test": "", - "lint": "eslint -c ../.eslintrc.js --ext .ts,.tsx ./src", + "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", "start:server": "yarn ts-node src/index.ts" diff --git a/examples/workflow-server/tsconfig.json b/examples/workflow-server/tsconfig.json index 53504ac..17bb265 100644 --- a/examples/workflow-server/tsconfig.json +++ b/examples/workflow-server/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "lib", "reactNamespace": "JSX", "baseUrl": ".", - "types": ["node", "reflect-metadata"] + "types": ["node", "mocha", "reflect-metadata"] }, "include": ["src"], "exclude": ["**/*.spec.ts"] diff --git a/packages/graph/package.json b/packages/graph/package.json index 96ed358..faf20ac 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -43,21 +43,13 @@ "devDependencies": { "@types/ws": "^8.2.0", "typescript": "^3.9.2", - "@types/chai": "^4.2.22", - "@types/mocha": "^9.0.0", - "@types/sinon": "^10.0.6", - "@types/moxios": "^0.4.13", - "moxios": "^0.4.0", - "mocha": "^9.1.3", - "chai": "^4.3.4", - "sinon": "^12.0.1", - "ts-node": "^10.4.0", "@types/uuid": "8.3.1" }, "scripts": { "clean": "rimraf lib", "build": "yarn run clean && tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w" diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json index 920ba34..c28991c 100644 --- a/packages/graph/tsconfig.json +++ b/packages/graph/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "lib", "reactNamespace": "JSX", "baseUrl": "src", - "types": ["node", "reflect-metadata"] + "types": ["node", "mocha", "reflect-metadata"] }, "include": ["src"], "exclude": ["**/*.spec.ts"] diff --git a/packages/server-node/package.json b/packages/server-node/package.json index 0da3938..69fd10d 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -48,19 +48,13 @@ "@types/fs-extra": "^9.0.13", "@types/node": "12.x", "reflect-metadata": "^0.1.13", - "typescript": "^3.9.2", - "@types/chai": "^4.2.22", - "@types/mocha": "^9.0.0", - "@types/sinon": "^10.0.6", - "mocha": "^9.1.3", - "chai": "^4.3.4", - "sinon": "^12.0.1", - "ts-node": "^10.4.0" + "typescript": "^3.9.2" }, "scripts": { "clean": "rimraf lib", "build": "yarn run clean && tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w" diff --git a/packages/server-node/tsconfig.json b/packages/server-node/tsconfig.json index 920ba34..c28991c 100644 --- a/packages/server-node/tsconfig.json +++ b/packages/server-node/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "lib", "reactNamespace": "JSX", "baseUrl": "src", - "types": ["node", "reflect-metadata"] + "types": ["node", "mocha", "reflect-metadata"] }, "include": ["src"], "exclude": ["**/*.spec.ts"] From bff4f6597fb39d3ce30b4424d55c81cd093a4a9b Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 18 Jan 2022 17:01:15 +0100 Subject: [PATCH 077/566] #507 Implement `dispatchOnNextUpdate` for ActionDispatcher (#1) - Implement `dispatchOnNextUpdate` for ActionDispatcher - Use the new API in the `CreateOperationHandler` to automatically select newly created nodes. Also: - Consume new @eclipse-glsp/mocha-config package and remove redundant devdependencies - Update debug config for workflow example - Add missing CHANGELOG.md file Contributed on behalf of STMicroelectronics Closes eclipse-glsp/glsp/issues/507 --- examples/workflow-server/package.json | 3 -- packages/server-node/package.json | 1 - .../src/actions/action-dispatcher.spec.ts | 36 ++++++++++++++++++- .../src/actions/action-dispatcher.ts | 26 ++++++++++++-- .../operations/create-operation-handler.ts | 3 +- packages/server-node/src/test/mock-util.ts | 4 +++ 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 98e6bbf..f316ba6 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -40,9 +40,6 @@ "@eclipse-glsp/server-node": "0.9.0" }, "devDependencies": { - "@types/node": "12.x", - "reflect-metadata": "^0.1.13", - "ts-node": "^10.4.0", "typescript": "^3.9.2" }, "scripts": { diff --git a/packages/server-node/package.json b/packages/server-node/package.json index 69fd10d..d4f3485 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -46,7 +46,6 @@ }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/node": "12.x", "reflect-metadata": "^0.1.13", "typescript": "^3.9.2" }, diff --git a/packages/server-node/src/actions/action-dispatcher.spec.ts b/packages/server-node/src/actions/action-dispatcher.spec.ts index 8168fc5..dff7ece 100644 --- a/packages/server-node/src/actions/action-dispatcher.spec.ts +++ b/packages/server-node/src/actions/action-dispatcher.spec.ts @@ -23,7 +23,7 @@ import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; import * as sinon from 'sinon'; import { expect } from 'chai'; -import { Action } from '@eclipse-glsp/protocol'; +import { Action, UpdateModelAction } from '@eclipse-glsp/protocol'; function waitSync(timeInMillis: number): void { const start = Date.now(); @@ -252,4 +252,38 @@ describe('test DefaultActionDispatcher', () => { sinon.assert.callOrder(spy_responseHandler1_execute, spy_responseHandler2_execute); }); }); + + describe('test dispatch after next update', () => { + it('dispatchAfterNextUpdate', async () => { + // Mock setup + const updateModelAction = new UpdateModelAction({ id: 'newRoot', type: 'myType' }); + const intermediateAction = 'intermediate'; + const postUpdateAction = 'postUpdate'; + const handler = new mock.StubActionHandler([updateModelAction.kind, intermediateAction]); + const postUpdateHandler = new mock.StubActionHandler([postUpdateAction]); + + const getHandler = (kind: string): ActionHandler[] => { + if (kind === updateModelAction.kind || kind === intermediateAction) { + return [handler]; + } else if (kind === postUpdateAction) { + return [postUpdateHandler]; + } + + return []; + }; + registry_get_stub.callsFake(getHandler); + const spy_postUpdateHandler_execute = sinon.spy(postUpdateHandler, 'execute'); + + // Test execution + actionDispatcher.dispatchAfterNextUpdate({ kind: postUpdateAction }); + expect(spy_postUpdateHandler_execute.called).to.be.false; + await actionDispatcher.dispatch({ kind: intermediateAction }); + expect(spy_postUpdateHandler_execute.called).to.be.false; + await actionDispatcher.dispatch(updateModelAction); + expect(spy_postUpdateHandler_execute.calledOnce); + // Check that action does not get dispatched again + await actionDispatcher.dispatch(updateModelAction); + expect(spy_postUpdateHandler_execute.calledOnce); + }); + }); }); diff --git a/packages/server-node/src/actions/action-dispatcher.ts b/packages/server-node/src/actions/action-dispatcher.ts index e4e44c6..5a9764d 100644 --- a/packages/server-node/src/actions/action-dispatcher.ts +++ b/packages/server-node/src/actions/action-dispatcher.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, flatPush, isAction, isRequestAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol'; +import { Action, flatPush, isAction, isRequestAction, isUpdateModelAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { ClientActionKinds, ClientId } from '../di/service-identifiers'; @@ -46,6 +46,16 @@ export interface ActionDispatcher { */ dispatchAll(actions: Action[]): Promise; dispatchAll(...actions: Action[]): Promise; + + /** + * Processes all given actions, by dispatching them to the corresponding handlers, after the next model update. + * The given actions are queued until the next model update cycle has been completed i.e. an + * `UpdateModelAction` has been dispatched and processed by this action dispatcher. + * + * @param actions The actions that should be dispatched after the next model update + */ + dispatchAfterNextUpdate(actions: Action[]): void; + dispatchAfterNextUpdate(...actions: Action[]): void; } @injectable() @@ -63,6 +73,7 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc protected clientId: string; protected actionQueue = new PromiseQueue(); + protected postUpdateQueue: Action[] = []; dispatch(action: Action): Promise { // Dont queue actions that are just delegated to the client @@ -85,7 +96,12 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc responses.push(...response); } - return this.dispatchResponses(responses); + if (isUpdateModelAction(action) && this.postUpdateQueue.length > 0) { + responses.push(...this.postUpdateQueue); + this.postUpdateQueue = []; + } + + await this.dispatchResponses(responses); } protected async executeHandler(handler: ActionHandler, request: Action): Promise { @@ -111,6 +127,12 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc return Promise.all(flat.map(action => this.dispatch(action))).then(() => Promise.resolve()); } + dispatchAfterNextUpdate(...actions: MaybeArray[]): void { + if (actions.length !== 0) { + flatPush(this.postUpdateQueue, actions); + } + } + doDispose(): void { this.actionQueue.clear(); } diff --git a/packages/server-node/src/operations/create-operation-handler.ts b/packages/server-node/src/operations/create-operation-handler.ts index d23b195..6463f65 100644 --- a/packages/server-node/src/operations/create-operation-handler.ts +++ b/packages/server-node/src/operations/create-operation-handler.ts @@ -21,6 +21,7 @@ import { CreateOperation, Operation, Point, + SelectAction, TriggerEdgeCreationAction, TriggerElementCreationAction, TriggerNodeCreationAction @@ -84,8 +85,8 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler if (element) { container.children.push(element); element.parent = container; + this.actionDispatcher.dispatchAfterNextUpdate(new SelectAction(), new SelectAction([element.id])); } - // TODO: Dispatch Select Action after next update } getContainer(operation: CreateNodeOperation): GModelElement | undefined { diff --git a/packages/server-node/src/test/mock-util.ts b/packages/server-node/src/test/mock-util.ts index 6950bd1..65599dc 100644 --- a/packages/server-node/src/test/mock-util.ts +++ b/packages/server-node/src/test/mock-util.ts @@ -104,6 +104,10 @@ export class StubCreateEdgeOperationHandler extends CreateEdgeOperationHandler { } export class StubActionDispatcher implements ActionDispatcher { + dispatchAfterNextUpdate(...actions: MaybeArray): void { + // NO-OP + } + dispatch(action: Action): Promise { return Promise.resolve(); } From 0081097eae88fc9e292c1d01474177de251767ff Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 26 Jan 2022 13:59:03 -0800 Subject: [PATCH 078/566] #509 Implement ELK autolayout support (#2) * #509 Implement ELK autolayout support - Provide a new `@eclipse-glsp/layout-elk` package that provides the glue code to integrate an ELK Layout Engine into GLSP node servers. - Add example layout engine using elk-layered algorithm to workflow example. Also: - Implement support for async operation handlers by using `MaybePromise`s - Add missing default exports to index.ts (server-node) Contributed on behalf of STMicroelectronics --- examples/workflow-server/package.json | 3 +- examples/workflow-server/src/app.ts | 5 +- .../layout/workflow-layout-configurator.ts | 28 + .../src/workflow-diagram-module.ts | 4 +- packages/layout-elk/LICENSE | 642 ++++++++++++++++++ packages/layout-elk/README.md | 9 + packages/layout-elk/package.json | 60 ++ packages/layout-elk/src/basic-type-mapper.ts | 73 ++ packages/layout-elk/src/di.config.spec.ts | 92 +++ packages/layout-elk/src/di.config.ts | 117 ++++ .../layout-elk/src/glsp-element-filter.ts | 38 ++ .../layout-elk/src/glsp-elk-layout-engine.ts | 57 ++ .../src/glsp-layout-configurator.ts | 59 ++ packages/layout-elk/src/index.ts | 22 + packages/layout-elk/tsconfig.json | 11 + .../src/base-impl/gmodel-diagram-module.ts | 14 +- .../{ => features}/layout/layout-engine.ts | 4 +- .../layout/layout-operation-handler.ts | 47 ++ .../model/model-submission-handler.ts | 2 +- packages/server-node/src/index.ts | 17 +- .../operations/operation-action-handler.ts | 8 +- .../src/operations/operation-handler.ts | 4 +- 22 files changed, 1293 insertions(+), 23 deletions(-) create mode 100644 examples/workflow-server/src/layout/workflow-layout-configurator.ts create mode 100644 packages/layout-elk/LICENSE create mode 100644 packages/layout-elk/README.md create mode 100644 packages/layout-elk/package.json create mode 100644 packages/layout-elk/src/basic-type-mapper.ts create mode 100644 packages/layout-elk/src/di.config.spec.ts create mode 100644 packages/layout-elk/src/di.config.ts create mode 100644 packages/layout-elk/src/glsp-element-filter.ts create mode 100644 packages/layout-elk/src/glsp-elk-layout-engine.ts create mode 100644 packages/layout-elk/src/glsp-layout-configurator.ts create mode 100644 packages/layout-elk/src/index.ts create mode 100644 packages/layout-elk/tsconfig.json rename packages/server-node/src/{ => features}/layout/layout-engine.ts (92%) create mode 100644 packages/server-node/src/features/layout/layout-operation-handler.ts diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index f316ba6..6b64cd5 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -37,7 +37,8 @@ "src" ], "dependencies": { - "@eclipse-glsp/server-node": "0.9.0" + "@eclipse-glsp/server-node": "0.9.0", + "@eclipse-glsp/layout-elk": "0.9.0" }, "devDependencies": { "typescript": "^3.9.2" diff --git a/examples/workflow-server/src/app.ts b/examples/workflow-server/src/app.ts index 6de9ebe..76cfb46 100644 --- a/examples/workflow-server/src/app.ts +++ b/examples/workflow-server/src/app.ts @@ -13,8 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk'; import { createAppModule, createSocketCliParser, LoggerFactory, resolveAndCatch, SocketServerLauncher } from '@eclipse-glsp/server-node'; import { Container } from 'inversify'; +import { WorkflowLayoutConfigurator } from './layout/workflow-layout-configurator'; import { WorkflowDiagramModule, WorkflowServerModule } from './workflow-diagram-module'; export function launch(argv?: string[]): void { @@ -24,7 +26,8 @@ export function launch(argv?: string[]): void { const logger = appContainer.get(LoggerFactory)('WorkflowServerApp'); const launcher = appContainer.resolve(SocketServerLauncher); - const serverModule = new WorkflowServerModule().configureDiagramModule(new WorkflowDiagramModule()); + const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator }); + const serverModule = new WorkflowServerModule().configureDiagramModule(new WorkflowDiagramModule(), elkLayoutModule); const errorHandler = (error: any): void => logger.error('Error in workflow server launcher:', error); launcher.configure(serverModule); diff --git a/examples/workflow-server/src/layout/workflow-layout-configurator.ts b/examples/workflow-server/src/layout/workflow-layout-configurator.ts new file mode 100644 index 0000000..d740047 --- /dev/null +++ b/examples/workflow-server/src/layout/workflow-layout-configurator.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GlspLayoutConfigurator, SModelIndex } from '@eclipse-glsp/layout-elk'; +import { GGraph } from '@eclipse-glsp/server-node'; +import { LayoutOptions } from 'elkjs'; +import { injectable } from 'inversify'; + +@injectable() +export class WorkflowLayoutConfigurator extends GlspLayoutConfigurator { + protected graphOptions(sgraph: GGraph, index: SModelIndex): LayoutOptions | undefined { + return { + 'elk.algorithm': 'layered' + }; + } +} diff --git a/examples/workflow-server/src/workflow-diagram-module.ts b/examples/workflow-server/src/workflow-diagram-module.ts index 5f28ab0..cb48101 100644 --- a/examples/workflow-server/src/workflow-diagram-module.ts +++ b/examples/workflow-server/src/workflow-diagram-module.ts @@ -26,13 +26,13 @@ import { JsonRpcGLSPServer, LabelEditValidator, ModelValidator, + NavigationTargetProvider, + NavigationTargetResolver, OperationHandlerConstructor, PopupModelFactory, ServerModule, ToolPaletteItemProvider } from '@eclipse-glsp/server-node'; -import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; -import { NavigationTargetResolver } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolver'; import { injectable, interfaces } from 'inversify'; import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler'; import { CreateCategoryHandler } from './handler/create-category-handler'; diff --git a/packages/layout-elk/LICENSE b/packages/layout-elk/LICENSE new file mode 100644 index 0000000..4ee1e0a --- /dev/null +++ b/packages/layout-elk/LICENSE @@ -0,0 +1,642 @@ +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0, or GNU General Public License, version 2 +with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html. + +# Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + + "Contributor" means any person or entity that Distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions Distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement + or any Secondary License (as applicable), including Contributors. + + "Derivative Works" shall mean any work, whether in Source Code or other + form, that is based on (or derived from) the Program and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Modified Works" shall mean any work in Source Code or other form that + results from an addition to, deletion from, or modification of the + contents of the Program, including, for purposes of clarity any new file + in Source Code form that contains any contents of the Program. Modified + Works shall not include works that contain only declarations, + interfaces, types, classes, structures, or files of the Program solely + in each case in order to link to, bind by name, or subclass the Program + or Modified Works thereof. + + "Distribute" means the acts of a) distributing or b) making available + in any manner that enables the transfer of a copy. + + "Source Code" means the form of a Program preferred for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + "Secondary License" means either the GNU General Public License, + Version 2.0, or any later versions of that license, including any + exceptions or additional permissions as identified by the initial + Contributor. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + + 3. REQUIREMENTS + + 3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + + 3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + + 3.3 Contributors may not remove or alter any copyright, patent, + trademark, attribution notices, disclaimers of warranty, or limitations + of liability ("notices") contained within the Program from any copy of + the Program which they Distribute, provided that Contributors may add + their own appropriate notices. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the Indemnified + Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program + in a commercial product offering. The obligations in this section do not + apply to any claims or Losses relating to any actual or alleged + intellectual property infringement. In order to qualify, an Indemnified + Contributor must: a) promptly notify the Commercial Contributor in + writing of such claim, and b) allow the Commercial Contributor to control, + and cooperate with the Commercial Contributor in, the defense and any + related settlement negotiations. The Indemnified Contributor may + participate in any such claim at its own expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those performance + claims and warranties, and if a court requires any other Contributor to + pay any damages as a result, the Commercial Contributor must pay + those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR + IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF + TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all + risks associated with its exercise of rights under this Agreement, + including but not limited to the risks and costs of program errors, + compliance with applicable laws, damage to or loss of data, programs + or equipment, and unavailability or interruption of operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT + PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS + SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST + PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that the + Program itself (excluding combinations of the Program with other software + or hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it + fails to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of + time after becoming aware of such noncompliance. If all Recipient's + rights under this Agreement terminate, Recipient agrees to cease use + and distribution of the Program as soon as reasonably practicable. + However, Recipient's obligations under this Agreement and any licenses + granted by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a distinguishing + version number. The Program (including Contributions) may always be + Distributed subject to the version of the Agreement under which it was + received. In addition, after a new version of the Agreement is published, + Contributor may elect to Distribute the Program (including its + Contributions) under the new version. + + Except as expressly stated in Sections 2(a) and 2(b) above, Recipient + receives no rights or licenses to the intellectual property of any + Contributor under this Agreement, whether expressly, by implication, + estoppel or otherwise. All rights in the Program not expressly granted + under this Agreement are reserved. Nothing in this Agreement is intended + to be enforceable by any entity that is not a Contributor or Recipient. + No third-party beneficiary rights are created under this Agreement. + + Exhibit A - Form of Secondary Licenses Notice + + "This Source Code may also be made available under the following + Secondary Licenses when the conditions for such availability set forth + in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), + version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + +--- + +## The GNU General Public License (GPL) Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1335 + USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your freedom to + share and change it. By contrast, the GNU General Public License is + intended to guarantee your freedom to share and change free software--to + make sure the software is free for all its users. This General Public + License applies to most of the Free Software Foundation's software and + to any other program whose authors commit to using it. (Some other Free + Software Foundation software is covered by the GNU Library General + Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not price. + Our General Public Licenses are designed to make sure that you have the + freedom to distribute copies of free software (and charge for this + service if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid anyone + to deny you these rights or to ask you to surrender the rights. These + restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether gratis + or for a fee, you must give the recipients all the rights that you have. + You must make sure that they, too, receive or can get the source code. + And you must show them these terms so they know their rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software patents. + We wish to avoid the danger that redistributors of a free program will + individually obtain patent licenses, in effect making the program + proprietary. To prevent this, we have made it clear that any patent must + be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains a + notice placed by the copyright holder saying it may be distributed under + the terms of this General Public License. The "Program", below, refers + to any such program or work, and a "work based on the Program" means + either the Program or any derivative work under copyright law: that is + to say, a work containing the Program or a portion of it, either + verbatim or with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the term + "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + the Program is not restricted, and the output from the Program is + covered only if its contents constitute a work based on the Program + (independent of having been made by running the Program). Whether that + is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's source + code as you receive it, in any medium, provided that you conspicuously + and appropriately publish on each copy an appropriate copyright notice + and disclaimer of warranty; keep intact all the notices that refer to + this License and to the absence of any warranty; and give any other + recipients of the Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion of + it, thus forming a work based on the Program, and copy and distribute + such modifications or work under the terms of Section 1 above, provided + that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this License. + (Exception: if the Program itself is interactive but does not + normally print such an announcement, your work based on the Program + is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Program, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your cost + of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source code + means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to control + compilation and installation of the executable. However, as a special + exception, the source code distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies the + executable. + + If distribution of executable or object code is made by offering access + to copy from a designated place, then offering equivalent access to copy + the source code from the same place counts as distribution of the source + code, even though third parties are not compelled to copy the source + along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt otherwise + to copy, modify, sublicense or distribute the Program is void, and will + automatically terminate your rights under this License. However, parties + who have received copies, or rights, from you under this License will + not have their licenses terminated so long as such parties remain in + full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and all + its terms and conditions for copying, distributing or modifying the + Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further restrictions + on the recipients' exercise of the rights granted herein. You are not + responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot distribute + so as to satisfy simultaneously your obligations under this License and + any other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent license would + not permit royalty-free redistribution of the Program by all those who + receive copies directly or indirectly through you, then the only way you + could satisfy both it and this License would be to refrain entirely from + distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that choice. + + This section is intended to make thoroughly clear what is believed to be + a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License may + add an explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Program does not specify a version + number of this License, you may choose any version ever published by the + Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the + author to ask for permission. For software which is copyrighted by the + Free Software Foundation, write to the Free Software Foundation; we + sometimes make exceptions for this. Our decision will be guided by the + two goals of preserving the free status of all derivatives of our free + software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH + YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively convey + the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type + `show w'. This is free software, and you are welcome to redistribute + it under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the + appropriate parts of the General Public License. Of course, the commands + you use may be called something other than `show w' and `show c'; they + could even be mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (which makes passes at compilers) written by + James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications + with the library. If this is what you want to do, use the GNU Library + General Public License instead of this License. + +--- + +## CLASSPATH EXCEPTION + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License version 2 cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from or + based on this library. If you modify this library, you may extend this + exception to your version of the library, but you are not obligated to + do so. If you do not wish to do so, delete this exception statement + from your version. \ No newline at end of file diff --git a/packages/layout-elk/README.md b/packages/layout-elk/README.md new file mode 100644 index 0000000..9244906 --- /dev/null +++ b/packages/layout-elk/README.md @@ -0,0 +1,9 @@ +# Eclipse GLSP - Layout ELK + +Integration of the [Eclipse Layout Kernel (ELK)](https://www.eclipse.org/elk/) with the GLSP node server framework. +This packages provides an ELK layout engine that can be used for autolayouting the graphical model (GModel) on node servers. + +## More information + +For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). +If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/). diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json new file mode 100644 index 0000000..e262206 --- /dev/null +++ b/packages/layout-elk/package.json @@ -0,0 +1,60 @@ +{ + "name": "@eclipse-glsp/layout-elk", + "version": "0.9.0", + "description": "Integration of ELK graph layout algorithms in GLSP Node Server", + "license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)", + "keywords": [ + "eclipse", + "graphics", + "diagram", + "modeling", + "visualization", + "glsp", + "diagram editor" + ], + "author": { + "name": "Eclipse GLSP" + }, + "homepage": "https://www.eclipse.org/glsp/", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-glsp/glsp-server-node.git" + }, + "bugs": "https://github.com/eclipse-glsp/glsp/issues", + "contributors": [ + { + "name": "STMicroelectronics", + "url": "https://www.st.com/" + }, + { + "name": "Eclipse GLSP Project", + "email": "glsp-dev@eclipse.org", + "url": "https://projects.eclipse.org/projects/ecd.glsp" + } + ], + "files": [ + "lib", + "src", + "css" + ], + "dependencies": { + "sprotty-elk": "next", + "inversify": "^5.0.1", + "@eclipse-glsp/server-node": "0.9.0", + "elkjs": "^0.7.1" + }, + "scripts": { + "clean": "rimraf lib", + "build": "yarn run clean && tsc", + "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", + "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "lint:fix": "eslint --fix -c ../../.eslintrc.js --ext .ts,.tsx ./src", + "watch": "tsc -w" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index", + "types": "lib/index" +} diff --git a/packages/layout-elk/src/basic-type-mapper.ts b/packages/layout-elk/src/basic-type-mapper.ts new file mode 100644 index 0000000..2eb9b38 --- /dev/null +++ b/packages/layout-elk/src/basic-type-mapper.ts @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DiagramConfiguration, GEdge, GGraph, GLabel, GModelElementConstructor, GNode, GPort } from '@eclipse-glsp/server-node'; +import { inject, injectable, postConstruct } from 'inversify'; +import { SModelElement } from 'sprotty-protocol'; + +export type BasicGModelType = 'graph' | 'node' | 'edge' | 'label' | 'port' | 'unknown'; + +@injectable() +/** + * The `BasicTypeMapper` provides the means to derive the basic type from a given graph model element. + * The basic type information is used by the `ElkLayoutEngine` to transform graphic model elements into their corresponding ELK object + * representation. + */ +export class BasicTypeMapper { + @inject(DiagramConfiguration) + protected diagramConfiguration: DiagramConfiguration; + + protected basicTypeMap: Map; + + @postConstruct() + protected postConstruct(): void { + this.basicTypeMap = new Map(); + this.diagramConfiguration.typeMapping.forEach((constructor, type) => { + const basicType = this.toBasicType(constructor); + if (basicType) { + this.basicTypeMap.set(type, basicType); + } + }); + } + + protected toBasicType(constructor: GModelElementConstructor): BasicGModelType { + const element = new constructor(); + if (element instanceof GNode) { + return 'node'; + } else if (element instanceof GEdge) { + return 'edge'; + } else if (element instanceof GGraph) { + return 'graph'; + } else if (element instanceof GLabel) { + return 'label'; + } else if (element instanceof GPort) { + return 'port'; + } + return 'unknown'; + } + + getBasicType(input: SModelElement | string): BasicGModelType { + const type = typeof input === 'string' ? input : input.type; + const basicType = this.basicTypeMap.get(type); + if (basicType) { + return basicType; + } + const subtypeSeparator = type.lastIndexOf(':'); + if (subtypeSeparator > 0) { + return this.getBasicType(type.substring(0, subtypeSeparator)); + } + return 'unknown'; + } +} diff --git a/packages/layout-elk/src/di.config.spec.ts b/packages/layout-elk/src/di.config.spec.ts new file mode 100644 index 0000000..6fed25f --- /dev/null +++ b/packages/layout-elk/src/di.config.spec.ts @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { DefaultModelState, DiagramConfiguration, GGraph, GModelElementConstructor, ModelState } from '@eclipse-glsp/server-node'; +import { StubDiagramConfiguration } from '@eclipse-glsp/server-node/lib/test/mock-util'; +import { expect } from 'chai'; +import { Container, ContainerModule, injectable } from 'inversify'; +import * as sinon from 'sinon'; +import { IElementFilter, ILayoutConfigurator } from 'sprotty-elk'; +import { SModelIndex } from 'sprotty-protocol'; +import { GlspLayoutConfigurator } from '.'; +import { configureELKLayoutModule } from './di.config'; +import { GlspElementFilter } from './glsp-element-filter'; +import { FallbackGlspLayoutConfigurator } from './glsp-layout-configurator'; + +@injectable() +class CustomLayoutConfigurator extends GlspLayoutConfigurator {} + +@injectable() +class CustomElementFilter extends GlspElementFilter {} + +describe('test configureELKLayoutModule', () => { + const sandbox = sinon.createSandbox(); + const mockDiagramConfiguration = new StubDiagramConfiguration(); + const typeMappings = new Map(); + + typeMappings.set('graph', GGraph); + sandbox.stub(mockDiagramConfiguration, 'typeMapping').value(typeMappings); + const modelState = new DefaultModelState(); + const baseModule = new ContainerModule(bind => { + bind(DiagramConfiguration).toConstantValue(mockDiagramConfiguration); + bind(ModelState).toConstantValue(modelState); + }); + it('configure with minimal options', () => { + const algorithm = 'layered'; + const elkModule = configureELKLayoutModule({ algorithms: [algorithm] }); + const container = new Container(); + container.load(baseModule, elkModule); + const filter = container.get(IElementFilter); + expect(filter).to.be.instanceOf(GlspElementFilter); + const configurator = container.get(ILayoutConfigurator); + expect(configurator).to.be.instanceOf(FallbackGlspLayoutConfigurator); + const graphOptions = configurator.apply(new GGraph(), new SModelIndex()); + expect(graphOptions).not.to.be.undefined; + expect(graphOptions!['elk.algorithm']).to.equal(algorithm); + }); + + it('configure with additional default layout options', () => { + const algorithm = 'layered'; + const defaultLayoutOptions = { + 'elk.direction': 'LEFT', + 'elk.edgeRouting': 'POLYLINE' + }; + const elkModule = configureELKLayoutModule({ algorithms: [algorithm], defaultLayoutOptions }); + const container = new Container(); + container.load(baseModule, elkModule); + const configurator = container.get(ILayoutConfigurator); + const graphOptions = configurator.apply(new GGraph(), new SModelIndex()); + expect(graphOptions).not.to.be.undefined; + expect(graphOptions).to.include(defaultLayoutOptions); + }); + + it('configure with custom layout configurator', () => { + const algorithm = 'layered'; + const elkModule = configureELKLayoutModule({ algorithms: [algorithm], layoutConfigurator: CustomLayoutConfigurator }); + const container = new Container(); + container.load(baseModule, elkModule); + const configurator = container.get(ILayoutConfigurator); + expect(configurator).to.be.instanceOf(CustomLayoutConfigurator); + }); + + it('configure with custom element filter', () => { + const algorithm = 'layered'; + const elkModule = configureELKLayoutModule({ algorithms: [algorithm], elementFilter: CustomElementFilter }); + const container = new Container(); + container.load(baseModule, elkModule); + const filter = container.get(IElementFilter); + expect(filter).to.be.instanceOf(CustomElementFilter); + }); +}); diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts new file mode 100644 index 0000000..5108b97 --- /dev/null +++ b/packages/layout-elk/src/di.config.ts @@ -0,0 +1,117 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LayoutEngine } from '@eclipse-glsp/server-node/'; +import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled'; +import { ContainerModule } from 'inversify'; +import { ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk'; +import { GlspElkLayoutEngine } from '.'; +import { BasicTypeMapper } from './basic-type-mapper'; +import { GlspElementFilter } from './glsp-element-filter'; +import { BaseElkLayoutEngine } from './glsp-elk-layout-engine'; +import { FallbackGlspLayoutConfigurator, GlspLayoutConfigurator } from './glsp-layout-configurator'; + +type Constructor = new (...args: any[]) => T; + +/** + * Configuration options for the {@link configureELKLayoutModule} function. + */ +export interface ElkModuleOptions { + /** + * The set of elk algorithms that is used by the {@link GlspElkLayoutEngine}. + */ + algorithms: string[]; + /** + * Additional default layout options. This options are passed to the underlying {@link ElkFactory}. + * In addition, they are used to configure the {@link FallbackGlspLayoutConfigurator}. + */ + defaultLayoutOptions?: LayoutOptions; + /** + * The custom {@link GlspLayoutConfigurator} class that should be bound. If this option is not set + * the {@link FallbackGlspLayoutConfigurator} is bound instead. + */ + layoutConfigurator?: Constructor; + /** + * The custom {@link GlspElementFilter} class that should be bound. If this option is not set, the default implementation + * is bound. + */ + elementFilter?: Constructor; +} + +/** + * Utility method to create a DI module that provides all necessary bindings to use the {@link GlspElkLayoutEngine} in a node GLSP server + * implementation. A set of configuration options is provided to enable easy customization. In most cases at least + * the custom {@link layoutConfigurator} binding should be provided (in addition to the required `algorithms' property) via these options. + * + * The constructed module is not intended for standalone use cases and only works in combination with a GLSPDiagramModule. + * + * * The following bindings are provided: + * - {@link BasicTypeMapper} + * - {@link BaseElkLayoutEngine} + * - {@link ElkLayoutEngine} + * - {@link GlspElementFilter} + * - {@link IElementFilter} + * - {@link GlspLayoutConfigurator} + * - {@link ILayoutConfigurator} + * - {@link LayoutEngine} + * - {@link ElkFactory} + * + * @param options The configuration options + * @returns A DI module that can be loaded as additional module when configuring a diagram module for a GLSP server. + */ +export function configureELKLayoutModule(options: ElkModuleOptions): ContainerModule { + return new ContainerModule(bind => { + bind(BasicTypeMapper).toSelf().inSingletonScope(); + bind(BaseElkLayoutEngine) + .toDynamicValue(context => { + const elkFactory = context.container.get(ElkFactory); + const elementFilter = context.container.get(IElementFilter); + const layoutConfigurator = context.container.get(ILayoutConfigurator); + const typeMapper = context.container.get(BasicTypeMapper); + return new BaseElkLayoutEngine(elkFactory, elementFilter, layoutConfigurator, typeMapper); + }) + .inSingletonScope(); + + bind(ElkLayoutEngine).to(BaseElkLayoutEngine); + + if (options.elementFilter) { + bind(GlspElementFilter).to(options.elementFilter).inSingletonScope(); + } else { + bind(GlspElementFilter).toSelf().inSingletonScope(); + } + bind(IElementFilter).toService(GlspElementFilter); + + if (options.layoutConfigurator) { + bind(GlspLayoutConfigurator).to(options.layoutConfigurator); + } else { + bind(GlspLayoutConfigurator) + .toDynamicValue(context => { + const typeMapper = context.container.get(BasicTypeMapper); + return new FallbackGlspLayoutConfigurator(typeMapper, options.algorithms, options.defaultLayoutOptions); + }) + .inSingletonScope(); + } + bind(ILayoutConfigurator).toService(GlspLayoutConfigurator); + bind(LayoutEngine).to(GlspElkLayoutEngine).inSingletonScope(); + + const elkFactory: ElkFactory = () => + new ElkConstructor({ + algorithms: options.algorithms, + defaultLayoutOptions: options.defaultLayoutOptions + }); + + bind(ElkFactory).toConstantValue(elkFactory); + }); +} diff --git a/packages/layout-elk/src/glsp-element-filter.ts b/packages/layout-elk/src/glsp-element-filter.ts new file mode 100644 index 0000000..5c68daf --- /dev/null +++ b/packages/layout-elk/src/glsp-element-filter.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { inject, injectable } from 'inversify'; +import { DefaultElementFilter } from 'sprotty-elk'; +import { SModelElement } from 'sprotty-protocol'; +import { BasicTypeMapper } from './basic-type-mapper'; + +/** + * The default `IElementFilter` used to determine which model elements should be included in the automatic layout. + * Without further configuration this filter includes all basic model elements (nodes,edges,labels,ports) that are + * part of the graphical model. For each a custom filter behavior is in place. Edges that have no source or target are filtered out. + * In addition, edges that are connected to a filtered element are filtered out as well. + * The filter behavior for each of the basic types can be customized by overriding the corresponding `filter` method. + * (e.g. {@link GlspElementFilter.filterNode}) + * + */ +@injectable() +export class GlspElementFilter extends DefaultElementFilter { + @inject(BasicTypeMapper) + protected typeMapper: BasicTypeMapper; + + protected getBasicType(smodel: SModelElement): string { + return this.typeMapper.getBasicType(smodel); + } +} diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts new file mode 100644 index 0000000..b51c3e3 --- /dev/null +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LayoutEngine, ModelState } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/inversify'; +import { SModelElement } from 'sprotty-protocol/lib/model'; +import { BasicTypeMapper } from './basic-type-mapper'; + +/** + * Layout engine that delegates to ELK by transforming the graphical model into an ELK graph. + */ +@injectable() +export class BaseElkLayoutEngine extends ElkLayoutEngine { + constructor( + @inject(ElkFactory) elkFactory: ElkFactory, + @inject(IElementFilter) protected readonly filter: IElementFilter, + @inject(ILayoutConfigurator) protected readonly configurator: ILayoutConfigurator, + @inject(BasicTypeMapper) protected typeMapper: BasicTypeMapper + ) { + super(elkFactory, filter, configurator); + } + + protected getBasicType(smodel: SModelElement): string { + return this.typeMapper.getBasicType(smodel); + } +} + +/** + * A implement of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState} + * and delegates the to an underlying {@link BaseElkLayoutEngine} instance for computing the layout. + */ +@injectable() +export class GlspElkLayoutEngine implements LayoutEngine { + @inject(ModelState) + protected modelState: ModelState; + + @inject(BaseElkLayoutEngine) + protected elkLayoutEngine: BaseElkLayoutEngine; + + async layout(): Promise { + const graph = this.modelState.root; + await this.elkLayoutEngine.layout(graph); + } +} diff --git a/packages/layout-elk/src/glsp-layout-configurator.ts b/packages/layout-elk/src/glsp-layout-configurator.ts new file mode 100644 index 0000000..72b71a0 --- /dev/null +++ b/packages/layout-elk/src/glsp-layout-configurator.ts @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LayoutOptions } from 'elkjs'; +import { inject, injectable } from 'inversify'; +import { DefaultLayoutConfigurator } from 'sprotty-elk'; +import { SGraph, SModelElement, SModelIndex } from 'sprotty-protocol'; +import { BasicTypeMapper } from './basic-type-mapper'; + +/** + * Configurator for ELK layout algorithms; provides mappings of layout options for each model element. + * For a list of all available layout options checkout the official ELK documentation. + * A minimal configuration should at least specify the `elk.algorithm` option as + * return value of {@link GlspLayoutConfigurator.graphOptions}. + * {@link https://www.eclipse.org/elk/reference/options.html} + */ +@injectable() +export class GlspLayoutConfigurator extends DefaultLayoutConfigurator { + @inject(BasicTypeMapper) + protected typeMapper: BasicTypeMapper; + + protected getBasicType(smodel: SModelElement): string { + return this.typeMapper.getBasicType(smodel); + } +} + +/** + * A fallback configurator that is used in the `configureELKLayoutModule` utility method. + * If no explicit layout configurator binding is provided a new instance of the fallback configurator is + * bound as replacement. Basic layout options for the root graph element are derived from the + * configuration parameters that haven been passed to the `configureELKLayoutModule` function. + */ +@injectable() +export class FallbackGlspLayoutConfigurator extends GlspLayoutConfigurator { + protected fallbackGraphOptions: LayoutOptions; + constructor(protected typeMapper: BasicTypeMapper, algorithms: string[], defaultLayoutOptions: LayoutOptions = {}) { + super(); + this.fallbackGraphOptions = { + 'elk.algorithm': algorithms[0], + ...defaultLayoutOptions + }; + } + + protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions | undefined { + return this.fallbackGraphOptions; + } +} diff --git a/packages/layout-elk/src/index.ts b/packages/layout-elk/src/index.ts new file mode 100644 index 0000000..29e21ca --- /dev/null +++ b/packages/layout-elk/src/index.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from 'sprotty-elk'; +export { SModelIndex } from 'sprotty-protocol/lib/utils/model-utils'; +export * from './basic-type-mapper'; +export * from './di.config'; +export * from './glsp-element-filter'; +export * from './glsp-elk-layout-engine'; +export * from './glsp-layout-configurator'; diff --git a/packages/layout-elk/tsconfig.json b/packages/layout-elk/tsconfig.json new file mode 100644 index 0000000..1fe0813 --- /dev/null +++ b/packages/layout-elk/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@eclipse-glsp/ts-config/tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "reactNamespace": "JSX", + "baseUrl": "src", + "types": ["node", "mocha", "reflect-metadata"] + }, + "include": ["src"] +} diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/base-impl/gmodel-diagram-module.ts index 681d49a..e7ce506 100644 --- a/packages/server-node/src/base-impl/gmodel-diagram-module.ts +++ b/packages/server-node/src/base-impl/gmodel-diagram-module.ts @@ -14,12 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, interfaces } from 'inversify'; -import { ApplyLabelEditOperationHandler } from '../features/directediting/apply-label-edit-operation-handler'; import { ActionHandlerConstructor } from '../actions/action-handler'; +import { SaveModelActionHandler } from '../actions/save-model-action-handler'; +import { ChangeBoundsOperationHandler } from '../base-impl/change-bounds-operation-handler'; import { CommandStack, DefaultCommandStack } from '../command/command-stack'; import { DiagramModule } from '../di/diagram-module'; import { InstanceMultiBinding } from '../di/multi-binding'; import { RequestClipboardDataActionHandler } from '../features/clipboard/request-clipboard-data-action-handler'; +import { ApplyLabelEditOperationHandler } from '../features/directediting/apply-label-edit-operation-handler'; +import { LayoutOperationHandler } from '../features/layout/layout-operation-handler'; import { GModelFactory, GModelFactoryNullImpl } from '../features/model/gmodel-factory'; import { GModelIndex } from '../features/model/gmodel-index'; import { ModelSourceLoader } from '../features/model/model-source-loader'; @@ -27,15 +30,11 @@ import { ModelState } from '../features/model/model-state'; import { CutOperationHandler } from '../operations/cut-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; import { PasteOperationHandler } from '../operations/paste-operation-handler'; +import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; +import { DeleteOperationHandler } from './delete-operation-handler'; import { GModelLoader } from './gmodel-loader'; import { GModelState } from './gmodel-state'; -import { DeleteOperationHandler } from './delete-operation-handler'; -// TODO: investigate why direct import does not work -// eslint-disable-next-line no-restricted-imports -import { ChangeBoundsOperationHandler } from '../base-impl/change-bounds-operation-handler'; -import { SaveModelActionHandler } from '../actions/save-model-action-handler'; -import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; /** * Extension of the {@link DiagramModule} to provide GModel integration. @@ -86,5 +85,6 @@ export abstract class GModelDiagramModule extends DiagramModule { binding.add(DeleteOperationHandler); binding.add(PasteOperationHandler); binding.add(ReconnectEdgeOperationHandler); + binding.add(LayoutOperationHandler); } } diff --git a/packages/server-node/src/layout/layout-engine.ts b/packages/server-node/src/features/layout/layout-engine.ts similarity index 92% rename from packages/server-node/src/layout/layout-engine.ts rename to packages/server-node/src/features/layout/layout-engine.ts index ee6dff2..372b8e8 100644 --- a/packages/server-node/src/layout/layout-engine.ts +++ b/packages/server-node/src/features/layout/layout-engine.ts @@ -14,6 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { MaybePromise } from '@eclipse-glsp/protocol'; + /** * A layout engine is able to compute layout information for a model. */ @@ -21,7 +23,7 @@ export interface LayoutEngine { /* * Compute a layout for the model state and modify the model accordingly. */ - layout(): void; + layout(): MaybePromise; } export const LayoutEngine = Symbol('LayoutEngine'); diff --git a/packages/server-node/src/features/layout/layout-operation-handler.ts b/packages/server-node/src/features/layout/layout-operation-handler.ts new file mode 100644 index 0000000..5ad826a --- /dev/null +++ b/packages/server-node/src/features/layout/layout-operation-handler.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { LayoutOperation, Operation } from '@eclipse-glsp/protocol'; +import { inject, injectable, optional } from 'inversify'; +import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; +import { OperationHandler } from '../../operations/operation-handler'; +import { Logger } from '../../utils/logger'; +import { LayoutEngine } from './layout-engine'; + +@injectable() +export class LayoutOperationHandler implements OperationHandler { + @inject(Logger) + protected logger: Logger; + + @inject(LayoutEngine) + @optional() + protected layoutEngine?: LayoutEngine; + + @inject(DiagramConfiguration) + protected diagramConfiguration: DiagramConfiguration; + + readonly operationType = LayoutOperation.KIND; + async execute(operation: Operation): Promise { + if (operation.kind === LayoutOperation.KIND) { + if (this.diagramConfiguration.layoutKind === ServerLayoutKind.MANUAL) { + if (this.layoutEngine) { + await this.layoutEngine.layout(); + return; + } + this.logger.warn('Could not execute layout operation. No `LayoutEngine` is bound!'); + } + } + } +} diff --git a/packages/server-node/src/features/model/model-submission-handler.ts b/packages/server-node/src/features/model/model-submission-handler.ts index aa856f9..9143948 100644 --- a/packages/server-node/src/features/model/model-submission-handler.ts +++ b/packages/server-node/src/features/model/model-submission-handler.ts @@ -17,7 +17,7 @@ import { GModelRootSchema } from '@eclipse-glsp/graph'; import { Action, RequestBoundsAction, SetDirtyStateAction, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; -import { LayoutEngine } from '../../layout/layout-engine'; +import { LayoutEngine } from '../layout/layout-engine'; import { GModelFactory } from './gmodel-factory'; import { GModelSerializer } from './gmodel-serializer'; import { ModelState } from './model-state'; diff --git a/packages/server-node/src/index.ts b/packages/server-node/src/index.ts index fd28246..8e19f0d 100644 --- a/packages/server-node/src/index.ts +++ b/packages/server-node/src/index.ts @@ -22,8 +22,9 @@ export * from './actions/action-handler-registry'; export * from './actions/client-action-handler'; export * from './actions/global-action-provider'; export * from './base-impl'; -export * from './base-impl/computed-bounds-action-handler'; export * from './base-impl/change-bounds-operation-handler'; +export * from './base-impl/computed-bounds-action-handler'; +export * from './base-impl/delete-operation-handler'; export * from './command/command'; export * from './command/command-stack'; export * from './di/app-module'; @@ -47,22 +48,30 @@ export * from './features/directediting/context-edit-validator'; export * from './features/directediting/context-edit-validator-registry'; export * from './features/directediting/label-edit-validator'; export * from './features/directediting/request-edit-validation-handler'; +export * from './features/layout/layout-engine'; +export * from './features/layout/layout-operation-handler'; export * from './features/model/gmodel-factory'; export * from './features/model/gmodel-index'; export * from './features/model/gmodel-serializer'; -export * from './features/popup/popup-model-factory'; -export * from './features/popup/request-popup-model-action-handler'; export * from './features/model/model-source-loader'; export * from './features/model/model-state'; export * from './features/model/model-submission-handler'; export * from './features/model/request-model-action-handler'; +export * from './features/navigation/json-opener-options'; +export * from './features/navigation/navigation-target-provider'; +export * from './features/navigation/navigation-target-provider-registry'; +export * from './features/navigation/navigation-target-resolution'; +export * from './features/navigation/navigation-target-resolver'; +export * from './features/navigation/request-navigation-targets-action-handler'; +export * from './features/navigation/resolve-navigation-targets-action-handler'; +export * from './features/popup/popup-model-factory'; +export * from './features/popup/request-popup-model-action-handler'; export * from './features/validation/model-validator'; export * from './features/validation/request-markers-handler'; export * from './launch/cli-parser'; export * from './launch/glsp-server-launcher'; export * from './launch/socket-cli-parser'; export * from './launch/socket-server-launcher'; -export * from './base-impl/delete-operation-handler'; export * from './operations/compound-operation-handler'; export * from './operations/create-operation-handler'; export * from './operations/cut-operation-handler'; diff --git a/packages/server-node/src/operations/operation-action-handler.ts b/packages/server-node/src/operations/operation-action-handler.ts index 4a8b01d..d707487 100644 --- a/packages/server-node/src/operations/operation-action-handler.ts +++ b/packages/server-node/src/operations/operation-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isCreateOperation, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { Action, isCreateOperation, MaybePromise, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { GModelState } from '../base-impl/gmodel-state'; @@ -30,7 +30,7 @@ export class OperationActionHandler implements ActionHandler { constructor(@inject(Operations) readonly actionKinds: string[]) {} - execute(action: Action): Action[] { + execute(action: Action): MaybePromise { if (this.handles(action)) { if (this.modelState.isReadonly) { return [new ServerMessageAction('WARNING', `Server is in readonly-mode! Could not execute operation: ${action.kind}`)]; @@ -43,9 +43,9 @@ export class OperationActionHandler implements ActionHandler { return []; } - executeHandler(operation: Operation, handler: OperationHandler): Action[] { + async executeHandler(operation: Operation, handler: OperationHandler): Promise { // TODO: Create GModelRecordingCommand; - handler.execute(operation); + await handler.execute(operation); this.modelState.index.indexRoot(this.modelState.root); // TODO: this.modelState.execute(command) return this.modelSubmissionHandler.submitModel(); // TODO: Add SetDirtyStateAction.Reason.Operation diff --git a/packages/server-node/src/operations/operation-handler.ts b/packages/server-node/src/operations/operation-handler.ts index d6b309e..9ba2513 100644 --- a/packages/server-node/src/operations/operation-handler.ts +++ b/packages/server-node/src/operations/operation-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Operation } from '@eclipse-glsp/protocol'; +import { MaybePromise, Operation } from '@eclipse-glsp/protocol'; import { interfaces } from 'inversify'; export const OperationHandler = Symbol('OperationHandler'); @@ -45,7 +45,7 @@ export interface OperationHandler { * * @param operation The operation that should be executed. */ - execute(operation: Operation): void; + execute(operation: Operation): MaybePromise; } export const OperationHandlerConstructor = Symbol('OperationHandlerConstructor'); From 76dec3f5b22b071eec34d49da31990864d2b6e30 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 2 Feb 2022 07:53:01 -0800 Subject: [PATCH 079/566] #507 Fix edge deletion for workflow example (#4) Update `GModelIndex` to set missing parent references during indexing. This ensures that deletion of newly created elements works as expected. Also update the implementaion of `DeleteOperationHandler` to use a `Logger` instance instead of console.log. Fixes eclipse-glsp/glsp/issues/517 --- .../src/base-impl/delete-operation-handler.ts | 22 ++++++++++--------- .../src/features/model/gmodel-index.ts | 8 ++++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/server-node/src/base-impl/delete-operation-handler.ts b/packages/server-node/src/base-impl/delete-operation-handler.ts index 75c28e4..d2a5a88 100644 --- a/packages/server-node/src/base-impl/delete-operation-handler.ts +++ b/packages/server-node/src/base-impl/delete-operation-handler.ts @@ -13,15 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GEdge, GModelElement, GNode } from '@eclipse-glsp/graph'; import { DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { GModelIndex } from '../features/model/gmodel-index'; -import { GModelState } from './gmodel-state'; import { OperationHandler } from '../operations/operation-handler'; -import { GEdge, GModelElement, GNode } from '@eclipse-glsp/graph'; +import { Logger } from '../utils/logger'; +import { GModelState } from './gmodel-state'; @injectable() export class DeleteOperationHandler implements OperationHandler { + @inject(Logger) + protected logger: Logger; + protected allDependantsIds: Set; @inject(GModelState) protected readonly modelState: GModelState; @@ -33,14 +37,14 @@ export class DeleteOperationHandler implements OperationHandler { execute(operation: DeleteElementOperation): void { const elementIds = operation.elementIds; if (!elementIds || elementIds.length === 0) { - console.log('Elements to delete are not specified'); + this.logger.warn('Elements to delete are not specified'); return; } const index = this.modelState.index; this.allDependantsIds = new Set(); const success = elementIds.every(eId => this.delete(eId, index)); if (!success) { - console.log('Could not delete all elements as requested (see messages above to find out why)'); + this.logger.warn('Could not delete all elements as requested (see messages above to find out why)'); } } @@ -49,17 +53,15 @@ export class DeleteOperationHandler implements OperationHandler { return true; } - let element; - try { - element = index.get(elementId); - } catch { - console.log('Element not found: ' + elementId); + const element = index.find(elementId); + if (!element) { + this.logger.warn('Element not found: ' + elementId); return false; } const nodeToDelete = this.findTopLevelElement(element); if (!nodeToDelete.parent) { - console.log("The requested node doesn't have a parent; it can't be deleted"); + this.logger.warn("The requested node doesn't have a parent; it can't be deleted"); return false; } diff --git a/packages/server-node/src/features/model/gmodel-index.ts b/packages/server-node/src/features/model/gmodel-index.ts index 9b59895..3490f9e 100644 --- a/packages/server-node/src/features/model/gmodel-index.ts +++ b/packages/server-node/src/features/model/gmodel-index.ts @@ -50,7 +50,13 @@ export class GModelIndex { const typeSet = this.typeToElements.get(element.type) ?? []; typeSet.push(element); this.typeToElements.set(element.type, typeSet); - (element.children ?? []).forEach(child => this.doIndex(child)); + (element.children ?? []).forEach(child => { + this.doIndex(child); + // Double check wether the parent reference of the child is set correctly + if (!child.parent) { + child.parent = element; + } + }); } find(elementId: string, predicate?: (test: GModelElement) => boolean): GModelElement | undefined { From 403c79ba83dda00c084c06fc802e14b3adcb6551 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 3 Feb 2022 04:20:18 -0800 Subject: [PATCH 080/566] #514 Refactor GlspElkLayoutEngine (#5) * #514 Refactor GlspElkLayoutEngine - Refactor the `GlspElkLayoutEngine` and related concepts to properly support layout computation for ports. In this process we also opted out of trying to reuse the existing `SprottyElkLayoutEngine` and instead implement a new layout engine specifically for the GLSP graphmodel. This implementation is more in line with the Java ELK-Layout Engine and implements similar features like automatic detection of common ancestors of edge source/targets to correctly transform them in the ELK graph. - Removes the no longer needed `BasicTypeMapper` Also: - Provide utility functions for querying parent elements of a given element that match a predicate. (gmodel-util.ts) - Only rerexport the the subset of types that are really needed from "sprotty-elk" to avoid unncessary naming clashes. - Add jsdoc for `GModelSerializer` - Fix implementation of `getAllEdges` in `GModelIndex` - Remove unused (and unset) `source` and `target` references in `GEdge` Contributed on behalf of STMicroelectronics --- .../layout/workflow-layout-configurator.ts | 7 +- .../src/workflow-diagram-module.ts | 3 - packages/graph/src/gedge.ts | 24 +- packages/graph/src/gmodel-element.ts | 7 +- packages/graph/src/gmodel-util.ts | 58 +++ packages/graph/src/index.ts | 3 +- packages/layout-elk/src/basic-type-mapper.ts | 73 ---- packages/layout-elk/src/di.config.spec.ts | 33 +- packages/layout-elk/src/di.config.ts | 71 ++-- packages/layout-elk/src/element-filter.ts | 84 +++++ .../layout-elk/src/glsp-element-filter.ts | 38 -- .../layout-elk/src/glsp-elk-layout-engine.ts | 335 ++++++++++++++++-- .../src/glsp-layout-configurator.ts | 59 --- packages/layout-elk/src/index.ts | 10 +- .../layout-elk/src/layout-configurator.ts | 101 ++++++ .../layout-elk/src/sprotty-elk.ts | 19 +- .../src/features/layout/layout-engine.ts | 8 +- .../src/features/model/gmodel-index.ts | 11 +- .../src/features/model/gmodel-serializer.ts | 44 ++- 19 files changed, 653 insertions(+), 335 deletions(-) create mode 100644 packages/graph/src/gmodel-util.ts delete mode 100644 packages/layout-elk/src/basic-type-mapper.ts create mode 100644 packages/layout-elk/src/element-filter.ts delete mode 100644 packages/layout-elk/src/glsp-element-filter.ts delete mode 100644 packages/layout-elk/src/glsp-layout-configurator.ts create mode 100644 packages/layout-elk/src/layout-configurator.ts rename examples/workflow-server/src/workflow-model-index.ts => packages/layout-elk/src/sprotty-elk.ts (61%) diff --git a/examples/workflow-server/src/layout/workflow-layout-configurator.ts b/examples/workflow-server/src/layout/workflow-layout-configurator.ts index d740047..12d62ce 100644 --- a/examples/workflow-server/src/layout/workflow-layout-configurator.ts +++ b/examples/workflow-server/src/layout/workflow-layout-configurator.ts @@ -13,14 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GlspLayoutConfigurator, SModelIndex } from '@eclipse-glsp/layout-elk'; +import { AbstractLayoutConfigurator, LayoutOptions } from '@eclipse-glsp/layout-elk'; import { GGraph } from '@eclipse-glsp/server-node'; -import { LayoutOptions } from 'elkjs'; import { injectable } from 'inversify'; @injectable() -export class WorkflowLayoutConfigurator extends GlspLayoutConfigurator { - protected graphOptions(sgraph: GGraph, index: SModelIndex): LayoutOptions | undefined { +export class WorkflowLayoutConfigurator extends AbstractLayoutConfigurator { + protected graphOptions(graph: GGraph): LayoutOptions | undefined { return { 'elk.algorithm': 'layered' }; diff --git a/examples/workflow-server/src/workflow-diagram-module.ts b/examples/workflow-server/src/workflow-diagram-module.ts index cb48101..a6d091c 100644 --- a/examples/workflow-server/src/workflow-diagram-module.ts +++ b/examples/workflow-server/src/workflow-diagram-module.ts @@ -21,7 +21,6 @@ import { DiagramConfiguration, GLSPServer, GModelDiagramModule, - GModelIndex, InstanceMultiBinding, JsonRpcGLSPServer, LabelEditValidator, @@ -53,7 +52,6 @@ import { WorkflowCommandPaletteActionProvider } from './provider/workflow-comman import { WorkflowContextMenuItemProvider } from './provider/workflow-context-menu-item-provider'; import { WorkflowDiagramConfiguration } from './workflow-diagram-configuration'; import { WorkflowGLSPServer } from './workflow-glsp-server'; -import { WorkflowModelIndex } from './workflow-model-index'; import { WorkflowPopupFactory } from './workflow-popup-factory'; @injectable() @@ -82,7 +80,6 @@ export class WorkflowDiagramModule extends GModelDiagramModule { bind(PopupModelFactory).to(WorkflowPopupFactory).inSingletonScope(); bind(ModelValidator).to(WorkflowModelValidator).inSingletonScope(); bind(ToolPaletteItemProvider).to(DefaultToolPaletteItemProvider).inSingletonScope(); - rebind(GModelIndex).to(WorkflowModelIndex).inSingletonScope(); } protected configureOperationHandlers(binding: InstanceMultiBinding): void { diff --git a/packages/graph/src/gedge.ts b/packages/graph/src/gedge.ts index 2c54821..17950ea 100644 --- a/packages/graph/src/gedge.ts +++ b/packages/graph/src/gedge.ts @@ -26,26 +26,6 @@ export class GEdge extends GModelElement { sourceId: string; targetId: string; routerKind?: string; - private _source: GModelElement; - private _target: GModelElement; - - get source(): GModelElement { - return this._source; - } - - set source(value: GModelElement) { - this._source = value; - this.sourceId = value.id; - } - - get target(): GModelElement { - return this._target; - } - - set target(value: GModelElement) { - this._target = value; - this.targetId = value.id; - } } export namespace GEdge { @@ -56,7 +36,7 @@ export namespace GEdge { export class GEdgeBuilder extends GModelElementBuilder { source(source: GModelElement): this { - this.proxy.source = source; + this.proxy.sourceId = source.id; return this; } @@ -66,7 +46,7 @@ export class GEdgeBuilder extends GModelElementBuilder< } target(target: GModelElement): this { - this.proxy.target = target; + this.proxy.targetId = target.id; return this; } diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts index e340324..97afd26 100644 --- a/packages/graph/src/gmodel-element.ts +++ b/packages/graph/src/gmodel-element.ts @@ -19,7 +19,6 @@ import { Dimension, flatPush, isSModelElementSchema, - isSModelRootSchema, JsonPrimitive, MaybeArray, Point, @@ -28,7 +27,6 @@ import { } from '@eclipse-glsp/protocol'; import * as uuid from 'uuid'; export type GModelElementConstructor = new () => G; - /** * Represents a `GModeElement` serialized as plain JSON object. */ @@ -75,6 +73,9 @@ export abstract class GModelElement implements GModelElementSchema { */ args?: Args; + /** + * Retrieve the {@link GModelRoot} element by traversing up the parent hierachy. + */ get root(): GModelRoot { let current: GModelElement = this; while (!(current instanceof GModelRoot)) { @@ -183,7 +184,7 @@ export abstract class GModelElementBuilder { export type GModelRootSchema = SModelRootSchema; export function isGModelRootSchema(schema: any): schema is GModelRootSchema { - return isSModelRootSchema(schema); + return isSModelElementSchema(schema); } export class GModelRoot extends GModelElement implements SModelRootSchema { diff --git a/packages/graph/src/gmodel-util.ts b/packages/graph/src/gmodel-util.ts new file mode 100644 index 0000000..6b8fd6c --- /dev/null +++ b/packages/graph/src/gmodel-util.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GModelElement, GModelElementConstructor } from '.'; + +export type Predicate = (element: T) => boolean; + +/** + * Returns the first element matching the search predicate starting from the given + * element and walking up the parent hierarchy. + * + * @param element The element to start the search from. + * @param searchPredicate The predicate which the element should match. + * @returns The first matching element or `undefined`. + */ +export function findParent(element: GModelElement, searchPredicate: Predicate): GModelElement | undefined { + if (!element) { + return undefined; + } + if (searchPredicate(element)) { + return element; + } + const parent = element.parent; + return parent ? findParent(parent, searchPredicate) : undefined; +} + +/** + * Returns the first parent element that is an instance of the given {@link GModelElementConstructor} starting from the given element. + * (recursively walking up the parent hierarchy). + * + * @param element The element to start the search from. + * @param constructor The queried parent class ({@link GModelElementConstructor}). + * @returns The first matching parent element or `undefined`. + */ +export function findParentByClass( + element: GModelElement, + constructor: GModelElementConstructor +): G | undefined { + const predicate: Predicate = element => element instanceof constructor; + const result = findParent(element, predicate); + if (result) { + return result as G; + } + return undefined; +} diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index 8addc77..c4f127d 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -25,8 +25,9 @@ export * from './gissue-marker'; export * from './glabel'; export * from './glayout-container'; export * from './glayoutable'; -export * from './gpre-rendered-element'; export * from './gmodel-element'; +export * from './gmodel-util'; export * from './gnode'; export * from './gport'; +export * from './gpre-rendered-element'; export * from './gshape-element'; diff --git a/packages/layout-elk/src/basic-type-mapper.ts b/packages/layout-elk/src/basic-type-mapper.ts deleted file mode 100644 index 2eb9b38..0000000 --- a/packages/layout-elk/src/basic-type-mapper.ts +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { DiagramConfiguration, GEdge, GGraph, GLabel, GModelElementConstructor, GNode, GPort } from '@eclipse-glsp/server-node'; -import { inject, injectable, postConstruct } from 'inversify'; -import { SModelElement } from 'sprotty-protocol'; - -export type BasicGModelType = 'graph' | 'node' | 'edge' | 'label' | 'port' | 'unknown'; - -@injectable() -/** - * The `BasicTypeMapper` provides the means to derive the basic type from a given graph model element. - * The basic type information is used by the `ElkLayoutEngine` to transform graphic model elements into their corresponding ELK object - * representation. - */ -export class BasicTypeMapper { - @inject(DiagramConfiguration) - protected diagramConfiguration: DiagramConfiguration; - - protected basicTypeMap: Map; - - @postConstruct() - protected postConstruct(): void { - this.basicTypeMap = new Map(); - this.diagramConfiguration.typeMapping.forEach((constructor, type) => { - const basicType = this.toBasicType(constructor); - if (basicType) { - this.basicTypeMap.set(type, basicType); - } - }); - } - - protected toBasicType(constructor: GModelElementConstructor): BasicGModelType { - const element = new constructor(); - if (element instanceof GNode) { - return 'node'; - } else if (element instanceof GEdge) { - return 'edge'; - } else if (element instanceof GGraph) { - return 'graph'; - } else if (element instanceof GLabel) { - return 'label'; - } else if (element instanceof GPort) { - return 'port'; - } - return 'unknown'; - } - - getBasicType(input: SModelElement | string): BasicGModelType { - const type = typeof input === 'string' ? input : input.type; - const basicType = this.basicTypeMap.get(type); - if (basicType) { - return basicType; - } - const subtypeSeparator = type.lastIndexOf(':'); - if (subtypeSeparator > 0) { - return this.getBasicType(type.substring(0, subtypeSeparator)); - } - return 'unknown'; - } -} diff --git a/packages/layout-elk/src/di.config.spec.ts b/packages/layout-elk/src/di.config.spec.ts index 6fed25f..53e00ec 100644 --- a/packages/layout-elk/src/di.config.spec.ts +++ b/packages/layout-elk/src/di.config.spec.ts @@ -13,23 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DefaultModelState, DiagramConfiguration, GGraph, GModelElementConstructor, ModelState } from '@eclipse-glsp/server-node'; +import { DefaultModelState, GGraph, GModelElementConstructor, ModelState } from '@eclipse-glsp/server-node'; import { StubDiagramConfiguration } from '@eclipse-glsp/server-node/lib/test/mock-util'; import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; import * as sinon from 'sinon'; -import { IElementFilter, ILayoutConfigurator } from 'sprotty-elk'; -import { SModelIndex } from 'sprotty-protocol'; -import { GlspLayoutConfigurator } from '.'; +import { AbstractLayoutConfigurator } from '.'; import { configureELKLayoutModule } from './di.config'; -import { GlspElementFilter } from './glsp-element-filter'; -import { FallbackGlspLayoutConfigurator } from './glsp-layout-configurator'; +import { DefaultElementFilter, ElementFilter } from './element-filter'; +import { FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator'; @injectable() -class CustomLayoutConfigurator extends GlspLayoutConfigurator {} +class CustomLayoutConfigurator extends AbstractLayoutConfigurator {} @injectable() -class CustomElementFilter extends GlspElementFilter {} +class CustomElementFilter extends DefaultElementFilter {} describe('test configureELKLayoutModule', () => { const sandbox = sinon.createSandbox(); @@ -40,7 +38,6 @@ describe('test configureELKLayoutModule', () => { sandbox.stub(mockDiagramConfiguration, 'typeMapping').value(typeMappings); const modelState = new DefaultModelState(); const baseModule = new ContainerModule(bind => { - bind(DiagramConfiguration).toConstantValue(mockDiagramConfiguration); bind(ModelState).toConstantValue(modelState); }); it('configure with minimal options', () => { @@ -48,11 +45,11 @@ describe('test configureELKLayoutModule', () => { const elkModule = configureELKLayoutModule({ algorithms: [algorithm] }); const container = new Container(); container.load(baseModule, elkModule); - const filter = container.get(IElementFilter); - expect(filter).to.be.instanceOf(GlspElementFilter); - const configurator = container.get(ILayoutConfigurator); - expect(configurator).to.be.instanceOf(FallbackGlspLayoutConfigurator); - const graphOptions = configurator.apply(new GGraph(), new SModelIndex()); + const filter = container.get(ElementFilter); + expect(filter).to.be.instanceOf(DefaultElementFilter); + const configurator = container.get(LayoutConfigurator); + expect(configurator).to.be.instanceOf(FallbackLayoutConfigurator); + const graphOptions = configurator.apply(new GGraph()); expect(graphOptions).not.to.be.undefined; expect(graphOptions!['elk.algorithm']).to.equal(algorithm); }); @@ -66,8 +63,8 @@ describe('test configureELKLayoutModule', () => { const elkModule = configureELKLayoutModule({ algorithms: [algorithm], defaultLayoutOptions }); const container = new Container(); container.load(baseModule, elkModule); - const configurator = container.get(ILayoutConfigurator); - const graphOptions = configurator.apply(new GGraph(), new SModelIndex()); + const configurator = container.get(LayoutConfigurator); + const graphOptions = configurator.apply(new GGraph()); expect(graphOptions).not.to.be.undefined; expect(graphOptions).to.include(defaultLayoutOptions); }); @@ -77,7 +74,7 @@ describe('test configureELKLayoutModule', () => { const elkModule = configureELKLayoutModule({ algorithms: [algorithm], layoutConfigurator: CustomLayoutConfigurator }); const container = new Container(); container.load(baseModule, elkModule); - const configurator = container.get(ILayoutConfigurator); + const configurator = container.get(LayoutConfigurator); expect(configurator).to.be.instanceOf(CustomLayoutConfigurator); }); @@ -86,7 +83,7 @@ describe('test configureELKLayoutModule', () => { const elkModule = configureELKLayoutModule({ algorithms: [algorithm], elementFilter: CustomElementFilter }); const container = new Container(); container.load(baseModule, elkModule); - const filter = container.get(IElementFilter); + const filter = container.get(ElementFilter); expect(filter).to.be.instanceOf(CustomElementFilter); }); }); diff --git a/packages/layout-elk/src/di.config.ts b/packages/layout-elk/src/di.config.ts index 5108b97..fdcabfe 100644 --- a/packages/layout-elk/src/di.config.ts +++ b/packages/layout-elk/src/di.config.ts @@ -13,15 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LayoutEngine } from '@eclipse-glsp/server-node/'; +import { LayoutEngine, ModelState } from '@eclipse-glsp/server-node'; import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled'; import { ContainerModule } from 'inversify'; -import { ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk'; -import { GlspElkLayoutEngine } from '.'; -import { BasicTypeMapper } from './basic-type-mapper'; -import { GlspElementFilter } from './glsp-element-filter'; -import { BaseElkLayoutEngine } from './glsp-elk-layout-engine'; -import { FallbackGlspLayoutConfigurator, GlspLayoutConfigurator } from './glsp-layout-configurator'; +import { ElkFactory } from 'sprotty-elk'; +import { ElementFilter, LayoutConfigurator } from '.'; +import { DefaultElementFilter } from './element-filter'; +import { GlspElkLayoutEngine } from './glsp-elk-layout-engine'; +import { FallbackLayoutConfigurator } from './layout-configurator'; type Constructor = new (...args: any[]) => T; @@ -39,15 +38,15 @@ export interface ElkModuleOptions { */ defaultLayoutOptions?: LayoutOptions; /** - * The custom {@link GlspLayoutConfigurator} class that should be bound. If this option is not set - * the {@link FallbackGlspLayoutConfigurator} is bound instead. + * The custom {@link LayoutConfigurator} class that should be bound. If this option is not set + * the {@link FallbackLayoutConfigurator} is bound instead. */ - layoutConfigurator?: Constructor; + layoutConfigurator?: Constructor; /** - * The custom {@link GlspElementFilter} class that should be bound. If this option is not set, the default implementation + * The custom {@link ElementFilter} class that should be bound. If this option is not set, the default implementation * is bound. */ - elementFilter?: Constructor; + elementFilter?: Constructor; } /** @@ -58,13 +57,8 @@ export interface ElkModuleOptions { * The constructed module is not intended for standalone use cases and only works in combination with a GLSPDiagramModule. * * * The following bindings are provided: - * - {@link BasicTypeMapper} - * - {@link BaseElkLayoutEngine} - * - {@link ElkLayoutEngine} - * - {@link GlspElementFilter} - * - {@link IElementFilter} - * - {@link GlspLayoutConfigurator} * - {@link ILayoutConfigurator} + * - {@link IElementFilter} * - {@link LayoutEngine} * - {@link ElkFactory} * @@ -73,38 +67,19 @@ export interface ElkModuleOptions { */ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerModule { return new ContainerModule(bind => { - bind(BasicTypeMapper).toSelf().inSingletonScope(); - bind(BaseElkLayoutEngine) - .toDynamicValue(context => { - const elkFactory = context.container.get(ElkFactory); - const elementFilter = context.container.get(IElementFilter); - const layoutConfigurator = context.container.get(ILayoutConfigurator); - const typeMapper = context.container.get(BasicTypeMapper); - return new BaseElkLayoutEngine(elkFactory, elementFilter, layoutConfigurator, typeMapper); - }) - .inSingletonScope(); - - bind(ElkLayoutEngine).to(BaseElkLayoutEngine); - if (options.elementFilter) { - bind(GlspElementFilter).to(options.elementFilter).inSingletonScope(); + bind(ElementFilter).to(options.elementFilter).inSingletonScope(); } else { - bind(GlspElementFilter).toSelf().inSingletonScope(); + bind(ElementFilter).to(DefaultElementFilter).inSingletonScope(); } - bind(IElementFilter).toService(GlspElementFilter); if (options.layoutConfigurator) { - bind(GlspLayoutConfigurator).to(options.layoutConfigurator); + bind(LayoutConfigurator).to(options.layoutConfigurator); } else { - bind(GlspLayoutConfigurator) - .toDynamicValue(context => { - const typeMapper = context.container.get(BasicTypeMapper); - return new FallbackGlspLayoutConfigurator(typeMapper, options.algorithms, options.defaultLayoutOptions); - }) + bind(LayoutConfigurator) + .toDynamicValue(context => new FallbackLayoutConfigurator(options.algorithms, options.defaultLayoutOptions)) .inSingletonScope(); } - bind(ILayoutConfigurator).toService(GlspLayoutConfigurator); - bind(LayoutEngine).to(GlspElkLayoutEngine).inSingletonScope(); const elkFactory: ElkFactory = () => new ElkConstructor({ @@ -113,5 +88,17 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo }); bind(ElkFactory).toConstantValue(elkFactory); + + bind(GlspElkLayoutEngine) + .toDynamicValue(context => { + const container = context.container; + const factory = container.get(ElkFactory); + const filter = container.get(ElementFilter); + const configurator = container.get(LayoutConfigurator); + const modelState = container.get(ModelState); + return new GlspElkLayoutEngine(factory, filter, configurator, modelState); + }) + .inSingletonScope(); + bind(LayoutEngine).toService(GlspElkLayoutEngine); }); } diff --git a/packages/layout-elk/src/element-filter.ts b/packages/layout-elk/src/element-filter.ts new file mode 100644 index 0000000..60e9b95 --- /dev/null +++ b/packages/layout-elk/src/element-filter.ts @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2018-2022 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, GLabel, GModelElement, GNode, GPort, ModelState } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; + +export const ElementFilter = Symbol('ElementFilter'); +/** + * Filter used to determine which model elements should be included in the automatic layout. + */ +export interface ElementFilter { + /** + * Applies the element filter on the given element, + * @param element The element on which the filter should be applied. + * @returns `true` if the element should be included in the automatic layout, `false` otherwise. + */ + apply(element: GModelElement): boolean; +} + +/** + * Default implementation of {@link ElementFilter}. + * Without further configuration this filter includes all basic model elements (nodes,edges,labels,ports) that are + * part of the graphical model. For each basic type a custom filter behavior is in place. + * Edges that have no source or target are filtered out. In addition, edges that are connected to a filtered element are filtered out + * as well. The filter behavior for each of the basic types can be customized by overriding the corresponding `filter` method. + * (e.g. {@link DefaultElementFilter.filterNode}) + */ +@injectable() +export class DefaultElementFilter implements ElementFilter { + @inject(ModelState) + protected modelState: ModelState; + + apply(element: GModelElement): boolean { + if (element instanceof GNode) { + return this.filterNode(element); + } else if (element instanceof GEdge) { + return this.filterEdge(element); + } else if (element instanceof GLabel) { + this.filterLabel(element); + } else if (element instanceof GPort) { + this.filterPort(element); + } + return true; + } + + protected filterNode(node: GNode): boolean { + return true; + } + + protected filterEdge(edge: GEdge): boolean { + const source = this.modelState.index.get(edge.sourceId); + + if (!source || (source instanceof GNode && !this.filterNode(source)) || (source instanceof GPort && !this.filterPort(source))) { + return false; + } + + const target = this.modelState.index.get(edge.targetId); + + if (!target || (target instanceof GNode && !this.filterNode(target)) || (target instanceof GPort && !this.filterPort(target))) { + return false; + } + return true; + } + + protected filterLabel(label: GLabel): boolean { + return true; + } + + protected filterPort(port: GPort): boolean { + return true; + } +} diff --git a/packages/layout-elk/src/glsp-element-filter.ts b/packages/layout-elk/src/glsp-element-filter.ts deleted file mode 100644 index 5c68daf..0000000 --- a/packages/layout-elk/src/glsp-element-filter.ts +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { inject, injectable } from 'inversify'; -import { DefaultElementFilter } from 'sprotty-elk'; -import { SModelElement } from 'sprotty-protocol'; -import { BasicTypeMapper } from './basic-type-mapper'; - -/** - * The default `IElementFilter` used to determine which model elements should be included in the automatic layout. - * Without further configuration this filter includes all basic model elements (nodes,edges,labels,ports) that are - * part of the graphical model. For each a custom filter behavior is in place. Edges that have no source or target are filtered out. - * In addition, edges that are connected to a filtered element are filtered out as well. - * The filter behavior for each of the basic types can be customized by overriding the corresponding `filter` method. - * (e.g. {@link GlspElementFilter.filterNode}) - * - */ -@injectable() -export class GlspElementFilter extends DefaultElementFilter { - @inject(BasicTypeMapper) - protected typeMapper: BasicTypeMapper; - - protected getBasicType(smodel: SModelElement): string { - return this.typeMapper.getBasicType(smodel); - } -} diff --git a/packages/layout-elk/src/glsp-elk-layout-engine.ts b/packages/layout-elk/src/glsp-elk-layout-engine.ts index b51c3e3..cb76b8f 100644 --- a/packages/layout-elk/src/glsp-elk-layout-engine.ts +++ b/packages/layout-elk/src/glsp-elk-layout-engine.ts @@ -1,6 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * + * Copyright (c) 2018-2022 TypeFox and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -13,45 +12,321 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LayoutEngine, ModelState } from '@eclipse-glsp/server-node'; -import { inject, injectable } from 'inversify'; -import { ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/inversify'; -import { SModelElement } from 'sprotty-protocol/lib/model'; -import { BasicTypeMapper } from './basic-type-mapper'; +import { + findParent, + findParentByClass, + GCompartment, + GEdge, + GGraph, + GLabel, + GModelElement, + GModelElementConstructor, + GModelRoot, + GNode, + GPort, + GShapeElement, + LayoutEngine, + MaybePromise, + ModelState, + Point +} from '@eclipse-glsp/server-node'; +import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkPrimitiveEdge, ElkShape } from 'elkjs/lib/elk-api'; +import { injectable } from 'inversify'; +import { ElkFactory } from 'sprotty-elk'; +import { ElementFilter } from './element-filter'; +import { LayoutConfigurator } from './layout-configurator'; /** - * Layout engine that delegates to ELK by transforming the graphical model into an ELK graph. + * An implementation of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState}, + * transforms this model into an ELK graph and then invokes the underlying ELK instance for layout computation. */ @injectable() -export class BaseElkLayoutEngine extends ElkLayoutEngine { +export class GlspElkLayoutEngine implements LayoutEngine { + protected readonly elk: ELK; + + protected elkEdges: ElkEdge[]; + protected idToElkElement: Map; + constructor( - @inject(ElkFactory) elkFactory: ElkFactory, - @inject(IElementFilter) protected readonly filter: IElementFilter, - @inject(ILayoutConfigurator) protected readonly configurator: ILayoutConfigurator, - @inject(BasicTypeMapper) protected typeMapper: BasicTypeMapper + elkFactory: ElkFactory, + protected readonly filter: ElementFilter, + protected readonly configurator: LayoutConfigurator, + protected modelState: ModelState ) { - super(elkFactory, filter, configurator); + this.elk = elkFactory(); } + layout(): MaybePromise { + const root = this.modelState.root; + if (!(root instanceof GGraph)) { + return root; + } - protected getBasicType(smodel: SModelElement): string { - return this.typeMapper.getBasicType(smodel); + this.elkEdges = []; + this.idToElkElement = new Map(); + const elkGraph = this.transformToElk(root) as ElkNode; + return this.elk.layout(elkGraph).then(result => { + this.applyLayout(result); + return root; + }); } -} -/** - * A implement of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState} - * and delegates the to an underlying {@link BaseElkLayoutEngine} instance for computing the layout. - */ -@injectable() -export class GlspElkLayoutEngine implements LayoutEngine { - @inject(ModelState) - protected modelState: ModelState; + protected transformToElk(model: GModelElement): ElkGraphElement { + if (model instanceof GGraph) { + const graph = this.transformGraph(model); + this.elkEdges.forEach(elkEdge => { + const parent = this.findCommonAncestor(elkEdge as ElkPrimitiveEdge); + if (parent) { + parent.edges!.push(elkEdge); + } + }); + return graph; + } else if (model instanceof GNode) { + return this.transformNode(model); + } else if (model instanceof GEdge) { + return this.transformEdge(model); + } else if (model instanceof GLabel) { + return this.transformPort(model); + } else if (model instanceof GPort) { + return this.transformPort(model); + } + + throw new Error('Type not supported: ' + model.type); + } + + /** + * Searches for all children of the given element that are an instance of the given {@link GModelElementConstructor} + * and are included by the {@link ElementFilter}. Also considers children that are nested inside of {@link GCompartment}s. + * @param element The element whose children should be queried. + * @param constructor The class instance that should be matched + * @returns A list of all matching children. + */ + protected findChildren(element: GModelElement, constructor: GModelElementConstructor): G[] { + const result: G[] = []; + element.children.forEach(child => { + if (child instanceof constructor && this.filter.apply(element)) { + result.push(child); + } else if (child instanceof GCompartment) { + result.push(...this.findChildren(child, constructor)); + } + }); + + return result; + } + + protected findCommonAncestor(elkEdge: ElkPrimitiveEdge): ElkNode | undefined { + const source = this.modelState.index.get(elkEdge.source); + const target = this.modelState.index.get(elkEdge.target); + if (!source || !target) { + return undefined; + } + + const sourceParent = findParent(source.parent, parent => parent instanceof GNode || parent instanceof GGraph); + const targetParent = findParent(target.parent, parent => parent instanceof GNode || parent instanceof GGraph); + + if (!sourceParent || !targetParent) { + return undefined; + } + + if (sourceParent === targetParent) { + return this.idToElkElement.get(sourceParent.id); + } else if (source === targetParent) { + return this.idToElkElement.get(source.id); + } else if (target === sourceParent) { + return this.idToElkElement.get(target.id); + } + return undefined; + } + + protected transformGraph(graph: GGraph): ElkGraphElement { + const elkGraph: ElkNode = { + id: graph.id, + layoutOptions: this.configurator.apply(graph) + }; + if (graph.children) { + elkGraph.children = this.findChildren(graph, GNode).map(child => this.transformToElk(child)) as ElkNode[]; + elkGraph.edges = []; + this.elkEdges.push(...(this.findChildren(graph, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); + } + + this.idToElkElement.set(graph.id, elkGraph); + return elkGraph; + } + + protected transformNode(node: GNode): ElkNode { + const elkNode: ElkNode = { + id: node.id, + layoutOptions: this.configurator.apply(node) + }; + + if (node.children) { + elkNode.children = this.findChildren(node, GNode).map(child => this.transformToElk(child)) as ElkNode[]; + elkNode.edges = []; + this.elkEdges.push(...(this.findChildren(node, GEdge).map(child => this.transformToElk(child)) as ElkEdge[])); + + elkNode.labels = this.findChildren(node, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; + elkNode.ports = this.findChildren(node, GPort).map(child => this.transformToElk(child)) as ElkPort[]; + } + + this.transformShape(elkNode, node); + this.idToElkElement.set(node.id, elkNode); + + return elkNode; + } + + protected transformShape(elkShape: ElkShape, shape: GShapeElement): void { + if (shape.position) { + elkShape.x = shape.position.x; + elkShape.y = shape.position.y; + } + if (shape.size) { + elkShape.width = shape.size.width; + elkShape.height = shape.size.height; + } + } + + protected transformEdge(edge: GEdge): ElkEdge { + const elkEdge: ElkPrimitiveEdge = { + id: edge.id, + source: edge.sourceId, + target: edge.targetId, + layoutOptions: this.configurator.apply(edge) + }; + const sourceElement = this.modelState.index.get(edge.sourceId); + if (sourceElement instanceof GPort) { + const parentNode = findParentByClass(sourceElement, GNode); + if (parentNode) { + elkEdge.source = parentNode.id; + elkEdge.sourcePort = sourceElement.id; + } + } + + const targetElement = this.modelState.index.get(edge.targetId); + if (sourceElement instanceof GPort) { + const parentNode = findParentByClass(targetElement, GNode); + if (parentNode) { + elkEdge.target = parentNode.id; + elkEdge.targetPort = targetElement.id; + } + } + + if (edge.children) { + elkEdge.labels = this.findChildren(edge, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; + } + const points = edge.routingPoints; + if (points && points.length >= 2) { + elkEdge.sourcePoint = points[0]; + elkEdge.bendPoints = points.slice(1, points.length - 1); + elkEdge.targetPoint = points[points.length - 1]; + } + this.idToElkElement.set(edge.id, elkEdge); + return elkEdge; + } + + protected transformLabel(label: GLabel): ElkLabel { + const elkLabel: ElkLabel = { + id: label.id, + text: label.text, + layoutOptions: this.configurator.apply(label) + }; + this.transformShape(elkLabel, label); + this.idToElkElement.set(label.id, elkLabel); + return elkLabel; + } + + protected transformPort(port: GPort): ElkPort { + const elkPort: ElkPort = { + id: port.id, + layoutOptions: this.configurator.apply(port) + }; + if (port.children) { + elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)) as ElkLabel[]; + } + this.transformShape(elkPort, port); + this.idToElkElement.set(port.id, elkPort); + return elkPort; + } + + protected applyLayout(elkNode: ElkNode): void { + const element = this.modelState.index.get(elkNode.id); + if (element instanceof GNode) { + this.applyShape(element, elkNode); + } + if (elkNode.children) { + for (const child of elkNode.children) { + this.applyLayout(child); + } + } + if (elkNode.edges) { + for (const elkEdge of elkNode.edges) { + const edge = this.modelState.index.get(elkEdge.id); + if (edge instanceof GEdge) { + this.applyEdge(edge, elkEdge); + } + } + } + + if (elkNode.ports) { + for (const elkPort of elkNode.ports) { + const port = this.modelState.index.findByClass(elkPort.id, GPort); + if (port) { + this.applyShape(port, elkPort); + } + } + } + } + + protected applyShape(shape: GShapeElement, elkShape: ElkShape): void { + if (elkShape.x !== undefined && elkShape.y !== undefined) { + shape.position = { x: elkShape.x, y: elkShape.y }; + } + if (elkShape.width !== undefined && elkShape.height !== undefined) { + shape.size = { width: elkShape.width, height: elkShape.height }; + } + + if (elkShape.labels) { + for (const elkLabel of elkShape.labels) { + const label = this.modelState.index.findByClass(elkLabel.id, GLabel); + if (label) { + this.applyShape(label, elkLabel); + } + } + } + } - @inject(BaseElkLayoutEngine) - protected elkLayoutEngine: BaseElkLayoutEngine; + protected applyEdge(edge: GEdge, elkEdge: ElkEdge): void { + const points: Point[] = []; + if ((elkEdge as any).sections && (elkEdge as any).sections.length > 0) { + const section = (elkEdge as ElkExtendedEdge).sections[0]; + if (section.startPoint) { + points.push(section.startPoint); + } + if (section.bendPoints) { + points.push(...section.bendPoints); + } + if (section.endPoint) { + points.push(section.endPoint); + } + } else { + const section = elkEdge as ElkPrimitiveEdge; + if (section.sourcePoint) { + points.push(section.sourcePoint); + } + if (section.bendPoints) { + points.push(...section.bendPoints); + } + if (section.targetPoint) { + points.push(section.targetPoint); + } + } + edge.routingPoints = points; - async layout(): Promise { - const graph = this.modelState.root; - await this.elkLayoutEngine.layout(graph); + if (elkEdge.labels) { + elkEdge.labels.forEach(elkLabel => { + const label = this.modelState.index.findByClass(elkLabel.id, GLabel); + if (label) { + this.applyShape(label, elkLabel); + } + }); + } } } diff --git a/packages/layout-elk/src/glsp-layout-configurator.ts b/packages/layout-elk/src/glsp-layout-configurator.ts deleted file mode 100644 index 72b71a0..0000000 --- a/packages/layout-elk/src/glsp-layout-configurator.ts +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { LayoutOptions } from 'elkjs'; -import { inject, injectable } from 'inversify'; -import { DefaultLayoutConfigurator } from 'sprotty-elk'; -import { SGraph, SModelElement, SModelIndex } from 'sprotty-protocol'; -import { BasicTypeMapper } from './basic-type-mapper'; - -/** - * Configurator for ELK layout algorithms; provides mappings of layout options for each model element. - * For a list of all available layout options checkout the official ELK documentation. - * A minimal configuration should at least specify the `elk.algorithm` option as - * return value of {@link GlspLayoutConfigurator.graphOptions}. - * {@link https://www.eclipse.org/elk/reference/options.html} - */ -@injectable() -export class GlspLayoutConfigurator extends DefaultLayoutConfigurator { - @inject(BasicTypeMapper) - protected typeMapper: BasicTypeMapper; - - protected getBasicType(smodel: SModelElement): string { - return this.typeMapper.getBasicType(smodel); - } -} - -/** - * A fallback configurator that is used in the `configureELKLayoutModule` utility method. - * If no explicit layout configurator binding is provided a new instance of the fallback configurator is - * bound as replacement. Basic layout options for the root graph element are derived from the - * configuration parameters that haven been passed to the `configureELKLayoutModule` function. - */ -@injectable() -export class FallbackGlspLayoutConfigurator extends GlspLayoutConfigurator { - protected fallbackGraphOptions: LayoutOptions; - constructor(protected typeMapper: BasicTypeMapper, algorithms: string[], defaultLayoutOptions: LayoutOptions = {}) { - super(); - this.fallbackGraphOptions = { - 'elk.algorithm': algorithms[0], - ...defaultLayoutOptions - }; - } - - protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions | undefined { - return this.fallbackGraphOptions; - } -} diff --git a/packages/layout-elk/src/index.ts b/packages/layout-elk/src/index.ts index 29e21ca..be72fc2 100644 --- a/packages/layout-elk/src/index.ts +++ b/packages/layout-elk/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022 EclipseSource and others. + * Copyright (c) 2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export * from 'sprotty-elk'; -export { SModelIndex } from 'sprotty-protocol/lib/utils/model-utils'; -export * from './basic-type-mapper'; export * from './di.config'; -export * from './glsp-element-filter'; +export * from './element-filter'; export * from './glsp-elk-layout-engine'; -export * from './glsp-layout-configurator'; +export * from './layout-configurator'; +export * from './sprotty-elk'; diff --git a/packages/layout-elk/src/layout-configurator.ts b/packages/layout-elk/src/layout-configurator.ts new file mode 100644 index 0000000..fc34347 --- /dev/null +++ b/packages/layout-elk/src/layout-configurator.ts @@ -0,0 +1,101 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GEdge, GGraph, GLabel, GModelElement, GNode, GPort, ModelState } from '@eclipse-glsp/server-node'; +import { inject, injectable } from 'inversify'; +import { LayoutOptions } from './sprotty-elk'; + +export const LayoutConfigurator = Symbol('LayoutConfigurator'); +/** + * Configurator for ELK layout algorithms; provides mappings of layout options for each model element. + * For a list of all available layout options checkout the official ELK documentation: + * {@link https://www.eclipse.org/elk/reference/options.html} + */ +export interface LayoutConfigurator { + /** + * Computes the {@link LayoutOptions} for the given {@link GModelElement}. + * @param element The element for which the layout options should be computed. + * @returns The set of layout options for the given element or `undefined`. + */ + apply(element: GModelElement): LayoutOptions | undefined; +} + +/** + * Abstract default implementation of {@link LayoutConfigurator}. + * A minimal configuration should at least specify the `elk.algorithm` option as + * return value of {@link DefaultLayoutConfigurator.graphOptions}. + */ +@injectable() +export abstract class AbstractLayoutConfigurator implements LayoutConfigurator { + @inject(ModelState) + protected modelState: ModelState; + + apply(element: GModelElement): LayoutOptions | undefined { + if (element instanceof GGraph) { + return this.graphOptions(element); + } else if (element instanceof GNode) { + return this.nodeOptions(element); + } else if (element instanceof GEdge) { + return this.edgeOptions(element); + } else if (element instanceof GLabel) { + this.labelOptions(element); + } else if (element instanceof GPort) { + this.portOptions(element); + } + return undefined; + } + + protected graphOptions(graph: GGraph): LayoutOptions | undefined { + return undefined; + } + + protected nodeOptions(node: GNode): LayoutOptions | undefined { + return undefined; + } + + protected edgeOptions(edge: GEdge): LayoutOptions | undefined { + return undefined; + } + + protected labelOptions(label: GLabel): LayoutOptions | undefined { + return undefined; + } + + protected portOptions(sport: GPort): LayoutOptions | undefined { + return undefined; + } +} + +/** + * A fallback configurator that is used in the `configureELKLayoutModule` utility method. + * If no explicit layout configurator binding is provided a new instance of the fallback configurator is + * bound as replacement. Basic layout options for the root graph element are derived from the + * configuration parameters that haven been passed to the `configureELKLayoutModule` function. + */ +@injectable() +export class FallbackLayoutConfigurator extends AbstractLayoutConfigurator { + protected fallbackGraphOptions: LayoutOptions; + constructor(algorithms: string[], defaultLayoutOptions: LayoutOptions = {}) { + super(); + this.fallbackGraphOptions = { + 'elk.algorithm': algorithms[0], + ...defaultLayoutOptions + }; + } + + protected graphOptions(sgraph: GGraph): LayoutOptions | undefined { + return this.fallbackGraphOptions; + } +} diff --git a/examples/workflow-server/src/workflow-model-index.ts b/packages/layout-elk/src/sprotty-elk.ts similarity index 61% rename from examples/workflow-server/src/workflow-model-index.ts rename to packages/layout-elk/src/sprotty-elk.ts index 51065d9..a6a129c 100644 --- a/examples/workflow-server/src/workflow-model-index.ts +++ b/packages/layout-elk/src/sprotty-elk.ts @@ -13,19 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GEdge, GModelIndex } from '@eclipse-glsp/server-node'; -import { injectable } from 'inversify'; -import { ModelTypes } from './util/model-types'; - -@injectable() -export class WorkflowModelIndex extends GModelIndex { - getAllEdges(): GEdge[] { - const edges: GEdge[] = super.getAllEdges(); - this.getElements(ModelTypes.WEIGHTED_EDGE).forEach(weighted => { - if (weighted instanceof GEdge) { - edges.push(weighted); - } - }); - return edges; - } -} +// Reexport of the relevant sprotty-elk typings +export { LayoutOptions } from 'elkjs'; +export { ElkFactory } from 'sprotty-elk/lib/inversify'; diff --git a/packages/server-node/src/features/layout/layout-engine.ts b/packages/server-node/src/features/layout/layout-engine.ts index 372b8e8..40ae241 100644 --- a/packages/server-node/src/features/layout/layout-engine.ts +++ b/packages/server-node/src/features/layout/layout-engine.ts @@ -14,16 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GModelRoot } from '@eclipse-glsp/graph'; import { MaybePromise } from '@eclipse-glsp/protocol'; /** * A layout engine is able to compute layout information for a model. */ export interface LayoutEngine { - /* - * Compute a layout for the model state and modify the model accordingly. + /** + * Computes a layout for the model state and modify the model accordingly. + * @returns the layouted {@link GModelRoot}. */ - layout(): MaybePromise; + layout(): MaybePromise; } export const LayoutEngine = Symbol('LayoutEngine'); diff --git a/packages/server-node/src/features/model/gmodel-index.ts b/packages/server-node/src/features/model/gmodel-index.ts index 3490f9e..c9adfea 100644 --- a/packages/server-node/src/features/model/gmodel-index.ts +++ b/packages/server-node/src/features/model/gmodel-index.ts @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GEdge, GModelElement, GModelElementConstructor, GModelRoot, GNode } from '@eclipse-glsp/graph'; -import { DefaultTypes } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { getOrThrow, GLSPServerError } from '../../utils/glsp-server-error'; @@ -102,7 +101,7 @@ export class GModelIndex { * @param constructor The class of which the returned elements should be instances. * @returns A set containing the elements of type constructor. */ - getAllByClass(constructor: GModelElementConstructor): G[] { + getAllByClass(constructor: GModelElementConstructor): G[] { return Array.from(this.idToElement.values()).filter(element => element instanceof constructor) as G[]; } @@ -164,12 +163,6 @@ export class GModelIndex { * @returns All edges in the index. */ getAllEdges(): GEdge[] { - const edges: GEdge[] = []; - this.getElements(DefaultTypes.EDGE).forEach(edge => { - if (edge instanceof GEdge) { - edges.push(edge); - } - }); - return edges; + return this.getAllByClass(GEdge); } } diff --git a/packages/server-node/src/features/model/gmodel-serializer.ts b/packages/server-node/src/features/model/gmodel-serializer.ts index 24947b0..9909134 100644 --- a/packages/server-node/src/features/model/gmodel-serializer.ts +++ b/packages/server-node/src/features/model/gmodel-serializer.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* Derived from sprotty (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel-factory.ts) */ -import { GModelElement, GModelElementConstructor, GModelRoot } from '@eclipse-glsp/graph'; +import { GModelElement, GModelElementConstructor, GModelElementSchema, GModelRoot, GModelRootSchema } from '@eclipse-glsp/graph'; import { SModelElementSchema, SModelRootSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { DiagramConfiguration } from '../../diagram/diagram-configuration'; @@ -22,13 +22,41 @@ import { GLSPServerError } from '../../utils/glsp-server-error'; export const GModelSerializer = Symbol('GModelSerializer'); +/** + * The `GModelSerializer` is used to transform a graphical model received as serializable JSON schema into the ES6-class based + * model representation and vice versa. It has to be invoked for whenever a graphical model is received in JSON format + * (i.e. by the GLSP client) to derive the corresponding class based model which is used internally by the GLSP server. In addition, + * it has to be invoked before the graphical model is sent based to the client. This is necessary to resolve the parent-child cycles + * inside of the graphical model which ensures that the model is serializable. + */ export interface GModelSerializer { - createRoot(schema: SModelRootSchema): GModelRoot; - - createElement(schema: SModelElementSchema, parent?: GModelElement): GModelElement; - - createSchema(element: GModelElement): SModelElementSchema; - + /** + * Transform the given {@link GModelRootSchema} into its {@link GModelRoot} representation. + * @throws An error if the received element cannot be transformed into a {@link GModelRoot}. + * @param schema The root schema that should be transformed. + * @returns The transformed {@link GModelRoot}. + */ + createRoot(schema: GModelRootSchema): GModelRoot; + + /** + * Transform the given {@link GModelRootSchema} into its {@link GModelElement} representation. + * @throws An error if the received element cannot be transformed into a {@link GModelElement}. + * @param schema The element schema that should be transformed. + * @returns The transformed {@link GModelElement}. + */ + createElement(schema: GModelElementSchema, parent?: GModelElement): GModelElement; + + /** + * Transforms the given {@link GModelElement} into its serializable {@link GModelElementSchema} representation. + * @param element The element that should be serialized. + * @returns The transformed {@link GModelElementSchema}. + */ + createSchema(element: GModelElement): GModelElementSchema; + + /** + * The set of reserved property keys that should be excluded from serialization. Typically + * this set contains at least the `children` and `parent` keys to avoid parent-child cycles. + */ reservedKeys: string[]; } @@ -36,7 +64,7 @@ export interface GModelSerializer { export class DefaultGModelSerializer implements GModelSerializer { @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; - reservedKeys = ['children', 'parent', 'index']; + reservedKeys = ['children', 'parent', 'index', 'source', 'target']; createRoot(schema: SModelElementSchema): GModelRoot { const constructor = this.getConfiguredConstructor(schema); if (constructor) { From faff303dba8cc4d9f807af68ce4575214fc8c471 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 4 Feb 2022 11:51:45 +0100 Subject: [PATCH 081/566] Fix typos (#164) * Fix typos Co-authored-by: Tobias Ortmayr --- .../tool-feedback/feedback-action-dispatcher.ts | 4 ++-- .../client/src/features/tools/base-glsp-tool.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index 15621c5..ee0a845 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,7 +30,7 @@ export interface IFeedbackEmitter {} * extending the `SModelRoot`. Therefore, tools can register themselves * as feedback emitters with actions they want to place for showing * feedback. This dispatcher will then re-establish all feedback actions - * of the registered tools, whenever the `SModelRoot` has been set or updated. + * of the registered emitters, whenever the `SModelRoot` has been set or updated. */ export interface IFeedbackActionDispatcher { /** diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 4d10371..5c3baf3 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -39,15 +39,15 @@ export abstract class BaseGLSPTool implements GLSPTool { return true; } - dispatchFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter): void { - this.feedbackDispatcher.registerFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); - } - dispatchActions(actions: Action[]): void { this.actionDispatcher.dispatchAll(actions); } - deregisterFeedback(actions: Action[], feedbackeEmitter?: IFeedbackEmitter): void { - this.feedbackDispatcher.deregisterFeedback(feedbackeEmitter ? feedbackeEmitter : this, actions); + dispatchFeedback(actions: Action[], feedbackEmitter?: IFeedbackEmitter): void { + this.feedbackDispatcher.registerFeedback(feedbackEmitter ?? this, actions); + } + + deregisterFeedback(actions: Action[], feedbackEmitter?: IFeedbackEmitter): void { + this.feedbackDispatcher.deregisterFeedback(feedbackEmitter ?? this, actions); } } From ae79767f16d631df897f7e7856c108de135de7d0 Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Mon, 7 Feb 2022 11:51:56 +0100 Subject: [PATCH 082/566] Adjust logging file name (#6) Format is now more unique and works on windows. Contributed on behalf of STMicroelectronics --- packages/server-node/src/di/app-module.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server-node/src/di/app-module.ts b/packages/server-node/src/di/app-module.ts index 80690ed..2335bc9 100644 --- a/packages/server-node/src/di/app-module.ts +++ b/packages/server-node/src/di/app-module.ts @@ -50,7 +50,11 @@ export function createWinstonInstance(options: T): wins } if (options.fileLog) { - const filename = `${new Date().toLocaleTimeString()}.log`; + const date = new Date(); + const dateString = `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}_ + ${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; + + const filename = `GLSP-Server_${dateString}.log`; transports.push(new winston.transports.File({ dirname: options.logDir, filename, format: fileLogFormat })); } From 51e14061956889541f7b150c610a546575f33323 Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Mon, 7 Feb 2022 13:05:26 +0100 Subject: [PATCH 083/566] Fix wrong line break in logging file name (#7) Contributed on behalf of STMicroelectronics --- packages/server-node/src/di/app-module.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server-node/src/di/app-module.ts b/packages/server-node/src/di/app-module.ts index 2335bc9..e7cebdb 100644 --- a/packages/server-node/src/di/app-module.ts +++ b/packages/server-node/src/di/app-module.ts @@ -51,8 +51,9 @@ export function createWinstonInstance(options: T): wins if (options.fileLog) { const date = new Date(); - const dateString = `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}_ - ${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; + const dateString = `${date.getDate()}-${ + date.getMonth() + 1 + }-${date.getFullYear()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; const filename = `GLSP-Server_${dateString}.log`; transports.push(new winston.transports.File({ dirname: options.logDir, filename, format: fileLogFormat })); From 74de20daa8b1576d2d789f9c18430e9f5082378c Mon Sep 17 00:00:00 2001 From: Lukas <42733123+ivy-lli@users.noreply.github.com> Date: Thu, 17 Mar 2022 13:10:02 +0100 Subject: [PATCH 084/566] Fix DefaultModelInitializationConstraint (#168) * Fix DefaultModelInitializationConstraint Wait for SetModelAction instead of UpdateModelAction to mark model as initialized. --- .../src/base/model-initialization-constraint.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts index fd320de..0dc3325 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { Action, isSetModelAction, isUpdateModelAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { InitializeCanvasBoundsAction } from 'sprotty'; import { Deferred } from 'sprotty/lib/utils/async'; @@ -74,21 +74,20 @@ export abstract class ModelInitializationConstraint { */ @injectable() export class DefaultModelInitializationConstraint extends ModelInitializationConstraint { - protected seenNonEmptyUpdateModel = false; + protected seenNonEmptyModelAction = false; isInitializedAfter(action: Action): boolean { - if (this.isNonEmptyUpdateModel(action)) { - this.seenNonEmptyUpdateModel = true; - } else if (this.seenNonEmptyUpdateModel && action.kind === InitializeCanvasBoundsAction.KIND) { + if (this.isNonEmptyModelAction(action)) { + this.seenNonEmptyModelAction = true; + } else if (this.seenNonEmptyModelAction && action.kind === InitializeCanvasBoundsAction.KIND) { return true; } return false; } - protected isNonEmptyUpdateModel(action: Action): boolean { - if (action && action.kind === UpdateModelAction.KIND) { - const updateModelAction = action as UpdateModelAction; - return updateModelAction.newRoot !== undefined && updateModelAction.newRoot.type !== 'NONE'; + protected isNonEmptyModelAction(action: Action): boolean { + if (isSetModelAction(action) || isUpdateModelAction(action)) { + return action.newRoot.type !== 'NONE'; } return false; } From 335980aa35b93d94a46de647950d5b743fb0ec67 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 22 Mar 2022 01:31:30 -0700 Subject: [PATCH 085/566] #559 Update to Typescript 4.5.5 (#167) #559 Update to Typescript 4.5.5 - Consume new @eclipse-glsp/config version and update to typescript 4.5.5. - Adapt code to conform to new "strict" mode and "noImplicitOverride" - Cleanup dev dependencies and move common dependencies into package root - Fix moch test setup Also: - Clean up and refactor `GLSPMouseTool` - Clean up and document `smodel-util` and `array-util` utility functions. - Align inversify version of @eclipse-glsp/protocol (#553) Part of eclipse-glsp/glsp/issues/559 Part of eclipse-glsp/glsp/issues/533 --- examples/workflow-glsp/package.json | 6 +- .../direct-task-editing/direct-task-editor.ts | 19 +- examples/workflow-glsp/src/model.ts | 12 +- examples/workflow-glsp/src/workflow-views.tsx | 2 +- examples/workflow-standalone/package.json | 5 +- examples/workflow-standalone/src/di.config.ts | 4 +- packages/client/.nycrc | 16 -- packages/client/mocha.opts | 7 - packages/client/package.json | 17 +- packages/client/src/base/action-dispatcher.ts | 8 +- packages/client/src/base/command-stack.ts | 6 +- .../src/base/drag-aware-mouse-listener.ts | 8 +- .../client/src/base/model/model-registry.ts | 4 +- .../src/base/model/update-model-command.ts | 4 +- .../base/selection-clearing-mouse-listener.ts | 2 +- .../base/tool-manager/glsp-tool-manager.ts | 14 +- .../client/src/base/view/view-registry.ts | 4 +- .../src/features/change-bounds/model.ts | 10 +- ...rvice-aware-context-menu-mouse-listener.ts | 2 +- .../features/decoration/decoration-placer.ts | 4 +- .../client/src/features/hints/type-hints.ts | 4 +- packages/client/src/features/hover/hover.ts | 2 +- .../src/features/layout/freeform-layout.ts | 6 +- .../src/features/layout/layout-commands.ts | 18 +- .../client/src/features/layout/vbox-layout.ts | 28 +-- .../src/features/mouse-tool/mouse-tool.ts | 64 ++----- .../client/src/features/reconnect/model.ts | 4 +- .../src/features/save/save-keylistener.ts | 4 +- .../src/features/select/selection-service.ts | 9 +- .../change-bounds-tool-feedback.ts | 19 +- .../tool-feedback/creation-tool-feedback.ts | 20 +-- .../tool-feedback/edge-edit-tool-feedback.ts | 30 ++-- .../tool-feedback/marquee-tool-feedback.ts | 4 +- .../src/features/tool-feedback/model.ts | 4 +- .../src/features/tool-palette/tool-palette.ts | 8 +- .../src/features/tools/change-bounds-tool.ts | 12 +- .../client/src/features/tools/delete-tool.ts | 6 +- .../src/features/tools/edge-creation-tool.ts | 6 +- .../src/features/tools/edge-edit-tool.ts | 10 +- .../src/features/tools/marquee-mouse-tool.ts | 12 +- .../client/src/features/tools/marquee-tool.ts | 4 +- packages/client/src/features/tools/model.ts | 4 +- .../src/features/tools/node-creation-tool.ts | 6 +- packages/client/src/features/tools/view.tsx | 5 +- .../features/validation/marker-navigator.ts | 10 +- .../src/features/validation/validate.ts | 8 +- .../viewport/glsp-scroll-mouse-listener.ts | 4 +- packages/client/src/lib/model.ts | 5 +- .../src/model-source/glsp-diagram-server.ts | 10 +- packages/client/src/utils/smodel-util.ts | 162 +++++++++++++----- packages/client/src/views/glsp-edge-view.tsx | 8 +- .../client/src/views/issue-marker-view.tsx | 4 +- .../client/src/views/rounded-corner-view.tsx | 5 +- packages/protocol/package.json | 9 +- .../src/action-protocol/tool-palette.ts | 14 +- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 7 +- packages/protocol/src/utils/array-util.ts | 92 ++++++++-- 57 files changed, 424 insertions(+), 357 deletions(-) delete mode 100644 packages/client/.nycrc delete mode 100644 packages/client/mocha.opts diff --git a/examples/workflow-glsp/package.json b/examples/workflow-glsp/package.json index 1219111..babf64f 100644 --- a/examples/workflow-glsp/package.json +++ b/examples/workflow-glsp/package.json @@ -30,13 +30,11 @@ "balloon-css": "^0.5.0" }, "devDependencies": { - "mvn-artifact-download": "5.1.0", - "rimraf": "^2.6.1", - "typescript": "^3.9.2" + "mvn-artifact-download": "5.1.0" }, "scripts": { "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf lib", + "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w" diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 78e5b33..3a0ffd4 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -34,9 +34,8 @@ import { ViewerOptions } from '@eclipse-glsp/client'; import { inject, injectable } from 'inversify'; -import { TYPES } from 'sprotty/lib'; +import { SModelIndex, TYPES } from 'sprotty/lib'; import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; - import { isTaskNode, TaskNode } from '../model'; export class ApplyTaskEditOperation implements Action { @@ -68,7 +67,7 @@ export class TaskEditor extends AbstractUIExtension { protected domHelper: DOMHelper; @inject(TYPES.ILogger) - protected logger: ILogger; + protected override logger: ILogger; protected task: TaskNode; protected autoSuggestion: AutoCompleteWidget; @@ -98,13 +97,13 @@ export class TaskEditor extends AbstractUIExtension { this.autoSuggestion.initialize(containerElement); } - show(root: Readonly, ...contextElementIds: string[]): void { + override show(root: Readonly, ...contextElementIds: string[]): void { super.show(root, ...contextElementIds); this.autoSuggestion.open(root); } - protected onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { - this.task = getTask(contextElementIds, root)[0]; + protected override onBeforeShow(containerElement: HTMLElement, root: Readonly, ...contextElementIds: string[]): void { + this.task = getTask(contextElementIds, root.index)[0]; this.autoSuggestion.inputField.value = ''; this.setPosition(containerElement); } @@ -151,12 +150,12 @@ export class TaskEditor extends AbstractUIExtension { this.actionDispatcher.dispatch(action); } - hide(): void { + override hide(): void { this.autoSuggestion.dispose(); super.hide(); } } -function getTask(ids: string[], element: SModelElement): TaskNode[] { - return ids.map(id => element.index.getById(id)).filter(isTaskNode); +function getTask(ids: string[], index: SModelIndex): TaskNode[] { + return ids.map(id => index.getById(id)).filter(element => element && isTaskNode(element)) as TaskNode[]; } diff --git a/examples/workflow-glsp/src/model.ts b/examples/workflow-glsp/src/model.ts index 5020515..cbe1ced 100644 --- a/examples/workflow-glsp/src/model.ts +++ b/examples/workflow-glsp/src/model.ts @@ -40,7 +40,7 @@ import { } from '@eclipse-glsp/client'; export class TaskNode extends RectangularNode implements Nameable, WithEditableLabel { - static readonly DEFAULT_FEATURES = [ + static override readonly DEFAULT_FEATURES = [ connectableFeature, deletableFeature, selectFeature, @@ -81,11 +81,11 @@ export class WeightedEdge extends SEdge { export class ActivityNode extends DiamondNode { nodeType: string = ActivityNode.Type.UNDEFINED; - size = { + override size = { width: 32, height: 32 }; - strokeWidth = 1; + override strokeWidth = 1; } export namespace ActivityNode { @@ -104,15 +104,15 @@ export class Icon extends SShapeElement implements LayoutContainer { static readonly DEFAULT_FEATURES = [boundsFeature, layoutContainerFeature, layoutableChildFeature, fadeFeature]; layout: string; - layoutOptions?: { [key: string]: string | number | boolean }; - size = { + override layoutOptions?: { [key: string]: string | number | boolean }; + override size = { width: 32, height: 32 }; } export class CategoryNode extends RectangularNode implements Nameable, WithEditableLabel { - static readonly DEFAULT_FEATURES = [ + static override readonly DEFAULT_FEATURES = [ deletableFeature, selectFeature, boundsFeature, diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index e35f1e0..7c918d5 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -33,7 +33,7 @@ const JSX = { createElement: svg }; @injectable() export class WorkflowEdgeView extends PolylineEdgeViewWithGapsOnIntersections { - protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { + protected override renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { const additionals = super.renderAdditionals(edge, segments, context); const p1 = segments[segments.length - 2]; const p2 = segments[segments.length - 1]; diff --git a/examples/workflow-standalone/package.json b/examples/workflow-standalone/package.json index bdead0d..160949e 100644 --- a/examples/workflow-standalone/package.json +++ b/examples/workflow-standalone/package.json @@ -27,11 +27,8 @@ "circular-dependency-plugin": "^5.2.2", "css-loader": "^2.1.0", "file-loader": "^6.2.0", - "reflect-metadata": "^0.1.13", - "rimraf": "^3.0.2", "source-map-loader": "^1.0.2", "style-loader": "^2.0.0", - "typescript": "^3.9.2", "webpack": "^4.46.0", "webpack-cli": "^4.9.1" }, @@ -40,7 +37,7 @@ }, "scripts": { "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf lib app/bundle.js app/bundle.js.map app/css", + "clean": "rimraf lib tsconfig.tsbuildinfo app/bundle.js app/bundle.js.map app/css", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "build": "tsc && webpack", "watch": "tsc -w -p ./tsconfig.json" diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 260a7d3..03320b8 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp/lib'; +import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; import { GLSPDiagramServer, GLSP_TYPES } from '@eclipse-glsp/client'; import { Container } from 'inversify'; import { ConsoleLogger, LogLevel, TYPES } from 'sprotty'; diff --git a/packages/client/.nycrc b/packages/client/.nycrc deleted file mode 100644 index 7f5c6c6..0000000 --- a/packages/client/.nycrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "typings", - "**/*.spec.ts" - ], - "reporter": [ - "html" - ], - "extension": [ - ".ts" - ], - "report-dir": "./artifacts/coverage" -} diff --git a/packages/client/mocha.opts b/packages/client/mocha.opts deleted file mode 100644 index 4af313b..0000000 --- a/packages/client/mocha.opts +++ /dev/null @@ -1,7 +0,0 @@ ---require ts-node/register ---require reflect-metadata/Reflect ---require ignore-styles ---reporter spec ---colors ---watch-extensions ts,tsx ---timeout 10000 diff --git a/packages/client/package.json b/packages/client/package.json index f4d2eb5..b6c1b43 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -39,27 +39,16 @@ "sprotty": "0.10.0" }, "devDependencies": { - "@babel/runtime": "^7.11.2", - "@types/chai": "4.1.3", - "@types/mocha": "^5.2.7", - "@types/node": "10.14.18", "@vscode/codicons": "^0.0.25", - "chai": "^4.2.0", - "ignore-styles": "^5.0.1", - "mocha": "^6.2.0", - "mocha-jenkins-reporter": "0.4.5", - "reflect-metadata": "^0.1.13", - "rimraf": "^2.6.1", - "ts-node": "^8.3.0", - "typescript": "^3.9.2" + "typescript": "^4.5.5" }, "scripts": { "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf lib", + "clean": "rimraf lib tsconfig.tsbuildinfo ", "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts,.tsx ./src", "watch": "tsc -w", - "test": "mocha --opts ./mocha.opts \"./src/**/*.spec.?(ts|tsx)\"", + "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter" }, "publishConfig": { diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 1f84bae..5595e4d 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,7 +23,7 @@ export class GLSPActionDispatcher extends ActionDispatcher { protected initializedConstraint = false; @inject(ModelInitializationConstraint) protected initializationConstraint: ModelInitializationConstraint; - initialize(): Promise { + override initialize(): Promise { return super.initialize().then(() => this.startModelInitialization()); } @@ -39,13 +39,13 @@ export class GLSPActionDispatcher extends ActionDispatcher { return this.initializationConstraint.onInitialized(); } - dispatch(action: Action): Promise { + override dispatch(action: Action): Promise { const result = super.dispatch(action); this.initializationConstraint.notifyDispatched(action); return result; } - protected handleAction(action: Action): Promise { + protected override handleAction(action: Action): Promise { if (isResponseAction(action)) { // clear timeout const timeout = this.timeouts.get(action.responseId); diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index ed5a70f..4ed3e51 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,12 +21,12 @@ import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from 'sprotty'; export class GLSPCommandStack extends CommandStack { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - undo(): Promise { + override undo(): Promise { this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new UndoOperation())); return this.thenUpdate(); } - redo(): Promise { + override redo(): Promise { this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new RedoOperation())); return this.thenUpdate(); } diff --git a/packages/client/src/base/drag-aware-mouse-listener.ts b/packages/client/src/base/drag-aware-mouse-listener.ts index 43a1fa4..f9e3eb2 100644 --- a/packages/client/src/base/drag-aware-mouse-listener.ts +++ b/packages/client/src/base/drag-aware-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,19 +28,19 @@ export class DragAwareMouseListener extends MouseListener { private _isMouseDown = false; private _isMouseDrag = false; - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { this._isMouseDown = true; return []; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { if (this._isMouseDown) { this._isMouseDrag = true; } return []; } - mouseUp(element: SModelElement, event: MouseEvent): Action[] { + override mouseUp(element: SModelElement, event: MouseEvent): Action[] { this._isMouseDown = false; if (this._isMouseDrag) { this._isMouseDrag = false; diff --git a/packages/client/src/base/model/model-registry.ts b/packages/client/src/base/model/model-registry.ts index a918438..cc85214 100644 --- a/packages/client/src/base/model/model-registry.ts +++ b/packages/client/src/base/model/model-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { SModelElement, SModelRegistry } from 'sprotty'; @injectable() export class GLSPModelRegistry extends SModelRegistry { - register(key: string, factory: (u: void) => SModelElement): void { + override register(key: string, factory: (u: void) => SModelElement): void { if (key === undefined) { throw new Error('Key is undefined'); } diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index b187d18..b1b6764 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -68,7 +68,7 @@ export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { this.actionHandlerRegistryProvider().then(registry => (this.actionHandlerRegistry = registry)); } - protected performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { + protected override performUpdate(oldRoot: SModelRoot, newRoot: SModelRoot, context: CommandExecutionContext): CommandReturn { if (this.feedbackActionDispatcher && this.actionHandlerRegistry) { // Create a temporary context wich defines the `newRoot` as `root` // This way we do not corrupt the redo/undo behavior of the super class diff --git a/packages/client/src/base/selection-clearing-mouse-listener.ts b/packages/client/src/base/selection-clearing-mouse-listener.ts index fe02a7b..7ae7bf5 100644 --- a/packages/client/src/base/selection-clearing-mouse-listener.ts +++ b/packages/client/src/base/selection-clearing-mouse-listener.ts @@ -30,7 +30,7 @@ import { MouseListener, SModelElement } from 'sprotty'; */ @injectable() export class SelectionClearingMouseListener extends MouseListener { - mouseDown(_target: SModelElement, event: MouseEvent): never[] { + override mouseDown(_target: SModelElement, event: MouseEvent): never[] { const selection = document.getSelection(); // eslint-disable-next-line no-null/no-null if (selection === null) { diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index 28a0a28..9c51cd9 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,8 +28,8 @@ export interface IGLSPToolManager extends ToolManager { export class GLSPToolManager extends ToolManager implements IGLSPToolManager, EditModeListener { protected editorContext?: EditorContextService; - @multiInject(GLSP_TYPES.ITool) @optional() tools: Tool[]; - @multiInject(GLSP_TYPES.IDefaultTool) @optional() defaultTools: Tool[]; + @multiInject(GLSP_TYPES.ITool) @optional() override tools: Tool[]; + @multiInject(GLSP_TYPES.IDefaultTool) @optional() override defaultTools: Tool[]; @inject(GLSP_TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider; @postConstruct() @@ -43,19 +43,19 @@ export class GLSPToolManager extends ToolManager implements IGLSPToolManager, Ed }); } - registerDefaultTools(...tools: Tool[]): void { + override registerDefaultTools(...tools: Tool[]): void { for (const tool of tools) { distinctAdd(this.defaultTools, tool); } } - registerTools(...tools: Tool[]): void { + override registerTools(...tools: Tool[]): void { for (const tool of tools) { distinctAdd(this.tools, tool); } } - enable(toolIds: string[]): void { + override enable(toolIds: string[]): void { this.disableActiveTools(); let tools = toolIds.map(id => this.tool(id)); if (this.editorContext && this.editorContext.isReadonly) { @@ -90,6 +90,6 @@ export interface GLSPTool extends Tool { isEditTool: boolean; } -export function isGLSPTool(tool: Tool): tool is GLSPTool { +export function isGLSPTool(tool: any): tool is GLSPTool { return 'isEditTool' in tool && typeof tool['isEditTool'] === 'boolean'; } diff --git a/packages/client/src/base/view/view-registry.ts b/packages/client/src/base/view/view-registry.ts index bb204d3..10e1b63 100644 --- a/packages/client/src/base/view/view-registry.ts +++ b/packages/client/src/base/view/view-registry.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { IView, ViewRegistry } from 'sprotty'; @injectable() export class GLSPViewRegistry extends ViewRegistry { - register(key: string, instance: IView): void { + override register(key: string, instance: IView): void { if (key === undefined) { throw new Error('Key is undefined'); } diff --git a/packages/client/src/features/change-bounds/model.ts b/packages/client/src/features/change-bounds/model.ts index 0561ad8..ac28fb6 100644 --- a/packages/client/src/features/change-bounds/model.ts +++ b/packages/client/src/features/change-bounds/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -51,14 +51,14 @@ export class SResizeHandle extends SChildElement implements Hoverable { static readonly TYPE = 'resize-handle'; constructor( - public readonly location?: ResizeHandleLocation, - public readonly type: string = SResizeHandle.TYPE, - public readonly hoverFeedback: boolean = false + readonly location?: ResizeHandleLocation, + override readonly type: string = SResizeHandle.TYPE, + readonly hoverFeedback: boolean = false ) { super(); } - hasFeature(feature: symbol): boolean { + override hasFeature(feature: symbol): boolean { return feature === hoverFeedbackFeature; } diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index c58d824..22ac02c 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -37,7 +37,7 @@ export class SelectionServiceAwareContextMenuMouseListener extends MouseListener /** * Opens the context menu on right-click. */ - mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { if (event.button === 2 && this.contextMenuService && this.menuProvider) { return this.openContextMenu(event, target); } diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 5970820..63934d2 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,7 +21,7 @@ import { Decoration, DecorationPlacer, isSizeable, ORIGIN_POINT, SChildElement, export class GlspDecorationPlacer extends DecorationPlacer { protected static readonly DECORATION_OFFSET: Point = { x: 12, y: 10 }; - protected getPosition(element: SModelElement & Decoration): Point { + protected override getPosition(element: SModelElement & Decoration): Point { if (element instanceof SChildElement && element.parent instanceof SRoutableElement) { return super.getPosition(element); } diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index 4b6bdd1..f5cc079 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -48,7 +48,7 @@ export class ApplyTypeHintsAction implements Action { @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { public static KIND = 'applyTypeHints'; - public readonly priority = 10; + public override readonly priority = 10; @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index 32cea53..e85855e 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -53,7 +53,7 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio } } - protected startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { + protected override startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { this.stopMouseOverTimer(); return new Promise(resolve => { this.state.mouseOverTimer = window.setTimeout(() => { diff --git a/packages/client/src/features/layout/freeform-layout.ts b/packages/client/src/features/layout/freeform-layout.ts index ccb83cc..b2f58a6 100644 --- a/packages/client/src/features/layout/freeform-layout.ts +++ b/packages/client/src/features/layout/freeform-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -35,7 +35,7 @@ import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-option export class FreeFormLayouter extends AbstractLayout { static KIND = 'freeform'; - layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -97,7 +97,7 @@ export class FreeFormLayouter extends AbstractLayout { return currentOffset; } - protected getFinalContainerBounds( + protected override getFinalContainerBounds( container: SParentElement & LayoutContainer, lastOffset: Point, options: AbstractLayoutOptions, diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index bb621d6..6d0db95 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -166,10 +166,10 @@ export class ResizeElementsCommand extends LayoutElementsCommand { static readonly KIND = 'layout:resize'; constructor( - @inject(TYPES.Action) protected action: ResizeElementsAction, - @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + @inject(TYPES.Action) protected override action: ResizeElementsAction, + @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, + @inject(GLSP_TYPES.SelectionService) protected override selectionService: SelectionService, + @inject(GLSP_TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor ) { super(action, actionDispatcher, selectionService, movementRestrictor); } @@ -280,10 +280,10 @@ export class AlignElementsCommand extends LayoutElementsCommand { static readonly KIND = 'layout:align'; constructor( - @inject(TYPES.Action) protected action: AlignElementsAction, - @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + @inject(TYPES.Action) protected override action: AlignElementsAction, + @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, + @inject(GLSP_TYPES.SelectionService) protected override selectionService: SelectionService, + @inject(GLSP_TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor ) { super(action, actionDispatcher, selectionService, movementRestrictor); } diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/layout/vbox-layout.ts index 995f210..9c8fee9 100644 --- a/packages/client/src/features/layout/vbox-layout.ts +++ b/packages/client/src/features/layout/vbox-layout.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -44,9 +44,9 @@ export interface VBoxLayoutOptionsExt extends VBoxLayoutOptions { */ @injectable() export class VBoxLayouterExt extends VBoxLayouter { - static KIND = VBoxLayouter.KIND; + static override KIND = VBoxLayouter.KIND; - layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { const boundsData = layouter.getBoundsData(container); const options = this.getLayoutOptions(container); const childrenSize = this.getChildrenSize(container, options, layouter); @@ -80,7 +80,7 @@ export class VBoxLayouterExt extends VBoxLayouter { } } - protected getChildrenSize( + protected override getChildrenSize( container: SParentElement & LayoutContainer, containerOptions: VBoxLayoutOptionsExt, layouter: StatefulLayouter @@ -109,7 +109,7 @@ export class VBoxLayouterExt extends VBoxLayouter { return result; } - protected layoutChildren( + protected override layoutChildren( container: SParentElement & LayoutContainer, layouter: StatefulLayouter, containerOptions: VBoxLayoutOptionsExt, @@ -147,7 +147,7 @@ export class VBoxLayouterExt extends VBoxLayouter { return currentOffset; } - protected layoutChild( + protected override layoutChild( child: SChildElement, boundsData: BoundsData, bounds: Bounds, @@ -183,7 +183,11 @@ export class VBoxLayouterExt extends VBoxLayouter { return offset; } - protected getFixedContainerBounds(container: SModelElement, layoutOptions: VBoxLayoutOptionsExt, layouter: StatefulLayouter): Bounds { + protected override getFixedContainerBounds( + container: SModelElement, + layoutOptions: VBoxLayoutOptionsExt, + layouter: StatefulLayouter + ): Bounds { const currentContainer = container; // eslint-disable-next-line no-constant-condition if (isBoundsAware(currentContainer)) { @@ -196,11 +200,11 @@ export class VBoxLayouterExt extends VBoxLayouter { return EMPTY_BOUNDS; } - protected getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + protected override getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { return super.getChildLayoutOptions(child, containerOptions) as VBoxLayoutOptionsExt; } - protected getLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt { + protected override getLayoutOptions(element: SModelElement): VBoxLayoutOptionsExt { return super.getLayoutOptions(element) as VBoxLayoutOptionsExt; } @@ -208,7 +212,7 @@ export class VBoxLayouterExt extends VBoxLayouter { return (element as any).layoutOptions; } - protected getFinalContainerBounds( + protected override getFinalContainerBounds( container: SParentElement & LayoutContainer, lastOffset: Point, options: VBoxLayoutOptionsExt, @@ -229,7 +233,7 @@ export class VBoxLayouterExt extends VBoxLayouter { return result; } - protected getDefaultLayoutOptions(): VBoxLayoutOptionsExt { + protected override getDefaultLayoutOptions(): VBoxLayoutOptionsExt { return { resizeContainer: true, paddingTop: 5, @@ -250,7 +254,7 @@ export class VBoxLayouterExt extends VBoxLayouter { }; } - protected spread(a: VBoxLayoutOptionsExt, b: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { + protected override spread(a: VBoxLayoutOptionsExt, b: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { return { ...a, ...b }; } } diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index e854bbe..bf5f0f4 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,6 @@ ********************************************************************************/ import { Action, isAction } from '@eclipse-glsp/protocol'; import { injectable, multiInject, optional } from 'inversify'; -import { On, VNode, VNodeData } from 'snabbdom'; import { MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; import { getRank } from '../rank/model'; @@ -24,48 +23,36 @@ export interface IMouseTool { deregister(mouseListener: MouseListener): void; } +/** + * Custom helper type to declare the explicit mouse listener methods + * of {@link MouseListener} i.e. omitting the `decorate` method. + */ +type MouseListenerMethods = keyof Omit; + @injectable() export class RankingMouseTool extends MouseTool implements IMouseTool { protected rankedMouseListeners: Map; - constructor(@multiInject(TYPES.MouseListener) @optional() protected mouseListeners: MouseListener[] = []) { + constructor(@multiInject(TYPES.MouseListener) @optional() protected override mouseListeners: MouseListener[] = []) { super(mouseListeners); this.rankedMouseListeners = groupBy(mouseListeners, listener => getRank(listener)); } - register(mouseListener: MouseListener): void { + override register(mouseListener: MouseListener): void { super.register(mouseListener); this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); } - deregister(mouseListener: MouseListener): void { + override deregister(mouseListener: MouseListener): void { super.deregister(mouseListener); this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener)); } - decorate(vnode: VNode, element: SModelElement): VNode { - // we need to overwrite the existing event handlers registered by the original mouse tool - if (element instanceof SModelRoot) { - overwriteOn(vnode, 'mouseover', this.mouseOver.bind(this, element), element); - overwriteOn(vnode, 'mouseout', this.mouseOut.bind(this, element), element); - overwriteOn(vnode, 'mouseenter', this.mouseEnter.bind(this, element), element); - overwriteOn(vnode, 'mouseleave', this.mouseLeave.bind(this, element), element); - overwriteOn(vnode, 'mousedown', this.mouseDown.bind(this, element), element); - overwriteOn(vnode, 'mouseup', this.mouseUp.bind(this, element), element); - overwriteOn(vnode, 'mousemove', this.mouseMove.bind(this, element), element); - overwriteOn(vnode, 'wheel', this.wheel.bind(this, element), element); - overwriteOn(vnode, 'contextmenu', this.contextMenu.bind(this, element), element); - overwriteOn(vnode, 'dblclick', this.doubleClick.bind(this, element), element); - } - vnode = this.mouseListeners.reduce((n: VNode, listener: MouseListener) => listener.decorate(n, element), vnode); - return vnode; - } - contextMenu(model: SModelRoot, event: MouseEvent): void { event.preventDefault(); } - protected handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { + protected override handleEvent(methodName: K, model: SModelRoot, event: MouseEvent): void { this.focusOnMouseEvent(methodName, model); const element = this.getTargetElement(model, event); if (!element) { @@ -74,7 +61,7 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { this.notifyListenersByRank(element, methodName, model, event); } - async notifyListenersByRank( + async notifyListenersByRank( element: SModelElement, methodName: K, model: SModelRoot, @@ -85,15 +72,13 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { } } - async dispatchActions( + async dispatchActions( mouseListeners: MouseListener[], methodName: K, element: SModelElement, event: MouseEvent ): Promise { - const actions = mouseListeners - .map(listener => listener[methodName].apply(listener, [element, event])) - .reduce((a, b) => a.concat(b)); + const actions = mouseListeners.map(listener => listener[methodName](element, event as WheelEvent)).reduce((a, b) => a.concat(b)); if (actions.length > 0) { event.preventDefault(); for (const actionOrPromise of actions) { @@ -124,24 +109,3 @@ function groupBy(array: Array, keyFunction: (x: T) => K): Map { }, new Map()); return new Map([...unsortedMap.entries()].sort()); } - -function overwriteOn(vnode: VNode, event: string, listener: (model: SModelElement, _event: Event) => void, element: SModelElement): void { - const val = getOn(vnode); - // ignore any previous val[event] registrations - (val as any)[event] = [listener, element]; -} - -function getOn(vnode: VNode): On { - const data = getData(vnode); - if (!data.on) { - data.on = {}; - } - return data.on; -} - -function getData(vnode: VNode): VNodeData { - if (!vnode.data) { - vnode.data = {}; - } - return vnode.data; -} diff --git a/packages/client/src/features/reconnect/model.ts b/packages/client/src/features/reconnect/model.ts index b192d32..a0eee52 100644 --- a/packages/client/src/features/reconnect/model.ts +++ b/packages/client/src/features/reconnect/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -69,7 +69,7 @@ export function createReconnectHandle(edge: SRoutableElement, kind: RoutingHandl } export class SReconnectHandle extends SRoutingHandle { - hasFeature(feature: symbol): boolean { + override hasFeature(feature: symbol): boolean { return feature !== selectFeature && super.hasFeature(feature); } } diff --git a/packages/client/src/features/save/save-keylistener.ts b/packages/client/src/features/save/save-keylistener.ts index 1d56bd6..d9a4ff3 100644 --- a/packages/client/src/features/save/save-keylistener.ts +++ b/packages/client/src/features/save/save-keylistener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,7 +18,7 @@ import { KeyListener, SModelRoot } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; export class SaveModelKeyboardListener extends KeyListener { - keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { + override keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { return [new SaveModelAction()]; } diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index 393e1d0..a2e48a0 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,8 +20,8 @@ import { CommandExecutionContext, ILogger, isSelectable, - isSelected, SChildElement, + Selectable, SelectAllCommand as SprottySelectAllCommand, SelectCommand as SprottySelectCommand, SModelElement, @@ -31,6 +31,7 @@ import { } from 'sprotty'; import { SModelRootListener } from '../../base/model/update-model-command'; import { GLSP_TYPES } from '../../base/types'; +import { getMatchingElements } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; @@ -122,8 +123,8 @@ export class SelectionService implements SModelRootListener { return this.root; } - getSelectedElements(): Readonly[] { - return Array.from(this.root.index.all().filter(isSelected)); + getSelectedElements(): Readonly[] { + return getMatchingElements(this.root.index, isSelectable); } /** diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index e38db18..b8b7bff 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -31,7 +31,7 @@ import { SModelRoot, TYPES } from 'sprotty'; -import { isNotUndefined } from '../../utils/smodel-util'; +import { forEachElement, isNotUndefined } from '../../utils/smodel-util'; import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from '../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; @@ -54,7 +54,8 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index.all().filter(isResizable).forEach(removeResizeHandles); + + forEachElement(index, isResizable, removeResizeHandles); if (isNotUndefined(this.action.elementId)) { const resizeElement = index.getById(this.action.elementId); @@ -74,7 +75,7 @@ export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index.all().filter(isResizable).forEach(removeResizeHandles); + forEachElement(index, isResizable, removeResizeHandles); return context.root; } } @@ -95,7 +96,7 @@ export class FeedbackMoveMouseListener extends MouseListener { super(); } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { if (event.button === 0 && !(target instanceof SResizeHandle)) { const moveable = findParentByFeature(target, isMoveable); if (moveable !== undefined) { @@ -108,7 +109,7 @@ export class FeedbackMoveMouseListener extends MouseListener { return []; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { this.mouseUp(target, event); @@ -219,14 +220,14 @@ export class FeedbackMoveMouseListener extends MouseListener { } } - mouseEnter(target: SModelElement, event: MouseEvent): Action[] { + override mouseEnter(target: SModelElement, event: MouseEvent): Action[] { if (target instanceof SModelRoot && event.buttons === 0 && !this.startDragPosition) { this.mouseUp(target, event); } return []; } - mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (this.startDragPosition) { const moveAction = this.getElementMoves(target, event, true); @@ -244,7 +245,7 @@ export class FeedbackMoveMouseListener extends MouseListener { return result; } - decorate(vnode: VNode, _element: SModelElement): VNode { + override decorate(vnode: VNode, _element: SModelElement): VNode { return vnode; } } diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 0a5055b..66e986d 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -38,7 +38,7 @@ import { SRoutableElement, TYPES } from 'sprotty'; -import { isRoutable, BoundsAwareModelElement } from '../../utils/smodel-util'; +import { BoundsAwareModelElement, isRoutable } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackCommand } from './model'; @@ -86,7 +86,7 @@ export class FeedbackEdgeEnd extends SDanglingAnchor { readonly sourceId: string, readonly elementTypeId: string, public feedbackEdge: SRoutableElement | undefined = undefined, - public readonly type: string = FeedbackEdgeEnd.TYPE + override readonly type: string = FeedbackEdgeEnd.TYPE ) { super(); } @@ -97,7 +97,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { super(); } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const root = target.root; const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { @@ -106,9 +106,9 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { const edge = edgeEnd.feedbackEdge; const position = getAbsolutePosition(edgeEnd, event); - const endAtMousePosition = findChildrenAtPosition(target.root, position).reverse().find( - element => isConnectable(element) && element.canConnect(edge, 'target') - ); + const endAtMousePosition = findChildrenAtPosition(target.root, position) + .reverse() + .find(element => isConnectable(element) && element.canConnect(edge, 'target')); if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(toAbsoluteBounds(edge.source))); @@ -146,8 +146,8 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { * @param absolutePoint a point in absolute coordinates * @returns the equivalent point, relative to the element's parent coordinates */ -function absoluteToParent(element: BoundsAwareModelElement & SChildElement, absolutePoint: Point): Point{ - if (isBoundsAware(element.parent)){ +function absoluteToParent(element: BoundsAwareModelElement & SChildElement, absolutePoint: Point): Point { + if (isBoundsAware(element.parent)) { return absoluteToLocal(element.parent, absolutePoint); } // If the parent is not bounds-aware, assume it's at 0; 0 and proceed @@ -163,7 +163,7 @@ function absoluteToParent(element: BoundsAwareModelElement & SChildElement, abso */ function absoluteToLocal(element: BoundsAwareModelElement, absolutePoint: Point): Point { const absoluteElementBounds = toAbsoluteBounds(element); - return {x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y}; + return { x: absolutePoint.x - absoluteElementBounds.x, y: absolutePoint.y - absoluteElementBounds.y }; } export function feedbackEdgeId(root: SModelRoot): string { diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index d89f239..a45da48 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -42,7 +42,7 @@ import { SwitchEditModeCommand, TYPES } from 'sprotty'; -import { isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; +import { forEachElement, isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; import { FeedbackEdgeEnd, feedbackEdgeEndId, FeedbackEdgeEndMovingMouseListener, feedbackEdgeId } from './creation-tool-feedback'; @@ -70,7 +70,7 @@ export class ShowEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index.all().filter(isRoutable).forEach(removeReconnectHandles); + forEachElement(index, isRoutable, removeReconnectHandles); if (isNotUndefined(this.action.elementId)) { const routableElement = index.getById(this.action.elementId); @@ -92,7 +92,7 @@ export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { execute(context: CommandExecutionContext): CommandReturn { const index = context.root.index; - index.all().filter(isRoutable).forEach(removeReconnectHandles); + forEachElement(index, isRoutable, removeReconnectHandles); return context.root; } } @@ -102,9 +102,9 @@ export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { export class SwitchRoutingModeAction extends SwitchEditModeAction { constructor( - public readonly elementsToActivate: string[] = [], - public readonly elementsToDeactivate: string[] = [], - public readonly kind: string = SwitchRoutingModeCommand.KIND + override readonly elementsToActivate: string[] = [], + override readonly elementsToDeactivate: string[] = [], + override readonly kind: string = SwitchRoutingModeCommand.KIND ) { super(elementsToActivate, elementsToDeactivate); } @@ -112,7 +112,7 @@ export class SwitchRoutingModeAction extends SwitchEditModeAction { @injectable() export class SwitchRoutingModeCommand extends SwitchEditModeCommand { - static KIND = 'switchRoutingMode'; + static override KIND = 'switchRoutingMode'; constructor(@inject(TYPES.Action) action: SwitchRoutingModeAction) { super(action); } @@ -149,7 +149,7 @@ export class DrawFeedbackEdgeSourceCommand extends FeedbackCommand { */ export class FeedbackEdgeTargetMovingMouseListener extends FeedbackEdgeEndMovingMouseListener { - constructor(protected anchorRegistry: AnchorComputerRegistry) { + constructor(protected override anchorRegistry: AnchorComputerRegistry) { super(anchorRegistry); } } @@ -159,7 +159,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { super(); } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const root = target.root; const edgeEnd = root.index.getById(feedbackEdgeEndId(root)); if (!(edgeEnd instanceof FeedbackEdgeEnd) || !edgeEnd.feedbackEdge) { @@ -208,7 +208,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { super(); } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.button === 0) { const routingHandle = findParentByFeature(target, isRoutingHandle); @@ -223,7 +223,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return result; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { this.mouseUp(target, event); @@ -273,20 +273,20 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return undefined; } - mouseEnter(target: SModelElement, event: MouseEvent): Action[] { + override mouseEnter(target: SModelElement, event: MouseEvent): Action[] { if (target instanceof SModelRoot && event.buttons === 0) { this.mouseUp(target, event); } return []; } - mouseUp(_target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(_target: SModelElement, event: MouseEvent): Action[] { this.hasDragged = false; this.lastDragPosition = undefined; return []; } - decorate(vnode: VNode, _element: SModelElement): VNode { + override decorate(vnode: VNode, _element: SModelElement): VNode { return vnode; } } diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index 0515110..2751773 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -68,7 +68,7 @@ export class MarqueeEndMovingMouseListener extends MouseListener { super(); } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { return []; } } diff --git a/packages/client/src/features/tool-feedback/model.ts b/packages/client/src/features/tool-feedback/model.ts index cdf9783..af834d8 100644 --- a/packages/client/src/features/tool-feedback/model.ts +++ b/packages/client/src/features/tool-feedback/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,8 +19,6 @@ export abstract class FeedbackCommand extends Command { // used by the `FeedbackAwareUpdateModelCommand` readonly priority: number = 0; - abstract execute(context: CommandExecutionContext): CommandReturn; - undo(context: CommandExecutionContext): CommandReturn { return context.root; } diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index b0cc990..05721ad 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -75,7 +75,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.editorContext.register(this); } - initialize(): boolean { + override initialize(): boolean { if (!this.paletteItems) { return false; } @@ -88,7 +88,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, this.lastActivebutton = this.defaultToolsButton; } - protected onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { + protected override onBeforeShow(_containerElement: HTMLElement, root: Readonly): void { this.modelRootId = root.id; this.containerElement.style.maxHeight = PALETTE_HEIGHT; } @@ -410,10 +410,12 @@ export function createToolGroup(item: PaletteItem): HTMLElement { } export function changeCSSClass(element: Element, css: string): void { + // eslint-disable-next-line chai-friendly/no-unused-expressions element.classList.contains(css) ? element.classList.remove(css) : element.classList.add(css); } export function changeCodiconClass(element: Element, codiconId: string): void { + // eslint-disable-next-line chai-friendly/no-unused-expressions element.classList.contains(codiconCSSClasses(codiconId)[1]) ? element.classList.remove(codiconCSSClasses(codiconId)[1]) : element.classList.add(codiconCSSClasses(codiconId)[1]); diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index d822ccc..36b55af 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -138,7 +138,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele super(); } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { super.mouseDown(target, event); if (event.button !== 0) { return []; @@ -157,7 +157,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return []; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { super.mouseMove(target, event); if (this.isMouseDrag && this.activeResizeHandle) { // rely on the FeedbackMoveMouseListener to update the element bounds of selected elements @@ -176,7 +176,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return []; } - draggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { + override draggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { if (this.lastDragPosition === undefined) { this.resetPosition(); return []; @@ -209,7 +209,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele const result: Operation[] = []; const newBounds: ElementAndBounds[] = []; const selectedElements: (SModelElement & BoundsAware)[] = []; - forEachElement(target, isNonRoutableSelectedMovableBoundsAware, element => { + forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { selectedElements.push(element); }); @@ -238,7 +238,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected handleMoveRoutingPointsOnServer(target: SModelElement): Action[] { const result: Operation[] = []; const newRoutingPoints: ElementAndRoutingPoints[] = []; - forEachElement(target, isNonRoutableSelectedMovableBoundsAware, element => { + forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { // If client routing is enabled -> delegate routingpoints of connected edges to server if (this.tool.edgeRouterRegistry && element instanceof SConnectableElement) { element.incomingEdges.map(toElementAndRoutingPoints).forEach(ear => newRoutingPoints.push(ear)); diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index defefaa..d717f75 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -60,7 +60,7 @@ export class DelKeyDeleteTool implements GLSPTool { @injectable() export class DeleteKeyListener extends KeyListener { - keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Delete')) { const deleteElementIds = Array.from( element.root.index @@ -108,7 +108,7 @@ export class MouseDeleteTool implements GLSPTool { @injectable() export class DeleteToolMouseListener extends MouseListener { - mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: SModelElement, event: MouseEvent): Action[] { const deletableParent = findParentByFeature(target, isDeletable); if (deletableParent === undefined) { return []; diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index dd68ab6..364c357 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -99,7 +99,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.tool.dispatchFeedback([new RemoveFeedbackEdgeAction()]); } - nonDraggingMouseUp(_element: SModelElement, event: MouseEvent): Action[] { + override nonDraggingMouseUp(_element: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.button === 0) { if (!this.isSourceSelected()) { @@ -134,7 +134,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { return this.target !== undefined; } - mouseOver(target: SModelElement, event: MouseEvent): Action[] { + override mouseOver(target: SModelElement, event: MouseEvent): Action[] { const newCurrentTarget = findParentByFeature(target, isConnectable); if (newCurrentTarget !== this.currentTarget) { this.currentTarget = newCurrentTarget; diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 9a593c5..afb37ef 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -185,7 +185,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return this.routingHandle !== undefined; } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = super.mouseDown(target, event); if (event.button === 0) { const reconnectHandle = findParentByFeature(target, isReconnectHandle); @@ -208,7 +208,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return result; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result = super.mouseMove(target, event); if (this.isMouseDrag) { // reset any selected connectables when we are dragging, maybe the user is just panning @@ -217,7 +217,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return result; } - mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: SModelElement, event: MouseEvent): Action[] { const result = super.mouseUp(target, event); if (!this.isReadyToReconnect() && !this.isReadyToReroute()) { return result; @@ -242,7 +242,7 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen return result; } - mouseOver(target: SModelElement, event: MouseEvent): Action[] { + override mouseOver(target: SModelElement, event: MouseEvent): Action[] { if (this.edge && this.isReconnecting()) { const currentTarget = findParentByFeature(target, isConnectable); if (!this.newConnectable || currentTarget !== this.newConnectable) { diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index 8e3bcb3..567150b 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -28,8 +28,8 @@ import { TYPES } from 'sprotty'; import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; -import { GLSP_TYPES } from '../../base/types'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; +import { GLSP_TYPES } from '../../base/types'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { RemoveMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; @@ -93,7 +93,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { this.edges = Array.from(document.querySelectorAll('g')).filter(e => sEdges.includes(this.domHelper.findSModelIdByDOMElement(e))); } - mouseDown(target: SModelElement, event: MouseEvent): Action[] { + override mouseDown(target: SModelElement, event: MouseEvent): Action[] { this.isActive = true; this.marqueeUtil.updateStartPoint(getAbsolutePosition(target, event)); if (event.ctrlKey) { @@ -108,7 +108,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { return []; } - mouseMove(target: SModelElement, event: MouseEvent): Action[] { + override mouseMove(target: SModelElement, event: MouseEvent): Action[] { this.marqueeUtil.updateCurrentPoint(getAbsolutePosition(target, event)); if (this.isActive) { const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isNodeMarked(toAbsoluteBounds(e))).map(e => e.id); @@ -123,7 +123,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { return []; } - mouseUp(target: SModelElement, event: MouseEvent): Action[] { + override mouseUp(target: SModelElement, event: MouseEvent): Action[] { this.isActive = false; if (event.shiftKey) { return [new RemoveMarqueeAction()]; @@ -144,7 +144,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener { @injectable() export class ShiftKeyListener extends KeyListener { - keyUp(element: SModelElement, event: KeyboardEvent): Action[] { + override keyUp(element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return []; } diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index acf0a45..3654d83 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -42,7 +42,7 @@ export class MarqueeTool extends BaseGLSPTool { @injectable() export class MarqueeKeyListener extends KeyListener { - keyDown(element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { return [new EnableToolsAction([MarqueeMouseTool.ID])]; } diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/model.ts index c57bd24..52ad4e6 100644 --- a/packages/client/src/features/tools/model.ts +++ b/packages/client/src/features/tools/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { boundsFeature, Point, RectangularNode } from 'sprotty'; export class MarqueeNode extends RectangularNode { - static readonly DEFAULT_FEATURES = [boundsFeature]; + static override readonly DEFAULT_FEATURES = [boundsFeature]; startPoint: Point; endPoint: Point; } diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index bfac386..0788b00 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -83,7 +83,7 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { return this.triggerAction.elementTypeId; } - nonDraggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { + override nonDraggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (this.creationAllowed(this.elementTypeId)) { const containerId = this.container ? this.container.id : undefined; @@ -102,7 +102,7 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { return result; } - mouseOver(target: SModelElement, event: MouseEvent): Action[] { + override mouseOver(target: SModelElement, event: MouseEvent): Action[] { const currentContainer = findParentByFeature(target, isContainable); if (!this.container || currentContainer !== this.container) { this.container = currentContainer; diff --git a/packages/client/src/features/tools/view.tsx b/packages/client/src/features/tools/view.tsx index 4c56d03..96c8034 100644 --- a/packages/client/src/features/tools/view.tsx +++ b/packages/client/src/features/tools/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,6 @@ import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { RectangularNodeView, RenderingContext, svg } from 'sprotty'; - import { MarqueeNode } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -24,7 +23,7 @@ const JSX = { createElement: svg }; @injectable() export class MarqueeView extends RectangularNodeView { - render(node: MarqueeNode, context: RenderingContext): VNode { + override render(node: MarqueeNode, context: RenderingContext): VNode { const graph = ( 0) { selectedIds = this.action.selectedElementIds; } else { - selectedIds = Array.from(this.selectionService.getSelectedElementIDs()); + return this.selectionService.getSelectedElements(); } - return selectedIds.map(id => root.index.getById(id)).filter(isSelectable); + return selectedIds.map(id => root.index.getById(id)).filter(element => element !== undefined && isSelectable(element)) as Array< + SModelElement & Selectable + >; } protected getTarget(selected: SModelElement[], root: SModelRoot): SIssueMarker | undefined { @@ -250,7 +252,7 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP @injectable() export class MarkerNavigatorKeyListener extends KeyListener { - keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { + override keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Period', 'ctrl')) { return [new NavigateToMarkerAction('next')]; } else if (matchesKeystroke(event, 'Comma', 'ctrl')) { diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 5d02130..1cd849d 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -151,7 +151,7 @@ export class ApplyMarkersAction implements MarkersAction { @injectable() export class ApplyMarkersCommand extends FeedbackCommand { static KIND = 'applyMarkers'; - readonly priority = 0; + override readonly priority = 0; constructor(@inject(TYPES.Action) protected action: ApplyMarkersAction) { super(); @@ -171,11 +171,11 @@ export class ApplyMarkersCommand extends FeedbackCommand { return context.root; } - undo(context: CommandExecutionContext): CommandReturn { + override undo(context: CommandExecutionContext): CommandReturn { return context.root; } - redo(context: CommandExecutionContext): CommandReturn { + override redo(context: CommandExecutionContext): CommandReturn { return this.execute(context); } } diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index f865a3e..ec5cd87 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -32,7 +32,7 @@ export class GLSPScrollMouseListener extends ScrollMouseListener implements IAct } } - mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + override mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise)[] { if (this.preventScrolling) { return []; } diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index 48e8a23..bae7c17 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,12 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { exportFeature, SGraph, SModelElement, SModelElementSchema, viewportFeature } from 'sprotty/lib'; - import { Containable, containerFeature } from '../features/hints/model'; import { Saveable, saveFeature } from '../features/save/model'; export class GLSPGraph extends SGraph implements Saveable, Containable { - static readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, saveFeature, containerFeature]; + static override readonly DEFAULT_FEATURES = [viewportFeature, exportFeature, saveFeature, containerFeature]; dirty = false; isContainableElement(input: string | SModelElement | SModelElementSchema): boolean { return true; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index a58414a..121e9c6 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -55,21 +55,21 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { } } - initialize(registry: ActionHandlerRegistry): void { + override initialize(registry: ActionHandlerRegistry): void { registerDefaultGLSPServerActions(registry, this); if (!this.clientId) { this.clientId = this.viewerOptions.baseDiv; } } - handle(action: Action): void | ICommand | Action { + override handle(action: Action): void | ICommand | Action { if (isRequestModelAction(action) && action.options) { this._sourceUri = action.options.sourceUri as string; } return super.handle(action); } - handleLocally(action: Action): boolean { + override handleLocally(action: Action): boolean { if (isServerMessageAction(action)) { return this.handleServerMessageAction(action); } @@ -84,7 +84,7 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { return false; } - protected handleComputedBounds(_action: ComputedBoundsAction): boolean { + protected override handleComputedBounds(_action: ComputedBoundsAction): boolean { return true; } diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index b5d39b0..2fe6e08 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ElementAndRoutingPoints } from '@eclipse-glsp/protocol'; +import { distinctAdd, ElementAndRoutingPoints, remove } from '@eclipse-glsp/protocol'; import { BoundsAware, ElementAndBounds, @@ -28,93 +28,161 @@ import { SRoutableElement, SRoutingHandle } from 'sprotty'; +import { FluentIterable } from 'sprotty/lib/utils/iterable'; -export function getIndex(element: SModelElement): SModelIndex { - return element.root.index; +/** + * Helper type to represent a filter predicate for {@link SModelElement}s. This is used to retrieve + * elements from the {@link SModelIndex} that also conform to a second type `T`. Its mainly used for + * retrieving elements that also implement a certain model features (e.g. selectable) + */ +export type ModelFilterPredicate = (modelElement: SModelElement) => modelElement is SModelElement & T; + +/** + * Retrieves all elements from the given {@link SModelIndex} that match the given {@link ModelFilterPredicate} + * @param index The {@link SModelIndex}. + * @param predicate The {@link ModelFilterPredicate} that should be used. + * @returns A {@link FluentIterable} of all indexed element that match the predicate + * (correctly casted to also include the additional type of the predicate). + */ +export function filter(index: SModelIndex, predicate: ModelFilterPredicate): FluentIterable { + return index.all().filter(predicate) as FluentIterable; } +/** + * Retrieves all elements from the given {@link SModelIndex} that match the given {@link ModelFilterPredicate} and executes + * the given runnable for each element of the result set. + * @param index The {@link SModelIndex}. + * @param predicate The {@link ModelFilterPredicate} that should be used. + * @param runnable The runnable that should be executed for each matching element. + */ export function forEachElement( - element: SModelElement, - predicate: (modelElement: SModelElement) => modelElement is SModelElement & T, + index: SModelIndex, + predicate: ModelFilterPredicate, runnable: (modelElement: SModelElement & T) => void ): void { - getIndex(element).all().filter(predicate).forEach(runnable); -} - -export function getMatchingElements( - element: SModelElement, - predicate: (modelElement: SModelElement) => modelElement is SModelElement & T -): (SModelElement & T)[] { - const matching: (SModelElement & T)[] = []; - forEachElement(element, predicate, item => matching.push(item)); - return matching; + filter(index, predicate).forEach(runnable); } -export function hasSelectedElements(element: SModelElement): boolean { - return getSelectedElementCount(element) > 0; +/** + * Retrieves an array of all elements that match the given {@link ModelFilterPredicate} from the given {@link SModelIndex}. + * @param index The {@link SModelIndex}. + * @param predicate The {@link ModelFilterPredicate} that should be used. + * @returns An array of all indexed element that match the predicate + * (correctly casted to also include the additional type of the predicate). + */ +export function getMatchingElements(index: SModelIndex, predicate: ModelFilterPredicate): (SModelElement & T)[] { + return Array.from(filter(index, predicate)); } -export function getSelectedElementCount(element: SModelElement): number { +/** + * Retrieves the amount of currently selected elements in the given {@link SModelIndex}. + * @param index The {@link SModelIndex}. + * @returns The amount of selected elements. + */ +export function getSelectedElementCount(index: SModelIndex): number { let selected = 0; - getIndex(element) - .all() - .filter(isSelected) - .forEach(e => (selected = selected + 1)); + forEachElement(index, isSelected, element => selected++); return selected; } +/** + * Helper function to check wether an any element is selected in the given {@link SModelIndex}. + * @param index The {@link SModelIndex}. + * @returns `true` if at least one element is selected, `false` otherwise. + */ +export function hasSelectedElements(index: SModelIndex): boolean { + return getSelectedElementCount(index) > 0; +} + +/** + * Helper function to check wether an element is defined. Can be used as {@link ModelFilterPredicate}. + * @param element The element that should be checked. + * @returns the type predicate for `T` + */ export function isNotUndefined(element: T | undefined): element is T { return element !== undefined; } -export function addCssClasses(root: SModelElement, cssClasses: string[]): void { - if (root.cssClasses === undefined) { - root.cssClasses = []; - } - for (const cssClass of cssClasses) { - if (root.cssClasses.indexOf(cssClass) < 0) { - root.cssClasses.push(cssClass); - } - } +/** + * Adds a set of css classes to the given {@link SModelElement}. + * @param element The element to which the css classes should be added. + * @param cssClasses The set of css classes as string array. + */ +export function addCssClasses(element: SModelElement, cssClasses: string[]): void { + const elementCssClasses: string[] = element.cssClasses ?? []; + distinctAdd(elementCssClasses, ...cssClasses); + element.cssClasses = elementCssClasses; } +/** + * Removes a set of css classes from the given {@link SModelElement}. + * @param element The element from which the css classes should be removed. + * @param cssClasses The set of css classes as string array. + */ export function removeCssClasses(root: SModelElement, cssClasses: string[]): void { - if (root.cssClasses === undefined || root.cssClasses.length === 0) { + if (!root.cssClasses || root.cssClasses.length === 0) { return; } - for (const cssClass of cssClasses) { - const index = root.cssClasses.indexOf(cssClass); - if (index !== -1) { - root.cssClasses.splice(root.cssClasses.indexOf(cssClass), 1); - } - } + remove(root.cssClasses, ...cssClasses); } export function isNonRoutableSelectedMovableBoundsAware(element: SModelElement): element is SelectableBoundsAware { return isNonRoutableSelectedBoundsAware(element) && isMoveable(element); } +/** + * A typeguard function to check wether a given {@link SModelElement} implements the {@link BoundsAware} model feature, + * the {@link Selectable} model feature and is actually selected. In addition, the element must not be a {@link SRoutableElement}. + * @param element The element to check. + * @returns A type predicate indicating wether the element is of type {@link SelectableBoundsAware}. + */ export function isNonRoutableSelectedBoundsAware(element: SModelElement): element is SelectableBoundsAware { return isBoundsAware(element) && isSelected(element) && !isRoutable(element); } +/** + * A type guard function to check wether a given {@link SModelElement} is a {@link SRoutableElement}. + * @param element The element to check. + * @returns A type predicate indicating wether the element is a {@link SRoutableElement}. + */ export function isRoutable(element: T): element is T & SRoutableElement { return element instanceof SRoutableElement && (element as any).routingPoints !== undefined; } +/** + * A typeguard function to check wether a given {@link SModelElement} is a {@link SRoutingHandle}. + * @param element The element to check. + * @returns A type predicate indicating wether the element is a {@link SRoutingHandle} + */ export function isRoutingHandle(element: SModelElement | undefined): element is SRoutingHandle { return element !== undefined && element instanceof SRoutingHandle; } -export function isSelectableAndBoundsAware(element: SModelElement): element is BoundsAware & Selectable & SModelElement { +/** + * A typeguard function to check wether a given {@link SModelElement} implements the {@link Selectable} model feature and + * the {@link BoundsAware} model feature. + * @returns A type predicate indicating wether the element is of type {@link SelectableBoundsAware}. + */ +export function isSelectableAndBoundsAware(element: SModelElement): element is SelectableBoundsAware { return isSelectable(element) && isBoundsAware(element); } +/** + * Union type to describe {@link SModelElement}s that implement the {@link Selectable} and {@link BoundsAware} feature. + */ export type SelectableBoundsAware = SModelElement & BoundsAware & Selectable; +/** + * Union type to describe {@link SModelElement}s that implement the {@link BoundsAware} feature. + */ export type BoundsAwareModelElement = SModelElement & BoundsAware; -export function toElementAndBounds(element: SModelElement & BoundsAware): ElementAndBounds { +/** + * Helper function to translate a given {@link SModelElement} into its corresponding {@link ElementAndBounds} representation. + * @param element The element to translate. + * @returns The corresponding {@link ElementAndBounds} for the given element. + */ +export function toElementAndBounds(element: BoundsAwareModelElement): ElementAndBounds { return { elementId: element.id, newPosition: { @@ -128,6 +196,12 @@ export function toElementAndBounds(element: SModelElement & BoundsAware): Elemen }; } +/** + * Helper function to translate a given {@link SRoutableElement} into its corresponding + * {@ElementAndRoutingPoints ElementAndBounds} representation. + * @param element The element to translate. + * @returns The corresponding {@link ElementAndRoutingPoints} for the given element. + */ export function toElementAndRoutingPoints(element: SRoutableElement): ElementAndRoutingPoints { return { elementId: element.id, @@ -144,6 +218,12 @@ export function hasCompatibleType(input: SModelElement | SModelElementSchema | s return inputType === type ? true : inputType.split(':').includes(type); } +/** + * Convenience function to retrieve the model element type from a given input. The input + * can either be a {@link SModelElement}, {@link SModelElementSchema} or a string. + * @param input The type input. + * @returns The corresponding model type as string. + */ export function getElementTypeId(input: SModelElement | SModelElementSchema | string): string { if (typeof input === 'string') { return input as string; diff --git a/packages/client/src/views/glsp-edge-view.tsx b/packages/client/src/views/glsp-edge-view.tsx index 0b8a01d..4677b90 100644 --- a/packages/client/src/views/glsp-edge-view.tsx +++ b/packages/client/src/views/glsp-edge-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -24,7 +24,7 @@ const JSX = { createElement: svg }; @injectable() export class GEdgeView extends PolylineEdgeView { - render(edge: Readonly, context: RenderingContext): VNode { + override render(edge: Readonly, context: RenderingContext): VNode { const router = this.edgeRouterRegistry.get(edge.routerKind); const route = router.route(edge); if (route.length === 0) { @@ -44,11 +44,11 @@ export class GEdgeView extends PolylineEdgeView { return {}; } - protected renderLine(_edge: SEdge, segments: Point[], _context: RenderingContext): VNode { + protected override renderLine(_edge: SEdge, segments: Point[], _context: RenderingContext): VNode { return ; } - protected renderAdditionals(edge: SEdge, segments: Point[], _context: RenderingContext): VNode[] { + protected override renderAdditionals(edge: SEdge, segments: Point[], _context: RenderingContext): VNode[] { // for additional padding we draw another transparent path with larger stroke width const edgePadding = EdgePadding.from(edge); return edgePadding ? [this.renderMouseHandle(segments, edgePadding)] : []; diff --git a/packages/client/src/views/issue-marker-view.tsx b/packages/client/src/views/issue-marker-view.tsx index 7d712b1..fca0df1 100644 --- a/packages/client/src/views/issue-marker-view.tsx +++ b/packages/client/src/views/issue-marker-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -23,7 +23,7 @@ const JSX = { createElement: svg }; @injectable() export class GIssueMarkerView extends IssueMarkerView { - render(marker: SIssueMarker, _context: RenderingContext): VNode { + override render(marker: SIssueMarker, _context: RenderingContext): VNode { const maxSeverity = super.getMaxSeverity(marker); const group = ( diff --git a/packages/client/src/views/rounded-corner-view.tsx b/packages/client/src/views/rounded-corner-view.tsx index 2faf97d..30767c8 100644 --- a/packages/client/src/views/rounded-corner-view.tsx +++ b/packages/client/src/views/rounded-corner-view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,6 @@ import { injectable } from 'inversify'; import { Classes, VNode } from 'snabbdom'; import { Hoverable, RectangularNodeView, RenderingContext, Selectable, SNode, SPort, SShapeElement, svg } from 'sprotty'; - import { CornerRadius } from '../utils/argument-utils'; import { RoundedCornerWrapper } from './rounded-corner'; @@ -25,7 +24,7 @@ const JSX = { createElement: svg }; @injectable() export class RoundedCornerNodeView extends RectangularNodeView { - render(node: Readonly, context: RenderingContext): VNode | undefined { + override render(node: Readonly, context: RenderingContext): VNode | undefined { const cornerRadius = CornerRadius.from(node); if (!cornerRadius) { return this.renderWithoutRadius(node, context); diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 0f810a2..9a9a2df 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -33,19 +33,16 @@ "src" ], "dependencies": { - "inversify": "^5.0.1", + "inversify": "^5.1.1", "uuid": "7.0.3", "vscode-ws-jsonrpc": "0.2.0" }, "devDependencies": { - "@babel/runtime": "^7.11.2", - "@types/uuid": "3.4.5", - "rimraf": "^2.6.1", - "typescript": "^3.9.2" + "@types/uuid": "3.4.5" }, "scripts": { "prepare": "yarn clean && yarn build && yarn lint", - "clean": "rimraf lib", + "clean": "rimraf lib tsconfig.tsbuildinfo ", "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts ./src", "watch": "tsc -w" diff --git a/packages/protocol/src/action-protocol/tool-palette.ts b/packages/protocol/src/action-protocol/tool-palette.ts index f3c0782..3dfc6ab 100644 --- a/packages/protocol/src/action-protocol/tool-palette.ts +++ b/packages/protocol/src/action-protocol/tool-palette.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,7 +33,11 @@ export function isTriggerElementTypeCreationAction(action: any): action is Trigg export class TriggerNodeCreationAction extends TriggerElementCreationAction { static readonly KIND = 'triggerNodeCreation'; - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind = TriggerNodeCreationAction.KIND) { + constructor( + override readonly elementTypeId: string, + override readonly args?: Args, + override readonly kind = TriggerNodeCreationAction.KIND + ) { super(elementTypeId, args, kind); } } @@ -50,9 +54,9 @@ export class TriggerEdgeCreationAction extends TriggerElementCreationAction { static readonly KIND = 'triggerEdgeCreation'; constructor( - public readonly elementTypeId: string, - readonly args?: Args, - public readonly kind: string = TriggerEdgeCreationAction.KIND + override readonly elementTypeId: string, + override readonly args?: Args, + override readonly kind: string = TriggerEdgeCreationAction.KIND ) { super(elementTypeId, args, kind); } diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index bc86e90..e522f95 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,6 @@ ********************************************************************************/ import { ActionMessage } from 'sprotty'; import { Message, MessageConnection } from 'vscode-ws-jsonrpc'; - import { ActionMessageHandler, ClientState, @@ -126,12 +125,12 @@ export class BaseJsonrpcGLSPClient implements GLSPClient { protected async doCreateConnection(): Promise { const connection = typeof this.connectionProvider === 'function' ? await this.connectionProvider() : this.connectionProvider; - connection.onError((data: [Error, Message, number]) => this.handleConnectionError(data[0], data[1], data[2])); + connection.onError(data => this.handleConnectionError(data[0], data[1], data[2])); connection.onClose(() => this.handleConnectionClosed()); return connection; } - protected handleConnectionError(error: Error, message: Message, count: number): void { + protected handleConnectionError(error: Error, message: Message | undefined, count: number | undefined): void { JsonrpcGLSPClient.error('Connection to server is erroring. Shutting down server.', error); this.stop(); this.state = ClientState.ServerError; diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 27611b2..d04cd70 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,50 +13,108 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** + * A union type for for objects that can either be a single element or and array of elements. + */ export type MaybeArray = T | T[]; -export function remove(array: T[], value: T): boolean { - const index = array.indexOf(value); - if (index >= 0) { - array.splice(index, 1); - return true; - } - return false; +/** + * Removes the given values from the given array (if present). + * @param array The array to execute the remove operation on. + * @param values The values that should be removed from the array. + */ +export function remove(array: T[], ...values: T[]): void { + values.forEach(value => { + const index = array.indexOf(value); + if (index >= 0) { + array.splice(index, 1); + } + }); } +/** + * Push an array of values to the given array. The values can either be single objects of a concrete type `T` + * or can also be nested arrays of T. If nested arrays are passed the they will be destructured (i.e. flattened) + * so that they can be pushed to the given array. + * @param array The array to push to. + * @param toPush The values of {@link MaybeArray}s that should be pushed. + */ export function flatPush(array: T[], toPush: MaybeArray[]): void { toPush.forEach(value => (Array.isArray(value) ? array.push(...value) : array.push(value))); } -export function distinctAdd(array: T[], value: T): boolean { - if (!array.includes(value)) { - array.push(value); - return true; - } - return false; +/** + * Adds the given values to the given array. The add operation is executed distinct meaning + * a value will not be pushed to the array if its already present in the array. + * @param array The array to push to. + * @param values The values that should be added distinctively. + */ +export function distinctAdd(array: T[], ...values: T[]): void { + values.forEach(value => { + if (!array.includes(value)) { + array.push(value); + } + }); } - interface Constructor { new (...args: any[]): T; } type PrimitiveType = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'; +/** + * A typeguard function to check wether a given object is an array of a specific type `T`. As it checks the type of each individual + * array element this guard check is expensive and should only be used in cases where complete type-safety is required. + * @param object The object to check. + * @param typeGuard A typeguard to check the type of the individual elements. + * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. + * @returns A type predicate indicating wether the given object has passed the type guard check. + */ export function isArrayOfType(object: any, typeGuard: (elem: any) => elem is T, supportEmpty = false): object is T[] { return isArrayMatching(object, element => typeGuard(element), supportEmpty); } -export function isArrayOfClass(object: any, className: Constructor, supportEmpty = false): object is T[] { - return isArrayMatching(object, element => element instanceof className, supportEmpty); +/** + * A typeguard function to check wether a given object is an array of a class`T`. As it checks the wether each individual element + * is an instance of the given class this guard check is expensive and should only be used in cases where complete type-safety is required. + * @param object The object to check. + * @param constructor The constructor for the class under test. + * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. + * @returns A type predicate indicating wether the given object has passed the type guard check. + */ +export function isArrayOfClass(object: any, constructor: Constructor, supportEmpty = false): object is T[] { + return isArrayMatching(object, element => element instanceof constructor, supportEmpty); } +/** + * A typeguard function to check wether a given object is an array of a {@link PrimitiveType} `T. As it checks the type of each individual + * array element this guard check is expensive and should only be used in cases where complete type-safety is required. + * @param object The object to check. + * @param primitiveType The expected primitive type of the elements. + * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. + * @returns A type predicate indicating wether the given object has passed the type guard check. + */ export function isArrayOfPrimitive(object: any, primitiveType: PrimitiveType, supportEmpty = false): object is T[] { return isArrayMatching(object, element => typeof element === primitiveType, supportEmpty); } +/** + * A typeguard function to check wether a given object is an array of a strings. As it checks the type of each individual + * array element this guard check is expensive and should only be used in cases where complete type-safety is required. + * @param object The object to check. + * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. + * @returns A type predicate indicating wether the given object has passed the type guard check. + */ export function isStringArray(object: any, supportEmpty = false): object is string[] { return isArrayOfPrimitive(object, 'string', supportEmpty); } +/** + * A typeguard function to check wether a given object is an array where each element matches the given predicate. + * @param object The object to check. + * @param predicate The predicate to test with. + * @param supportEmpty A flag to determine wether empty arrays be matched by the predicate.. + * @returns `true` if the given object is an array and all elements match the given predicate. `false` otherwise. + */ export function isArrayMatching(object: any, predicate: (elem: any) => boolean, supportEmpty = false): boolean { return Array.isArray(object) && object.every(predicate) && (supportEmpty || object.length > 0); } From adaa4b9c12e8c62f6d80cd1b5a299e5b20e42d33 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 24 Mar 2022 03:34:28 -0700 Subject: [PATCH 086/566] #602 Fix SelectionService.getSelectedElements() method (#169) With eclipse-glsp/glsp-client/pull/167 a bug in the selection service was introduced. The `getSelectedElements()` query method returns all `selectable` elements instead of the elements that are actually selected. - Fix `SelectionService.getSelectedElements()` behavior. This also includes a performance improvement. We now only map the currently selected element ids to their SModelElement Instead of iterating over all elements of the index to receive the selected elements - Introduce a new smodel util function that allows retrieving all model elements that correspond to a given set of element ids from the index. - Introduce a `TypeGuard` convenience type to `typeguard-util` Fixes eclipse-glsp/glsp/issues/602 --- .../src/features/select/selection-service.ts | 4 ++-- packages/client/src/utils/smodel-util.ts | 20 ++++++++++++++++++- packages/protocol/src/utils/typeguard-util.ts | 7 ++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index a2e48a0..a4c46b7 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -31,7 +31,7 @@ import { } from 'sprotty'; import { SModelRootListener } from '../../base/model/update-model-command'; import { GLSP_TYPES } from '../../base/types'; -import { getMatchingElements } from '../../utils/smodel-util'; +import { getElements } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; @@ -124,7 +124,7 @@ export class SelectionService implements SModelRootListener { } getSelectedElements(): Readonly[] { - return getMatchingElements(this.root.index, isSelectable); + return getElements(this.root.index, Array.from(this.selectedElementIDs), isSelectable); } /** diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index 2fe6e08..9c70abd 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { distinctAdd, ElementAndRoutingPoints, remove } from '@eclipse-glsp/protocol'; +import { distinctAdd, ElementAndRoutingPoints, remove, TypeGuard } from '@eclipse-glsp/protocol'; import { BoundsAware, ElementAndBounds, @@ -74,6 +74,24 @@ export function getMatchingElements(index: SModelIndex, predic return Array.from(filter(index, predicate)); } +/** + * Invokes the given model index to retrieve the corresponding model elements for the given set of ids. Ids that + * have no corresponding element in the index will be ignored. + * @param index THe model index. + * @param elementsIDs The element ids. + * @param guard Optional typeguard. If defined only elements that match the guard will be returned. + * @returns An array of the model elements that correspond to the given ids and filter predicate. + */ +export function getElements(index: SModelIndex, elementsIDs: string[], guard?: TypeGuard): S[] { + // Internal filter function that filters out undefined model elements and runs an optional typeguard check. + const filterFn = (element?: SModelElement): element is S => { + if (element !== undefined) { + return guard ? guard(element) : true; + } + return false; + }; + return elementsIDs.map(id => index.getById(id)).filter(filterFn); +} /** * Retrieves the amount of currently selected elements in the given {@link SModelIndex}. * @param index The {@link SModelIndex}. diff --git a/packages/protocol/src/utils/typeguard-util.ts b/packages/protocol/src/utils/typeguard-util.ts index bddbff4..48e0414 100644 --- a/packages/protocol/src/utils/typeguard-util.ts +++ b/packages/protocol/src/utils/typeguard-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,6 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** + * Utility type to describe typeguard functions. + */ +export type TypeGuard = (element: any, ...args: any[]) => element is T; + /** * Validates whether the given object as a property of type `string` with the given key. * @param object The object that should be validated From 2e1a42e3ae3e86272cf8728d571a680a2c8e36be Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 25 Mar 2022 16:22:22 +0100 Subject: [PATCH 087/566] Refactorings: Source model loading and saving - Rename ModelSourceLoader to SourceModelPersistence - Add saving to the SourceModelPersistence interface - Delegate from the SaveModelActionHandler to that implementation - Update javadoc and variable names where fit https://github.com/eclipse-glsp/glsp/issues/582 --- .../src/actions/save-model-action-handler.ts | 33 +++++------------ .../src/base-impl/gmodel-diagram-module.ts | 8 ++--- .../{gmodel-loader.ts => gmodel-storage.ts} | 25 +++++++++---- packages/server-node/src/base-impl/index.ts | 2 +- packages/server-node/src/di/diagram-module.ts | 36 +++++++++---------- .../src/features/model/gmodel-factory.ts | 4 +-- .../model/request-model-action-handler.ts | 11 +++--- ...urce-loader.ts => source-model-storage.ts} | 17 ++++++--- packages/server-node/src/index.ts | 2 +- .../operations/operation-action-handler.ts | 1 + 10 files changed, 71 insertions(+), 68 deletions(-) rename packages/server-node/src/base-impl/{gmodel-loader.ts => gmodel-storage.ts} (72%) rename packages/server-node/src/features/model/{model-source-loader.ts => source-model-storage.ts} (69%) diff --git a/packages/server-node/src/actions/save-model-action-handler.ts b/packages/server-node/src/actions/save-model-action-handler.ts index e22daa5..55effa3 100644 --- a/packages/server-node/src/actions/save-model-action-handler.ts +++ b/packages/server-node/src/actions/save-model-action-handler.ts @@ -13,14 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; +import { Action, DirtyStateChangeReason, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { GModelState } from '../base-impl/gmodel-state'; -import { ClientOptionsUtil } from '../utils/client-options-util'; -import { ActionHandler } from './action-handler'; -import { writeFileSync } from 'fs'; -import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { SourceModelStorage } from '../features/model/source-model-storage'; import { GLSPServerError } from '../utils/glsp-server-error'; +import { ActionHandler } from './action-handler'; @injectable() export class SaveModelActionHandler implements ActionHandler { @@ -29,29 +27,16 @@ export class SaveModelActionHandler implements ActionHandler { @inject(GModelState) protected modelState: GModelState; - @inject(GModelSerializer) - protected modelSerializer: GModelSerializer; + @inject(SourceModelStorage) + protected sourceModelStorage: SourceModelStorage; execute(action: Action): MaybePromise { - this.saveModelState(action); - return [new SetDirtyStateAction(this.modelState.isDirty)]; // TODO: set reason to SAVE - } - - protected saveModelState(action: SaveModelAction): void { try { - const data = this.modelSerializer.createSchema(this.modelState.root); - // eslint-disable-next-line no-null/no-null - writeFileSync(this.modelState.sourceUri!, JSON.stringify(data, null, 2)); - if (this.saveIsDone(action)) { - // TODO: call save is done when available - } + this.sourceModelStorage.saveSourceModel(action); + this.modelState.isDirty = false; // TODO: call save is done when available } catch (err) { - throw new GLSPServerError(`An error occured during save process: ${err}`); + throw new GLSPServerError(`An error occurred during save process: ${err}`); } - } - - protected saveIsDone(action: SaveModelAction): boolean { - const sourceUri = this.modelState.sourceUri; - return action.fileUri ? ClientOptionsUtil.adaptUri(action.fileUri) === sourceUri : true; + return [new SetDirtyStateAction(this.modelState.isDirty, DirtyStateChangeReason.SAVE)]; } } diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/base-impl/gmodel-diagram-module.ts index e7ce506..ab3cdd4 100644 --- a/packages/server-node/src/base-impl/gmodel-diagram-module.ts +++ b/packages/server-node/src/base-impl/gmodel-diagram-module.ts @@ -25,16 +25,16 @@ import { ApplyLabelEditOperationHandler } from '../features/directediting/apply- import { LayoutOperationHandler } from '../features/layout/layout-operation-handler'; import { GModelFactory, GModelFactoryNullImpl } from '../features/model/gmodel-factory'; import { GModelIndex } from '../features/model/gmodel-index'; -import { ModelSourceLoader } from '../features/model/model-source-loader'; import { ModelState } from '../features/model/model-state'; +import { SourceModelStorage } from '../features/model/source-model-storage'; import { CutOperationHandler } from '../operations/cut-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; import { PasteOperationHandler } from '../operations/paste-operation-handler'; import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; import { DeleteOperationHandler } from './delete-operation-handler'; -import { GModelLoader } from './gmodel-loader'; import { GModelState } from './gmodel-state'; +import { GModelStorage } from './gmodel-storage'; /** * Extension of the {@link DiagramModule} to provide GModel integration. @@ -42,7 +42,7 @@ import { GModelState } from './gmodel-state'; * Contains all bindings of {@link DiagramModule}. * * Additionally binds: - * - {@link ModelSourceLoader} to {@link GModelLoader} + * - {@link SourceModelStorage} to {@link GModelLoader} * - {@link CommandStack} to {@link DefaultCommandStack} * - {@link ModelState} to {@link GModelState} * - {@link GModelFactory} to {@link GModelFactoryNullImpl} @@ -59,7 +59,7 @@ export abstract class GModelDiagramModule extends DiagramModule { protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { super.configure(bind, unbind, isBound, rebind); - bind(ModelSourceLoader).to(GModelLoader); + bind(SourceModelStorage).to(GModelStorage); bind(CommandStack).to(DefaultCommandStack).inSingletonScope(); // bind GModelState diff --git a/packages/server-node/src/base-impl/gmodel-loader.ts b/packages/server-node/src/base-impl/gmodel-storage.ts similarity index 72% rename from packages/server-node/src/base-impl/gmodel-loader.ts rename to packages/server-node/src/base-impl/gmodel-storage.ts index ac055d6..f399669 100644 --- a/packages/server-node/src/base-impl/gmodel-loader.ts +++ b/packages/server-node/src/base-impl/gmodel-storage.ts @@ -14,22 +14,24 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GGraph, GModelRoot } from '@eclipse-glsp/graph'; -import { isSModelRootSchema, RequestModelAction } from '@eclipse-glsp/protocol'; +import { isSModelRootSchema, MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; +import { writeFileSync } from 'fs'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import { GModelSerializer } from '../features/model/gmodel-serializer'; -import { ModelSourceLoader } from '../features/model/model-source-loader'; +import { SourceModelStorage } from '../features/model/source-model-storage'; import { getOrThrow, GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; import { GModelState } from './gmodel-state'; -export const EMTPY_ROOT = GGraph.builder().id('empty').build(); +export const EMPTY_ROOT = GGraph.builder().id('empty').build(); /** - * A {@link ModelSourceLoader} that reads the graph model directly from a JSON file and uses it as source model. + * A {@link SourceModelStorage} that reads and writes the graph model directly from / into + * a JSON file and uses it as source model. */ @injectable() -export class GModelLoader implements ModelSourceLoader { +export class GModelStorage implements SourceModelStorage { @inject(Logger) protected logger: Logger; @@ -39,7 +41,7 @@ export class GModelLoader implements ModelSourceLoader { @inject(GModelState) protected modelState: GModelState; - loadSourceModel(action: RequestModelAction): void { + loadSourceModel(action: RequestModelAction): MaybePromise { const sourceUri = getOrThrow( this.modelState.sourceUri, `Invalid RequestModelAction! Missing argument with key '${GModelState.SOURCE_URI}'` @@ -53,7 +55,7 @@ export class GModelLoader implements ModelSourceLoader { try { const fileContent = this.readFile(url); if (!fileContent) { - return EMTPY_ROOT; + return EMPTY_ROOT; } if (!isSModelRootSchema(fileContent)) { throw new Error('The loaded root object is not of type SModelRootSchema'); @@ -72,4 +74,13 @@ export class GModelLoader implements ModelSourceLoader { throw new GLSPServerError(`Could not read & parse file contents of '${url}' as json`, error); } } + + saveSourceModel(action: SaveModelAction): MaybePromise { + try { + const data = this.modelSerializer.createSchema(this.modelState.root); + writeFileSync(this.modelState.sourceUri!, JSON.stringify(data, undefined, 2)); + } catch (error) { + throw new GLSPServerError(`Could not load model from file: ${this.modelState.sourceUri}`, error); + } + } } diff --git a/packages/server-node/src/base-impl/index.ts b/packages/server-node/src/base-impl/index.ts index f601e7d..eaafa01 100644 --- a/packages/server-node/src/base-impl/index.ts +++ b/packages/server-node/src/base-impl/index.ts @@ -15,5 +15,5 @@ ********************************************************************************/ export * from './gmodel-diagram-module'; -export * from './gmodel-loader'; export * from './gmodel-state'; +export * from './gmodel-storage'; diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index 6841074..5d52ea1 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -43,7 +43,7 @@ import { TriggerNodeCreationAction, UpdateModelAction } from '@eclipse-glsp/protocol'; -import { RequestContextActionsHandler } from '../features/contextactions/request-context-actions-handler'; +import { interfaces } from 'inversify'; import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dispatcher'; import { ActionHandlerConstructor, ActionHandlerFactory } from '../actions/action-handler'; import { ActionHandlerRegistry, ActionHandlerRegistryInitializer } from '../actions/action-handler-registry'; @@ -51,14 +51,30 @@ import { ClientActionHandler } from '../actions/client-action-handler'; import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler'; import { ContextActionsProvider } from '../features/contextactions/context-actions-provider'; import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry'; +import { RequestContextActionsHandler } from '../features/contextactions/request-context-actions-handler'; +import { + ContextEditValidatorRegistry, + DefaultContextEditValidatorRegistry +} from '../features/directediting/context-edit-validator-registry'; +import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler'; import { DefaultGModelSerializer, GModelSerializer } from '../features/model/gmodel-serializer'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; import { RequestModelActionHandler } from '../features/model/request-model-action-handler'; +import { NavigationTargetProvider } from '../features/navigation/navigation-target-provider'; +import { + DefaultNavigationTargetProviderRegistry, + NavigationTargetProviderRegistry +} from '../features/navigation/navigation-target-provider-registry'; +import { RequestNavigationTargetsActionHandler } from '../features/navigation/request-navigation-targets-action-handler'; +import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler'; +import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; +import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; import { CompoundOperationHandler } from '../operations/compound-operation-handler'; import { OperationActionHandler } from '../operations/operation-action-handler'; import { OperationHandlerConstructor, OperationHandlerFactory } from '../operations/operation-handler'; import { OperationHandlerRegistry, OperationHandlerRegistryInitializer } from '../operations/operation-handler-registry'; import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { GLSPModule } from './glsp-module'; import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; import { ClientActionKinds, @@ -69,22 +85,6 @@ import { NavigationTargetProviders, Operations } from './service-identifiers'; -import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; -import { - ContextEditValidatorRegistry, - DefaultContextEditValidatorRegistry -} from '../features/directediting/context-edit-validator-registry'; -import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler'; -import { NavigationTargetProvider } from '../features/navigation/navigation-target-provider'; -import { - DefaultNavigationTargetProviderRegistry, - NavigationTargetProviderRegistry -} from '../features/navigation/navigation-target-provider-registry'; -import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler'; -import { RequestNavigationTargetsActionHandler } from '../features/navigation/request-navigation-targets-action-handler'; -import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; -import { interfaces } from 'inversify'; -import { GLSPModule } from './glsp-module'; /** * The diagram module is the central configuration artifact for configuring a client session specific injector. For each @@ -116,7 +116,7 @@ import { GLSPModule } from './glsp-module'; * The following bindings are required in either a subclass or an additional module: * - {@link DiagramType} via the subclasses diagramType property * - {@link DiagramConfiguration} - * - {@link ModelSourceLoader} + * - {@link SourceModelStorage} * - {@link GModelState} * - {@link GModelFactory} * - {@link CommandStack} diff --git a/packages/server-node/src/features/model/gmodel-factory.ts b/packages/server-node/src/features/model/gmodel-factory.ts index e97f55c..27ed235 100644 --- a/packages/server-node/src/features/model/gmodel-factory.ts +++ b/packages/server-node/src/features/model/gmodel-factory.ts @@ -22,7 +22,7 @@ export const GModelFactory = Symbol('GModelFactory'); * * The responsibility of a {@link GModelFactory} implementation is to define how a {@link GModelState} is to be * translated into a {@link GModelRoot} that is sent to the client for rendering. Before a {@link GModelFactory} - * is invoked, the {@link ModelSourceLoader} has already been executed for loading the source model into the + * is invoked, the {@link SourceModelStorage} has already been executed for loading the source model into the * {@link GModelState}. The {@link GModelFactory} then produces the {@link GModelRoot} from the source model in the * {@link GModelState}. Implementations of {@link GModelFactory} are usually specific to the type of source model, as * they need to understand the source model in order to translate it into a graph model. @@ -37,7 +37,7 @@ export const GModelFactory = Symbol('GModelFactory'); * shall be put into the model state too. Typically the {@link GModelIndex} is extended for a particular model source * type as well. * - * @see ModelSourceLoader + * @see SourceModelStorage * @see GModelIndex */ export interface GModelFactory { diff --git a/packages/server-node/src/features/model/request-model-action-handler.ts b/packages/server-node/src/features/model/request-model-action-handler.ts index dd2608d..245e53a 100644 --- a/packages/server-node/src/features/model/request-model-action-handler.ts +++ b/packages/server-node/src/features/model/request-model-action-handler.ts @@ -13,17 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GGraph } from '@eclipse-glsp/graph'; import { Action, GLSPServerStatusAction, isRequestModelAction, RequestModelAction, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../../actions/action-dispatcher'; import { ActionHandler } from '../../actions/action-handler'; import { Logger } from '../../utils/logger'; -import { ModelSourceLoader } from './model-source-loader'; import { ModelState } from './model-state'; import { ModelSubmissionHandler } from './model-submission-handler'; - -export const EMPTY_ROOT = GGraph.builder().id('empty'); +import { SourceModelStorage } from './source-model-storage'; @injectable() export class RequestModelActionHandler implements ActionHandler { @@ -32,8 +29,8 @@ export class RequestModelActionHandler implements ActionHandler { @inject(Logger) private logger: Logger; - @inject(ModelSourceLoader) - protected modelSourceLoader: ModelSourceLoader; + @inject(SourceModelStorage) + protected sourceModelStorage: SourceModelStorage; @inject(ActionDispatcher) protected actionDispatcher: ActionDispatcher; @@ -50,7 +47,7 @@ export class RequestModelActionHandler implements ActionHandler { this.modelState.setAll(action.options ?? {}); this.notifyClient('Model loading in progress'); - await this.modelSourceLoader.loadSourceModel(action); + await this.sourceModelStorage.loadSourceModel(action); this.notifyClient(); } return this.submissionHandler.submitModel(); diff --git a/packages/server-node/src/features/model/model-source-loader.ts b/packages/server-node/src/features/model/source-model-storage.ts similarity index 69% rename from packages/server-node/src/features/model/model-source-loader.ts rename to packages/server-node/src/features/model/source-model-storage.ts index 2ee1950..9111dca 100644 --- a/packages/server-node/src/features/model/model-source-loader.ts +++ b/packages/server-node/src/features/model/source-model-storage.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { MaybePromise, RequestModelAction } from '@eclipse-glsp/protocol'; +import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; -export const ModelSourceLoader = Symbol('ModelSourceLoader'); +export const SourceModelStorage = Symbol('SourceModelStorage'); /** - * A source model loader loads models into the model state. + * A source model storage handles the persistence of source models, i.e. loading and saving it from/into the model state. * * A `source model` is an arbitrary model from which the graph model of the diagram is to be created. * Implementations of source model loaders are specific to the type of source model or persistence format that is used @@ -26,15 +26,24 @@ export const ModelSourceLoader = Symbol('ModelSourceLoader'); * {@link RequestModelAction}; typically its client options. Once the source model is loaded, a model loader is expected * to put the loaded source model into the model state for further processing, such as transforming the loaded model * into a graph model (see GModelFactory). + * On `saveSourceModel(SaveModelAction)`, the source model storage persists the source model from the model state. * * @see ClientOptionsUtil * @see GModelFactory */ -export interface ModelSourceLoader { +export interface SourceModelStorage { /** * Loads a source model into the `modelState`. * * @param action Action sent by the client to specifying the information needed to load the source model. */ loadSourceModel(action: RequestModelAction): MaybePromise; + + /** + * Saves the source model from the `modelState`. + * Implementations are expected to throw an error if the save didn't work. + * + * @param action Action sent by the client to specifying the information needed to save the source model. + */ + saveSourceModel(action: SaveModelAction): MaybePromise; } diff --git a/packages/server-node/src/index.ts b/packages/server-node/src/index.ts index 8e19f0d..64b9d7c 100644 --- a/packages/server-node/src/index.ts +++ b/packages/server-node/src/index.ts @@ -53,10 +53,10 @@ export * from './features/layout/layout-operation-handler'; export * from './features/model/gmodel-factory'; export * from './features/model/gmodel-index'; export * from './features/model/gmodel-serializer'; -export * from './features/model/model-source-loader'; export * from './features/model/model-state'; export * from './features/model/model-submission-handler'; export * from './features/model/request-model-action-handler'; +export * from './features/model/source-model-storage'; export * from './features/navigation/json-opener-options'; export * from './features/navigation/navigation-target-provider'; export * from './features/navigation/navigation-target-provider-registry'; diff --git a/packages/server-node/src/operations/operation-action-handler.ts b/packages/server-node/src/operations/operation-action-handler.ts index d707487..9263610 100644 --- a/packages/server-node/src/operations/operation-action-handler.ts +++ b/packages/server-node/src/operations/operation-action-handler.ts @@ -47,6 +47,7 @@ export class OperationActionHandler implements ActionHandler { // TODO: Create GModelRecordingCommand; await handler.execute(operation); this.modelState.index.indexRoot(this.modelState.root); + this.modelState.isDirty = true; // TODO: this.modelState.execute(command) return this.modelSubmissionHandler.submitModel(); // TODO: Add SetDirtyStateAction.Reason.Operation } From aafebf7aeb9c6da904b486ed66b23337e66e36fc Mon Sep 17 00:00:00 2001 From: Lukas <42733123+ivy-lli@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:11:18 +0100 Subject: [PATCH 088/566] #604 Support snapping support for edges (#170) * Support snapping support for edges - Add snapping to mouseMove in FeedbackEdgeRouteMovingMouseListener - Remove unused 'hasDragged' attribute from FeedbackEdgeRouteMovingMouseListener * Refactor duplications to own util class with tests --- .../src/features/change-bounds/snap.spec.ts | 89 +++++++++++++++++++ .../client/src/features/change-bounds/snap.ts | 88 +++++++++++++++++- .../tool-feedback/edge-edit-tool-feedback.ts | 87 ++++++++++-------- .../src/features/tools/change-bounds-tool.ts | 47 ++-------- .../src/features/tools/edge-edit-tool.ts | 7 +- 5 files changed, 236 insertions(+), 82 deletions(-) create mode 100644 packages/client/src/features/change-bounds/snap.spec.ts diff --git a/packages/client/src/features/change-bounds/snap.spec.ts b/packages/client/src/features/change-bounds/snap.spec.ts new file mode 100644 index 0000000..eccdf53 --- /dev/null +++ b/packages/client/src/features/change-bounds/snap.spec.ts @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { expect } from 'chai'; +import { SModelElement } from 'sprotty'; +import { GridSnapper, PointPositionUpdater } from './snap'; + +describe('GridSnapper', () => { + it('snap', () => { + const element = new SModelElement(); + const snapper = new GridSnapper(); + expect(snapper.snap({ x: 0, y: 0 }, element)).to.be.deep.equals({ x: 0, y: 0 }); + expect(snapper.snap({ x: 4, y: 5 }, element)).to.be.deep.equals({ x: 0, y: 10 }); + expect(snapper.snap({ x: 8, y: 11 }, element)).to.be.deep.equals({ x: 10, y: 10 }); + expect(snapper.snap({ x: -7, y: -4 }, element)).to.be.deep.equals({ x: -10, y: -0 }); + }); +}); + +describe('PointPositionUpdater', () => { + it('updatePosition with no last drag position', () => { + const element = new SModelElement(); + const updater = new PointPositionUpdater(); + expect(updater.updatePosition(element, { x: 0, y: 0 }, false)).to.be.undefined; + expect(updater.updatePosition(element, { x: 0, y: 0 }, true)).to.be.undefined; + }); + + it('update last position and reset', () => { + const updater = new PointPositionUpdater(); + expect(updater.isLastDragPositionUndefined()).to.be.true; + updater.updateLastDragPosition({ x: 0, y: 0 }); + expect(updater.isLastDragPositionUndefined()).to.be.false; + updater.resetPosition(); + expect(updater.isLastDragPositionUndefined()).to.be.true; + }); + + it('updatePosition with no snapper', () => { + const element = new SModelElement(); + const updater = new PointPositionUpdater(); + resetUpdater(updater); + expect(updater.updatePosition(element, { x: 0, y: 0 }, false)).to.be.undefined; + expect(updater.updatePosition(element, { x: 0, y: 0 }, true)).to.be.undefined; + + resetUpdater(updater); + expect(updater.updatePosition(element, { x: 5, y: 3 }, false)).to.be.deep.equals({ x: 5, y: 3 }); + expect(updater.updatePosition(element, { x: 5, y: 3 }, true)).to.be.undefined; + expect(updater.updatePosition(element, { x: 11, y: 6 }, false)).to.be.deep.equals({ x: 6, y: 3 }); + expect(updater.updatePosition(element, { x: -3, y: 2 }, true)).to.be.deep.equals({ x: -14, y: -4 }); + }); + + it('updatePosition with snapper', () => { + const element = new SModelElement(); + const snapper = new GridSnapper(); + const updater = new PointPositionUpdater(snapper); + resetUpdater(updater); + expect(updater.updatePosition(element, { x: 0, y: 0 }, false)).to.be.undefined; + expect(updater.updatePosition(element, { x: 0, y: 0 }, true)).to.be.undefined; + + resetUpdater(updater); + expect(updater.updatePosition(element, { x: 5, y: 3 }, true)).to.be.deep.equals({ x: 10, y: 0 }); + expect(updater.updatePosition(element, { x: 5, y: 3 }, true)).to.be.undefined; + expect(updater.updatePosition(element, { x: 11, y: 6 }, true)).to.be.deep.equals({ x: 0, y: 10 }); + expect(updater.updatePosition(element, { x: -3, y: 2 }, true)).to.be.deep.equals({ x: -10, y: -10 }); + + // disable snapping (alt key) + resetUpdater(updater); + expect(updater.updatePosition(element, { x: 5, y: 3 }, false)).to.be.deep.equals({ x: 5, y: 3 }); + expect(updater.updatePosition(element, { x: 5, y: 3 }, false)).to.be.undefined; + expect(updater.updatePosition(element, { x: 11, y: 6 }, false)).to.be.deep.equals({ x: 6, y: 3 }); + expect(updater.updatePosition(element, { x: -3, y: 2 }, false)).to.be.deep.equals({ x: -14, y: -4 }); + }); + + function resetUpdater(updater: PointPositionUpdater): void { + updater.resetPosition(); + updater.updateLastDragPosition({ x: 0, y: 0 }); + } +}); diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index cb34768..1141e7e 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,8 @@ ********************************************************************************/ import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { ISnapper, SModelElement } from 'sprotty'; +import { findParentByFeature, ISnapper, isViewport, SModelElement } from 'sprotty'; +import { WriteablePoint } from '../../utils/layout-utils'; @injectable() export class GridSnapper implements ISnapper { @@ -34,3 +35,86 @@ export class GridSnapper implements ISnapper { }; } } + +/** + * This class can be used to calculate the current position, when an element is + * moved. This includes node movements, node resizing (resize handle movement) + * or edge routing-point movements. + * + * You can initialize a this class with a optional {@link ISnapper}. If a + * snapper is present, the positions will be snapped to the defined grid. + */ +export class PointPositionUpdater { + protected lastDragPosition?: Point; + protected positionDelta: WriteablePoint = { x: 0, y: 0 }; + + constructor(protected snapper?: ISnapper) {} + + /** + * Init the position with the {@link Point} of your mouse cursor. + * This method is normally called in the `mouseDown` event. + * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` + */ + public updateLastDragPosition(mousePosition: Point): void { + this.lastDragPosition = mousePosition; + } + + /** + * Check if the mouse is currently not in a drag mode. + * @returns true if the last drag position is undefined + */ + public isLastDragPositionUndefined(): boolean { + return this.lastDragPosition === undefined; + } + + /** + * Reset the updater for new movements. + * This method is normally called in the `mouseUp` event. + */ + public resetPosition(): void { + this.lastDragPosition = undefined; + this.positionDelta = { x: 0, y: 0 }; + } + + /** + * Calculate the current position of your movement. + * This method is normally called in the `mouseMove` event. + * @param target node which is moved around + * @param mousePosition current mouse position e.g `{x: event.pageX, y: event.pageY }` + * @param isSnapEnabled if a snapper is defined you can disable it, e.g when a specific key is pressed `!event.altKey` + * @returns current position or undefined if updater has no last drag position initialized + */ + public updatePosition(target: SModelElement, mousePosition: Point, isSnapEnabled: boolean): Point | undefined { + if (this.lastDragPosition) { + const newDragPosition = mousePosition; + + const viewport = findParentByFeature(target, isViewport); + const zoom = viewport?.zoom ?? 1; + const dx = (mousePosition.x - this.lastDragPosition.x) / zoom; + const dy = (mousePosition.y - this.lastDragPosition.y) / zoom; + const deltaToLastPosition = { x: dx, y: dy }; + this.lastDragPosition = newDragPosition; + + // update position delta with latest delta + this.positionDelta.x += deltaToLastPosition.x; + this.positionDelta.y += deltaToLastPosition.y; + + // snap our delta and only send update if the position actually changes + // otherwise accumulate delta until we do snap to an update + const positionUpdate = this.snap(this.positionDelta, target, isSnapEnabled); + if (positionUpdate.x === 0 && positionUpdate.y === 0) { + return undefined; + } + + // we update our position so we update our delta by the snapped position + this.positionDelta.x -= positionUpdate.x; + this.positionDelta.y -= positionUpdate.y; + return positionUpdate; + } + return undefined; + } + + protected snap(position: Point, element: SModelElement, isSnap: boolean): Point { + return isSnap && this.snapper ? this.snapper.snap(position, element) : { x: position.x, y: position.y }; + } +} diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index a45da48..7f3a349 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -29,8 +29,8 @@ import { findParentByFeature, isBoundsAware, isConnectable, + ISnapper, isSelected, - isViewport, MouseListener, MoveAction, PolylineEdgeRouter, @@ -44,6 +44,7 @@ import { } from 'sprotty'; import { forEachElement, isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; +import { PointPositionUpdater } from '../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; import { FeedbackEdgeEnd, feedbackEdgeEndId, FeedbackEdgeEndMovingMouseListener, feedbackEdgeId } from './creation-tool-feedback'; import { FeedbackCommand } from './model'; @@ -201,11 +202,11 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { } export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { - protected hasDragged = false; - protected lastDragPosition: Point | undefined; + protected pointPositionUpdater: PointPositionUpdater; - constructor(protected edgeRouterRegistry?: EdgeRouterRegistry) { + constructor(protected edgeRouterRegistry?: EdgeRouterRegistry, protected snapper?: ISnapper) { super(); + this.pointPositionUpdater = new PointPositionUpdater(snapper); } override mouseDown(target: SModelElement, event: MouseEvent): Action[] { @@ -214,11 +215,10 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { const routingHandle = findParentByFeature(target, isRoutingHandle); if (routingHandle !== undefined) { result.push(new SwitchRoutingModeAction([target.id], [])); - this.lastDragPosition = { x: event.pageX, y: event.pageY }; + this.pointPositionUpdater.updateLastDragPosition({ x: event.pageX, y: event.pageY }); } else { - this.lastDragPosition = undefined; + this.pointPositionUpdater.resetPosition(); } - this.hasDragged = false; } return result; } @@ -226,40 +226,50 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { override mouseMove(target: SModelElement, event: MouseEvent): Action[] { const result: Action[] = []; if (event.buttons === 0) { - this.mouseUp(target, event); - } else if (this.lastDragPosition) { - const viewport = findParentByFeature(target, isViewport); - this.hasDragged = true; - const zoom = viewport ? viewport.zoom : 1; - const dx = (event.pageX - this.lastDragPosition.x) / zoom; - const dy = (event.pageY - this.lastDragPosition.y) / zoom; - const handleMoves: ElementMove[] = []; - target.root.index - .all() - .filter(element => isSelected(element)) - .forEach(element => { - if (isRoutingHandle(element)) { - const point = this.getHandlePosition(element); - if (point !== undefined) { - handleMoves.push({ - elementId: element.id, - fromPosition: point, - toPosition: { - x: point.x + dx, - y: point.y + dy - } - }); - } - } - }); - this.lastDragPosition = { x: event.pageX, y: event.pageY }; - if (handleMoves.length > 0) { - result.push(new MoveAction(handleMoves, false)); - } + return this.mouseUp(target, event); + } + const positionUpdate = this.pointPositionUpdater.updatePosition(target, { x: event.pageX, y: event.pageY }, !event.altKey); + if (positionUpdate) { + const moveActions = this.handleMoveOnClient(target, positionUpdate); + result.push(...moveActions); } return result; } + protected handleMoveOnClient(target: SModelElement, positionUpdate: Point): Action[] { + const handleMoves: ElementMove[] = []; + target.root.index + .all() + .filter(element => isSelected(element)) + .forEach(element => { + if (isRoutingHandle(element)) { + const elementMove = this.toElementMove(element, positionUpdate); + if (elementMove) { + handleMoves.push(elementMove); + } + } + }); + if (handleMoves.length > 0) { + return [new MoveAction(handleMoves, false)]; + } + return []; + } + + protected toElementMove(element: SRoutingHandle, positionDelta: Point): ElementMove | undefined { + const point = this.getHandlePosition(element); + if (point !== undefined) { + return { + elementId: element.id, + fromPosition: point, + toPosition: { + x: point.x + positionDelta.x, + y: point.y + positionDelta.y + } + }; + } + return undefined; + } + protected getHandlePosition(handle: SRoutingHandle): Point | undefined { if (this.edgeRouterRegistry) { const parent = handle.parent; @@ -281,8 +291,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } override mouseUp(_target: SModelElement, event: MouseEvent): Action[] { - this.hasDragged = false; - this.lastDragPosition = undefined; + this.pointPositionUpdater.resetPosition(); return []; } diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 36b55af..701b7d3 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -32,7 +32,6 @@ import { findParentByFeature, ISnapper, isSelected, - isViewport, MouseListener, SChildElement, SConnectableElement, @@ -44,7 +43,7 @@ import { } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { GLSP_TYPES } from '../../base/types'; -import { isValidMove, isValidSize, WriteablePoint } from '../../utils/layout-utils'; +import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { forEachElement, isNonRoutableSelectedMovableBoundsAware, @@ -57,6 +56,7 @@ import { IMovementRestrictor, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; +import { PointPositionUpdater } from '../change-bounds/snap'; import { SelectionListener, SelectionService } from '../select/selection-service'; import { FeedbackMoveMouseListener, @@ -127,8 +127,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele // members for calculating the correct position change protected initialBounds: Bounds | undefined; - protected lastDragPosition?: Point; - protected positionDelta: WriteablePoint = { x: 0, y: 0 }; + protected pointPositionUpdater: PointPositionUpdater; // members for resize mode protected activeResizeElement?: SModelElement; @@ -136,6 +135,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele constructor(protected tool: ChangeBoundsTool) { super(); + this.pointPositionUpdater = new PointPositionUpdater(tool.snapper); } override mouseDown(target: SModelElement, event: MouseEvent): Action[] { @@ -166,7 +166,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele cursorFeedbackAction(this.activeResizeHandle.isNwSeResize() ? CursorCSS.RESIZE_NWSE : CursorCSS.RESIZE_NESW), applyCssClasses(this.activeResizeHandle, ChangeBoundsListener.CSS_CLASS_ACTIVE) ]; - const positionUpdate = this.updatePosition(target, event); + const positionUpdate = this.pointPositionUpdater.updatePosition(target, { x: event.pageX, y: event.pageY }, !event.altKey); if (positionUpdate) { const resizeActions = this.handleResizeOnClient(positionUpdate); actions.push(...resizeActions); @@ -177,7 +177,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } override draggingMouseUp(target: SModelElement, event: MouseEvent): Action[] { - if (this.lastDragPosition === undefined) { + if (this.pointPositionUpdater.isLastDragPositionUndefined()) { this.resetPosition(); return []; } @@ -299,7 +299,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } protected initPosition(event: MouseEvent): void { - this.lastDragPosition = { x: event.pageX, y: event.pageY }; + this.pointPositionUpdater.updateLastDragPosition({ x: event.pageX, y: event.pageY }); if (this.activeResizeHandle) { const resizeElement = findParentByFeature(this.activeResizeHandle, isResizable); this.initialBounds = { @@ -311,36 +311,6 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } } - protected updatePosition(target: SModelElement, event: MouseEvent): Point | undefined { - if (this.lastDragPosition) { - const newDragPosition = { x: event.pageX, y: event.pageY }; - - const viewport = findParentByFeature(target, isViewport); - const zoom = viewport ? viewport.zoom : 1; - const dx = (event.pageX - this.lastDragPosition.x) / zoom; - const dy = (event.pageY - this.lastDragPosition.y) / zoom; - const deltaToLastPosition = { x: dx, y: dy }; - this.lastDragPosition = newDragPosition; - - // update position delta with latest delta - this.positionDelta.x += deltaToLastPosition.x; - this.positionDelta.y += deltaToLastPosition.y; - - // snap our delta and only send update if the position actually changes - // otherwise accumulate delta until we do snap to an update - const positionUpdate = this.snap(this.positionDelta, target, !event.altKey); - if (positionUpdate.x === 0 && positionUpdate.y === 0) { - return undefined; - } - - // we update our position so we update our delta by the snapped position - this.positionDelta.x -= positionUpdate.x; - this.positionDelta.y -= positionUpdate.y; - return positionUpdate; - } - return undefined; - } - protected reset(): void { if (this.activeResizeElement && isResizable(this.activeResizeElement)) { this.tool.dispatchFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this); @@ -351,8 +321,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected resetPosition(): void { this.activeResizeHandle = undefined; - this.lastDragPosition = undefined; - this.positionDelta = { x: 0, y: 0 }; + this.pointPositionUpdater.resetPosition(); } protected handleResizeOnClient(positionUpdate: Point): Action[] { diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index afb37ef..68f9ce8 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -22,11 +22,13 @@ import { EdgeRouterRegistry, findParentByFeature, isConnectable, + ISnapper, isSelected, SModelElement, SModelRoot, SRoutableElement, - SRoutingHandle + SRoutingHandle, + TYPES } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; import { GLSP_TYPES } from '../../base/types'; @@ -53,6 +55,7 @@ export class EdgeEditTool extends BaseGLSPTool { @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() protected edgeRouterRegistry?: EdgeRouterRegistry; + @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; protected feedbackEdgeSourceMovingListener: FeedbackEdgeSourceMovingMouseListener; protected feedbackEdgeTargetMovingListener: FeedbackEdgeTargetMovingMouseListener; @@ -71,7 +74,7 @@ export class EdgeEditTool extends BaseGLSPTool { // install feedback move mouse listener for client-side move updates this.feedbackEdgeSourceMovingListener = new FeedbackEdgeSourceMovingMouseListener(this.anchorRegistry); this.feedbackEdgeTargetMovingListener = new FeedbackEdgeTargetMovingMouseListener(this.anchorRegistry); - this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.edgeRouterRegistry); + this.feedbackMovingListener = new FeedbackEdgeRouteMovingMouseListener(this.edgeRouterRegistry, this.snapper); } registerFeedbackListeners(): void { From d89e64a19ef50bfe02834f9794519247f09e8a3f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 30 Mar 2022 02:45:34 -0700 Subject: [PATCH 089/566] #472 Refactor action defintion to interfaces (#171) * #472 Refactor action definition to interfaces Sprotty moved away from class based approach for action definition and solely relies on interfaces now. To be able to update to the latest sprotty version GLSP needs to to do the same. Otherwise action definitions might clash and there will be an inconsistent mix of class and interface definitions. This PR refactors the action definitions in the base protocol & the glsp client to interfaces. Action definitions that are reused from sprotty are marked with `extends sprotty.XYZAction`. In addition, to the pure interface definition for each action/operation type a namespace is provided that offers utility functions for type checking and creating new instances. - Refactor all action definitions to interfaces+namespaces - Refactor all action constructions calls to use the new `Action.create() `approach instead of the old `new Action()` approach - Add tests for the action utility function (is, create etc.) of all protocol actions. - Replace all old typeguard functions like `isComputedBoundsAction` with their new namespace counterpart (SetBoundsAction.is) - Add `isOperation`:true boolean property to the operation interface. This serves as discriminator to make actions & operations distinguishable on a type level. - Update to latest sprotty version - Update to latest glsp-config package to make use of the new no-deprecated eslint rules. - Merge TYPES & GLSP_TYPES service identifier definitions and reexport them in @eclipse-glsp/client. The "old" GLSP_TYPES namespace has been deprecated but is still available to ease the migration pain for (early) adopters. - Add package specific eslint warnings for restricted/discourages imports. Also: - Disable hover-popup listener when the Ede creation tool is active - Update changelog Contributed on behalf of STMicroelectronics --- examples/workflow-glsp/.eslintrc.js | 14 + .../src/direct-task-editing/di.config.ts | 5 +- .../direct-task-editing/direct-task-editor.ts | 60 ++- examples/workflow-glsp/src/workflow-views.tsx | 5 +- examples/workflow-standalone/.eslintrc.js | 14 + examples/workflow-standalone/app/example1.wf | 14 +- examples/workflow-standalone/src/app.ts | 30 +- examples/workflow-standalone/src/di.config.ts | 5 +- packages/client/.eslintrc.js | 13 + packages/client/package.json | 3 +- packages/client/src/base/action-dispatcher.ts | 17 +- .../src/base/actions/focus-change-action.ts | 33 +- .../src/base/actions/sprotty-actions.ts} | 25 +- .../auto-complete/auto-complete-actions.ts | 12 +- .../auto-complete/auto-complete-widget.ts | 21 +- packages/client/src/base/command-stack.ts | 7 +- packages/client/src/base/di.config.ts | 9 +- .../client/src/base/editor-context-service.ts | 14 +- packages/client/src/base/focus-tracker.ts | 9 +- .../base/model-initialization-constraint.ts | 7 +- .../src/base/model/update-model-command.ts | 15 +- .../base/tool-manager/glsp-tool-manager.ts | 8 +- .../src/base/tool-manager/tool-actions.ts | 37 ++ packages/client/src/base/types.ts | 55 +-- .../change-bounds/movement-restrictor.ts | 6 +- .../src/features/command-palette/di.config.ts | 12 +- .../server-command-palette-provider.ts | 17 +- .../delete-element-context-menu.ts | 12 +- .../src/features/context-menu/di.config.ts | 5 +- ...rvice-aware-context-menu-mouse-listener.ts | 11 +- .../server-context-menu-provider.ts | 13 +- .../copy-paste/copy-paste-context-menu.ts | 53 +-- .../features/copy-paste/copy-paste-handler.ts | 12 +- .../src/features/copy-paste/di.config.ts | 22 +- .../features/decoration/decoration-placer.ts | 4 +- .../src/features/decoration/di.config.ts | 8 +- .../src/features/edit-label/di.config.ts | 9 +- .../features/edit-label/edit-label-tool.ts | 3 +- .../edit-label/edit-label-validator.ts | 16 +- .../client/src/features/hints/di.config.ts | 6 +- .../client/src/features/hints/type-hints.ts | 47 ++- .../client/src/features/hover/di.config.ts | 4 + packages/client/src/features/hover/hover.ts | 40 +- .../client/src/features/layout/di.config.ts | 2 +- .../src/features/layout/freeform-layout.ts | 15 +- .../features/layout/layout-commands.spec.ts | 60 +-- .../src/features/layout/layout-commands.ts | 184 +++++++--- .../client/src/features/layout/vbox-layout.ts | 12 +- .../model-source-changed-action-handler.ts | 19 +- .../src/features/mouse-tool/di.config.ts | 7 +- .../src/features/mouse-tool/mouse-tool.ts | 13 +- .../navigation/navigation-action-handler.ts | 118 +++--- .../navigation-target-resolver.spec.ts | 6 +- .../navigation/navigation-target-resolver.ts | 21 +- .../client/src/features/save/di.config.ts | 5 +- .../src/features/save/save-keylistener.ts | 2 +- .../client/src/features/select/di.config.ts | 14 +- .../features/select/select-feedback-action.ts | 64 +--- .../features/select/select-mouse-listener.ts | 3 +- .../features/select/selection-service.spec.ts | 19 +- .../src/features/select/selection-service.ts | 20 +- .../change-bounds-tool-feedback.ts | 55 ++- .../tool-feedback/creation-tool-feedback.ts | 63 ++-- .../features/tool-feedback/css-feedback.ts | 77 ++-- .../src/features/tool-feedback/di.config.ts | 9 +- .../tool-feedback/edge-edit-tool-feedback.ts | 115 ++++-- .../feedback-action-dispatcher.ts | 3 +- .../tool-feedback/marquee-tool-feedback.ts | 47 ++- .../src/features/tool-feedback/view.tsx | 6 +- .../src/features/tool-palette/di.config.ts | 5 +- .../src/features/tool-palette/palette-item.ts | 38 -- .../src/features/tool-palette/tool-palette.ts | 46 ++- .../src/features/tools/base-glsp-tool.ts | 8 +- .../src/features/tools/change-bounds-tool.ts | 45 +-- .../client/src/features/tools/delete-tool.ts | 12 +- .../client/src/features/tools/di.config.ts | 20 +- .../src/features/tools/edge-creation-tool.ts | 27 +- .../src/features/tools/edge-edit-tool.ts | 35 +- .../src/features/tools/marquee-behavior.ts | 4 +- .../src/features/tools/marquee-mouse-tool.ts | 17 +- .../client/src/features/tools/marquee-tool.ts | 2 +- packages/client/src/features/tools/model.ts | 3 +- .../src/features/tools/node-creation-tool.ts | 10 +- .../src/features/validation/di.config.ts | 9 +- .../validation/marker-navigator.spec.ts | 6 +- .../features/validation/marker-navigator.ts | 62 ++-- .../src/features/validation/validate.ts | 47 ++- .../client/src/features/viewport/di.config.ts | 3 +- .../viewport/glsp-scroll-mouse-listener.ts | 6 +- packages/client/src/index.ts | 9 +- packages/client/src/lib/model.ts | 3 +- .../src/model-source/glsp-diagram-server.ts | 18 +- packages/client/src/protocol-reexport.ts | 64 ---- packages/client/src/sprotty-reexport.ts | 283 ++++++++++++++ packages/client/src/utils/layout-utils.ts | 6 +- packages/client/src/utils/smodel-util.ts | 40 +- packages/client/src/utils/viewpoint-util.ts | 19 +- packages/client/tsconfig.json | 1 + packages/protocol/.eslintrc.js | 24 ++ packages/protocol/package.json | 6 +- .../src/action-protocol/base-protocol.spec.ts | 236 ++++++++++++ .../src/action-protocol/base-protocol.ts | 220 ++++++++--- .../client-notification.spec.ts | 90 +++++ .../action-protocol/client-notification.ts | 131 +++++-- .../src/action-protocol/clipboard.spec.ts | 168 +++++++++ .../protocol/src/action-protocol/clipboard.ts | 135 ++++--- .../src/action-protocol/contexts.spec..ts | 107 ++++++ .../protocol/src/action-protocol/contexts.ts | 85 +++-- .../action-protocol/edge-modification.spec.ts | 114 ++++++ .../src/action-protocol/edge-modification.ts | 99 +++-- .../action-protocol/element-creation.spec.ts | 165 +++++++++ .../src/action-protocol/element-creation.ts | 165 ++++++--- .../src/action-protocol/element-hover.spec.ts | 108 ++++++ .../src/action-protocol/element-hover.ts | 76 ++-- .../element-navigation.spec.ts | 347 ++++++++++++++++++ .../src/action-protocol/element-navigation.ts | 289 ++++++++++++--- .../action-protocol/element-selection.spec.ts | 99 +++++ .../src/action-protocol/element-selection.ts | 67 +++- .../element-text-editing.spec.ts | 149 ++++++++ .../action-protocol/element-text-editing.ts | 205 ++++++++--- .../element-type-hints.spec.ts | 139 +++++++ .../src/action-protocol/element-type-hints.ts | 73 +++- .../element-validation.spec.ts | 133 +++++++ .../src/action-protocol/element-validation.ts | 105 ++++-- .../protocol/src/action-protocol/index.ts | 4 +- .../src/action-protocol/mode-layout.ts | 73 ---- .../src/action-protocol/model-data.spec.ts | 171 +++++++++ .../src/action-protocol/model-data.ts | 137 +++++-- .../action-protocol/model-edit-mode.spec.ts | 49 +++ .../src/action-protocol/model-edit-mode.ts | 43 ++- .../src/action-protocol/model-layout.spec.ts | 138 +++++++ .../src/action-protocol/model-layout.ts | 131 +++++++ .../src/action-protocol/model-saving.spec.ts | 174 +++++++++ .../src/action-protocol/model-saving.ts | 132 +++++-- .../src/action-protocol/model-structure.ts | 47 ++- .../action-protocol/node-modification.spec.ts | 100 +++++ .../src/action-protocol/node-modification.ts | 80 +++- .../src/action-protocol/tool-palette.spec.ts | 96 +++++ .../src/action-protocol/tool-palette.ts | 89 +++-- .../protocol/src/action-protocol/types.ts | 159 +++++--- .../src/action-protocol/undo-redo.spec.ts | 75 ++++ .../protocol/src/action-protocol/undo-redo.ts | 48 ++- .../src/action-protocol/viewport.spec.ts | 105 ++++++ .../protocol/src/action-protocol/viewport.ts | 113 ++++-- packages/protocol/src/index.ts | 42 ++- .../src/jsonrpc/base-jsonrpc-glsp-client.ts | 2 +- .../src/jsonrpc/glsp-jsonrpc-client.ts | 4 +- .../src/{types => model}/default-types.ts | 5 +- packages/protocol/src/model/model-schema.ts | 34 ++ packages/protocol/src/utils/array-util.ts | 20 + packages/protocol/src/utils/type-util.spec.ts | 109 ++++++ .../utils/{typeguard-util.ts => type-util.ts} | 31 +- 152 files changed, 6570 insertions(+), 1715 deletions(-) rename packages/{protocol/src/utils/launch-util.ts => client/src/base/actions/sprotty-actions.ts} (51%) create mode 100644 packages/client/src/base/tool-manager/tool-actions.ts delete mode 100644 packages/client/src/features/tool-palette/palette-item.ts delete mode 100644 packages/client/src/protocol-reexport.ts create mode 100644 packages/client/src/sprotty-reexport.ts create mode 100644 packages/protocol/src/action-protocol/base-protocol.spec.ts create mode 100644 packages/protocol/src/action-protocol/client-notification.spec.ts create mode 100644 packages/protocol/src/action-protocol/clipboard.spec.ts create mode 100644 packages/protocol/src/action-protocol/contexts.spec..ts create mode 100644 packages/protocol/src/action-protocol/edge-modification.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-creation.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-hover.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-navigation.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-selection.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-text-editing.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-type-hints.spec.ts create mode 100644 packages/protocol/src/action-protocol/element-validation.spec.ts delete mode 100644 packages/protocol/src/action-protocol/mode-layout.ts create mode 100644 packages/protocol/src/action-protocol/model-data.spec.ts create mode 100644 packages/protocol/src/action-protocol/model-edit-mode.spec.ts create mode 100644 packages/protocol/src/action-protocol/model-layout.spec.ts create mode 100644 packages/protocol/src/action-protocol/model-layout.ts create mode 100644 packages/protocol/src/action-protocol/model-saving.spec.ts create mode 100644 packages/protocol/src/action-protocol/node-modification.spec.ts create mode 100644 packages/protocol/src/action-protocol/tool-palette.spec.ts create mode 100644 packages/protocol/src/action-protocol/undo-redo.spec.ts create mode 100644 packages/protocol/src/action-protocol/viewport.spec.ts rename packages/protocol/src/{types => model}/default-types.ts (93%) create mode 100644 packages/protocol/src/model/model-schema.ts create mode 100644 packages/protocol/src/utils/type-util.spec.ts rename packages/protocol/src/utils/{typeguard-util.ts => type-util.ts} (70%) diff --git a/examples/workflow-glsp/.eslintrc.js b/examples/workflow-glsp/.eslintrc.js index 727bf62..2af1914 100644 --- a/examples/workflow-glsp/.eslintrc.js +++ b/examples/workflow-glsp/.eslintrc.js @@ -4,5 +4,19 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' + }, + rules: { + 'no-restricted-imports': [ + 'warn', + { + name: 'sprotty', + message: "The sprotty default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" + }, + { + name: 'sprotty-protocol', + message: + "The sprotty-protocol default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" + } + ] } }; diff --git a/examples/workflow-glsp/src/direct-task-editing/di.config.ts b/examples/workflow-glsp/src/direct-task-editing/di.config.ts index 935f657..ec0d138 100644 --- a/examples/workflow-glsp/src/direct-task-editing/di.config.ts +++ b/examples/workflow-glsp/src/direct-task-editing/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,9 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { TYPES } from '@eclipse-glsp/client'; import { ContainerModule } from 'inversify'; -import { TYPES } from 'sprotty'; - import { TaskEditor } from './direct-task-editor'; export const directTaskEditor = new ContainerModule((bind, _unbind, isBound) => { diff --git a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts index 3a0ffd4..100e32c 100644 --- a/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts +++ b/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts @@ -20,28 +20,58 @@ import { EditorContextService, getAbsoluteClientBounds, GLSPActionDispatcher, + hasStringProp, ILogger, - isSetContextActionsAction, - isSetEditValidationResultAction, LabeledAction, + ModelIndexImpl, + Operation, RequestContextActions, RequestEditValidationAction, - SModelElement, + SetContextActions, + SetEditValidationResultAction, SModelRoot, toActionArray, + TYPES, ValidationDecorator, ValidationStatus, ViewerOptions } from '@eclipse-glsp/client'; import { inject, injectable } from 'inversify'; -import { SModelIndex, TYPES } from 'sprotty/lib'; import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; import { isTaskNode, TaskNode } from '../model'; -export class ApplyTaskEditOperation implements Action { - static readonly KIND = 'applyTaskEdit'; - readonly kind = ApplyTaskEditOperation.KIND; - constructor(readonly taskId: string, readonly expression: string) {} +/** + * Is send from the {@link TaskEditor} to the GLSP server + * to execute a task edit operation. + */ +export interface ApplyTaskEditOperation extends Operation { + kind: typeof ApplyTaskEditOperation.KIND; + + /** + * Id of the task that should be edited + */ + taskId: string; + + /** + * The edit expression + */ + expression: string; +} + +export namespace ApplyTaskEditOperation { + export const KIND = 'applyTaskEdit'; + + export function is(object: any): object is ApplyTaskEditOperation { + return Operation.hasKind(object, KIND) && hasStringProp(object, 'taskId') && hasStringProp(object, 'expression'); + } + + export function create(options: { taskId: string; expression: string }): ApplyTaskEditOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } } @injectable() @@ -125,17 +155,19 @@ export class TaskEditor extends AbstractUIExtension { protected async retrieveSuggestions(input: string): Promise { const response = await this.actionDispatcher.request( - new RequestContextActions(TaskEditor.ID, this.editorContextService.get({ ['text']: input })) + RequestContextActions.create({ contextId: TaskEditor.ID, editorContext: this.editorContextService.get({ ['text']: input }) }) ); - if (isSetContextActionsAction(response)) { + if (SetContextActions.is(response)) { return response.actions; } return Promise.reject(); } protected async validateInput(input: string): Promise { - const response = await this.actionDispatcher.request(new RequestEditValidationAction(TaskEditor.ID, this.task.id, input)); - if (isSetEditValidationResultAction(response)) { + const response = await this.actionDispatcher.request( + RequestEditValidationAction.create({ contextId: TaskEditor.ID, modelElementId: this.task.id, text: input }) + ); + if (SetEditValidationResultAction.is(response)) { return response.status; } return Promise.reject(); @@ -146,7 +178,7 @@ export class TaskEditor extends AbstractUIExtension { } protected executeFromTextOnlyInput(input: string): void { - const action = new ApplyTaskEditOperation(this.task.id, input); + const action = ApplyTaskEditOperation.create({ taskId: this.task.id, expression: input }); this.actionDispatcher.dispatch(action); } @@ -156,6 +188,6 @@ export class TaskEditor extends AbstractUIExtension { } } -function getTask(ids: string[], index: SModelIndex): TaskNode[] { +function getTask(ids: string[], index: ModelIndexImpl): TaskNode[] { return ids.map(id => index.getById(id)).filter(element => element && isTaskNode(element)) as TaskNode[]; } diff --git a/examples/workflow-glsp/src/workflow-views.tsx b/examples/workflow-glsp/src/workflow-views.tsx index 7c918d5..f605849 100644 --- a/examples/workflow-glsp/src/workflow-views.tsx +++ b/examples/workflow-glsp/src/workflow-views.tsx @@ -15,17 +15,19 @@ ********************************************************************************/ import { angleOfPoint, + findParentByFeature, getSubType, Point, PolylineEdgeViewWithGapsOnIntersections, RenderingContext, SEdge, setAttr, + ShapeView, + svg, toDegrees } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { findParentByFeature, ShapeView, svg } from 'sprotty'; import { Icon, isTaskNode } from './model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -90,6 +92,7 @@ export class IconView extends ShapeView { if (subType) { setAttr(vnode, 'class', subType); } + return vnode; } } diff --git a/examples/workflow-standalone/.eslintrc.js b/examples/workflow-standalone/.eslintrc.js index 727bf62..2af1914 100644 --- a/examples/workflow-standalone/.eslintrc.js +++ b/examples/workflow-standalone/.eslintrc.js @@ -4,5 +4,19 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' + }, + rules: { + 'no-restricted-imports': [ + 'warn', + { + name: 'sprotty', + message: "The sprotty default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" + }, + { + name: 'sprotty-protocol', + message: + "The sprotty-protocol default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead" + } + ] } }; diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 2d108d0..1405953 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -63,7 +63,7 @@ } }, { - "name": "Push2", + "name": "ChkWt", "taskType": "automated", "id": "task0_automated", "cssClasses": [ @@ -242,7 +242,7 @@ "targetId": "bd94e44e-19f9-446e-89d4-97ca1a63c17b" }, { - "name": "AutomatedTask8", + "name": "WtOK", "taskType": "automated", "id": "e47d5eba-612d-4c43-9aba-2c5502ff4f04", "cssClasses": [ @@ -330,7 +330,7 @@ } }, { - "name": "Push", + "name": "RflWt", "taskType": "manual", "id": "a63cfd87-da7c-4846-912b-29040b776bfb", "cssClasses": [ @@ -418,7 +418,7 @@ } }, { - "name": "AutomatedTask7", + "name": "Brew", "taskType": "automated", "id": "7afd430b-5031-4082-8190-7e755c57cde9", "cssClasses": [ @@ -488,7 +488,7 @@ } }, { - "name": "AutomatedTask8", + "name": "ChkTp", "taskType": "automated", "id": "6c26f0a4-4354-45fa-9d9f-bc2b48adee17", "cssClasses": [ @@ -567,7 +567,7 @@ } }, { - "name": "AutomatedTask8", + "name": "KeepTp", "taskType": "automated", "id": "4b06ed95-c9be-47df-9941-98099259be4a", "cssClasses": [ @@ -628,7 +628,7 @@ } }, { - "name": "AutomatedTask9", + "name": "PreHeat", "taskType": "automated", "id": "80d1792f-9c7e-41c0-8eca-eeb0509397b6", "cssClasses": [ diff --git a/examples/workflow-standalone/src/app.ts b/examples/workflow-standalone/src/app.ts index bb8675c..c45e050 100644 --- a/examples/workflow-standalone/src/app.ts +++ b/examples/workflow-standalone/src/app.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { configureServerActions, EnableToolPaletteAction, GLSPDiagramServer, RequestTypeHintsAction } from '@eclipse-glsp/client'; -import { ApplicationIdProvider, BaseJsonrpcGLSPClient, GLSPClient, JsonrpcGLSPClient } from '@eclipse-glsp/protocol'; +import { + ApplicationIdProvider, + BaseJsonrpcGLSPClient, + configureServerActions, + EnableToolPaletteAction, + GLSPClient, + GLSPDiagramServer, + IActionDispatcher, + JsonrpcGLSPClient, + RequestModelAction, + RequestTypeHintsAction, + TYPES +} from '@eclipse-glsp/client'; import { join, resolve } from 'path'; -import { IActionDispatcher, RequestModelAction, TYPES } from 'sprotty'; import createContainer from './di.config'; const port = 8081; @@ -51,13 +61,15 @@ async function initialize(client: GLSPClient): Promise { await client.initializeClientSession({ clientSessionId: diagramServer.clientId, diagramType }); actionDispatcher.dispatch( - new RequestModelAction({ - sourceUri: `file://${examplePath}`, - diagramType + RequestModelAction.create({ + options: { + sourceUri: `file://${examplePath}`, + diagramType + } }) ); - actionDispatcher.dispatch(new RequestTypeHintsAction()); - actionDispatcher.dispatch(new EnableToolPaletteAction()); + actionDispatcher.dispatch(RequestTypeHintsAction.create()); + actionDispatcher.dispatch(EnableToolPaletteAction.create()); } websocket.onerror = ev => alert('Connection to server errored. Please make sure that the server is running'); diff --git a/examples/workflow-standalone/src/di.config.ts b/examples/workflow-standalone/src/di.config.ts index 03320b8..2aee8e8 100644 --- a/examples/workflow-standalone/src/di.config.ts +++ b/examples/workflow-standalone/src/di.config.ts @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { createWorkflowDiagramContainer } from '@eclipse-glsp-examples/workflow-glsp'; -import { GLSPDiagramServer, GLSP_TYPES } from '@eclipse-glsp/client'; +import { ConsoleLogger, GLSPDiagramServer, LogLevel, TYPES } from '@eclipse-glsp/client'; import { Container } from 'inversify'; -import { ConsoleLogger, LogLevel, TYPES } from 'sprotty'; import '../css/diagram.css'; export default function createContainer(): Container { @@ -25,6 +24,6 @@ export default function createContainer(): Container { container.bind(TYPES.ModelSource).toService(GLSPDiagramServer); container.rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); container.rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); - container.bind(GLSP_TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); + container.bind(TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); return container; } diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 727bf62..4303f12 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -4,5 +4,18 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' + }, + rules: { + 'no-restricted-imports': [ + 'warn', + { + name: 'sprotty-protocol', + message: "Please use '@eclipse-glsp/protocol' instead" + }, + { + name: 'sprotty-protocol/*', + message: "Please use '@eclipse-glsp/protocol' instead" + } + ] } }; diff --git a/packages/client/package.json b/packages/client/package.json index b6c1b43..f952427 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -36,10 +36,11 @@ "dependencies": { "@eclipse-glsp/protocol": "0.9.0", "autocompleter": "5.1.0", - "sprotty": "0.10.0" + "sprotty": "next" }, "devDependencies": { "@vscode/codicons": "^0.0.25", + "eslint-import-resolver-typescript": "2.7.0", "typescript": "^4.5.5" }, "scripts": { diff --git a/packages/client/src/base/action-dispatcher.ts b/packages/client/src/base/action-dispatcher.ts index 5595e4d..a78ae98 100644 --- a/packages/client/src/base/action-dispatcher.ts +++ b/packages/client/src/base/action-dispatcher.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isResponseAction, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; +import { Action, RequestAction, ResponseAction } from '@eclipse-glsp/protocol'; import { inject } from 'inversify'; import { ActionDispatcher } from 'sprotty'; import { ModelInitializationConstraint } from './model-initialization-constraint'; @@ -46,7 +46,7 @@ export class GLSPActionDispatcher extends ActionDispatcher { } protected override handleAction(action: Action): Promise { - if (isResponseAction(action)) { + if (ResponseAction.hasValidResponseId(action)) { // clear timeout const timeout = this.timeouts.get(action.responseId); if (timeout !== undefined) { @@ -64,6 +64,14 @@ export class GLSPActionDispatcher extends ActionDispatcher { return super.handleAction(action); } + override request(action: RequestAction): Promise { + if (!action.requestId && action.requestId === '') { + // No request id has been specified. So we use a generated one. + action.requestId = RequestAction.generateRequestId(); + } + return super.request(action); + } + /** * Dispatch a request and waits for a response until the timeout given in `timeoutMs` has * been reached. The returned promise is resolved when a response with matching identifier @@ -75,8 +83,9 @@ export class GLSPActionDispatcher extends ActionDispatcher { * no value, otherwise it will be rejected. */ requestUntil(action: RequestAction, timeoutMs = 2000, rejectOnTimeout = false): Promise { - if (!action.requestId) { - return Promise.reject(new Error('Request without requestId')); + if (!action.requestId && action.requestId === '') { + // No request id has been specified. So we use a generated one. + action.requestId = RequestAction.generateRequestId(); } const requestId = action.requestId; diff --git a/packages/client/src/base/actions/focus-change-action.ts b/packages/client/src/base/actions/focus-change-action.ts index e6f2bfe..86689a6 100644 --- a/packages/client/src/base/actions/focus-change-action.ts +++ b/packages/client/src/base/actions/focus-change-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,14 +13,31 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from '@eclipse-glsp/protocol'; +import { Action, hasBooleanProp } from '@eclipse-glsp/protocol'; -export class FocusStateChangedAction implements Action { - static KIND = 'focusStateChanged'; - readonly kind = FocusStateChangedAction.KIND; - constructor(public readonly hasFocus: boolean) {} +/** + * A `FocusStateChangedAction` is dispatched by the client whenever the + * diagram focus changes (i.e. focus loss or focus gain). + */ +export interface FocusStateChangedAction extends Action { + kind: typeof FocusStateChangedAction.KIND; + /** + * The new focus state of the diagram. + */ + hasFocus: boolean; } -export function isFocusStateChangedAction(action: Action): action is FocusStateChangedAction { - return action.kind === FocusStateChangedAction.KIND; +export namespace FocusStateChangedAction { + export const KIND = 'focusStateChanged'; + + export function is(object: any): object is FocusStateChangedAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'hasFocus'); + } + + export function create(hasFocus = true): FocusStateChangedAction { + return { + kind: KIND, + hasFocus + }; + } } diff --git a/packages/protocol/src/utils/launch-util.ts b/packages/client/src/base/actions/sprotty-actions.ts similarity index 51% rename from packages/protocol/src/utils/launch-util.ts rename to packages/client/src/base/actions/sprotty-actions.ts index 510282c..515d653 100644 --- a/packages/protocol/src/utils/launch-util.ts +++ b/packages/client/src/base/actions/sprotty-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -9,21 +9,18 @@ * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at - * https://www.gnu.npmorg/software/classpath/license.html. + * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -/** - * Utility function to parse a serverport that is defined via command line arg. - * @param argsKey Name/Key of the commandline arg - * @param defaultPort Default port that should be returned if no (valid) port was passed via CLI - */ -export function getPort(argsKey: string, defaultPort?: number): number { - argsKey = `--${argsKey.replace('--', '').replace('=', '')}=`; - const args = process.argv.filter(a => a.startsWith(argsKey)); - if (args.length > 0) { - return Number.parseInt(args[0].substring(argsKey.length), 10); +import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; +import { SetBoundsAction } from 'sprotty-protocol/lib/actions'; +declare module 'sprotty-protocol/lib/actions' { + // eslint-disable-next-line no-shadow + namespace SetBoundsAction { + export function is(object: any): object is SetBoundsAction; } - return defaultPort ? defaultPort : NaN; } + +SetBoundsAction.is = (object: any): object is SetBoundsAction => + Action.hasKind(object, SetBoundsAction.KIND) && hasArrayProp(object, 'bounds'); diff --git a/packages/client/src/base/auto-complete/auto-complete-actions.ts b/packages/client/src/base/auto-complete/auto-complete-actions.ts index faf499a..53f0e6e 100644 --- a/packages/client/src/base/auto-complete/auto-complete-actions.ts +++ b/packages/client/src/base/auto-complete/auto-complete-actions.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { LabeledAction } from '@eclipse-glsp/protocol'; +import { hasStringProp, LabeledAction } from '@eclipse-glsp/protocol'; -export interface SetAutoCompleteValueAction extends LabeledAction { +export interface AutoCompleteValue extends LabeledAction { readonly text: string; } -export function isSetAutoCompleteValueAction(action: LabeledAction): action is SetAutoCompleteValueAction { - return action !== undefined && (action as SetAutoCompleteValueAction).text !== undefined; +export namespace AutoCompleteValue { + export function is(object: any): object is AutoCompleteValue { + return LabeledAction.is(object) && hasStringProp(object, 'text'); + } } diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 05ef531..7e41b57 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isAction, isLabeledAction, LabeledAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { Action, LabeledAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { AutocompleteResult, AutocompleteSettings } from 'autocompleter'; import { codiconCSSClasses, ILogger, SModelRoot } from 'sprotty'; import { toArray } from 'sprotty/lib/utils/iterable'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { isSetAutoCompleteValueAction } from './auto-complete-actions'; +import { AutoCompleteValue } from './auto-complete-actions'; import { IValidationDecorator } from './validation-decorator'; export interface AutoCompleteSettings { @@ -158,18 +158,13 @@ export class AutoCompleteWidget { fetch: (text: string, update: (items: LabeledAction[]) => void) => this.updateSuggestions(update, text, root), onSelect: (item: LabeledAction) => this.onSelect(item), render: (item: LabeledAction, currentValue: string): HTMLDivElement | undefined => this.renderSuggestions(item, currentValue), - customize: (input: HTMLInputElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) => { + customize: (input: HTMLInputElement, inputRect: DOMRect, container: HTMLDivElement, maxHeight: number) => { this.customizeInputElement(input, inputRect, container, maxHeight); } }; } - protected customizeInputElement( - input: HTMLInputElement, - inputRect: ClientRect | DOMRect, - container: HTMLDivElement, - maxHeight: number - ): void { + protected customizeInputElement(input: HTMLInputElement, inputRect: DOMRect, container: HTMLDivElement, maxHeight: number): void { // move container into our UIExtension container as this is already positioned correctly if (this.containerElement) { this.containerElement.appendChild(container); @@ -249,7 +244,7 @@ export class AutoCompleteWidget { } protected onSelect(item: LabeledAction): void { - if (isSetAutoCompleteValueAction(item)) { + if (AutoCompleteValue.is(item)) { this.inputElement.value = item.text; // trigger update of suggestions with an keyup event window.setTimeout(() => this.inputElement.dispatchEvent(new Event('keyup'))); @@ -317,9 +312,9 @@ export class AutoCompleteWidget { } export function toActionArray(input: LabeledAction | Action[] | Action): Action[] { - if (isLabeledAction(input)) { + if (LabeledAction.is(input)) { return input.actions; - } else if (isAction(input)) { + } else if (Action.is(input)) { return [input]; } return []; diff --git a/packages/client/src/base/command-stack.ts b/packages/client/src/base/command-stack.ts index 4ed3e51..005cc97 100644 --- a/packages/client/src/base/command-stack.ts +++ b/packages/client/src/base/command-stack.ts @@ -15,19 +15,20 @@ ********************************************************************************/ import { RedoOperation, UndoOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { CommandStack, IActionDispatcher, SModelRoot, TYPES } from 'sprotty'; +import { CommandStack, IActionDispatcher, SModelRoot } from 'sprotty'; +import { TYPES } from './types'; @injectable() export class GLSPCommandStack extends CommandStack { @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; override undo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new UndoOperation())); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(UndoOperation.create())); return this.thenUpdate(); } override redo(): Promise { - this.actionDispatcher().then(dispatcher => dispatcher.dispatch(new RedoOperation())); + this.actionDispatcher().then(dispatcher => dispatcher.dispatch(RedoOperation.create())); return this.thenUpdate(); } } diff --git a/packages/client/src/base/di.config.ts b/packages/client/src/base/di.config.ts index 76951dd..78183df 100644 --- a/packages/client/src/base/di.config.ts +++ b/packages/client/src/base/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { InitializeResult, SetEditModeAction } from '@eclipse-glsp/protocol'; import '@vscode/codicons/dist/codicon.css'; import { Container, ContainerModule } from 'inversify'; -import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand, TYPES } from 'sprotty'; +import { ActionHandlerRegistry, configureActionHandler, configureCommand, ModelSource, SetModelCommand } from 'sprotty'; import '../../css/glsp-sprotty.css'; import { GLSPActionDispatcher } from './action-dispatcher'; import { FocusStateChangedAction } from './actions/focus-change-action'; @@ -28,13 +28,13 @@ import { GLSPModelRegistry } from './model/model-registry'; import { FeedbackAwareUpdateModelCommand, SetModelActionHandler } from './model/update-model-command'; import { SelectionClearingMouseListener } from './selection-clearing-mouse-listener'; import { GLSPToolManager } from './tool-manager/glsp-tool-manager'; -import { GLSP_TYPES } from './types'; +import { TYPES } from './types'; import { GLSPViewRegistry } from './view/view-registry'; const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) => { const context = { bind, _unbind, isBound, rebind }; bind(EditorContextService).toSelf().inSingletonScope(); - bind(GLSP_TYPES.IEditorContextServiceProvider).toProvider( + bind(TYPES.IEditorContextServiceProvider).toProvider( ctx => () => new Promise((resolve, reject) => { if (ctx.container.isBound(EditorContextService)) { @@ -58,7 +58,6 @@ const defaultGLSPModule = new ContainerModule((bind, _unbind, isBound, rebind) = rebind(TYPES.ICommandStack).to(GLSPCommandStack); bind(GLSPToolManager).toSelf().inSingletonScope(); - bind(GLSP_TYPES.IGLSPToolManager).toService(GLSPToolManager); rebind(TYPES.IToolManager).toService(GLSPToolManager); bind(GLSPActionDispatcher).toSelf().inSingletonScope(); rebind(TYPES.IActionDispatcher).toService(GLSPActionDispatcher); diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 11740f9..31a8403 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Args, distinctAdd, EditMode, EditorContext, isSetEditModeAction, remove } from '@eclipse-glsp/protocol'; +import { Action, Args, distinctAdd, EditMode, EditorContext, remove, SetEditModeAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; -import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot, TYPES } from 'sprotty'; +import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot } from 'sprotty'; import { SelectionService } from '../features/select/selection-service'; import { isSourceUriAware } from './source-uri-aware'; -import { GLSP_TYPES } from './types'; +import { TYPES } from './types'; export interface EditModeListener { editModeChanged(newValue: string, oldvalue: string): void; @@ -26,12 +26,12 @@ export interface EditModeListener { @injectable() export class EditorContextService implements IActionHandler { - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.SelectionService) protected selectionService: SelectionService; @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; @inject(TYPES.ModelSourceProvider) protected modelSource: () => Promise; protected _editMode: string; - constructor(@multiInject(GLSP_TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) {} + constructor(@multiInject(TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) {} register(editModeListener: EditModeListener): void { distinctAdd(this.editModeListeners, editModeListener); @@ -58,7 +58,7 @@ export class EditorContextService implements IActionHandler { } handle(action: Action): void { - if (isSetEditModeAction(action)) { + if (SetEditModeAction.is(action)) { const oldValue = this._editMode; this._editMode = action.editMode; this.notifiyEditModeListeners(oldValue); diff --git a/packages/client/src/base/focus-tracker.ts b/packages/client/src/base/focus-tracker.ts index 827f044..fc0c77d 100644 --- a/packages/client/src/base/focus-tracker.ts +++ b/packages/client/src/base/focus-tracker.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,8 +15,9 @@ ********************************************************************************/ import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionHandler, ICommand, TYPES, ViewerOptions } from 'sprotty'; -import { isFocusStateChangedAction } from './actions/focus-change-action'; +import { IActionHandler, ICommand, ViewerOptions } from 'sprotty'; +import { FocusStateChangedAction } from './actions/focus-change-action'; +import { TYPES } from './types'; @injectable() export class FocusTracker implements IActionHandler { @@ -30,7 +31,7 @@ export class FocusTracker implements IActionHandler { } handle(action: Action): void | Action | ICommand { - if (isFocusStateChangedAction(action)) { + if (FocusStateChangedAction.is(action)) { this._hasFocus = action.hasFocus; const placeholder = document.getElementById(this.options.baseDiv); if (!placeholder) { diff --git a/packages/client/src/base/model-initialization-constraint.ts b/packages/client/src/base/model-initialization-constraint.ts index 0dc3325..64b70ed 100644 --- a/packages/client/src/base/model-initialization-constraint.ts +++ b/packages/client/src/base/model-initialization-constraint.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isSetModelAction, isUpdateModelAction } from '@eclipse-glsp/protocol'; +import { Action, Deferred, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { InitializeCanvasBoundsAction } from 'sprotty'; -import { Deferred } from 'sprotty/lib/utils/async'; /** * The constraint defining when the initialization of the GLSP model is completed. @@ -86,7 +85,7 @@ export class DefaultModelInitializationConstraint extends ModelInitializationCon } protected isNonEmptyModelAction(action: Action): boolean { - if (isSetModelAction(action) || isUpdateModelAction(action)) { + if (SetModelAction.is(action) || UpdateModelAction.is(action)) { return action.newRoot.type !== 'NONE'; } return false; diff --git a/packages/client/src/base/model/update-model-command.ts b/packages/client/src/base/model/update-model-command.ts index b1b6764..7e34db6 100644 --- a/packages/client/src/base/model/update-model-command.ts +++ b/packages/client/src/base/model/update-model-command.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isSetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { Action, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { ActionHandlerRegistry, @@ -24,20 +24,19 @@ import { IActionHandler, ILogger, SModelRoot, - TYPES, UpdateModelCommand } from 'sprotty'; import { IFeedbackActionDispatcher } from '../../features/tool-feedback/feedback-action-dispatcher'; import { FeedbackCommand } from '../../features/tool-feedback/model'; -import { GLSP_TYPES } from '../types'; +import { TYPES } from '../types'; /* ActionHandler that transforms a SetModelAction into an (feedback-aware) UpdateModelAction. This can be done because in sprotty * UpdateModel behaves the same as SetModel if no model is present yet.*/ @injectable() export class SetModelActionHandler implements IActionHandler { handle(action: Action): Action | void { - if (isSetModelAction(action)) { - return new UpdateModelAction(action.newRoot, false); + if (SetModelAction.is(action)) { + return UpdateModelAction.create(action.newRoot, { animate: false }); } } } @@ -53,14 +52,14 @@ export interface SModelRootListener { @injectable() export class FeedbackAwareUpdateModelCommand extends UpdateModelCommand { @inject(TYPES.ILogger) protected logger: ILogger; - @inject(GLSP_TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.IFeedbackActionDispatcher) @optional() protected readonly feedbackActionDispatcher: IFeedbackActionDispatcher; @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; - @multiInject(GLSP_TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []; + @multiInject(TYPES.SModelRootListener) @optional() protected modelRootListeners: SModelRootListener[] = []; protected actionHandlerRegistry?: ActionHandlerRegistry; constructor(@inject(TYPES.Action) action: UpdateModelAction) { - super(action); + super({ animate: true, ...action }); } @postConstruct() diff --git a/packages/client/src/base/tool-manager/glsp-tool-manager.ts b/packages/client/src/base/tool-manager/glsp-tool-manager.ts index 9c51cd9..c41cccf 100644 --- a/packages/client/src/base/tool-manager/glsp-tool-manager.ts +++ b/packages/client/src/base/tool-manager/glsp-tool-manager.ts @@ -17,7 +17,7 @@ import { distinctAdd, EditMode } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { Tool, ToolManager } from 'sprotty'; import { EditModeListener, EditorContextService, EditorContextServiceProvider } from '../editor-context-service'; -import { GLSP_TYPES } from '../types'; +import { TYPES } from '../types'; export interface IGLSPToolManager extends ToolManager { /* Disables all actives tools and activates only default tool with non-edit function*/ @@ -28,9 +28,9 @@ export interface IGLSPToolManager extends ToolManager { export class GLSPToolManager extends ToolManager implements IGLSPToolManager, EditModeListener { protected editorContext?: EditorContextService; - @multiInject(GLSP_TYPES.ITool) @optional() override tools: Tool[]; - @multiInject(GLSP_TYPES.IDefaultTool) @optional() override defaultTools: Tool[]; - @inject(GLSP_TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider; + @multiInject(TYPES.ITool) @optional() override tools: Tool[]; + @multiInject(TYPES.IDefaultTool) @optional() override defaultTools: Tool[]; + @inject(TYPES.IEditorContextServiceProvider) contextServiceProvider: EditorContextServiceProvider; @postConstruct() protected initialize(): void { diff --git a/packages/client/src/base/tool-manager/tool-actions.ts b/packages/client/src/base/tool-manager/tool-actions.ts new file mode 100644 index 0000000..2a816eb --- /dev/null +++ b/packages/client/src/base/tool-manager/tool-actions.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; +import { EnableDefaultToolsAction, EnableToolsAction } from 'sprotty/lib/base/tool-manager/tool'; +/* eslint-disable no-shadow */ + +/** + * Use module augmentation to add the `is` utility function to the tool action namespaces provided by sprotty + */ +declare module 'sprotty/lib/base/tool-manager/tool' { + export namespace EnableDefaultToolsAction { + export function is(object: any): object is EnableDefaultToolsAction; + } + export namespace EnableToolsAction { + export function is(object: any): object is EnableToolsAction; + } +} + +EnableDefaultToolsAction.is = (object: any): object is EnableDefaultToolsAction => Action.hasKind(object, EnableDefaultToolsAction.KIND); + +EnableToolsAction.is = (object: any): object is EnableToolsAction => + Action.hasKind(object, EnableToolsAction.KIND) && hasArrayProp(object, 'toolIds'); + +export { EnableDefaultToolsAction, EnableToolsAction }; diff --git a/packages/client/src/base/types.ts b/packages/client/src/base/types.ts index 6309f56..0311caa 100644 --- a/packages/client/src/base/types.ts +++ b/packages/client/src/base/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,27 +13,34 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export const GLSP_TYPES = { - IAsyncClipboardService: Symbol.for('IAsyncClipboardService'), - ICommandPaletteActionProviderRegistry: Symbol.for('ICommandPaletteActionProviderRegistry'), - IEditorContextServiceProvider: Symbol.for('IEditorContextProvider'), - IFeedbackActionDispatcher: Symbol.for('IFeedbackActionDispatcher'), - IToolFactory: Symbol.for('Factory'), - ITypeHintProvider: Symbol.for('ITypeHintProvider'), - IMovementRestrictor: Symbol.for('IMovmementRestrictor'), - SelectionService: Symbol.for('SelectionService'), - SelectionListener: Symbol.for('SelectionListener'), - SModelRootListener: Symbol.for('SModelRootListener'), - MouseTool: Symbol.for('MouseTool'), - IContextMenuService: Symbol.for('IContextMenuService'), - IContextMenuServiceProvider: Symbol.for('IContextMenuServiceProvider'), - IContextMenuProviderRegistry: Symbol.for('IContextMenuProviderRegistry'), - IContextMenuProvider: Symbol.for('IContextMenuProvider'), - ICopyPasteHandler: Symbol.for('ICopyPasteHandler'), - IGLSPToolManager: Symbol.for('IGLSPToolManager'), - ITool: Symbol.for('ITool'), - IDefaultTool: Symbol.for('IDefaultTool'), - IEditModeListener: Symbol.for('IEditModeListener'), - LayoutRegistration: Symbol.for('LayoutRegistration'), - IMarqueeBehavior: Symbol.for('IMarqueeBehavior') +import { TYPES as SprottyTYPES } from 'sprotty'; +/** + * Reexport of the TYPES namespace of sprotty augments with additional GLSP specific service + * identifiers. + */ +export const TYPES = { + ...SprottyTYPES, + IAsyncClipboardService: Symbol('IAsyncClipboardService'), + IEditorContextServiceProvider: Symbol('IEditorContextProvider'), + IFeedbackActionDispatcher: Symbol('IFeedbackActionDispatcher'), + IToolFactory: Symbol('Factory'), + ITypeHintProvider: Symbol('ITypeHintProvider'), + IMovementRestrictor: Symbol('IMovementRestrictor'), + SelectionService: Symbol('SelectionService'), + SelectionListener: Symbol('SelectionListener'), + SModelRootListener: Symbol('SModelRootListener'), + MouseTool: Symbol('MouseTool'), + IContextMenuProvider: Symbol('IContextMenuProvider'), + ICopyPasteHandler: Symbol('ICopyPasteHandler'), + ITool: Symbol('ITool'), + IDefaultTool: Symbol('IDefaultTool'), + IEditModeListener: Symbol('IEditModeListener'), + IMarqueeBehavior: Symbol('IMarqueeBehavior') }; + +/** + * Keep a reexport under the old deprecated namespace to avoid hard API breaks. + * This gives adopters a grace period but all deprecated API will be removed before the 1.0.0 release. + * @deprecated Please use `TYPES` from `@eclipse-glsp/client` instead + */ +export const GLSP_TYPES = TYPES; diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index 5ec458a..fb8857e 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,7 +33,7 @@ export function createMovementRestrictionFeedback( if (element instanceof SParentElement) { element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } - return new ModifyCSSFeedbackAction(elements, movementRestrictor.cssClasses); + return ModifyCSSFeedbackAction.create({ elements, add: movementRestrictor.cssClasses }); } export function removeMovementRestrictionFeedback( @@ -45,7 +45,7 @@ export function removeMovementRestrictionFeedback( element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } - return new ModifyCSSFeedbackAction(elements, undefined, movementRestrictor.cssClasses); + return ModifyCSSFeedbackAction.create({ elements, remove: movementRestrictor.cssClasses }); } @injectable() diff --git a/packages/client/src/features/command-palette/di.config.ts b/packages/client/src/features/command-palette/di.config.ts index 010772a..b6d60c3 100644 --- a/packages/client/src/features/command-palette/di.config.ts +++ b/packages/client/src/features/command-palette/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,12 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '../../../css/command-palette.css'; - import { ContainerModule } from 'inversify'; -import { CommandPalette, CommandPaletteActionProviderRegistry, TYPES } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; +import { CommandPalette, CommandPaletteActionProviderRegistry } from 'sprotty'; +import '../../../css/command-palette.css'; +import { TYPES } from '../../base/types'; import { CommandPaletteTool } from './command-palette-tool'; import { ServerCommandPaletteActionProvider } from './server-command-palette-provider'; @@ -28,7 +26,7 @@ const glspCommandPaletteModule = new ContainerModule(bind => { bind(CommandPaletteActionProviderRegistry).toSelf().inSingletonScope(); bind(TYPES.ICommandPaletteActionProviderRegistry).toService(CommandPaletteActionProviderRegistry); bind(TYPES.ICommandPaletteActionProvider).to(ServerCommandPaletteActionProvider); - bind(GLSP_TYPES.IDefaultTool).to(CommandPaletteTool); + bind(TYPES.IDefaultTool).to(CommandPaletteTool); }); export default glspCommandPaletteModule; diff --git a/packages/client/src/features/command-palette/server-command-palette-provider.ts b/packages/client/src/features/command-palette/server-command-palette-provider.ts index 648887b..7057b3d 100644 --- a/packages/client/src/features/command-palette/server-command-palette-provider.ts +++ b/packages/client/src/features/command-palette/server-command-palette-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isSetContextActionsAction, LabeledAction, Point, RequestContextActions } from '@eclipse-glsp/protocol'; +import { Action, LabeledAction, Point, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ICommandPaletteActionProvider, SModelElement, TYPES } from 'sprotty'; +import { ICommandPaletteActionProvider, SModelElement } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; +import { TYPES } from '../../base/types'; export namespace ServerCommandPalette { export const CONTEXT_ID = 'command-palette'; @@ -31,18 +32,18 @@ export class ServerCommandPaletteActionProvider implements ICommandPaletteAction @inject(EditorContextService) protected editorContext: EditorContextService; getActions(_root: Readonly, text: string, _lastMousePosition?: Point, index?: number): Promise { - const requestAction = new RequestContextActions( - ServerCommandPalette.CONTEXT_ID, - this.editorContext.get({ + const requestAction = RequestContextActions.create({ + contextId: ServerCommandPalette.CONTEXT_ID, + editorContext: this.editorContext.get({ [ServerCommandPalette.TEXT]: text, [ServerCommandPalette.INDEX]: index ? index : 0 }) - ); + }); return this.actionDispatcher.requestUntil(requestAction).then(response => this.getPaletteActionsFromResponse(response)); } getPaletteActionsFromResponse(action: Action): LabeledAction[] { - if (isSetContextActionsAction(action)) { + if (SetContextActions.is(action)) { return action.actions; } return []; diff --git a/packages/client/src/features/context-menu/delete-element-context-menu.ts b/packages/client/src/features/context-menu/delete-element-context-menu.ts index 3d04bec..c2f5953 100644 --- a/packages/client/src/features/context-menu/delete-element-context-menu.ts +++ b/packages/client/src/features/context-menu/delete-element-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DeleteElementOperation } from '@eclipse-glsp/protocol'; +import { DeleteElementOperation, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IContextMenuItemProvider, MenuItem, Point, SModelRoot } from 'sprotty'; +import { IContextMenuItemProvider, MenuItem, SModelRoot } from 'sprotty'; import { EditorContextService, EditorContextServiceProvider } from '../../base/editor-context-service'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; @injectable() export class DeleteElementContextMenuItemProvider implements IContextMenuItemProvider { - @inject(GLSP_TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; + @inject(TYPES.IEditorContextServiceProvider) editorContextServiceProvider: EditorContextServiceProvider; async getItems(_root: Readonly, _lastMousePosition?: Point): Promise { const editorContextService = await this.editorContextServiceProvider(); @@ -34,7 +34,7 @@ export class DeleteElementContextMenuItemProvider implements IContextMenuItemPro label: 'Delete', sortString: 'd', group: 'edit', - actions: [new DeleteElementOperation(editorContextService.selectedElements.map(e => e.id))], + actions: [DeleteElementOperation.create(editorContextService.selectedElements.map(e => e.id))], isEnabled: () => !editorContextService.isReadonly && editorContextService.selectedElements.length > 0 }; } diff --git a/packages/client/src/features/context-menu/di.config.ts b/packages/client/src/features/context-menu/di.config.ts index f148979..0d1b74c 100644 --- a/packages/client/src/features/context-menu/di.config.ts +++ b/packages/client/src/features/context-menu/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { ContextMenuProviderRegistry, IContextMenuService, TYPES } from 'sprotty'; +import { ContextMenuProviderRegistry, IContextMenuService } from 'sprotty'; +import { TYPES } from '../../base/types'; import { SelectionServiceAwareContextMenuMouseListener } from './selection-service-aware-context-menu-mouse-listener'; import { ServerContextMenuItemProvider } from './server-context-menu-provider'; diff --git a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts index 22ac02c..33d78f4 100644 --- a/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +++ b/packages/client/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts @@ -13,26 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { - Action, ContextMenuProviderRegistry, findParentByFeature, IContextMenuServiceProvider, isSelectable, MouseListener, - SModelElement, - TYPES + SModelElement } from 'sprotty'; import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { SelectionService } from '../select/selection-service'; @injectable() export class SelectionServiceAwareContextMenuMouseListener extends MouseListener { @inject(TYPES.IContextMenuServiceProvider) @optional() protected readonly contextMenuService: IContextMenuServiceProvider; @inject(TYPES.IContextMenuProviderRegistry) @optional() protected readonly menuProvider: ContextMenuProviderRegistry; - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.SelectionService) protected selectionService: SelectionService; /** * Opens the context menu on right-click. @@ -65,7 +64,7 @@ export class SelectionServiceAwareContextMenuMouseListener extends MouseListener const result = Promise.all([this.contextMenuService(), this.menuProvider.getItems(target.root, mousePosition)]) .then(([menuService, menuItems]) => menuService.show(menuItems, mousePosition, () => this.focusEventTarget(event))) - .then((): Action => new FocusStateChangedAction(false)); + .then((): Action => FocusStateChangedAction.create(false)); return [result]; } diff --git a/packages/client/src/features/context-menu/server-context-menu-provider.ts b/packages/client/src/features/context-menu/server-context-menu-provider.ts index 42974a3..00f2113 100644 --- a/packages/client/src/features/context-menu/server-context-menu-provider.ts +++ b/packages/client/src/features/context-menu/server-context-menu-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isSetContextActionsAction, LabeledAction, Point, RequestContextActions } from '@eclipse-glsp/protocol'; +import { Action, LabeledAction, Point, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IContextMenuItemProvider, isSelected, SModelElement, TYPES } from 'sprotty'; +import { IContextMenuItemProvider, isSelected, SModelElement } from 'sprotty'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; +import { TYPES } from '../../base/types'; export namespace ServerContextMenu { export const CONTEXT_ID = 'context-menu'; @@ -35,13 +36,13 @@ export class ServerContextMenuItemProvider implements IContextMenuItemProvider { .filter(isSelected) .map(e => e.id) ); - const context = this.editorContext.getWithSelection(selectedElementIds); - const requestAction = new RequestContextActions(ServerContextMenu.CONTEXT_ID, context); + const editorContext = this.editorContext.getWithSelection(selectedElementIds); + const requestAction = RequestContextActions.create({ contextId: ServerContextMenu.CONTEXT_ID, editorContext }); return this.actionDispatcher.requestUntil(requestAction).then(response => this.getContextActionsFromResponse(response)); } getContextActionsFromResponse(action: Action): LabeledAction[] { - if (isSetContextActionsAction(action)) { + if (SetContextActions.is(action)) { return action.actions; } return []; diff --git a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts index 3dab595..e855c4d 100644 --- a/packages/client/src/features/copy-paste/copy-paste-context-menu.ts +++ b/packages/client/src/features/copy-paste/copy-paste-context-menu.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,45 +13,52 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GLSPServerStatusAction, Point, ServerMessageAction } from '@eclipse-glsp/protocol'; +/* eslint-disable deprecation/deprecation */ +import { Action, hasStringProp, Point, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, IActionHandler, IContextMenuItemProvider, isSelected, MenuItem, SModelRoot, TYPES } from 'sprotty'; +import { IActionDispatcher, IActionHandler, IContextMenuItemProvider, isSelected, MenuItem, SModelRoot } from 'sprotty'; +import { TYPES } from '../../base/types'; -export class InvokeCopyAction implements Action { - static readonly KIND = 'invoke-copy'; - constructor(public readonly kind = InvokeCopyAction.KIND) {} +/** + * An `InvokeCopyPasteAction` is dispatched by the client to initiate a cut, copy or paste operation. + */ +export interface InvokeCopyPasteAction extends Action { + kind: typeof InvokeCopyPasteAction.KIND; + command: 'copy' | 'cut' | 'paste'; } -export class InvokePasteAction implements Action { - static readonly KIND = 'invoke-paste'; - constructor(public readonly kind = InvokePasteAction.KIND) {} -} +export namespace InvokeCopyPasteAction { + export const KIND = 'invokeCopyPaste'; + + export function is(object: any): object is InvokeCopyPasteAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'command'); + } -export class InvokeCutAction implements Action { - static readonly KIND = 'invoke-cut'; - constructor(public readonly kind = InvokeCutAction.KIND) {} + export function create(command: 'copy' | 'cut' | 'paste'): InvokeCopyPasteAction { + return { kind: KIND, command }; + } } @injectable() export class InvokeCopyPasteActionHandler implements IActionHandler { @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; - handle(action: Action): void { - switch (action.kind) { - case InvokeCopyAction.KIND: + handle(action: InvokeCopyPasteAction): void { + switch (action.command) { + case 'copy': if (supportsCopy()) { document.execCommand('copy'); } else { this.notifyUserToUseShortcut('copy'); } break; - case InvokePasteAction.KIND: + case 'paste': if (supportsPaste()) { document.execCommand('paste'); } else { this.notifyUserToUseShortcut('paste'); } break; - case InvokeCutAction.KIND: + case 'cut': if (supportsCut()) { document.execCommand('cut'); } else { @@ -66,8 +73,8 @@ export class InvokeCopyPasteActionHandler implements IActionHandler { const timeout = 10000; const severity = 'WARNING'; this.dispatcher.dispatchAll([ - { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, - { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction + ServerStatusAction.create(message, { severity, timeout }), + ServerMessageAction.create(message, { severity, timeout }) ]); } } @@ -88,7 +95,7 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide id: 'paste', label: 'Paste', group: 'copy-paste', - actions: [new InvokePasteAction()], + actions: [InvokeCopyPasteAction.create('paste')], isEnabled: () => true }; } @@ -98,7 +105,7 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide id: 'cut', label: 'Cut', group: 'copy-paste', - actions: [new InvokeCutAction()], + actions: [InvokeCopyPasteAction.create('cut')], isEnabled: () => hasSelectedElements }; } @@ -108,7 +115,7 @@ export class CopyPasteContextMenuItemProvider implements IContextMenuItemProvide id: 'copy', label: 'Copy', group: 'copy-paste', - actions: [new InvokeCopyAction()], + actions: [InvokeCopyPasteAction.create('copy')], isEnabled: () => hasSelectedElements }; } diff --git a/packages/client/src/features/copy-paste/copy-paste-handler.ts b/packages/client/src/features/copy-paste/copy-paste-handler.ts index e0681e2..07d91f5 100644 --- a/packages/client/src/features/copy-paste/copy-paste-handler.ts +++ b/packages/client/src/features/copy-paste/copy-paste-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,11 +15,11 @@ ********************************************************************************/ import { ClipboardData, CutOperation, PasteOperation, RequestClipboardDataAction, SetClipboardDataAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { TYPES, ViewerOptions } from 'sprotty'; +import { ViewerOptions } from 'sprotty'; import { v4 as uuid } from 'uuid'; import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditorContextService } from '../../base/editor-context-service'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; export interface ICopyPasteHandler { handleCopy(event: ClipboardEvent): void; @@ -96,7 +96,7 @@ const CLIPBOARD_DATA_FORMAT = 'text/plain'; export class ServerCopyPasteHandler implements ICopyPasteHandler { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; @inject(TYPES.ViewerOptions) protected viewerOptions: ViewerOptions; - @inject(GLSP_TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; + @inject(TYPES.IAsyncClipboardService) protected clipboadService: IAsyncClipboardService; @inject(EditorContextService) protected editorContext: EditorContextService; handleCopy(event: ClipboardEvent): void { @@ -118,7 +118,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { handleCut(event: ClipboardEvent): void { if (event.clipboardData && this.shouldCopy(event)) { this.handleCopy(event); - this.actionDispatcher.dispatch(new CutOperation(this.editorContext.get())); + this.actionDispatcher.dispatch(CutOperation.create(this.editorContext.get())); event.preventDefault(); } } @@ -128,7 +128,7 @@ export class ServerCopyPasteHandler implements ICopyPasteHandler { const clipboardId = getClipboardIdFromDataTransfer(event.clipboardData); const clipboardData = this.clipboadService.get(clipboardId); if (clipboardData) { - this.actionDispatcher.dispatch(new PasteOperation(clipboardData, this.editorContext.get())); + this.actionDispatcher.dispatch(PasteOperation.create({ clipboardData, editorContext: this.editorContext.get() })); } event.preventDefault(); } diff --git a/packages/client/src/features/copy-paste/di.config.ts b/packages/client/src/features/copy-paste/di.config.ts index bb7f846..4249cf7 100644 --- a/packages/client/src/features/copy-paste/di.config.ts +++ b/packages/client/src/features/copy-paste/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,19 +15,13 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { configureActionHandler } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; -import { - CopyPasteContextMenuItemProvider, - InvokeCopyAction, - InvokeCopyPasteActionHandler, - InvokeCutAction, - InvokePasteAction -} from './copy-paste-context-menu'; +import { TYPES } from '../../base/types'; +import { CopyPasteContextMenuItemProvider, InvokeCopyPasteAction, InvokeCopyPasteActionHandler } from './copy-paste-context-menu'; import { LocalClipboardService, ServerCopyPasteHandler } from './copy-paste-handler'; export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isBound) => { - bind(GLSP_TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); - bind(GLSP_TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); + bind(TYPES.ICopyPasteHandler).to(ServerCopyPasteHandler); + bind(TYPES.IAsyncClipboardService).to(LocalClipboardService).inSingletonScope(); }); /** @@ -36,9 +30,7 @@ export const glspServerCopyPasteModule = new ContainerModule((bind, _unbind, isB * `CopyPasteMenuContribution` in `glsp-theia-integration` instead. */ export const copyPasteContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { - bind(GLSP_TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); + bind(TYPES.IContextMenuProvider).to(CopyPasteContextMenuItemProvider).inSingletonScope(); bind(InvokeCopyPasteActionHandler).toSelf().inSingletonScope(); - configureActionHandler({ bind, isBound }, InvokeCopyAction.KIND, InvokeCopyPasteActionHandler); - configureActionHandler({ bind, isBound }, InvokeCutAction.KIND, InvokeCopyPasteActionHandler); - configureActionHandler({ bind, isBound }, InvokePasteAction.KIND, InvokeCopyPasteActionHandler); + configureActionHandler({ bind, isBound }, InvokeCopyPasteAction.KIND, InvokeCopyPasteActionHandler); }); diff --git a/packages/client/src/features/decoration/decoration-placer.ts b/packages/client/src/features/decoration/decoration-placer.ts index 63934d2..8363008 100644 --- a/packages/client/src/features/decoration/decoration-placer.ts +++ b/packages/client/src/features/decoration/decoration-placer.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { Decoration, DecorationPlacer, isSizeable, ORIGIN_POINT, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; +import { Decoration, DecorationPlacer, isSizeable, SChildElement, SModelElement, SRoutableElement } from 'sprotty'; @injectable() export class GlspDecorationPlacer extends DecorationPlacer { @@ -31,6 +31,6 @@ export class GlspDecorationPlacer extends DecorationPlacer { y: GlspDecorationPlacer.DECORATION_OFFSET.y * element.bounds.height }; } - return ORIGIN_POINT; + return Point.ORIGIN; } } diff --git a/packages/client/src/features/decoration/di.config.ts b/packages/client/src/features/decoration/di.config.ts index 1f7057a..336ee31 100644 --- a/packages/client/src/features/decoration/di.config.ts +++ b/packages/client/src/features/decoration/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import '../../../css/decoration.css'; - import { ContainerModule } from 'inversify'; -import { TYPES } from 'sprotty'; - +import '../../../css/decoration.css'; +import { TYPES } from '../../base/types'; import { GlspDecorationPlacer } from './decoration-placer'; const glspDecorationModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/edit-label/di.config.ts b/packages/client/src/features/edit-label/di.config.ts index ec27ce4..a6b96a0 100644 --- a/packages/client/src/features/edit-label/di.config.ts +++ b/packages/client/src/features/edit-label/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,16 +14,15 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { ApplyLabelEditCommand, configureCommand, TYPES } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; +import { ApplyLabelEditCommand, configureCommand } from 'sprotty'; +import { TYPES } from '../../base/types'; import { DirectLabelEditTool } from './edit-label-tool'; import { BalloonLabelValidationDecorator, ServerEditLabelValidator } from './edit-label-validator'; const glspEditLabelModule = new ContainerModule((bind, _unbind, isBound, _rebind) => { bind(TYPES.IEditLabelValidator).to(ServerEditLabelValidator); bind(TYPES.IEditLabelValidationDecorator).to(BalloonLabelValidationDecorator); - bind(GLSP_TYPES.IDefaultTool).to(DirectLabelEditTool); + bind(TYPES.IDefaultTool).to(DirectLabelEditTool); configureCommand({ bind, isBound }, ApplyLabelEditCommand); }); diff --git a/packages/client/src/features/edit-label/edit-label-tool.ts b/packages/client/src/features/edit-label/edit-label-tool.ts index b6597a5..e7a96df 100644 --- a/packages/client/src/features/edit-label/edit-label-tool.ts +++ b/packages/client/src/features/edit-label/edit-label-tool.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,7 +15,6 @@ ********************************************************************************/ import { injectable } from 'inversify'; import { EditLabelKeyListener, EditLabelMouseListener, KeyListener, MouseListener } from 'sprotty'; - import { BaseGLSPTool } from '../tools/base-glsp-tool'; @injectable() diff --git a/packages/client/src/features/edit-label/edit-label-validator.ts b/packages/client/src/features/edit-label/edit-label-validator.ts index 8377a6b..62c5aa9 100644 --- a/packages/client/src/features/edit-label/edit-label-validator.ts +++ b/packages/client/src/features/edit-label/edit-label-validator.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isSetEditValidationResultAction, RequestEditValidationAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { Action, RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - Action, EditableLabel, EditLabelValidationResult, IEditLabelValidationDecorator, @@ -25,7 +24,6 @@ import { SModelElement, TYPES } from 'sprotty'; - import { GLSPActionDispatcher } from '../../base/action-dispatcher'; export namespace LabelEditValidation { @@ -41,11 +39,9 @@ export namespace LabelEditValidation { } return { message, severity }; } -} -export class ValidateLabelEditAction extends RequestEditValidationAction { - constructor(value: string, labelId: string) { - super(LabelEditValidation.CONTEXT_ID, labelId, value); + export function createValidationRequestAction(value: string, labelId: string): RequestEditValidationAction { + return RequestEditValidationAction.create({ contextId: CONTEXT_ID, modelElementId: labelId, text: value }); } } @@ -54,12 +50,12 @@ export class ServerEditLabelValidator implements IEditLabelValidator { @inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher; validate(value: string, label: EditableLabel & SModelElement): Promise { - const action = new ValidateLabelEditAction(value, label.id); + const action = LabelEditValidation.createValidationRequestAction(value, label.id); return this.actionDispatcher.requestUntil(action).then(response => this.getValidationResultFromResponse(response)); } getValidationResultFromResponse(action: Action): EditLabelValidationResult { - if (isSetEditValidationResultAction(action)) { + if (SetEditValidationResultAction.is(action)) { return LabelEditValidation.toEditLabelValidationResult(action.status); } return { severity: 'ok' as Severity }; diff --git a/packages/client/src/features/hints/di.config.ts b/packages/client/src/features/hints/di.config.ts index dc30533..390f068 100644 --- a/packages/client/src/features/hints/di.config.ts +++ b/packages/client/src/features/hints/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,12 +16,12 @@ import { SetTypeHintsAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; import { configureActionHandler, configureCommand } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { ApplyTypeHintsCommand, TypeHintProvider } from './type-hints'; const modelHintsModule = new ContainerModule((bind, _unbind, isBound) => { bind(TypeHintProvider).toSelf().inSingletonScope(); - bind(GLSP_TYPES.ITypeHintProvider).toService(TypeHintProvider); + bind(TYPES.ITypeHintProvider).toService(TypeHintProvider); configureActionHandler({ bind, isBound }, SetTypeHintsAction.KIND, TypeHintProvider); configureCommand({ bind, isBound }, ApplyTypeHintsCommand); }); diff --git a/packages/client/src/features/hints/type-hints.ts b/packages/client/src/features/hints/type-hints.ts index f5cc079..0123de9 100644 --- a/packages/client/src/features/hints/type-hints.ts +++ b/packages/client/src/features/hints/type-hints.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, EdgeTypeHint, isSetTypeHintsAction, ShapeTypeHint, TypeHint } from '@eclipse-glsp/protocol'; +import { Action, EdgeTypeHint, SetTypeHintsAction, ShapeTypeHint, TypeHint } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { CommandExecutionContext, @@ -29,10 +29,9 @@ import { SEdge, SModelElement, SModelRoot, - SShapeElement, - TYPES + SShapeElement } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { getElementTypeId, hasCompatibleType } from '../../utils/smodel-util'; import { resizeFeature } from '../change-bounds/model'; import { reconnectFeature } from '../reconnect/model'; @@ -40,19 +39,35 @@ import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-disp import { FeedbackCommand } from '../tool-feedback/model'; import { Containable, containerFeature, reparentFeature } from './model'; -@injectable() -export class ApplyTypeHintsAction implements Action { - readonly kind = ApplyTypeHintsCommand.KIND; +/** + * Is dispatched by the {@link TypeHintProvider} to apply the type hints received from the server + * onto the graphical model. The action is dispatched as persistent feedback to ensure the applied type hints + * don't get lost after a server-side model update. + */ +export interface ApplyTypeHintsAction extends Action { + kind: typeof ApplyTypeHintsAction.KIND; +} + +export namespace ApplyTypeHintsAction { + export const KIND = 'applyTypeHints'; + + export function is(object: any): object is ApplyTypeHintsAction { + return Action.hasKind(object, KIND); + } + + export function create(): ApplyTypeHintsAction { + return { kind: KIND }; + } } @injectable() export class ApplyTypeHintsCommand extends FeedbackCommand { - public static KIND = 'applyTypeHints'; + public static KIND = ApplyTypeHintsAction.KIND; public override readonly priority = 10; - @inject(GLSP_TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; + @inject(TYPES.ITypeHintProvider) protected typeHintProvider: ITypeHintProvider; - constructor(@inject(TYPES.Action) protected action: ApplyTypeHintsAction) { + constructor(@inject(TYPES.Action) protected action: Action) { super(); } @@ -69,7 +84,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { protected applyEdgeTypeHint(element: SModelElement): void { const hint = this.typeHintProvider.getEdgeTypeHint(element); - if (hint && isModifiableFetureSet(element.features)) { + if (hint && isModifiableFeatureSet(element.features)) { addOrRemove(element.features, deletableFeature, hint.deletable); addOrRemove(element.features, editFeature, hint.routable); addOrRemove(element.features, reconnectFeature, hint.repositionable); @@ -78,7 +93,7 @@ export class ApplyTypeHintsCommand extends FeedbackCommand { protected applyShapeTypeHint(element: SModelElement): void { const hint = this.typeHintProvider.getShapeTypeHint(element); - if (hint && isModifiableFetureSet(element.features)) { + if (hint && isModifiableFeatureSet(element.features)) { addOrRemove(element.features, deletableFeature, hint.deletable); addOrRemove(element.features, moveFeature, hint.repositionable); addOrRemove(element.features, resizeFeature, hint.resizable); @@ -119,7 +134,7 @@ function addOrRemove(features: Set, feature: symbol, add: boolean): void } } -function isModifiableFetureSet(featureSet?: FeatureSet): featureSet is FeatureSet & Set { +function isModifiableFeatureSet(featureSet?: FeatureSet): featureSet is FeatureSet & Set { return featureSet !== undefined && featureSet instanceof Set; } @@ -131,17 +146,17 @@ export interface ITypeHintProvider { @injectable() export class TypeHintProvider implements IActionHandler, ITypeHintProvider { - @inject(GLSP_TYPES.IFeedbackActionDispatcher) + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; protected shapeHints: Map = new Map(); protected edgeHints: Map = new Map(); handle(action: Action): ICommand | Action | void { - if (isSetTypeHintsAction(action)) { + if (SetTypeHintsAction.is(action)) { action.shapeHints.forEach(hint => this.shapeHints.set(hint.elementTypeId, hint)); action.edgeHints.forEach(hint => this.edgeHints.set(hint.elementTypeId, hint)); - this.feedbackActionDispatcher.registerFeedback(this, [new ApplyTypeHintsAction()]); + this.feedbackActionDispatcher.registerFeedback(this, [ApplyTypeHintsAction.create()]); } } diff --git a/packages/client/src/features/hover/di.config.ts b/packages/client/src/features/hover/di.config.ts index b834461..d2d848d 100644 --- a/packages/client/src/features/hover/di.config.ts +++ b/packages/client/src/features/hover/di.config.ts @@ -19,6 +19,8 @@ import { ClosePopupActionHandler, configureActionHandler, configureCommand, + EnableDefaultToolsAction, + EnableToolsAction, FitToScreenCommand, HoverFeedbackCommand, HoverKeyListener, @@ -56,6 +58,8 @@ const glspHoverModule = new ContainerModule((bind, _unbind, isBound) => { configureActionHandler(context, SetViewportCommand.KIND, ClosePopupActionHandler); configureActionHandler(context, MoveCommand.KIND, ClosePopupActionHandler); configureActionHandler(context, FocusStateChangedAction.KIND, ClosePopupActionHandler); + configureActionHandler(context, EnableToolsAction.KIND, GlspHoverMouseListener); + configureActionHandler(context, EnableDefaultToolsAction.KIND, GlspHoverMouseListener); configureActionHandler(context, FocusStateChangedAction.KIND, GlspHoverMouseListener); }); diff --git a/packages/client/src/features/hover/hover.ts b/packages/client/src/features/hover/hover.ts index e85855e..f967b16 100644 --- a/packages/client/src/features/hover/hover.ts +++ b/packages/client/src/features/hover/hover.ts @@ -13,7 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Bounds, RequestPopupModelAction, SetPopupModelAction } from '@eclipse-glsp/protocol'; +import { + Action, + Bounds, + PreRenderedElementSchema, + RequestPopupModelAction, + SetPopupModelAction, + SModelElementSchema, + SModelRootSchema +} from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { EMPTY_ROOT, @@ -21,18 +29,17 @@ import { HoverMouseListener, IActionHandler, ICommand, - PreRenderedElementSchema, SIssueMarker, SIssueSeverity, - SModelElement, - SModelElementSchema, - SModelRootSchema + SModelElement } from 'sprotty'; -import { isFocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; +import { EnableDefaultToolsAction, EnableToolsAction } from '../../base/tool-manager/tool-actions'; +import { EdgeCreationTool } from '../tools/edge-creation-tool'; import { GIssueMarker } from '../validation/issue-marker'; - @injectable() export class GlspHoverMouseListener extends HoverMouseListener implements IActionHandler { + protected enableHover = true; /** * Stops mouse over timer and remove hover feedback, if focus is lost. * @@ -43,14 +50,25 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio * @returns a `HoverFeedbackAction` resetting the state, if the specified action indicates lost focus */ handle(action: Action): void | Action | ICommand { - if (isFocusStateChangedAction(action) && !action.hasFocus) { + if (FocusStateChangedAction.is(action) && !action.hasFocus) { this.stopMouseOverTimer(); if (this.lastHoverFeedbackElementId) { const previousTargetId = this.lastHoverFeedbackElementId; this.lastHoverFeedbackElementId = undefined; - return new HoverFeedbackAction(previousTargetId, false); + return HoverFeedbackAction.create({ mouseoverElement: previousTargetId, mouseIsOver: false }); } + } else if (EnableToolsAction.is(action)) { + this.enableHover = !(action as EnableToolsAction).toolIds.includes(EdgeCreationTool.ID); + } else if (EnableDefaultToolsAction.is(action)) { + this.enableHover = true; + } + } + + override mouseOver(target: SModelElement, event: MouseEvent): (Action | Promise)[] { + if (this.enableHover) { + return super.mouseOver(target, event); } + return []; } protected override startMouseOverTimer(target: SModelElement, event: MouseEvent): Promise { @@ -59,9 +77,9 @@ export class GlspHoverMouseListener extends HoverMouseListener implements IActio this.state.mouseOverTimer = window.setTimeout(() => { const popupBounds = this.computePopupBounds(target, { x: event.pageX, y: event.pageY }); if (target instanceof GIssueMarker) { - resolve(new SetPopupModelAction(this.createPopupModel(target as GIssueMarker, popupBounds))); + resolve(SetPopupModelAction.create(this.createPopupModel(target as GIssueMarker, popupBounds))); } else { - resolve(new RequestPopupModelAction(target.id, popupBounds)); + resolve(RequestPopupModelAction.create({ elementId: target.id, bounds: popupBounds })); } this.state.popupOpen = true; diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 753bd8f..93f59e8 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/packages/client/src/features/layout/freeform-layout.ts b/packages/client/src/features/layout/freeform-layout.ts index b2f58a6..712979a 100644 --- a/packages/client/src/features/layout/freeform-layout.ts +++ b/packages/client/src/features/layout/freeform-layout.ts @@ -13,18 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { - Bounds, - BoundsData, - Dimension, - isValidDimension, - LayoutContainer, - Point, - SChildElement, - SParentElement, - StatefulLayouter -} from 'sprotty'; +import { BoundsData, LayoutContainer, SChildElement, SParentElement, StatefulLayouter } from 'sprotty'; import { AbstractLayout } from 'sprotty/lib/features/bounds/abstract-layout'; import { AbstractLayoutOptions } from 'sprotty/lib/features/bounds/layout-options'; @@ -64,7 +55,7 @@ export class FreeFormLayouter extends AbstractLayout { let maxY = 0; container.children.forEach(child => { const bounds = layouter.getBoundsData(child).bounds; - if (bounds !== undefined && isValidDimension(bounds)) { + if (bounds !== undefined && Dimension.isValid(bounds)) { const childMaxX = bounds.x + bounds.width; const childMaxY = bounds.y + bounds.height; maxX = Math.max(maxX, childMaxX); diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-commands.spec.ts index da4d28c..d2500ab 100644 --- a/packages/client/src/features/layout/layout-commands.spec.ts +++ b/packages/client/src/features/layout/layout-commands.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable import/no-deprecated,no-unused-expressions */ -import { Action, Bounds, ChangeBoundsOperation, ElementAndBounds } from '@eclipse-glsp/protocol'; +/* eslint-disable deprecation/deprecation */ +import { + Action, + Bounds, + ChangeBoundsOperation, + ElementAndBounds, + RequestAction, + ResponseAction, + SetBoundsAction +} from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; import 'mocha'; @@ -29,16 +37,12 @@ import { IActionDispatcher, MoveAction, MoveCommand, - RequestAction, - ResponseAction, SChildElement, - SetBoundsAction, SetBoundsCommand, SGraphFactory, - SModelRoot, - TYPES + SModelRoot } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { resizeFeature } from '../change-bounds/model'; import { SelectionService } from '../select/selection-service'; import { FeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; @@ -69,12 +73,12 @@ class MockActionDispatcher implements IActionDispatcher { const container = new Container(); container.load(defaultModule); -container.bind(GLSP_TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); +container.bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); container.bind(SelectionService).toSelf().inSingletonScope(); -container.bind(GLSP_TYPES.SelectionService).toService(SelectionService); +container.bind(TYPES.SelectionService).toService(SelectionService); container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); const graphFactory = container.get(TYPES.IModelFactory); -const selectionService = container.get(GLSP_TYPES.SelectionService); +const selectionService = container.get(TYPES.SelectionService); const actionDispatcher = new MockActionDispatcher(); @@ -128,7 +132,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Left); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Left }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -148,7 +152,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Right); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Right }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -168,7 +172,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Center); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Center }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -188,7 +192,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Top); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Top }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -208,7 +212,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Bottom); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Bottom }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -228,7 +232,7 @@ describe('AlignElementsCommand', () => { { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); - const action = new AlignElementsAction(['node1', 'node2', 'node3'], Alignment.Middle); + const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Middle }); const command = new AlignElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -250,7 +254,11 @@ describe('ResizeElementsCommand', () => { { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } ]); - const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Width, Reduce.last); + const action = ResizeElementsAction.create({ + elementIds: ['node1', 'node2', 'node3'], + dimension: ResizeDimension.Width, + reductionFunction: Reduce.last + }); const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -271,7 +279,11 @@ describe('ResizeElementsCommand', () => { { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } ]); - const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Height, Reduce.last); + const action = ResizeElementsAction.create({ + elementIds: ['node1', 'node2', 'node3'], + dimension: ResizeDimension.Height, + reductionFunction: Reduce.last + }); const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -292,7 +304,11 @@ describe('ResizeElementsCommand', () => { { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, { elementId: 'node3', newPosition: { x: 100, y: 300 }, newSize: { height: 30, width: 30 } } ]); - const action = new ResizeElementsAction(['node1', 'node2', 'node3'], ResizeDimension.Width_And_Height, Reduce.last); + const action = ResizeElementsAction.create({ + elementIds: ['node1', 'node2', 'node3'], + dimension: ResizeDimension.Width_And_Height, + reductionFunction: Reduce.last + }); const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); command.execute(newContext(newModel)); @@ -308,7 +324,7 @@ describe('ResizeElementsCommand', () => { }); function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { - const mySetBoundsAction = new SetBoundsAction(elementAndBounds); + const mySetBoundsAction = SetBoundsAction.create(elementAndBounds); const setBoundsCommand = new SetBoundsCommand(mySetBoundsAction); return setBoundsCommand.execute(context) as SModelRoot; } diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-commands.ts index 6d0db95..c9731ee 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-commands.ts @@ -13,26 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ChangeBoundsOperation, ElementAndBounds } from '@eclipse-glsp/protocol'; +import { Action, ChangeBoundsOperation, ElementAndBounds, hasArrayProp, hasNumberProp, SetBoundsAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { - Command, - CommandExecutionContext, - CommandReturn, - ElementMove, - IActionDispatcher, - MoveAction, - SetBoundsAction, - SModelElement, - TYPES -} from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { Command, CommandExecutionContext, CommandReturn, ElementMove, IActionDispatcher, MoveAction, SModelElement } from 'sprotty'; +import { TYPES } from '../../base/types'; import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove } from '../../utils/layout-utils'; import { BoundsAwareModelElement } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; import { SelectionService } from '../select/selection-service'; +/** + * Used to specify the desired resize dimension for a {@link ResizeElementsCommand}. + */ // eslint-disable-next-line no-shadow export enum ResizeDimension { Width, @@ -41,43 +34,99 @@ export enum ResizeDimension { } export namespace Reduce { + /** + * Returns the minimal value of the given numbers. + * @param values Numbers to be evaluated. + * @returns The reduced number. + */ export function min(...values: number[]): number { return Math.min(...values); } + /** + * Returns the maximal value of the given numbers. + * @param values Numbers to be evaluated. + * @returns The reduced number. + */ export function max(...values: number[]): number { return Math.max(...values); } + /** + * Computes the average of the given numbers. + * @param values Numbers to be evaluated. + */ export function avg(...values: number[]): number { return values.reduce((a, b) => a + b, 0) / values.length; } + /** + * Returns the last value of the given numbers. + * @param values Numbers to be evaluated. + * @returns The reduced number. + */ export function first(...values: number[]): number { return values[0]; } + /** + * Returns the minimal value of the given numbers. + * @param values Numbers to be evaluated. + * @returns The reduced number. + */ export function last(...values: number[]): number { return values[values.length - 1]; } + + /** + * Returns the reduce function that corresponds to the given {@link ReduceFunctionKind}. + * @param type The reduce function kind. + * @returns The corresponding reduce function. + */ } -export class ResizeElementsAction implements Action { - constructor( - /** - * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized. - */ - public readonly elementIds: string[] = [], - /** - * Resize dimension. - */ - public readonly dimension: ResizeDimension = ResizeDimension.Width, - /** - * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. - */ - public readonly reductionFunction: (...values: number[]) => number, - public readonly kind: string = ResizeElementsCommand.KIND - ) {} +type ReduceFn = (...values: number[]) => number; +export interface ResizeElementsAction extends Action { + kind: typeof ResizeElementsAction.KIND; + + /** + * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized. + */ + elementIds: string[]; + /** + * Resize dimension. The default is {@link ResizeDimension.Width}. + */ + dimension: ResizeDimension; + /** + * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. + */ + reductionFunction: ReduceFn; +} + +export namespace ResizeElementsAction { + export const KIND = 'resizeElementAction'; + + export function is(object: any): object is ResizeElementsAction { + return ( + Action.hasKind(object, KIND) && + hasArrayProp(object, 'elementIds') && + hasNumberProp(object, 'dimension') && + 'reductionFunction' in object + ); + } + + export function create(options: { + elementIds?: string[]; + dimension?: ResizeDimension; + reductionFunction: ReduceFn; + }): ResizeElementsAction { + return { + kind: KIND, + dimension: ResizeDimension.Width, + elementIds: [], + ...options + }; + } } // eslint-disable-next-line no-shadow @@ -104,31 +153,56 @@ export namespace Select { } } -export class AlignElementsAction implements Action { - constructor( - /** - * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. - */ - public readonly elementIds: string[] = [], - /** - * Alignment direction. - */ - public readonly alignment: Alignment = Alignment.Left, - /** - * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. - */ - public readonly selectionFunction: (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[] = Select.all, - public readonly kind = AlignElementsCommand.KIND - ) {} +type SelectFn = (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[]; +export interface AlignElementsAction extends Action { + kind: typeof AlignElementsAction.KIND; + + /** + * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. + */ + elementIds: string[]; + /** + * Alignment direction. The default is {@link Alignment.Left} + */ + alignment: Alignment; + /** + * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. + * The default value is {@link Select.all}. + */ + selectionFunction: SelectFn; } +export namespace AlignElementsAction { + export const KIND = 'alignElements'; + + export function is(object: any): object is AlignElementsAction { + return ( + Action.hasKind(object, KIND) && + hasArrayProp(object, 'elementIds') && + hasNumberProp(object, 'alignment') && + 'selectionFunction' in object + ); + } + + export function create( + options: { elementIds?: string[]; alignment?: Alignment; selectionFunction?: SelectFn } = {} + ): AlignElementsAction { + return { + kind: KIND, + elementIds: [], + alignment: Alignment.Left, + selectionFunction: Select.all, + ...options + }; + } +} @injectable() abstract class LayoutElementsCommand extends Command { constructor( @inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction, @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor + @inject(TYPES.SelectionService) protected selectionService: SelectionService, + @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor ) { super(); } @@ -163,13 +237,13 @@ abstract class LayoutElementsCommand extends Command { @injectable() export class ResizeElementsCommand extends LayoutElementsCommand { - static readonly KIND = 'layout:resize'; + static readonly KIND = ResizeElementsAction.KIND; constructor( @inject(TYPES.Action) protected override action: ResizeElementsAction, @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, - @inject(GLSP_TYPES.SelectionService) protected override selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor + @inject(TYPES.SelectionService) protected override selectionService: SelectionService, + @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor ) { super(action, actionDispatcher, selectionService, movementRestrictor); } @@ -242,7 +316,7 @@ export class ResizeElementsCommand extends LayoutElementsCommand { elementAndBounds.push(elementChange); } }); - this.dispatchActions([new SetBoundsAction(elementAndBounds), new ChangeBoundsOperation(elementAndBounds)]); + this.dispatchActions([SetBoundsAction.create(elementAndBounds), ChangeBoundsOperation.create(elementAndBounds)]); } createElementAndBounds( @@ -277,13 +351,13 @@ export class ResizeElementsCommand extends LayoutElementsCommand { @injectable() export class AlignElementsCommand extends LayoutElementsCommand { - static readonly KIND = 'layout:align'; + static readonly KIND = AlignElementsAction.KIND; constructor( @inject(TYPES.Action) protected override action: AlignElementsAction, @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, - @inject(GLSP_TYPES.SelectionService) protected override selectionService: SelectionService, - @inject(GLSP_TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor + @inject(TYPES.SelectionService) protected override selectionService: SelectionService, + @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor ) { super(action, actionDispatcher, selectionService, movementRestrictor); } @@ -376,7 +450,7 @@ export class AlignElementsCommand extends LayoutElementsCommand { elementAndBounds.push(elementAndBound); } }); - this.dispatchActions([new MoveAction(moves), new ChangeBoundsOperation(elementAndBounds)]); + this.dispatchActions([MoveAction.create(moves), ChangeBoundsOperation.create(elementAndBounds)]); } createElementMove( diff --git a/packages/client/src/features/layout/vbox-layout.ts b/packages/client/src/features/layout/vbox-layout.ts index 9c8fee9..a45afe5 100644 --- a/packages/client/src/features/layout/vbox-layout.ts +++ b/packages/client/src/features/layout/vbox-layout.ts @@ -13,17 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { - Bounds, BoundsData, - Dimension, - EMPTY_BOUNDS, isBoundsAware, isLayoutableChild, - isValidDimension, LayoutContainer, - Point, SChildElement, SModelElement, SParentElement, @@ -91,7 +87,7 @@ export class VBoxLayouterExt extends VBoxLayouter { container.children.forEach(child => { if (isLayoutableChild(child)) { const bounds = layouter.getBoundsData(child).bounds; - if (bounds !== undefined && isValidDimension(bounds)) { + if (bounds !== undefined && Dimension.isValid(bounds)) { maxHeight += bounds.height; if (isFirst) { isFirst = false; @@ -128,7 +124,7 @@ export class VBoxLayouterExt extends VBoxLayouter { const boundsData = layouter.getBoundsData(child); const bounds = boundsData.bounds; const childOptions = this.getChildLayoutOptions(child, containerOptions); - if (bounds !== undefined && isValidDimension(bounds)) { + if (bounds !== undefined && Dimension.isValid(bounds)) { currentOffset = this.layoutChild( child, boundsData, @@ -197,7 +193,7 @@ export class VBoxLayouterExt extends VBoxLayouter { const height = elementOptions?.prefHeight ?? 0; return { ...bounds, width, height }; } - return EMPTY_BOUNDS; + return Bounds.EMPTY; } protected override getChildLayoutOptions(child: SChildElement, containerOptions: VBoxLayoutOptionsExt): VBoxLayoutOptionsExt { diff --git a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts index 7315cc5..07c8781 100644 --- a/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts +++ b/packages/client/src/features/model-source-watcher/model-source-changed-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,15 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - Action, - GLSPServerStatusAction, - isModelSourceChangedAction, - ModelSourceChangedAction, - ServerMessageAction -} from '@eclipse-glsp/protocol'; +import { Action, ModelSourceChangedAction, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { IActionDispatcher, IActionHandler, TYPES, ViewerOptions } from 'sprotty'; +import { IActionDispatcher, IActionHandler, ViewerOptions } from 'sprotty'; +import { TYPES } from '../../base/types'; /** * An external handler of the model source change event. @@ -51,7 +46,7 @@ export class ModelSourceChangedActionHandler implements IActionHandler { protected externalModelSourceChangedHandler?: ExternalModelSourceChangedHandler; handle(action: Action): void { - if (isModelSourceChangedAction(action)) { + if (ModelSourceChangedAction.is(action)) { if (this.externalModelSourceChangedHandler) { this.externalModelSourceChangedHandler .notifyModelSourceChange(action.modelSourceName, this.options) @@ -67,8 +62,8 @@ export class ModelSourceChangedActionHandler implements IActionHandler { const timeout = 0; const severity = 'WARNING'; this.dispatcher.dispatchAll([ - { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, - { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction + ServerStatusAction.create(message, { severity, timeout }), + ServerMessageAction.create(message, { severity, timeout }) ]); } } diff --git a/packages/client/src/features/mouse-tool/di.config.ts b/packages/client/src/features/mouse-tool/di.config.ts index 0c35385..bee91f4 100644 --- a/packages/client/src/features/mouse-tool/di.config.ts +++ b/packages/client/src/features/mouse-tool/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -15,13 +15,12 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; import { MouseTool } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { RankingMouseTool } from './mouse-tool'; const glspMouseToolModule = new ContainerModule((bind, _unbind, _isBound, rebind) => { bind(RankingMouseTool).toSelf().inSingletonScope(); - bind(GLSP_TYPES.MouseTool).toService(RankingMouseTool); + bind(TYPES.MouseTool).toService(RankingMouseTool); rebind(MouseTool).toService(RankingMouseTool); }); diff --git a/packages/client/src/features/mouse-tool/mouse-tool.ts b/packages/client/src/features/mouse-tool/mouse-tool.ts index bf5f0f4..f72e96a 100644 --- a/packages/client/src/features/mouse-tool/mouse-tool.ts +++ b/packages/client/src/features/mouse-tool/mouse-tool.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isAction } from '@eclipse-glsp/protocol'; import { injectable, multiInject, optional } from 'inversify'; -import { MouseListener, MouseTool, SModelElement, SModelRoot, TYPES } from 'sprotty'; +import { MouseListener, MouseTool, SModelElement, SModelRoot } from 'sprotty'; +import { TYPES } from '../../base/types'; import { getRank } from '../rank/model'; export interface IMouseTool { @@ -82,13 +82,8 @@ export class RankingMouseTool extends MouseTool implements IMouseTool { if (actions.length > 0) { event.preventDefault(); for (const actionOrPromise of actions) { - if (isAction(actionOrPromise)) { - await this.actionDispatcher.dispatch(actionOrPromise); - } else { - actionOrPromise.then((action: Action) => { - this.actionDispatcher.dispatch(action); - }); - } + const action = await actionOrPromise; + await this.actionDispatcher.dispatch(action); } } } diff --git a/packages/client/src/features/navigation/navigation-action-handler.ts b/packages/client/src/features/navigation/navigation-action-handler.ts index ab85cf2..e030e23 100644 --- a/packages/client/src/features/navigation/navigation-action-handler.ts +++ b/packages/client/src/features/navigation/navigation-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,10 +17,8 @@ import { Action, Args, CenterAction, - GLSPServerStatusAction, - isNavigateToExternalTargetAction, - isNavigateToTargetAction, - isSetNavigationTargetsAction, + hasObjectProp, + hasStringProp, NavigateToExternalTargetAction, NavigateToTargetAction, NavigationTarget, @@ -28,13 +26,15 @@ import { SelectAction, SelectAllAction, ServerMessageAction, + ServerSeverity, + ServerStatusAction, + SetNavigationTargetsAction, SetResolvedNavigationTargetAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ActionHandlerRegistry, IActionDispatcher, IActionHandler, ICommand, ILogger, TYPES } from 'sprotty'; - +import { ActionHandlerRegistry, IActionDispatcher, IActionHandler, ICommand, ILogger } from 'sprotty'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { NavigationTargetResolver } from './navigation-target-resolver'; /** @@ -48,38 +48,66 @@ import { NavigationTargetResolver } from './navigation-target-resolver'; * * This action is typically triggered by a user action. */ -export class NavigateAction implements Action { - static readonly KIND = 'navigate'; - readonly kind = NavigateAction.KIND; - constructor(readonly targetTypeId: string, readonly args?: Args) {} + +export interface NavigateAction extends Action { + kind: typeof NavigateAction.KIND; + /** + * Navigation target type, such as `documentation`, `implementation`, etc. + */ + targetTypeId: string; + /** + * Additional arguments for customization. + */ + args?: Args; } -export function isNavigateAction(action: Action): action is NavigateAction { - return action !== undefined && action.kind === NavigateAction.KIND && (action as NavigateAction).targetTypeId !== undefined; +export namespace NavigateAction { + export const KIND = 'navigate'; + + export function is(object: any): object is NavigateAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'targetTypeId'); + } + + export function create(targetTypeId: string, options: { args?: Args } = {}): NavigateAction { + return { + kind: KIND, + targetTypeId, + ...options + }; + } } -/** Action to trigger the processing of additional navigation arguments. - * +/** + * Action to trigger the processing of additional navigation arguments. * The resolution of a `NavigationTarget` may entail additional arguments. In this case, this action is * triggered allowing the client to react to those arguments. The default `NavigationActionHandler` will * only process the arguments' keys `info`, `warning`, and `error` to present them to the user. * Customizations, however, may add domain-specific arguments and register custom handler to also process * other arguments and trigger some additional behavior (e.g. update other views, etc.). */ -export class ProcessNavigationArgumentsAction implements Action { - static readonly KIND = 'processNavigationArguments'; - readonly kind = ProcessNavigationArgumentsAction.KIND; - constructor(readonly args: Args) {} -} -export function isProcessNavigationArgumentsAction(action: Action): action is ProcessNavigationArgumentsAction { - return ( - action !== undefined && - action.kind === ProcessNavigationArgumentsAction.KIND && - (action as ProcessNavigationArgumentsAction).args !== undefined - ); +export interface ProcessNavigationArgumentsAction extends Action { + kind: typeof ProcessNavigationArgumentsAction.KIND; + /** + * The navigation arguments. + */ + args: Args; } +export namespace ProcessNavigationArgumentsAction { + export const KIND = 'processNavigationArguments'; + + export function is(object: any): object is ProcessNavigationArgumentsAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'args'); + } + + export function create(args: Args): ProcessNavigationArgumentsAction { + return { + kind: KIND, + args + }; + } +} /** * Default handler for all actions that are related to the navigation. * @@ -109,17 +137,17 @@ export class NavigationActionHandler implements IActionHandler { @inject(TYPES.ILogger) protected logger: ILogger; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; @inject(TYPES.ActionHandlerRegistryProvider) protected actionHandlerRegistryProvider: () => Promise; - @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; + @inject(TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; @inject(NavigationTargetResolver) protected resolver: NavigationTargetResolver; handle(action: Action): ICommand | Action | void { - if (isNavigateAction(action)) { + if (NavigateAction.is(action)) { this.handleNavigateAction(action); - } else if (isNavigateToTargetAction(action)) { + } else if (NavigateToTargetAction.is(action)) { this.handleNavigateToTarget(action); - } else if (isProcessNavigationArgumentsAction(action)) { + } else if (ProcessNavigationArgumentsAction.is(action)) { this.processNavigationArguments(action.args); - } else if (isNavigateToExternalTargetAction(action)) { + } else if (NavigateToExternalTargetAction.is(action)) { this.handleNavigateToExternalTarget(action); } } @@ -127,9 +155,11 @@ export class NavigationActionHandler implements IActionHandler { protected async handleNavigateAction(action: NavigateAction): Promise { try { const editorContextService = await this.editorContextService(); - const context = editorContextService.get(action.args); - const response = await this.dispatcher.request(new RequestNavigationTargetsAction(action.targetTypeId, context)); - if (isSetNavigationTargetsAction(response) && response.targets && response.targets.length === 1) { + const editorContext = editorContextService.get(action.args); + const response = await this.dispatcher.request( + RequestNavigationTargetsAction.create({ targetTypeId: action.targetTypeId, editorContext }) + ); + if (SetNavigationTargetsAction.is(response) && response.targets && response.targets.length === 1) { if (response.targets.length > 1) { this.logger.warn( this, @@ -137,7 +167,7 @@ export class NavigationActionHandler implements IActionHandler { response.targets ); } - return this.dispatcher.dispatch(new NavigateToTargetAction(response.targets[0])); + return this.dispatcher.dispatch(NavigateToTargetAction.create(response.targets[0])); } this.warnAboutFailedNavigation('No valid navigation target found'); } catch (reason) { @@ -184,7 +214,11 @@ export class NavigationActionHandler implements IActionHandler { if (!this.containsElementIds(elementIds)) { return; } - this.dispatcher.dispatchAll([new SelectAllAction(false), new SelectAction(elementIds), new CenterAction(elementIds)]); + this.dispatcher.dispatchAll([ + SelectAllAction.create(false), + SelectAction.create({ selectedElementsIDs: elementIds }), + CenterAction.create(elementIds) + ]); } protected handleResolutionArguments(target: SetResolvedNavigationTargetAction): void { @@ -192,11 +226,11 @@ export class NavigationActionHandler implements IActionHandler { if (!this.containsArguments(args)) { return; } - this.dispatcher.dispatch(new ProcessNavigationArgumentsAction(args)); + this.dispatcher.dispatch(ProcessNavigationArgumentsAction.create(args)); } protected navigateToExternal(target: NavigationTarget): Promise { - return this.dispatcher.dispatch(new NavigateToExternalTargetAction(target)); + return this.dispatcher.dispatch(NavigateToExternalTargetAction.create(target)); } protected processNavigationArguments(args: Args): void { @@ -226,11 +260,11 @@ export class NavigationActionHandler implements IActionHandler { this.notify('WARNING', message); } - private notify(severity: string, message: string): void { + private notify(severity: ServerSeverity, message: string): void { const timeout = this.notificationTimeout; this.dispatcher.dispatchAll([ - { kind: GLSPServerStatusAction.KIND, severity, timeout, message } as GLSPServerStatusAction, - { kind: ServerMessageAction.KIND, severity, timeout, message } as ServerMessageAction + ServerStatusAction.create(message, { severity, timeout }), + ServerMessageAction.create(message, { severity, timeout }) ]); } } diff --git a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts index 2832269..ec4f3bb 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.spec.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -22,7 +22,7 @@ import 'reflect-metadata'; describe('NavigationTarget', () => { it('should be able to set and get element IDs', () => { const navigationTarget: NavigationTarget = { uri: 'uri' }; - NavigationTarget.setElementIds(navigationTarget, ['id1', 'id2']); + NavigationTarget.setElementIds(navigationTarget, 'id1', 'id2'); expect(NavigationTarget.getElementIds(navigationTarget)).to.be.eql(['id1', 'id2']); }); @@ -46,7 +46,7 @@ describe('NavigationTarget', () => { navigationTarget = { uri: 'uri' }; expect(NavigationTarget.hasArguments(navigationTarget)).to.be.false; - NavigationTarget.setElementIds(navigationTarget, ['id1', 'id2']); + NavigationTarget.setElementIds(navigationTarget, 'id1', 'id2'); expect(NavigationTarget.hasArguments(navigationTarget)).to.be.true; navigationTarget = { uri: 'uri' }; diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 8a3c879..92d6652 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,16 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - isSetResolvedNavigationTargets, - NavigationTarget, - ResolveNavigationTargetAction, - SetResolvedNavigationTargetAction -} from '@eclipse-glsp/protocol'; +import { NavigationTarget, ResolveNavigationTargetAction, ResponseAction, SetResolvedNavigationTargetAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, ILogger, ResponseAction, TYPES } from 'sprotty'; +import { IActionDispatcher, ILogger } from 'sprotty'; import { EditorContextServiceProvider } from '../../base/editor-context-service'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; /** * Resolves `NavigationTargets` to element ids. @@ -32,7 +27,7 @@ import { GLSP_TYPES } from '../../base/types'; */ @injectable() export class NavigationTargetResolver { - @inject(GLSP_TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; + @inject(TYPES.IEditorContextServiceProvider) protected editorContextService: EditorContextServiceProvider; @inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher; @inject(TYPES.ILogger) protected readonly logger: ILogger; @@ -53,16 +48,16 @@ export class NavigationTargetResolver { return undefined; } if (NavigationTarget.getElementIds(target).length > 0) { - return new SetResolvedNavigationTargetAction(NavigationTarget.getElementIds(target)); + return SetResolvedNavigationTargetAction.create(NavigationTarget.getElementIds(target)); } const response = await this.requestResolution(target); - if (isSetResolvedNavigationTargets(response)) { + if (SetResolvedNavigationTargetAction.is(response)) { return response; } return undefined; } protected requestResolution(target: NavigationTarget): Promise { - return this.dispatcher.request(new ResolveNavigationTargetAction(target)); + return this.dispatcher.request(ResolveNavigationTargetAction.create(target)); } } diff --git a/packages/client/src/features/save/di.config.ts b/packages/client/src/features/save/di.config.ts index 0b67b81..fb5504f 100644 --- a/packages/client/src/features/save/di.config.ts +++ b/packages/client/src/features/save/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { TYPES } from 'sprotty'; - +import { TYPES } from '../../base/types'; import { SaveModelKeyboardListener } from './save-keylistener'; /** diff --git a/packages/client/src/features/save/save-keylistener.ts b/packages/client/src/features/save/save-keylistener.ts index d9a4ff3..7626f69 100644 --- a/packages/client/src/features/save/save-keylistener.ts +++ b/packages/client/src/features/save/save-keylistener.ts @@ -20,7 +20,7 @@ import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; export class SaveModelKeyboardListener extends KeyListener { override keyDown(_element: SModelRoot, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { - return [new SaveModelAction()]; + return [SaveModelAction.create()]; } return []; } diff --git a/packages/client/src/features/select/di.config.ts b/packages/client/src/features/select/di.config.ts index 91cd953..0afea17 100644 --- a/packages/client/src/features/select/di.config.ts +++ b/packages/client/src/features/select/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,22 +14,20 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureCommand, TYPES } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; -import { SelectAllFeedbackCommand, SelectFeedbackCommand } from './select-feedback-action'; +import { configureCommand } from 'sprotty'; +import { TYPES } from '../../base/types'; +import { SelectFeedbackCommand } from './select-feedback-action'; import { RankedSelectMouseListener } from './select-mouse-listener'; import { SelectAllCommand, SelectCommand, SelectionService } from './selection-service'; const glspSelectModule = new ContainerModule((bind, _unbind, isBound) => { bind(SelectionService).toSelf().inSingletonScope(); - bind(GLSP_TYPES.SelectionService).toService(SelectionService); + bind(TYPES.SelectionService).toService(SelectionService); configureCommand({ bind, isBound }, SelectCommand); configureCommand({ bind, isBound }, SelectAllCommand); configureCommand({ bind, isBound }, SelectFeedbackCommand); - configureCommand({ bind, isBound }, SelectAllFeedbackCommand); bind(TYPES.MouseListener).to(RankedSelectMouseListener); - bind(GLSP_TYPES.SModelRootListener).toService(SelectionService); + bind(TYPES.SModelRootListener).toService(SelectionService); }); export default glspSelectModule; diff --git a/packages/client/src/features/select/select-feedback-action.ts b/packages/client/src/features/select/select-feedback-action.ts index 417f5b6..787f674 100644 --- a/packages/client/src/features/select/select-feedback-action.ts +++ b/packages/client/src/features/select/select-feedback-action.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,39 +13,34 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, hasArrayProp, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { - Command, - CommandExecutionContext, - SelectAllCommand as SprottySelectAllCommand, - SelectCommand as SprottySelectCommand, - SModelRoot, - TYPES -} from 'sprotty'; +import { Command, CommandExecutionContext, SelectCommand as SprottySelectCommand, SModelRoot } from 'sprotty'; +import { TYPES } from '../../base/types'; -export class SelectFeedbackAction { - constructor( - public readonly selectedElementsIDs: string[] = [], - public readonly deselectedElementsIDs: string[] = [], - public readonly kind: string = SelectFeedbackCommand.KIND - ) {} +export interface SelectFeedbackAction extends Omit, Action { + kind: typeof SelectFeedbackAction.KIND; } -export class SelectAllFeedbackAction { - /** - * If `select` is true, all elements are selected, othewise they are deselected. - */ - constructor(public readonly select: boolean = true, public readonly kind: string = SelectFeedbackCommand.KIND) {} -} +export namespace SelectFeedbackAction { + export const KIND = 'selectFeedback'; + + export function is(object: any): object is SelectFeedbackAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); + } + export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] }): SelectFeedbackAction { + return { ...SelectAction.create(options), kind: KIND }; + } +} @injectable() export class SelectFeedbackCommand extends Command { - static readonly KIND = 'elementSelectedFeedback'; + static readonly KIND = SelectFeedbackAction.KIND; private sprottySelectCommand: SprottySelectCommand; constructor(@inject(TYPES.Action) public action: SelectFeedbackAction) { super(); - this.sprottySelectCommand = new SprottySelectCommand(action); + this.sprottySelectCommand = new SprottySelectCommand({ ...action, kind: SelectAction.KIND }); } execute(context: CommandExecutionContext): SModelRoot { @@ -60,26 +55,3 @@ export class SelectFeedbackCommand extends Command { return this.sprottySelectCommand.redo(context); } } - -@injectable() -export class SelectAllFeedbackCommand extends Command { - static readonly KIND = 'allSelectedFeedback'; - private sprottySelectAllCommand: SprottySelectAllCommand; - - constructor(@inject(TYPES.Action) public action: SelectAllFeedbackAction) { - super(); - this.sprottySelectAllCommand = new SprottySelectAllCommand(action); - } - - execute(context: CommandExecutionContext): SModelRoot { - return this.sprottySelectAllCommand.execute(context); - } - - undo(context: CommandExecutionContext): SModelRoot { - return this.sprottySelectAllCommand.undo(context); - } - - redo(context: CommandExecutionContext): SModelRoot { - return this.sprottySelectAllCommand.redo(context); - } -} diff --git a/packages/client/src/features/select/select-mouse-listener.ts b/packages/client/src/features/select/select-mouse-listener.ts index 7e1aedc..3d96dc2 100644 --- a/packages/client/src/features/select/select-mouse-listener.ts +++ b/packages/client/src/features/select/select-mouse-listener.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,7 +14,6 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { SelectMouseListener } from 'sprotty'; - import { DEFAULT_RANK, Ranked } from '../rank/model'; /** diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index 6f33b24..b0417b3 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,11 +14,11 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ -import { Action } from '@eclipse-glsp/protocol'; +import { Action, SModelElementSchema } from '@eclipse-glsp/protocol'; import { AssertionError, expect } from 'chai'; import { Container, injectable } from 'inversify'; -import { defaultModule, SGraphFactory, SModelElementSchema, SModelRoot, TYPES } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { defaultModule, SGraphFactory, SModelRoot } from 'sprotty'; +import { TYPES } from '../../base/types'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; import { SelectionListener, SelectionService } from './selection-service'; @@ -74,14 +74,16 @@ class MockSelectionListener implements SelectionListener { function createContainer(): Container { const container = new Container(); container.load(defaultModule); + // eslint-disable-next-line deprecation/deprecation container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); - container.bind(GLSP_TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); + container.bind(TYPES.IFeedbackActionDispatcher).to(MockFeedbackActionDispatcher).inSingletonScope(); container.bind(SelectionService).toSelf().inSingletonScope(); - container.bind(GLSP_TYPES.SelectionService).toService(SelectionService); + container.bind(TYPES.SelectionService).toService(SelectionService); return container; } describe('SelectionService', () => { + // eslint-disable-next-line deprecation/deprecation let graphFactory: SGraphFactory; let root: SModelRoot; let selectionService: SelectionService; @@ -89,10 +91,11 @@ describe('SelectionService', () => { beforeEach(() => { const container = createContainer(); + // eslint-disable-next-line deprecation/deprecation graphFactory = container.get(TYPES.IModelFactory); root = createRoot('node1', 'node2', 'node3', 'node4', 'node5'); - selectionService = container.get(GLSP_TYPES.SelectionService); - feedbackDispatcher = container.get(GLSP_TYPES.IFeedbackActionDispatcher); + selectionService = container.get(TYPES.SelectionService); + feedbackDispatcher = container.get(TYPES.IFeedbackActionDispatcher); }); describe('Initial State', () => { diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index a4c46b7..99da278 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -26,11 +26,10 @@ import { SelectCommand as SprottySelectCommand, SModelElement, SModelRoot, - SParentElement, - TYPES + SParentElement } from 'sprotty'; import { SModelRootListener } from '../../base/model/update-model-command'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { getElements } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; @@ -44,10 +43,10 @@ export class SelectionService implements SModelRootListener { private root: Readonly; private selectedElementIDs: Set = new Set(); - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.ILogger) protected logger: ILogger; - constructor(@multiInject(GLSP_TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) {} + constructor(@multiInject(TYPES.SelectionListener) @optional() protected selectionListeners: SelectionListener[] = []) {} register(selectionListener: SelectionListener): void { distinctAdd(this.selectionListeners, selectionListener); @@ -101,7 +100,12 @@ export class SelectionService implements SModelRootListener { ![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value)); if (selectionChanged) { // aggregate to feedback action handling all elements as only the last feedback is restored - this.dispatchFeedback([new SelectFeedbackAction([...this.selectedElementIDs], [...deselectedElementIDs])]); + this.dispatchFeedback([ + SelectFeedbackAction.create({ + selectedElementsIDs: [...this.selectedElementIDs], + deselectedElementsIDs: [...deselectedElementIDs] + }) + ]); } const rootChanged = prevRoot !== root; @@ -157,7 +161,7 @@ export class SelectCommand extends Command { constructor( @inject(TYPES.Action) public action: SelectAction, - @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService + @inject(TYPES.SelectionService) public selectionService: SelectionService ) { super(); } @@ -201,7 +205,7 @@ export class SelectAllCommand extends Command { constructor( @inject(TYPES.Action) public action: SelectAllAction, - @inject(GLSP_TYPES.SelectionService) public selectionService: SelectionService + @inject(TYPES.SelectionService) public selectionService: SelectionService ) { super(); } diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index b8b7bff..f23f3ca 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Point } from '@eclipse-glsp/protocol'; +import { Action, hasStringProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { @@ -31,24 +31,53 @@ import { SModelRoot, TYPES } from 'sprotty'; -import { forEachElement, isNotUndefined } from '../../utils/smodel-util'; +import { forEachElement } from '../../utils/smodel-util'; import { addResizeHandles, isResizable, removeResizeHandles, SResizeHandle } from '../change-bounds/model'; import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../change-bounds/movement-restrictor'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { ChangeBoundsTool } from '../tools/change-bounds-tool'; import { FeedbackCommand } from './model'; -export class ShowChangeBoundsToolResizeFeedbackAction implements Action { - constructor(readonly elementId?: string, public readonly kind: string = ShowChangeBoundsToolResizeFeedbackCommand.KIND) {} +export interface ShowChangeBoundsToolResizeFeedbackAction extends Action { + kind: typeof ShowChangeBoundsToolResizeFeedbackAction.KIND; + + elementId: string; +} + +export namespace ShowChangeBoundsToolResizeFeedbackAction { + export const KIND = 'showChangeBoundsToolResizeFeedback'; + + export function is(object: any): object is ShowChangeBoundsToolResizeFeedbackAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'elementId'); + } + + export function create(elementId: string): ShowChangeBoundsToolResizeFeedbackAction { + return { + kind: KIND, + elementId + }; + } +} + +export interface HideChangeBoundsToolResizeFeedbackAction extends Action { + kind: typeof HideChangeBoundsToolResizeFeedbackAction.KIND; } -export class HideChangeBoundsToolResizeFeedbackAction implements Action { - constructor(public readonly kind: string = HideChangeBoundsToolResizeFeedbackCommand.KIND) {} +export namespace HideChangeBoundsToolResizeFeedbackAction { + export const KIND = 'hideChangeBoundsToolResizeFeedback'; + + export function is(object: any): object is HideChangeBoundsToolResizeFeedbackAction { + return Action.hasKind(object, KIND); + } + + export function create(): HideChangeBoundsToolResizeFeedbackAction { + return { kind: KIND }; + } } @injectable() export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { - static readonly KIND = 'showChangeBoundsToolResizeFeedback'; + static readonly KIND = ShowChangeBoundsToolResizeFeedbackAction.KIND; @inject(TYPES.Action) protected action: ShowChangeBoundsToolResizeFeedbackAction; @@ -57,9 +86,9 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { forEachElement(index, isResizable, removeResizeHandles); - if (isNotUndefined(this.action.elementId)) { + if (this.action.elementId) { const resizeElement = index.getById(this.action.elementId); - if (isNotUndefined(resizeElement) && isResizable(resizeElement)) { + if (resizeElement && isResizable(resizeElement)) { addResizeHandles(resizeElement); } } @@ -69,7 +98,7 @@ export class ShowChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { @injectable() export class HideChangeBoundsToolResizeFeedbackCommand extends FeedbackCommand { - static readonly KIND = 'hideChangeBoundsToolResizeFeedback'; + static readonly KIND = HideChangeBoundsToolResizeFeedbackAction.KIND; @inject(TYPES.Action) protected action: HideChangeBoundsToolResizeFeedbackAction; @@ -149,7 +178,7 @@ export class FeedbackMoveMouseListener extends MouseListener { return false; } - protected getElementMoves(target: SModelElement, event: MouseEvent, isFinished: boolean): MoveAction | undefined { + protected getElementMoves(target: SModelElement, event: MouseEvent, finished: boolean): MoveAction | undefined { if (!this.startDragPosition) { return undefined; } @@ -173,7 +202,7 @@ export class FeedbackMoveMouseListener extends MouseListener { ); if (isMoveable(element)) { - toPosition = this.validateMove(startPosition, toPosition, element, isFinished); + toPosition = this.validateMove(startPosition, toPosition, element, finished); elementMoves.push({ elementId: element.id, fromPosition: { @@ -186,7 +215,7 @@ export class FeedbackMoveMouseListener extends MouseListener { } }); if (elementMoves.length > 0) { - return new MoveAction(elementMoves, false, isFinished); + return MoveAction.create(elementMoves, { animate: false, finished }); } else { return undefined; } diff --git a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts index 66e986d..2eb730a 100644 --- a/packages/client/src/features/tool-feedback/creation-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/creation-tool-feedback.ts @@ -13,15 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Point } from '@eclipse-glsp/protocol'; +import { Action, Bounds, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { - add, AnchorComputerRegistry, - center, CommandExecutionContext, CommandReturn, - euclideanDistance, findChildrenAtPosition, findParentByFeature, isBoundsAware, @@ -32,7 +29,7 @@ import { SChildElement, SConnectableElement, SDanglingAnchor, - SEdgeSchema, + SEdge as SEdgeSchema, SModelElement, SModelRoot, SRoutableElement, @@ -41,19 +38,31 @@ import { import { BoundsAwareModelElement, isRoutable } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds, toAbsolutePosition } from '../../utils/viewpoint-util'; import { FeedbackCommand } from './model'; +export interface DrawFeedbackEdgeAction extends Action { + kind: typeof DrawFeedbackEdgeAction.KIND; + elementTypeId: string; + sourceId: string; + edgeSchema?: SEdgeSchema; +} -export class DrawFeedbackEdgeAction implements Action { - constructor( - public readonly elementTypeId: string, - public readonly sourceId: string, - public readonly edgeSchema?: SEdgeSchema, - public readonly kind: string = DrawFeedbackEdgeCommand.KIND - ) {} +export namespace DrawFeedbackEdgeAction { + export const KIND = 'drawFeedbackEdge'; + + export function is(object: any): object is DrawFeedbackEdgeAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { elementTypeId: string; sourceId: string; edgeSchema?: SEdgeSchema }): DrawFeedbackEdgeAction { + return { + kind: KIND, + ...options + }; + } } @injectable() export class DrawFeedbackEdgeCommand extends FeedbackCommand { - static readonly KIND = 'drawFeedbackEdge'; + static readonly KIND = DrawFeedbackEdgeAction.KIND; constructor(@inject(TYPES.Action) protected action: DrawFeedbackEdgeAction) { super(); @@ -66,13 +75,25 @@ export class DrawFeedbackEdgeCommand extends FeedbackCommand { } } -export class RemoveFeedbackEdgeAction implements Action { - constructor(public readonly kind: string = RemoveFeedbackEdgeCommand.KIND) {} +export interface RemoveFeedbackEdgeAction extends Action { + kind: typeof RemoveFeedbackEdgeAction.KIND; +} + +export namespace RemoveFeedbackEdgeAction { + export const KIND = 'removeFeedbackEdgeCommand'; + + export function is(object: any): object is RemoveFeedbackEdgeAction { + return Action.hasKind(object, KIND); + } + + export function create(): RemoveFeedbackEdgeAction { + return { kind: KIND }; + } } @injectable() export class RemoveFeedbackEdgeCommand extends FeedbackCommand { - static readonly KIND = 'removeFeedbackEdgeCommand'; + static readonly KIND = RemoveFeedbackEdgeAction.KIND; execute(context: CommandExecutionContext): CommandReturn { removeFeedbackEdge(context.root); @@ -111,12 +132,12 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { .find(element => isConnectable(element) && element.canConnect(edge, 'target')); if (endAtMousePosition instanceof SConnectableElement && edge.source && isBoundsAware(edge.source)) { - const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(toAbsoluteBounds(edge.source))); - if (euclideanDistance(anchor, edgeEnd.position) > 1) { - return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; + const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(toAbsoluteBounds(edge.source))); + if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { + return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })]; } } else { - return [new MoveAction([{ elementId: edgeEnd.id, toPosition: position }], false)]; + return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })]; } return []; @@ -132,7 +153,7 @@ export class FeedbackEdgeEndMovingMouseListener extends MouseListener { const parent = findParentByFeature(element.parent, isBoundsAware); if (parent) { const absoluteParentPosition = toAbsoluteBounds(parent); - anchor = add(absoluteParentPosition, anchor); + anchor = Point.add(absoluteParentPosition, anchor); } } return anchor; diff --git a/packages/client/src/features/tool-feedback/css-feedback.ts b/packages/client/src/features/tool-feedback/css-feedback.ts index e3dd420..16d1f97 100644 --- a/packages/client/src/features/tool-feedback/css-feedback.ts +++ b/packages/client/src/features/tool-feedback/css-feedback.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,50 +13,57 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isStringArray } from '@eclipse-glsp/protocol'; +import { Action, hasArrayProp } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { CommandExecutionContext, SModelElement, SModelRoot, TYPES } from 'sprotty'; -import { addCssClasses, removeCssClasses } from '../../utils/smodel-util'; +import { CommandExecutionContext, SModelElement, SModelRoot } from 'sprotty'; +import { TYPES } from '../../base/types'; +import { addCssClasses, getElements, removeCssClasses } from '../../utils/smodel-util'; import { FeedbackCommand } from './model'; -export class ModifyCSSFeedbackAction implements Action { - readonly elementIds?: string[]; +export interface ModifyCSSFeedbackAction extends Action { + kind: typeof ModifyCSSFeedbackAction.KIND; - constructor( - public readonly input?: string[] | SModelElement[], - public readonly addClasses?: string[], - public readonly removeClasses?: string[], - public kind = ModifyCssFeedbackCommand.KIND - ) { - if (input) { - this.elementIds = isStringArray(input) ? input : input.map(element => element.id); - } + elementIds?: string[]; + add?: string[]; + remove?: string[]; +} + +export namespace ModifyCSSFeedbackAction { + export const KIND = 'modifyCSSFeedback'; + + export function is(object: any): object is ModifyCSSFeedbackAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); + } + + export function create(options: { elements?: (string | SModelElement)[]; add?: string[]; remove?: string[] }): ModifyCSSFeedbackAction { + const { elements, ...rest } = options; + const elementIds = elements ? elements.map(element => (typeof element === 'string' ? element : element.id)) : undefined; + return { + kind: KIND, + elementIds, + ...rest + }; } } @injectable() export class ModifyCssFeedbackCommand extends FeedbackCommand { - static readonly KIND = 'modifyCSSFeedback'; + static readonly KIND = ModifyCSSFeedbackAction.KIND; constructor(@inject(TYPES.Action) readonly action: ModifyCSSFeedbackAction) { super(); } execute(context: CommandExecutionContext): SModelRoot { - const elements: SModelElement[] = []; - if (this.action.elementIds) { - elements.push(...this.action.elementIds.map(elementId => context.root.index.getById(elementId)).filter(exists)); - } else { - elements.push(context.root); - } + const elements = this.action.elementIds ? getElements(context.root.index, this.action.elementIds) : [context.root]; elements.forEach(e => { - if (this.action.removeClasses) { - removeCssClasses(e, this.action.removeClasses); + if (this.action.remove) { + removeCssClasses(e, this.action.remove); } - if (this.action.addClasses) { - addCssClasses(e, this.action.addClasses); + if (this.action.add) { + addCssClasses(e, this.action.add); } }); @@ -64,10 +71,6 @@ export class ModifyCssFeedbackCommand extends FeedbackCommand { } } -function exists(elt?: SModelElement): elt is SModelElement { - return elt !== undefined; -} - // eslint-disable-next-line no-shadow export enum CursorCSS { DEFAULT = 'default-mode', @@ -85,17 +88,17 @@ export enum CursorCSS { } export function cursorFeedbackAction(cursorCss?: CursorCSS): ModifyCSSFeedbackAction { - const addCss = []; + const add = []; if (cursorCss) { - addCss.push(cursorCss); + add.push(cursorCss); } - return new ModifyCSSFeedbackAction(undefined, addCss, Object.values(CursorCSS)); + return ModifyCSSFeedbackAction.create({ add, remove: Object.values(CursorCSS) }); } -export function applyCssClasses(element: SModelElement, ...classes: string[]): ModifyCSSFeedbackAction { - return new ModifyCSSFeedbackAction([element], classes, []); +export function applyCssClasses(element: SModelElement, ...add: string[]): ModifyCSSFeedbackAction { + return ModifyCSSFeedbackAction.create({ elements: [element], add }); } -export function deleteCssClasses(element: SModelElement, ...classes: string[]): ModifyCSSFeedbackAction { - return new ModifyCSSFeedbackAction([element], [], classes); +export function deleteCssClasses(element: SModelElement, ...remove: string[]): ModifyCSSFeedbackAction { + return ModifyCSSFeedbackAction.create({ elements: [element], remove }); } diff --git a/packages/client/src/features/tool-feedback/di.config.ts b/packages/client/src/features/tool-feedback/di.config.ts index 43973af..85f783d 100644 --- a/packages/client/src/features/tool-feedback/di.config.ts +++ b/packages/client/src/features/tool-feedback/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureCommand, configureView, LocationPostprocessor, MoveCommand, TYPES } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; +import { configureCommand, configureView, LocationPostprocessor, MoveCommand } from 'sprotty'; +import { TYPES } from '../../base/types'; import { SResizeHandle } from '../change-bounds/model'; import { HideChangeBoundsToolResizeFeedbackCommand, ShowChangeBoundsToolResizeFeedbackCommand } from './change-bounds-tool-feedback'; import { DrawFeedbackEdgeCommand, FeedbackEdgeEnd, RemoveFeedbackEdgeCommand } from './creation-tool-feedback'; @@ -32,7 +31,7 @@ import { DrawMarqueeCommand, RemoveMarqueeCommand } from './marquee-tool-feedbac import { FeedbackEdgeEndView, SResizeHandleView } from './view'; const toolFeedbackModule = new ContainerModule((bind, _unbind, isBound) => { - bind(GLSP_TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); + bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); configureCommand({ bind, isBound }, ModifyCssFeedbackCommand); diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 7f3a349..700a1fb 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -13,18 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Point } from '@eclipse-glsp/protocol'; +import { Action, Bounds, hasStringProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { - add, AnchorComputerRegistry, - center, CommandExecutionContext, CommandReturn, EdgeRouterRegistry, ElementMove, - euclideanDistance, findChildrenAtPosition, findParentByFeature, isBoundsAware, @@ -42,7 +39,7 @@ import { SwitchEditModeCommand, TYPES } from 'sprotty'; -import { forEachElement, isNotUndefined, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; +import { forEachElement, isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { PointPositionUpdater } from '../change-bounds/snap'; import { addReconnectHandles, removeReconnectHandles } from '../reconnect/model'; @@ -52,18 +49,41 @@ import { FeedbackCommand } from './model'; /** * RECONNECT HANDLES FEEDBACK */ +export interface ShowEdgeReconnectHandlesFeedbackAction extends Action { + kind: typeof ShowEdgeReconnectHandlesFeedbackAction.KIND; + readonly elementId: string; +} + +export namespace ShowEdgeReconnectHandlesFeedbackAction { + export const KIND = 'showReconnectHandlesFeedback'; + + export function is(object: any): object is ShowEdgeReconnectHandlesFeedbackAction { + return Action.hasKind(object, KIND); + } -export class ShowEdgeReconnectHandlesFeedbackAction implements Action { - constructor(readonly elementId?: string, public readonly kind: string = ShowEdgeReconnectHandlesFeedbackCommand.KIND) {} + export function create(elementId: string): ShowEdgeReconnectHandlesFeedbackAction { + return { kind: KIND, elementId }; + } +} +export interface HideEdgeReconnectHandlesFeedbackAction extends Action { + kind: typeof HideEdgeReconnectHandlesFeedbackAction.KIND; } -export class HideEdgeReconnectHandlesFeedbackAction implements Action { - constructor(public readonly kind: string = HideEdgeReconnectHandlesFeedbackCommand.KIND) {} +export namespace HideEdgeReconnectHandlesFeedbackAction { + export const KIND = 'hideReconnectHandlesFeedback'; + + export function is(object: any): object is HideEdgeReconnectHandlesFeedbackAction { + return Action.hasKind(object, KIND); + } + + export function create(): HideEdgeReconnectHandlesFeedbackAction { + return { kind: KIND }; + } } @injectable() export class ShowEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { - static readonly KIND = 'showReconnectHandlesFeedback'; + static readonly KIND = ShowEdgeReconnectHandlesFeedbackAction.KIND; constructor(@inject(TYPES.Action) protected action: ShowEdgeReconnectHandlesFeedbackAction) { super(); @@ -73,19 +93,18 @@ export class ShowEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { const index = context.root.index; forEachElement(index, isRoutable, removeReconnectHandles); - if (isNotUndefined(this.action.elementId)) { - const routableElement = index.getById(this.action.elementId); - if (isNotUndefined(routableElement) && isRoutable(routableElement)) { - addReconnectHandles(routableElement); - } + const routableElement = index.getById(this.action.elementId); + if (routableElement && isRoutable(routableElement)) { + addReconnectHandles(routableElement); } + return context.root; } } @injectable() export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { - static readonly KIND = 'hideReconnectHandlesFeedback'; + static readonly KIND = HideEdgeReconnectHandlesFeedbackAction.KIND; constructor(@inject(TYPES.Action) protected action: HideEdgeReconnectHandlesFeedbackAction) { super(); @@ -101,21 +120,24 @@ export class HideEdgeReconnectHandlesFeedbackCommand extends FeedbackCommand { * ROUTING FEEDBACK */ -export class SwitchRoutingModeAction extends SwitchEditModeAction { - constructor( - override readonly elementsToActivate: string[] = [], - override readonly elementsToDeactivate: string[] = [], - override readonly kind: string = SwitchRoutingModeCommand.KIND - ) { - super(elementsToActivate, elementsToDeactivate); - } +export interface SwitchRoutingModeAction extends Omit { + kind: typeof SwitchRoutingModeAction.KIND; } +export namespace SwitchRoutingModeAction { + export const KIND = 'switchRoutingMode'; + export function create(options: { elementsToActivate?: string[]; elementsToDeactivate?: string[] }): SwitchRoutingModeAction { + return { + ...SwitchEditModeAction.create(options), + kind: KIND + }; + } +} @injectable() export class SwitchRoutingModeCommand extends SwitchEditModeCommand { - static override KIND = 'switchRoutingMode'; + static override KIND = SwitchRoutingModeAction.KIND; constructor(@inject(TYPES.Action) action: SwitchRoutingModeAction) { - super(action); + super({ ...action, kind: SwitchEditModeAction.KIND }); } } @@ -123,17 +145,30 @@ export class SwitchRoutingModeCommand extends SwitchEditModeCommand { * SOURCE AND TARGET EDGE FEEDBACK */ -export class DrawFeedbackEdgeSourceAction implements Action { - constructor( - readonly elementTypeId: string, - readonly targetId: string, - public readonly kind: string = DrawFeedbackEdgeSourceCommand.KIND - ) {} +export interface DrawFeedbackEdgeSourceAction extends Action { + kind: typeof DrawFeedbackEdgeSourceAction.KIND; + elementTypeId: string; + targetId: string; +} + +export namespace DrawFeedbackEdgeSourceAction { + export const KIND = 'drawFeedbackEdgeSource'; + + export function is(object: any): object is DrawFeedbackEdgeSourceAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'elementTypeId') && hasStringProp(object, 'targetId'); + } + + export function create(options: { elementTypeId: string; targetId: string }): DrawFeedbackEdgeSourceAction { + return { + kind: KIND, + ...options + }; + } } @injectable() export class DrawFeedbackEdgeSourceCommand extends FeedbackCommand { - static readonly KIND = 'drawFeedbackEdgeSource'; + static readonly KIND = DrawFeedbackEdgeSourceAction.KIND; constructor(@inject(TYPES.Action) protected action: DrawFeedbackEdgeSourceAction) { super(); @@ -174,12 +209,12 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { ); if (endAtMousePosition instanceof SConnectableElement && edge.target && isBoundsAware(edge.target)) { - const anchor = this.computeAbsoluteAnchor(endAtMousePosition, center(edge.target.bounds)); - if (euclideanDistance(anchor, edgeEnd.position) > 1) { - return [new MoveAction([{ elementId: edgeEnd.id, toPosition: anchor }], false)]; + const anchor = this.computeAbsoluteAnchor(endAtMousePosition, Bounds.center(edge.target.bounds)); + if (Point.euclideanDistance(anchor, edgeEnd.position) > 1) { + return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: anchor }], { animate: false })]; } } else { - return [new MoveAction([{ elementId: edgeEnd.id, toPosition: position }], false)]; + return [MoveAction.create([{ elementId: edgeEnd.id, toPosition: position }], { animate: false })]; } return []; @@ -194,7 +229,7 @@ export class FeedbackEdgeSourceMovingMouseListener extends MouseListener { const parent = findParentByFeature(element.parent, isBoundsAware); if (parent) { const absoluteParentPosition = toAbsoluteBounds(parent); - anchor = add(absoluteParentPosition, anchor); + anchor = Point.add(absoluteParentPosition, anchor); } } return anchor; @@ -214,7 +249,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { if (event.button === 0) { const routingHandle = findParentByFeature(target, isRoutingHandle); if (routingHandle !== undefined) { - result.push(new SwitchRoutingModeAction([target.id], [])); + result.push(SwitchRoutingModeAction.create({ elementsToActivate: [target.id] })); this.pointPositionUpdater.updateLastDragPosition({ x: event.pageX, y: event.pageY }); } else { this.pointPositionUpdater.resetPosition(); @@ -250,7 +285,7 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } }); if (handleMoves.length > 0) { - return [new MoveAction(handleMoves, false)]; + return [MoveAction.create(handleMoves, { animate: false })]; } return []; } diff --git a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts index ee0a845..92bd309 100644 --- a/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts +++ b/packages/client/src/features/tool-feedback/feedback-action-dispatcher.ts @@ -15,7 +15,8 @@ ********************************************************************************/ import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, ILogger, TYPES } from 'sprotty'; +import { IActionDispatcher, ILogger } from 'sprotty'; +import { TYPES } from '../../base/types'; export interface IFeedbackEmitter {} diff --git a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts index 2751773..a3b91b5 100644 --- a/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/marquee-tool-feedback.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Point } from '@eclipse-glsp/protocol'; +import { Action, hasObjectProp, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { AnchorComputerRegistry, @@ -27,17 +27,30 @@ import { } from 'sprotty'; import { FeedbackCommand } from './model'; -export class DrawMarqueeAction implements Action { - constructor( - public readonly startPoint: Point, - public readonly endPoint: Point, - public readonly kind: string = DrawMarqueeCommand.KIND - ) {} +export interface DrawMarqueeAction extends Action { + kind: typeof DrawMarqueeAction.KIND; + startPoint: Point; + endPoint: Point; +} + +export namespace DrawMarqueeAction { + export const KIND = 'drawMarquee'; + + export function is(object: any): object is DrawMarqueeAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'startPoint') && hasObjectProp(object, 'endPoint'); + } + + export function create(options: { startPoint: Point; endPoint: Point }): DrawMarqueeAction { + return { + kind: KIND, + ...options + }; + } } @injectable() export class DrawMarqueeCommand extends FeedbackCommand { - static readonly KIND = 'drawMarquee'; + static readonly KIND = DrawMarqueeAction.KIND; constructor(@inject(TYPES.Action) protected action: DrawMarqueeAction) { super(); @@ -49,13 +62,25 @@ export class DrawMarqueeCommand extends FeedbackCommand { } } -export class RemoveMarqueeAction implements Action { - constructor(public readonly kind: string = RemoveMarqueeCommand.KIND) {} +export interface RemoveMarqueeAction extends Action { + kind: typeof RemoveMarqueeAction.KIND; +} + +export namespace RemoveMarqueeAction { + export const KIND = 'removeMarquee'; + + export function is(object: any): object is RemoveMarqueeAction { + return Action.hasKind(object, KIND); + } + + export function create(): RemoveMarqueeAction { + return { kind: KIND }; + } } @injectable() export class RemoveMarqueeCommand extends FeedbackCommand { - static readonly KIND = 'removeMarqueeCommand'; + static readonly KIND = RemoveMarqueeAction.KIND; execute(context: CommandExecutionContext): CommandReturn { removeMarquee(context.root); diff --git a/packages/client/src/features/tool-feedback/view.tsx b/packages/client/src/features/tool-feedback/view.tsx index cad13e0..fbc52f4 100644 --- a/packages/client/src/features/tool-feedback/view.tsx +++ b/packages/client/src/features/tool-feedback/view.tsx @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { IView, ORIGIN_POINT, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; +import { IView, RenderingContext, setAttr, SModelElement, svg } from 'sprotty'; import { isResizable, ResizeHandleLocation, SResizeHandle } from '../change-bounds/model'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -29,7 +29,7 @@ const JSX = { createElement: svg }; @injectable() export class FeedbackEdgeEndView implements IView { render(model: Readonly, context: RenderingContext): VNode { - const position: Point = (model as any).position || ORIGIN_POINT; + const position: Point = (model as any).position ?? Point.ORIGIN; return ; } } diff --git a/packages/client/src/features/tool-palette/di.config.ts b/packages/client/src/features/tool-palette/di.config.ts index 67b09bf..7625dba 100644 --- a/packages/client/src/features/tool-palette/di.config.ts +++ b/packages/client/src/features/tool-palette/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureActionHandler, EnableDefaultToolsAction, TYPES } from 'sprotty'; +import { configureActionHandler, EnableDefaultToolsAction } from 'sprotty'; import '../../../css/tool-palette.css'; +import { TYPES } from '../../base/types'; import { EnableToolPaletteAction, ToolPalette } from './tool-palette'; const toolPaletteModule = new ContainerModule((bind, _unbind, isBound, rebind) => { diff --git a/packages/client/src/features/tool-palette/palette-item.ts b/packages/client/src/features/tool-palette/palette-item.ts deleted file mode 100644 index 096b440..0000000 --- a/packages/client/src/features/tool-palette/palette-item.ts +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { isLabeledAction, isTriggerElementTypeCreationAction, LabeledAction, TriggerElementCreationAction } from '@eclipse-glsp/protocol'; - -export interface PaletteItem extends LabeledAction { - readonly id: string; - readonly sortString: string; - readonly children?: PaletteItem[]; -} - -export function isPaletteItem(object?: any): object is PaletteItem { - return isLabeledAction(object) && 'id' in object && 'sortString' in object; -} - -export namespace PaletteItem { - export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { - if (item) { - const initiAction = item.actions - .filter(a => isTriggerElementTypeCreationAction(a)) - .map(action => action as TriggerElementCreationAction); - return initiAction.length > 0 ? initiAction[0] : undefined; - } - return undefined; - } -} diff --git a/packages/client/src/features/tool-palette/tool-palette.ts b/packages/client/src/features/tool-palette/tool-palette.ts index 05721ad..a35de54 100644 --- a/packages/client/src/features/tool-palette/tool-palette.ts +++ b/packages/client/src/features/tool-palette/tool-palette.ts @@ -13,11 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isSetContextActionsAction, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; +import { Action, PaletteItem, RequestContextActions, RequestMarkersAction, SetContextActions } from '@eclipse-glsp/protocol'; import { inject, injectable, postConstruct } from 'inversify'; import { AbstractUIExtension, - Action, EnableDefaultToolsAction, EnableToolsAction, IActionHandler, @@ -33,7 +32,6 @@ import { GLSPActionDispatcher } from '../../base/action-dispatcher'; import { EditModeListener, EditorContextService } from '../../base/editor-context-service'; import { MouseDeleteTool } from '../tools/delete-tool'; import { MarqueeMouseTool } from '../tools/marquee-mouse-tool'; -import { PaletteItem } from './palette-item'; const CLICKED_CSS_CLASS = 'clicked'; const SEARCH_ICON_ID = 'search'; @@ -41,12 +39,21 @@ const PALETTE_ICON_ID = 'symbol-color'; const CHEVRON_DOWN_ICON_ID = 'chevron-right'; const PALETTE_HEIGHT = '500px'; -@injectable() -export class EnableToolPaletteAction implements Action { - static readonly KIND = 'enableToolPalette'; - readonly kind = EnableToolPaletteAction.KIND; +export interface EnableToolPaletteAction extends Action { + kind: typeof EnableToolPaletteAction.KIND; } +export namespace EnableToolPaletteAction { + export const KIND = 'enableToolPalette'; + + export function is(object: any): object is EnableToolPaletteAction { + return Action.hasKind(object, KIND); + } + + export function create(): EnableToolPaletteAction { + return { kind: KIND }; + } +} @injectable() export class ToolPalette extends AbstractUIExtension implements IActionHandler, EditModeListener { static readonly ID = 'tool-palette'; @@ -215,7 +222,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, validateActionButton.title = 'Validate model'; validateActionButton.onclick = _event => { const modelIds: string[] = [this.modelRootId]; - this.actionDispatcher.dispatch(new RequestMarkersAction(modelIds)); + this.actionDispatcher.dispatch(RequestMarkersAction.create(modelIds)); }; return validateActionButton; } @@ -284,7 +291,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected onClickStaticToolButton(button: HTMLElement, toolId?: string) { return (_ev: MouseEvent) => { if (!this.editorContext.isReadonly) { - const action = toolId ? new EnableToolsAction([toolId]) : new EnableDefaultToolsAction(); + const action = toolId ? EnableToolsAction.create([toolId]) : EnableDefaultToolsAction.create(); this.actionDispatcher.dispatch(action); this.changeActiveButton(button); button.focus(); @@ -307,23 +314,30 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, handle(action: Action): ICommand | Action | void { if (action.kind === EnableToolPaletteAction.KIND) { - const requestAction = new RequestContextActions(ToolPalette.ID, { - selectedElementIds: [] + const requestAction = RequestContextActions.create({ + contextId: ToolPalette.ID, + editorContext: { + selectedElementIds: [] + } }); this.actionDispatcher.requestUntil(requestAction).then(response => { - if (isSetContextActionsAction(response)) { + if (SetContextActions.is(response)) { this.paletteItems = response.actions.map(e => e as PaletteItem); - this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); + this.actionDispatcher.dispatch( + SetUIExtensionVisibilityAction.create({ extensionId: ToolPalette.ID, visible: !this.editorContext.isReadonly }) + ); } }); - } else if (action instanceof EnableDefaultToolsAction) { + } else if (action.kind === EnableDefaultToolsAction.KIND) { this.changeActiveButton(); this.restoreFocus(); } } editModeChanged(_oldValue: string, _newValue: string): void { - this.actionDispatcher.dispatch(new SetUIExtensionVisibilityAction(ToolPalette.ID, !this.editorContext.isReadonly)); + this.actionDispatcher.dispatch( + SetUIExtensionVisibilityAction.create({ extensionId: ToolPalette.ID, visible: !this.editorContext.isReadonly }) + ); } protected clearOnEscape(event: KeyboardEvent): void { @@ -335,7 +349,7 @@ export class ToolPalette extends AbstractUIExtension implements IActionHandler, protected clearToolOnEscape(event: KeyboardEvent): void { if (matchesKeystroke(event, 'Escape')) { - this.actionDispatcher.dispatch(new EnableDefaultToolsAction()); + this.actionDispatcher.dispatch(EnableDefaultToolsAction.create()); } } diff --git a/packages/client/src/features/tools/base-glsp-tool.ts b/packages/client/src/features/tools/base-glsp-tool.ts index 5c3baf3..328082a 100644 --- a/packages/client/src/features/tools/base-glsp-tool.ts +++ b/packages/client/src/features/tools/base-glsp-tool.ts @@ -15,18 +15,18 @@ ********************************************************************************/ import { Action } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { IActionDispatcher, KeyTool, TYPES } from 'sprotty'; +import { IActionDispatcher, KeyTool } from 'sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; @injectable() export abstract class BaseGLSPTool implements GLSPTool { - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher; - @inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(TYPES.MouseTool) protected mouseTool: IMouseTool; @inject(KeyTool) protected keyTool: KeyTool; @inject(EditorContextService) protected readonly editorContext: EditorContextService; diff --git a/packages/client/src/features/tools/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds-tool.ts index 701b7d3..79666de 100644 --- a/packages/client/src/features/tools/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds-tool.ts @@ -19,30 +19,29 @@ import { ChangeBoundsOperation, ChangeRoutingPointsOperation, CompoundOperation, + Dimension, + ElementAndBounds, ElementAndRoutingPoints, Operation, - Point + Point, + SetBoundsAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { BoundsAware, - Dimension, EdgeRouterRegistry, - ElementAndBounds, findParentByFeature, ISnapper, isSelected, MouseListener, SChildElement, SConnectableElement, - SetBoundsAction, SModelElement, SModelRoot, - SParentElement, - TYPES + SParentElement } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { isValidMove, isValidSize } from '../../utils/layout-utils'; import { forEachElement, @@ -83,10 +82,10 @@ import { BaseGLSPTool } from './base-glsp-tool'; export class ChangeBoundsTool extends BaseGLSPTool { static ID = 'glsp.change-bounds-tool'; - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.SelectionService) protected selectionService: SelectionService; @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; - @inject(GLSP_TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; + @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; protected feedbackMoveMouseListener: MouseListener; protected changeBoundsListener: MouseListener & SelectionListener; @@ -118,7 +117,7 @@ export class ChangeBoundsTool extends BaseGLSPTool { this.selectionService.deregister(this.changeBoundsListener); this.mouseTool.deregister(this.feedbackMoveMouseListener); this.deregisterFeedback([], this.feedbackMoveMouseListener); - this.deregisterFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this.changeBoundsListener); + this.deregisterFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this.changeBoundsListener); } } @@ -140,7 +139,9 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele override mouseDown(target: SModelElement, event: MouseEvent): Action[] { super.mouseDown(target, event); - if (event.button !== 0) { + // If another button than the left mouse button was clicked or we are + // still on the root element we don't need to execute the tool behavior + if (event.button !== 0 || target instanceof SModelRoot) { return []; } // check if we have a resize handle (only single-selection) @@ -200,12 +201,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele operations.push(...this.handleMoveElementsOnServer(target)); operations.push(...this.handleMoveRoutingPointsOnServer(target)); if (operations.length > 0) { - return [new CompoundOperation(operations)]; + return [CompoundOperation.create(operations)]; } return operations; } - protected handleMoveElementsOnServer(target: SModelElement): Action[] { + protected handleMoveElementsOnServer(target: SModelElement): Operation[] { const result: Operation[] = []; const newBounds: ElementAndBounds[] = []; const selectedElements: (SModelElement & BoundsAware)[] = []; @@ -220,7 +221,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele .forEach(bounds => newBounds.push(...bounds)); if (newBounds.length > 0) { - result.push(new ChangeBoundsOperation(newBounds)); + result.push(ChangeBoundsOperation.create(newBounds)); } return result; } @@ -235,7 +236,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele return false; } - protected handleMoveRoutingPointsOnServer(target: SModelElement): Action[] { + protected handleMoveRoutingPointsOnServer(target: SModelElement): Operation[] { const result: Operation[] = []; const newRoutingPoints: ElementAndRoutingPoints[] = []; forEachElement(target.index, isNonRoutableSelectedMovableBoundsAware, element => { @@ -246,7 +247,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele } }); if (newRoutingPoints.length > 0) { - result.push(new ChangeRoutingPointsOperation(newRoutingPoints)); + result.push(ChangeRoutingPointsOperation.create(newRoutingPoints)); } return result; } @@ -287,7 +288,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele // only allow one element to have the element resize handles this.activeResizeElement = moveableElement; if (isResizable(this.activeResizeElement)) { - this.tool.dispatchFeedback([new ShowChangeBoundsToolResizeFeedbackAction(this.activeResizeElement.id)], this); + this.tool.dispatchFeedback([ShowChangeBoundsToolResizeFeedbackAction.create(this.activeResizeElement.id)], this); } return true; } @@ -313,7 +314,7 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected reset(): void { if (this.activeResizeElement && isResizable(this.activeResizeElement)) { - this.tool.dispatchFeedback([new HideChangeBoundsToolResizeFeedbackAction()], this); + this.tool.dispatchFeedback([HideChangeBoundsToolResizeFeedbackAction.create()], this); } this.tool.dispatchActions([cursorFeedbackAction(CursorCSS.DEFAULT)]); this.resetPosition(); @@ -387,13 +388,13 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele protected createChangeBoundsAction(element: SModelElement & BoundsAware): Action[] { if (this.isValidBoundChange(element, element.bounds, element.bounds)) { - return [new ChangeBoundsOperation([toElementAndBounds(element)])]; + return [ChangeBoundsOperation.create([toElementAndBounds(element)])]; } else if (this.initialBounds) { const actions: Action[] = []; if (this.tool.movementRestrictor) { actions.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - actions.push(new SetBoundsAction([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }])); + actions.push(SetBoundsAction.create([{ elementId: element.id, newPosition: this.initialBounds, newSize: this.initialBounds }])); return actions; } return []; @@ -415,12 +416,12 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements Sele if (this.tool.movementRestrictor) { result.push(removeMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - result.push(new SetBoundsAction([{ elementId: element.id, newPosition, newSize }])); + result.push(SetBoundsAction.create([{ elementId: element.id, newPosition, newSize }])); } else if (this.isValidSize(element, newSize)) { if (this.tool.movementRestrictor) { result.push(createMovementRestrictionFeedback(element, this.tool.movementRestrictor)); } - result.push(new SetBoundsAction([{ elementId: element.id, newPosition, newSize }])); + result.push(SetBoundsAction.create([{ elementId: element.id, newPosition, newSize }])); } return result; diff --git a/packages/client/src/features/tools/delete-tool.ts b/packages/client/src/features/tools/delete-tool.ts index d717f75..3bcbb84 100644 --- a/packages/client/src/features/tools/delete-tool.ts +++ b/packages/client/src/features/tools/delete-tool.ts @@ -28,7 +28,7 @@ import { } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { GLSPTool } from '../../base/tool-manager/glsp-tool-manager'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { IMouseTool } from '../mouse-tool/mouse-tool'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; @@ -70,7 +70,7 @@ export class DeleteKeyListener extends KeyListener { .map(e => e.id) ); if (deleteElementIds.length > 0) { - return [new DeleteElementOperation(deleteElementIds)]; + return [DeleteElementOperation.create(deleteElementIds)]; } } return []; @@ -88,8 +88,8 @@ export class MouseDeleteTool implements GLSPTool { protected deleteToolMouseListener: DeleteToolMouseListener = new DeleteToolMouseListener(); - @inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool; - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.MouseTool) protected mouseTool: IMouseTool; + @inject(TYPES.IFeedbackActionDispatcher) protected readonly feedbackDispatcher: IFeedbackActionDispatcher; get id(): string { return MouseDeleteTool.ID; @@ -114,9 +114,9 @@ export class DeleteToolMouseListener extends MouseListener { return []; } const result: Action[] = []; - result.push(new DeleteElementOperation([deletableParent.id])); + result.push(DeleteElementOperation.create([deletableParent.id])); if (!isCtrlOrCmd(event)) { - result.push(new EnableDefaultToolsAction()); + result.push(EnableDefaultToolsAction.create()); } return result; } diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 2231957..4879af0 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,7 @@ import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { ContainerModule, interfaces } from 'inversify'; import { configureActionHandler, configureModelElement } from 'sprotty'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; import { ChangeBoundsTool } from './change-bounds-tool'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; @@ -30,16 +30,16 @@ import { MarqueeView } from './view'; const toolsModule = new ContainerModule((bind, _unbind, isBound) => { // Register default tools - bind(GLSP_TYPES.IDefaultTool).to(ChangeBoundsTool); - bind(GLSP_TYPES.IDefaultTool).to(EdgeEditTool); - bind(GLSP_TYPES.IDefaultTool).to(DelKeyDeleteTool); + bind(TYPES.IDefaultTool).to(ChangeBoundsTool); + bind(TYPES.IDefaultTool).to(EdgeEditTool); + bind(TYPES.IDefaultTool).to(DelKeyDeleteTool); // Register tools - bind(GLSP_TYPES.ITool).to(MouseDeleteTool); + bind(TYPES.ITool).to(MouseDeleteTool); bind(NodeCreationTool).toSelf().inSingletonScope(); bind(EdgeCreationTool).toSelf().inSingletonScope(); - bind(GLSP_TYPES.ITool).toService(EdgeCreationTool); - bind(GLSP_TYPES.ITool).toService(NodeCreationTool); + bind(TYPES.ITool).toService(EdgeCreationTool); + bind(TYPES.ITool).toService(NodeCreationTool); configureMarqueeTool({ bind, isBound }); configureActionHandler({ bind, isBound }, TriggerNodeCreationAction.KIND, NodeCreationTool); @@ -48,8 +48,8 @@ const toolsModule = new ContainerModule((bind, _unbind, isBound) => { export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: interfaces.IsBound }): void { configureModelElement(context, MARQUEE, MarqueeNode, MarqueeView); - context.bind(GLSP_TYPES.IDefaultTool).to(MarqueeTool); - context.bind(GLSP_TYPES.ITool).to(MarqueeMouseTool); + context.bind(TYPES.IDefaultTool).to(MarqueeTool); + context.bind(TYPES.ITool).to(MarqueeMouseTool); } export default toolsModule; diff --git a/packages/client/src/features/tools/edge-creation-tool.ts b/packages/client/src/features/tools/edge-creation-tool.ts index 364c357..1627b2e 100644 --- a/packages/client/src/features/tools/edge-creation-tool.ts +++ b/packages/client/src/features/tools/edge-creation-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CreateEdgeOperation, isTriggerElementTypeCreationAction, TriggerEdgeCreationAction } from '@eclipse-glsp/protocol'; +import { Action, CreateEdgeOperation, TriggerEdgeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { AnchorComputerRegistry, @@ -66,13 +66,13 @@ export class EdgeCreationTool extends BaseGLSPTool implements IActionHandler { disable(): void { this.mouseTool.deregister(this.creationToolMouseListener); this.mouseTool.deregister(this.feedbackEndMovingMouseListener); - this.deregisterFeedback([new RemoveFeedbackEdgeAction(), cursorFeedbackAction()]); + this.deregisterFeedback([RemoveFeedbackEdgeAction.create(), cursorFeedbackAction()]); } handle(action: Action): Action | void { - if (isTriggerElementTypeCreationAction(action)) { + if (TriggerEdgeCreationAction.is(action)) { this.triggerAction = action; - return new EnableToolsAction([this.id]); + return EnableToolsAction.create([this.id]); } } } @@ -96,7 +96,7 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { this.target = undefined; this.currentTarget = undefined; this.allowedTarget = false; - this.tool.dispatchFeedback([new RemoveFeedbackEdgeAction()]); + this.tool.dispatchFeedback([RemoveFeedbackEdgeAction.create()]); } override nonDraggingMouseUp(_element: SModelElement, event: MouseEvent): Action[] { @@ -105,7 +105,9 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { if (!this.isSourceSelected()) { if (this.currentTarget && this.allowedTarget) { this.source = this.currentTarget.id; - this.tool.dispatchFeedback([new DrawFeedbackEdgeAction(this.triggerAction.elementTypeId, this.source)]); + this.tool.dispatchFeedback([ + DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }) + ]); } } else { if (this.currentTarget && this.allowedTarget) { @@ -113,15 +115,22 @@ export class EdgeCreationToolMouseListener extends DragAwareMouseListener { } } if (this.source && this.target) { - result.push(new CreateEdgeOperation(this.triggerAction.elementTypeId, this.source, this.target, this.triggerAction.args)); + result.push( + CreateEdgeOperation.create({ + elementTypeId: this.triggerAction.elementTypeId, + sourceElementId: this.source, + targetElementId: this.target, + args: this.triggerAction.args + }) + ); if (!isCtrlOrCmd(event)) { - result.push(new EnableDefaultToolsAction()); + result.push(EnableDefaultToolsAction.create()); } else { this.reinitialize(); } } } else if (event.button === 2) { - result.push(new EnableDefaultToolsAction()); + result.push(EnableDefaultToolsAction.create()); } return result; } diff --git a/packages/client/src/features/tools/edge-edit-tool.ts b/packages/client/src/features/tools/edge-edit-tool.ts index 68f9ce8..273408f 100644 --- a/packages/client/src/features/tools/edge-edit-tool.ts +++ b/packages/client/src/features/tools/edge-edit-tool.ts @@ -27,11 +27,10 @@ import { SModelElement, SModelRoot, SRoutableElement, - SRoutingHandle, - TYPES + SRoutingHandle } from 'sprotty'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { isRoutable, isRoutingHandle } from '../../utils/smodel-util'; import { isReconnectable, isReconnectHandle, isSourceRoutingHandle, isTargetRoutingHandle, SReconnectHandle } from '../reconnect/model'; import { SelectionListener, SelectionService } from '../select/selection-service'; @@ -52,7 +51,7 @@ import { BaseGLSPTool } from './base-glsp-tool'; export class EdgeEditTool extends BaseGLSPTool { static ID = 'glsp.edge-edit-tool'; - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.SelectionService) protected selectionService: SelectionService; @inject(AnchorComputerRegistry) protected anchorRegistry: AnchorComputerRegistry; @inject(EdgeRouterRegistry) @optional() protected edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.ISnapper) @optional() readonly snapper?: ISnapper; @@ -126,10 +125,10 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen // note: order is important here as we want the reconnect handles to cover the routing handles const feedbackActions = []; if (canEditRouting(edge)) { - feedbackActions.push(new SwitchRoutingModeAction([this.edge.id], [])); + feedbackActions.push(SwitchRoutingModeAction.create({ elementsToActivate: [this.edge.id] })); } if (isReconnectable(edge)) { - feedbackActions.push(new ShowEdgeReconnectHandlesFeedbackAction(this.edge.id)); + feedbackActions.push(ShowEdgeReconnectHandlesFeedbackAction.create(this.edge.id)); } this.tool.dispatchFeedback(feedbackActions); } @@ -142,16 +141,16 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen if (this.edge && this.edge.target && this.edge.source) { if (isSourceRoutingHandle(edge, reconnectHandle)) { this.tool.dispatchFeedback([ - new HideEdgeReconnectHandlesFeedbackAction(), + HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(CursorCSS.EDGE_RECONNECT), - new DrawFeedbackEdgeSourceAction(this.edge.type, this.edge.targetId) + DrawFeedbackEdgeSourceAction.create({ elementTypeId: this.edge.type, targetId: this.edge.targetId }) ]); this.reconnectMode = 'NEW_SOURCE'; } else if (isTargetRoutingHandle(edge, reconnectHandle)) { this.tool.dispatchFeedback([ - new HideEdgeReconnectHandlesFeedbackAction(), + HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(CursorCSS.EDGE_CREATION_TARGET), - new DrawFeedbackEdgeAction(this.edge.type, this.edge.sourceId) + DrawFeedbackEdgeAction.create({ elementTypeId: this.edge.type, sourceId: this.edge.sourceId }) ]); this.reconnectMode = 'NEW_TARGET'; } @@ -227,10 +226,10 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen } if (this.edge && this.newConnectable) { - const sourceId = this.isReconnectingNewSource() ? this.newConnectable.id : this.edge.sourceId; - const targetId = this.isReconnectingNewSource() ? this.edge.targetId : this.newConnectable.id; - if (this.requiresReconnect(sourceId, targetId)) { - result.push(new ReconnectEdgeOperation(this.edge.id, sourceId, targetId)); + const sourceElementId = this.isReconnectingNewSource() ? this.newConnectable.id : this.edge.sourceId; + const targetElementId = this.isReconnectingNewSource() ? this.edge.targetId : this.newConnectable.id; + if (this.requiresReconnect(sourceElementId, targetElementId)) { + result.push(ReconnectEdgeOperation.create({ edgeElementId: this.edge.id, sourceElementId, targetElementId })); } this.reset(); } else if (this.edge && this.routingHandle) { @@ -238,7 +237,9 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen // reroute actions const latestEdge = target.index.getById(this.edge.id); if (latestEdge && isRoutable(latestEdge)) { - result.push(new ChangeRoutingPointsOperation([{ elementId: latestEdge.id, newRoutingPoints: latestEdge.routingPoints }])); + result.push( + ChangeRoutingPointsOperation.create([{ elementId: latestEdge.id, newRoutingPoints: latestEdge.routingPoints }]) + ); this.routingHandle = undefined; } } @@ -309,9 +310,9 @@ class EdgeEditListener extends DragAwareMouseListener implements SelectionListen protected resetFeedback(): void { const result: Action[] = []; if (this.edge) { - result.push(new SwitchRoutingModeAction([], [this.edge.id])); + result.push(SwitchRoutingModeAction.create({ elementsToDeactivate: [this.edge.id] })); } - result.push(...[new HideEdgeReconnectHandlesFeedbackAction(), cursorFeedbackAction(), new RemoveFeedbackEdgeAction()]); + result.push(...[HideEdgeReconnectHandlesFeedbackAction.create(), cursorFeedbackAction(), RemoveFeedbackEdgeAction.create()]); this.tool.deregisterFeedback(result); this.tool.deregisterFeedbackListeners(); } diff --git a/packages/client/src/features/tools/marquee-behavior.ts b/packages/client/src/features/tools/marquee-behavior.ts index 642dc4f..b898729 100644 --- a/packages/client/src/features/tools/marquee-behavior.ts +++ b/packages/client/src/features/tools/marquee-behavior.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -45,7 +45,7 @@ export class MarqueeUtil { } drawMarqueeAction(): DrawMarqueeAction { - return new DrawMarqueeAction(this.startPoint, this.currentPoint); + return DrawMarqueeAction.create({ startPoint: this.startPoint, endPoint: this.currentPoint }); } isEdgePathMarked(path: string | null): boolean { diff --git a/packages/client/src/features/tools/marquee-mouse-tool.ts b/packages/client/src/features/tools/marquee-mouse-tool.ts index 567150b..a580789 100644 --- a/packages/client/src/features/tools/marquee-mouse-tool.ts +++ b/packages/client/src/features/tools/marquee-mouse-tool.ts @@ -24,12 +24,11 @@ import { SEdge, SModelElement, SModelRoot, - SNode, - TYPES + SNode } from 'sprotty'; import { DOMHelper } from 'sprotty/lib/base/views/dom-helper'; import { DragAwareMouseListener } from '../../base/drag-aware-mouse-listener'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { getAbsolutePosition, toAbsoluteBounds } from '../../utils/viewpoint-util'; import { CursorCSS, cursorFeedbackAction } from '../tool-feedback/css-feedback'; import { RemoveMarqueeAction } from '../tool-feedback/marquee-tool-feedback'; @@ -41,7 +40,7 @@ export class MarqueeMouseTool extends BaseGLSPTool { static ID = 'glsp.marquee-mouse-tool'; @inject(TYPES.DOMHelper) protected domHelper: DOMHelper; - @inject(GLSP_TYPES.IMarqueeBehavior) @optional() protected marqueeBehavior: IMarqueeBehavior; + @inject(TYPES.IMarqueeBehavior) @optional() protected marqueeBehavior: IMarqueeBehavior; protected marqueeMouseListener: MarqueeMouseListener; protected shiftKeyListener: ShiftKeyListener = new ShiftKeyListener(); @@ -115,8 +114,8 @@ export class MarqueeMouseListener extends DragAwareMouseListener { const edgeIdsSelected = this.edges.filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e)); const selected = nodeIdsSelected.concat(edgeIdsSelected); return [ - new SelectAction([], Array.from(target.root.index.all().map(e => e.id))), - new SelectAction(selected.concat(this.previouslySelected), []), + SelectAction.create({ deselectedElementsIDs: Array.from(target.root.index.all().map(e => e.id)) }), + SelectAction.create({ selectedElementsIDs: selected.concat(this.previouslySelected) }), this.marqueeUtil.drawMarqueeAction() ]; } @@ -126,9 +125,9 @@ export class MarqueeMouseListener extends DragAwareMouseListener { override mouseUp(target: SModelElement, event: MouseEvent): Action[] { this.isActive = false; if (event.shiftKey) { - return [new RemoveMarqueeAction()]; + return [RemoveMarqueeAction.create()]; } - return [new RemoveMarqueeAction(), new EnableDefaultToolsAction()]; + return [RemoveMarqueeAction.create(), EnableDefaultToolsAction.create()]; } isEdgeMarked(element: SVGElement): boolean { @@ -148,6 +147,6 @@ export class ShiftKeyListener extends KeyListener { if (event.shiftKey) { return []; } - return [new RemoveMarqueeAction(), new EnableDefaultToolsAction()]; + return [RemoveMarqueeAction.create(), EnableDefaultToolsAction.create()]; } } diff --git a/packages/client/src/features/tools/marquee-tool.ts b/packages/client/src/features/tools/marquee-tool.ts index 3654d83..2228e5c 100644 --- a/packages/client/src/features/tools/marquee-tool.ts +++ b/packages/client/src/features/tools/marquee-tool.ts @@ -44,7 +44,7 @@ export class MarqueeTool extends BaseGLSPTool { export class MarqueeKeyListener extends KeyListener { override keyDown(element: SModelElement, event: KeyboardEvent): Action[] { if (event.shiftKey) { - return [new EnableToolsAction([MarqueeMouseTool.ID])]; + return [EnableToolsAction.create([MarqueeMouseTool.ID])]; } return []; } diff --git a/packages/client/src/features/tools/model.ts b/packages/client/src/features/tools/model.ts index 52ad4e6..e95068c 100644 --- a/packages/client/src/features/tools/model.ts +++ b/packages/client/src/features/tools/model.ts @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { boundsFeature, Point, RectangularNode } from 'sprotty'; +import { Point } from '@eclipse-glsp/protocol'; +import { boundsFeature, RectangularNode } from 'sprotty'; export class MarqueeNode extends RectangularNode { static override readonly DEFAULT_FEATURES = [boundsFeature]; diff --git a/packages/client/src/features/tools/node-creation-tool.ts b/packages/client/src/features/tools/node-creation-tool.ts index 0788b00..e37b5fe 100644 --- a/packages/client/src/features/tools/node-creation-tool.ts +++ b/packages/client/src/features/tools/node-creation-tool.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CreateNodeOperation, isTriggerNodeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; +import { Action, CreateNodeOperation, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { EnableDefaultToolsAction, @@ -60,9 +60,9 @@ export class NodeCreationTool extends BaseGLSPTool implements IActionHandler { } handle(action: Action): Action | void { - if (isTriggerNodeCreationAction(action)) { + if (TriggerNodeCreationAction.is(action)) { this.triggerAction = action; - return new EnableToolsAction([this.id]); + return EnableToolsAction.create([this.id]); } } } @@ -94,9 +94,9 @@ export class NodeCreationToolMouseListener extends DragAwareMouseListener { elementProxy.size = { width: 0, height: 0 }; location = this.tool.snapper.snap(location, elementProxy); } - result.push(new CreateNodeOperation(this.elementTypeId, location, containerId, this.triggerAction.args)); + result.push(CreateNodeOperation.create(this.elementTypeId, { location, containerId, args: this.triggerAction.args })); if (!isCtrlOrCmd(event)) { - result.push(new EnableDefaultToolsAction()); + result.push(EnableDefaultToolsAction.create()); } } return result; diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index 42c5a5d..5eed0eb 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureCommand, TYPES } from 'sprotty'; - -import { GLSP_TYPES } from '../../base/types'; +import { configureCommand } from 'sprotty'; +import { TYPES } from '../../base/types'; import { LeftToRightTopToBottomComparator, MarkerNavigator, @@ -46,6 +45,6 @@ export const markerNavigatorModule = new ContainerModule((bind, _unbind, isBound * `registerMarkerNavigationCommands()` in `glsp-theia-integration` instead. */ export const markerNavigatorContextMenuModule = new ContainerModule((bind, _unbind, isBound) => { - bind(GLSP_TYPES.IContextMenuProvider).to(MarkerNavigatorContextMenuItemProvider).inSingletonScope(); + bind(TYPES.IContextMenuProvider).to(MarkerNavigatorContextMenuItemProvider).inSingletonScope(); bind(TYPES.KeyListener).to(MarkerNavigatorKeyListener).inSingletonScope(); }); diff --git a/packages/client/src/features/validation/marker-navigator.spec.ts b/packages/client/src/features/validation/marker-navigator.spec.ts index 34cbeeb..07b580c 100644 --- a/packages/client/src/features/validation/marker-navigator.spec.ts +++ b/packages/client/src/features/validation/marker-navigator.spec.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ /* eslint-disable import/no-deprecated,no-unused-expressions */ +import { SNodeSchema } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import { Container } from 'inversify'; import 'mocha'; @@ -26,7 +27,6 @@ import { SIssueMarker, SModelElement, SModelRoot, - SNodeSchema, SParentElement, TYPES } from 'sprotty'; @@ -38,7 +38,9 @@ import { MarkerNavigator } from './marker-navigator'; describe('MarkerNavigator', () => { const container = new Container(); container.load(defaultModule, decorationModule, markerNavigatorModule); + // eslint-disable-next-line deprecation/deprecation container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); + // eslint-disable-next-line deprecation/deprecation const graphFactory = container.get(TYPES.IModelFactory); const markerNavigator = container.get(MarkerNavigator); diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 4d59a71..38caacc 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, CenterAction, Point } from '@eclipse-glsp/protocol'; +import { Action, CenterAction, hasArrayProp, hasStringProp, Point, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { BoundsAware, @@ -28,30 +28,47 @@ import { KeyListener, MenuItem, Selectable, - SelectAction, SIssueMarker, SIssueSeverity, SModelElement, - SModelRoot, - TYPES + SModelRoot } from 'sprotty'; import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { collectIssueMarkers, MarkerPredicates } from '../../utils/marker'; import { isSelectableAndBoundsAware } from '../../utils/smodel-util'; import { SelectCommand, SelectionService } from '../select/selection-service'; -export class NavigateToMarkerAction implements Action { - static readonly KIND = 'navigateToMarker'; +export interface NavigateToMarkerAction extends Action { + kind: typeof NavigateToMarkerAction.KIND; - constructor( - public readonly direction: 'next' | 'previous' = 'next', - public readonly selectedElementIds?: string[], - public readonly severities: SIssueSeverity[] = MarkerNavigator.ALL_SEVERITIES, - public kind = NavigateToMarkerAction.KIND - ) {} + direction: MarkerNavigationDirection; + selectedElementIds?: string[]; + severities: SIssueSeverity[]; } +export type MarkerNavigationDirection = 'next' | 'previous'; + +export namespace NavigateToMarkerAction { + export const KIND = 'navigateToMarker'; + + export function is(object: any): object is NavigateToMarkerAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'direction') && hasArrayProp(object, 'severities'); + } + + export function create(options: { + direction?: MarkerNavigationDirection; + selectedElementIds?: string[]; + severities?: SIssueSeverity[]; + }): NavigateToMarkerAction { + return { + kind: KIND, + direction: 'next', + severities: MarkerNavigator.ALL_SEVERITIES, + ...options + }; + } +} export class SModelElementComparator { compare(_one: SModelElement, _other: SModelElement): number { return 0; @@ -146,7 +163,7 @@ export class NavigateToMarkerCommand extends Command { @inject(MarkerNavigator) protected markerNavigator: MarkerNavigator; - @inject(GLSP_TYPES.SelectionService) + @inject(TYPES.SelectionService) protected selectionService: SelectionService; protected selectCommand: SelectCommand; @@ -167,8 +184,11 @@ export class NavigateToMarkerCommand extends Command { const selectableTarget = findParentByFeature(target, isSelectable); if (selectableTarget) { const deselect = selected.map(e => e.id).filter(id => id !== selectableTarget.id); - this.selectCommand = new SelectCommand(new SelectAction([selectableTarget.id], deselect), this.selectionService); - this.centerCommand = new CenterCommand(new CenterAction([selectableTarget.id])); + this.selectCommand = new SelectCommand( + SelectAction.create({ selectedElementsIDs: [selectableTarget.id], deselectedElementsIDs: deselect }), + this.selectionService + ); + this.centerCommand = new CenterCommand(CenterAction.create([selectableTarget.id])); this.centerCommand.execute(context); return this.selectCommand.execute(context); } @@ -218,7 +238,7 @@ export class NavigateToMarkerCommand extends Command { @injectable() export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemProvider { - @inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService; + @inject(TYPES.SelectionService) protected selectionService: SelectionService; getItems(root: Readonly, lastMousePosition?: Point): Promise { const selectedElementIds = Array.from(this.selectionService.getSelectedElementIDs()); @@ -234,14 +254,14 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP id: 'next-marker', label: 'Next marker', group: 'marker', - actions: [new NavigateToMarkerAction('next', selectedElementIds)], + actions: [NavigateToMarkerAction.create({ direction: 'next', selectedElementIds })], isEnabled: () => hasMarkers }, { id: 'previous-marker', label: 'Previous marker', group: 'marker', - actions: [new NavigateToMarkerAction('previous', selectedElementIds)], + actions: [NavigateToMarkerAction.create({ direction: 'previous', selectedElementIds })], isEnabled: () => hasMarkers } ] @@ -254,9 +274,9 @@ export class MarkerNavigatorContextMenuItemProvider implements IContextMenuItemP export class MarkerNavigatorKeyListener extends KeyListener { override keyDown(_element: SModelElement, event: KeyboardEvent): Action[] { if (matchesKeystroke(event, 'Period', 'ctrl')) { - return [new NavigateToMarkerAction('next')]; + return [NavigateToMarkerAction.create({ direction: 'next' })]; } else if (matchesKeystroke(event, 'Comma', 'ctrl')) { - return [new NavigateToMarkerAction('previous')]; + return [NavigateToMarkerAction.create({ direction: 'previous' })]; } return []; } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 1cd849d..2167644 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, DeleteMarkersAction, Marker, SetMarkersAction } from '@eclipse-glsp/protocol'; +import { Action, DeleteMarkersAction, hasArrayProp, Marker, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { Command, @@ -24,11 +24,10 @@ import { SIssueMarker, SModelElement, SModelRoot, - SParentElement, - TYPES + SParentElement } from 'sprotty'; import { EditorContextService } from '../../base/editor-context-service'; -import { GLSP_TYPES } from '../../base/types'; +import { TYPES } from '../../base/types'; import { removeCssClasses } from '../../utils/smodel-util'; import { getSeverity } from '../hover/hover'; import { IFeedbackActionDispatcher, IFeedbackEmitter } from '../tool-feedback/feedback-action-dispatcher'; @@ -41,11 +40,11 @@ import { createSIssue, getOrCreateSIssueMarker, getSIssueMarker } from './issue- */ @injectable() export class ValidationFeedbackEmitter implements IFeedbackEmitter { - @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; + @inject(TYPES.IFeedbackActionDispatcher) protected feedbackActionDispatcher: IFeedbackActionDispatcher; @inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise; - private registeredAction: MarkersAction; + private registeredAction: ApplyMarkersAction; // eslint-disable-next-line @typescript-eslint/no-empty-function private constructor() {} @@ -54,13 +53,13 @@ export class ValidationFeedbackEmitter implements IFeedbackEmitter { * Register the action that should be emitted for visualizing validation feedback. * @param action the action that should be emitted when the model is updated and that will visualize the model validation feedback. */ - registerValidationFeedbackAction(action: MarkersAction): void { + registerValidationFeedbackAction(action: ApplyMarkersAction): void { // De-register old action responsible for applying markers and re-applying them when the model is updated this.feedbackActionDispatcher.deregisterFeedback(this, []); // Clear existing markers if (this.registeredAction !== undefined) { - const deleteMarkersAction = new DeleteMarkersAction(this.registeredAction.markers); + const deleteMarkersAction = DeleteMarkersAction.create(this.registeredAction.markers); this.actionDispatcher().then(dispatcher => dispatcher.dispatch(deleteMarkersAction)); } @@ -88,7 +87,7 @@ export abstract class ExternalMarkerManager { removeMarkers(markers: Marker[]): void { if (this.actionDispatcher) { - this.actionDispatcher.dispatch(new DeleteMarkersAction(markers)); + this.actionDispatcher.dispatch(DeleteMarkersAction.create(markers)); } } @@ -116,7 +115,7 @@ export class SetMarkersCommand extends Command { if (this.externalMarkerManager) { this.externalMarkerManager.setMarkers(markers, uri); } - const applyMarkersAction: ApplyMarkersAction = new ApplyMarkersAction(markers); + const applyMarkersAction = ApplyMarkersAction.create(markers); this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); return context.root; } @@ -131,18 +130,26 @@ export class SetMarkersCommand extends Command { } /** - * Interface for actions processing markers + * Action for applying makers to a model */ -export interface MarkersAction extends Action { - readonly markers: Marker[]; +export interface ApplyMarkersAction extends Action { + kind: typeof ApplyMarkersAction.KIND; + markers: Marker[]; } -/** - * Action for applying makers to a model - */ -@injectable() -export class ApplyMarkersAction implements MarkersAction { - constructor(public readonly markers: Marker[], public readonly kind = ApplyMarkersCommand.KIND) {} +export namespace ApplyMarkersAction { + export const KIND = 'applyMarkers'; + + export function is(object: any): object is ApplyMarkersAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); + } + + export function create(markers: Marker[]): ApplyMarkersAction { + return { + kind: KIND, + markers + }; + } } /** @@ -150,7 +157,7 @@ export class ApplyMarkersAction implements MarkersAction { */ @injectable() export class ApplyMarkersCommand extends FeedbackCommand { - static KIND = 'applyMarkers'; + static KIND = ApplyMarkersAction.KIND; override readonly priority = 0; constructor(@inject(TYPES.Action) protected action: ApplyMarkersAction) { diff --git a/packages/client/src/features/viewport/di.config.ts b/packages/client/src/features/viewport/di.config.ts index 278834c..64871c5 100644 --- a/packages/client/src/features/viewport/di.config.ts +++ b/packages/client/src/features/viewport/di.config.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,7 +27,6 @@ import { TYPES, ZoomMouseListener } from 'sprotty'; - import { GLSPScrollMouseListener } from './glsp-scroll-mouse-listener'; const glspViewportModule = new ContainerModule((bind, _unbind, isBound) => { diff --git a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts index ec5cd87..2bf2032 100644 --- a/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts +++ b/packages/client/src/features/viewport/glsp-scroll-mouse-listener.ts @@ -23,11 +23,11 @@ export class GLSPScrollMouseListener extends ScrollMouseListener implements IAct preventScrolling = false; handle(action: Action): void | Action | ICommand { - if (action instanceof EnableToolsAction) { - if (action.toolIds.includes(MarqueeMouseTool.ID)) { + if (action.kind === EnableToolsAction.KIND) { + if ((action as EnableToolsAction).toolIds.includes(MarqueeMouseTool.ID)) { this.preventScrolling = true; } - } else if (action instanceof EnableDefaultToolsAction) { + } else if (action.kind === EnableDefaultToolsAction.KIND) { this.preventScrolling = false; } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index fe68816..75de6bb 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,7 +33,6 @@ import toolsModule from './features/tools/di.config'; import { markerNavigatorModule, validationModule } from './features/validation/di.config'; import glspViewportModule from './features/viewport/di.config'; -export * from 'sprotty'; export * from './base/action-dispatcher'; export * from './base/actions/focus-change-action'; export * from './base/argumentable'; @@ -51,6 +50,7 @@ export * from './base/model/model-registry'; export * from './base/model/update-model-command'; export * from './base/selection-clearing-mouse-listener'; export * from './base/source-uri-aware'; +export * from './base/tool-manager/tool-actions'; export * from './base/types'; export * from './base/view/view-registry'; export * from './features/change-bounds/model'; @@ -80,22 +80,21 @@ export * from './features/tool-feedback/css-feedback'; export * from './features/tool-feedback/edge-edit-tool-feedback'; export * from './features/tool-feedback/feedback-action-dispatcher'; export * from './features/tool-feedback/model'; -export * from './features/tool-palette/palette-item'; export * from './features/tool-palette/tool-palette'; export * from './features/tools/base-glsp-tool'; export * from './features/tools/change-bounds-tool'; export * from './features/tools/delete-tool'; export * from './features/tools/edge-creation-tool'; export * from './features/tools/edge-edit-tool'; -export * from './features/tools/node-creation-tool'; export * from './features/tools/marquee-behavior'; +export * from './features/tools/node-creation-tool'; export * from './features/validation/issue-marker'; export * from './features/validation/marker-navigator'; export * from './features/validation/validate'; export * from './features/viewport/glsp-scroll-mouse-listener'; export * from './lib/model'; export * from './model-source/glsp-diagram-server'; -export * from './protocol-reexport'; +export * from './sprotty-reexport'; export * from './utils/argument-utils'; export * from './utils/marker'; export * from './utils/smodel-util'; diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index bae7c17..b40f148 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -13,7 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { exportFeature, SGraph, SModelElement, SModelElementSchema, viewportFeature } from 'sprotty/lib'; +import { SModelElementSchema } from '@eclipse-glsp/protocol'; +import { exportFeature, SGraph, SModelElement, viewportFeature } from 'sprotty/lib'; import { Containable, containerFeature } from '../features/hints/model'; import { Saveable, saveFeature } from '../features/save/model'; diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index 121e9c6..d2b3d74 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -15,23 +15,21 @@ ********************************************************************************/ import { Action, + ActionMessage, ComputedBoundsAction, - ExportSvgAction, GLSPClient, - isRequestModelAction, - isServerMessageAction, - isSetEditModeAction, + RequestModelAction, ServerMessageAction, SetEditModeAction } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; -import { ActionHandlerRegistry, ActionMessage, DiagramServer, ICommand, ServerStatusAction, SwitchEditModeCommand } from 'sprotty'; +import { ActionHandlerRegistry, DiagramServerProxy, ExportSvgAction, ICommand, ServerStatusAction, SwitchEditModeCommand } from 'sprotty'; import { SourceUriAware } from '../base/source-uri-aware'; const receivedFromServerProperty = '__receivedFromServer'; @injectable() -export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { +export class GLSPDiagramServer extends DiagramServerProxy implements SourceUriAware { protected _sourceUri: string; protected _glspClient?: GLSPClient; protected ready = false; @@ -63,17 +61,17 @@ export class GLSPDiagramServer extends DiagramServer implements SourceUriAware { } override handle(action: Action): void | ICommand | Action { - if (isRequestModelAction(action) && action.options) { + if (RequestModelAction.is(action) && action.options) { this._sourceUri = action.options.sourceUri as string; } return super.handle(action); } override handleLocally(action: Action): boolean { - if (isServerMessageAction(action)) { + if (ServerMessageAction.is(action)) { return this.handleServerMessageAction(action); } - if (isSetEditModeAction(action)) { + if (SetEditModeAction.is(action)) { return this.handleSetEditModeAction(action); } return super.handleLocally(action); @@ -101,7 +99,7 @@ export function isReceivedFromServer(action: Action): boolean { return (action as any)[receivedFromServerProperty] === true; } -export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer): void { +export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServerProxy): void { registry.register(ServerMessageAction.KIND, diagramServer); registry.register(ServerStatusAction.KIND, diagramServer); registry.register(ExportSvgAction.KIND, diagramServer); diff --git a/packages/client/src/protocol-reexport.ts b/packages/client/src/protocol-reexport.ts deleted file mode 100644 index e0d1a31..0000000 --- a/packages/client/src/protocol-reexport.ts +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -/** - * Workaround for now to solve ambiguous exports in glsp-client. This module provides all glsp-only components. - * i.e. typings that are not defined in sprotty - */ - -export { - CompoundOperation, - isActionKind, - isCompoundOperation, - isRejectAction, - Operation -} from '@eclipse-glsp/protocol/lib/action-protocol/base-protocol'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/client-notification'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/clipboard'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/contexts'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/edge-modification'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/element-creation'; -export { isRequestPopupModelAction, isSetPopupModelAction } from '@eclipse-glsp/protocol/lib/action-protocol/element-hover'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/element-navigation'; -export { isSelectAction, isSelectAllAction } from '@eclipse-glsp/protocol/lib/action-protocol/element-selection'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/element-text-editing'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/element-type-hints'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/element-validation'; -export { LayoutOperation } from '@eclipse-glsp/protocol/lib/action-protocol/mode-layout'; -export { - isModelSourceChangedAction, - isRequestModelAction, - isSetModelAction, - ModelSourceChangedAction -} from '@eclipse-glsp/protocol/lib/action-protocol/model-data'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/model-edit-mode'; -export { - DirtyStateChangeReason, - isExportSvgAction, - isSaveModelAction, - isSetDirtyStateAction, - SaveModelAction, - SetDirtyStateAction -} from '@eclipse-glsp/protocol/lib/action-protocol/model-saving'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/node-modification'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/tool-palette'; -export { Args, EditorContext, ElementAndRoutingPoints } from '@eclipse-glsp/protocol/lib/action-protocol/types'; -export * from '@eclipse-glsp/protocol/lib/action-protocol/undo-redo'; -export * from '@eclipse-glsp/protocol/lib/glsp-client'; -export * from '@eclipse-glsp/protocol/lib/jsonrpc/base-jsonrpc-glsp-client'; -export * from '@eclipse-glsp/protocol/lib/jsonrpc/glsp-jsonrpc-client'; -export * from '@eclipse-glsp/protocol/lib/utils/array-util'; -export * from '@eclipse-glsp/protocol/lib/utils/launch-util'; diff --git a/packages/client/src/sprotty-reexport.ts b/packages/client/src/sprotty-reexport.ts new file mode 100644 index 0000000..e5f740b --- /dev/null +++ b/packages/client/src/sprotty-reexport.ts @@ -0,0 +1,283 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/** + * Sprotty still keeps the old class based action definitions as deprecated API. + * This causes name clashes with the GLSP protocol actions when reexport sprotty. + * The deprecated definitions are planned to be removed with Sprotty 1.0.0. + * Until then we have to manually reexport the sprotty main index and exclude the clashing types. + */ + +export * from '@eclipse-glsp/protocol'; +// sprotty-protocol +export { CollapseExpandAction, CollapseExpandAllAction, ElementAndAlignment, SetBoundsAction } from 'sprotty-protocol/lib/actions'; +export { Viewport } from 'sprotty-protocol/lib/model'; +// ------------------ Base ------------------ +export * from 'sprotty/lib/base/actions/action-dispatcher'; +export * from 'sprotty/lib/base/actions/action-handler'; +export * from 'sprotty/lib/base/actions/diagram-locker'; +export * from 'sprotty/lib/base/animations/animation'; +export * from 'sprotty/lib/base/animations/animation-frame-syncer'; +export * from 'sprotty/lib/base/animations/easing'; +export * from 'sprotty/lib/base/commands/command'; +export * from 'sprotty/lib/base/commands/command-registration'; +export * from 'sprotty/lib/base/commands/command-stack'; +export * from 'sprotty/lib/base/commands/command-stack-options'; +export * from 'sprotty/lib/base/features/initialize-canvas'; +export { SetModelCommand } from 'sprotty/lib/base/features/set-model'; +export { + createRandomId, + FeatureSet, + IModelIndex, + isParent, + ModelIndexImpl, + SChildElement, + SModelElement, + SModelRoot, + SParentElement +} from 'sprotty/lib/base/model/smodel'; +export * from 'sprotty/lib/base/model/smodel-extension'; +export * from 'sprotty/lib/base/model/smodel-factory'; +export { + containsSome, + findParent, + findParentByFeature, + registerModelElement, + translateBounds, + translatePoint +} from 'sprotty/lib/base/model/smodel-utils'; +export * from 'sprotty/lib/base/tool-manager/tool'; +export * from 'sprotty/lib/base/tool-manager/tool-manager'; +export * from 'sprotty/lib/base/ui-extensions/ui-extension'; +export * from 'sprotty/lib/base/ui-extensions/ui-extension-registry'; +export * from 'sprotty/lib/base/views/key-tool'; +export * from 'sprotty/lib/base/views/mouse-tool'; +export * from 'sprotty/lib/base/views/thunk-view'; +export * from 'sprotty/lib/base/views/view'; +export * from 'sprotty/lib/base/views/viewer'; +export * from 'sprotty/lib/base/views/viewer-cache'; +export * from 'sprotty/lib/base/views/viewer-options'; +export * from 'sprotty/lib/base/views/vnode-postprocessor'; +export * from 'sprotty/lib/base/views/vnode-utils'; +// ------------------ Features ------------------ +export { + RequestBoundsCommand, + ResolvedElementAndAlignment, + ResolvedElementAndBounds, + SetBoundsCommand +} from 'sprotty/lib/features/bounds/bounds-manipulation'; +export * from 'sprotty/lib/features/bounds/hbox-layout'; +export * from 'sprotty/lib/features/bounds/hidden-bounds-updater'; +export * from 'sprotty/lib/features/bounds/layout'; +export { + Alignable, + alignFeature, + BoundsAware, + boundsFeature, + findChildrenAtPosition, + getAbsoluteBounds, + getAbsoluteClientBounds, + isAlignable, + isBoundsAware, + isLayoutableChild, + isLayoutContainer, + isSizeable, + LayoutableChild, + layoutableChildFeature, + LayoutContainer, + layoutContainerFeature, + ModelLayoutOptions, + SShapeElement +} from 'sprotty/lib/features/bounds/model'; +export * from 'sprotty/lib/features/bounds/stack-layout'; +export * from 'sprotty/lib/features/bounds/vbox-layout'; +export * from 'sprotty/lib/features/bounds/views'; +export * from 'sprotty/lib/features/button/button-handler'; +export * from 'sprotty/lib/features/button/model'; +export * from 'sprotty/lib/features/command-palette/action-providers'; +export * from 'sprotty/lib/features/command-palette/command-palette'; +export { Anchor, IContextMenuService, IContextMenuServiceProvider } from 'sprotty/lib/features/context-menu/context-menu-service'; +export * from 'sprotty/lib/features/context-menu/menu-providers'; +export * from 'sprotty/lib/features/context-menu/mouse-listener'; +export * from 'sprotty/lib/features/decoration/decoration-placer'; +export * from 'sprotty/lib/features/decoration/model'; +export * from 'sprotty/lib/features/decoration/views'; +export * from 'sprotty/lib/features/edge-intersection/intersection-finder'; +export * from 'sprotty/lib/features/edge-intersection/sweepline'; +export * from 'sprotty/lib/features/edge-layout/di.config'; +export * from 'sprotty/lib/features/edge-layout/edge-layout'; +export * from 'sprotty/lib/features/edge-layout/model'; +export * from 'sprotty/lib/features/edit/create'; +export * from 'sprotty/lib/features/edit/create-on-drag'; +export * from 'sprotty/lib/features/edit/delete'; +export * from 'sprotty/lib/features/edit/di.config'; +export * from 'sprotty/lib/features/edit/edit-label'; +export * from 'sprotty/lib/features/edit/edit-label-ui'; +export * from 'sprotty/lib/features/edit/edit-routing'; +export * from 'sprotty/lib/features/edit/model'; +export * from 'sprotty/lib/features/edit/reconnect'; +export * from 'sprotty/lib/features/expand/expand'; +export * from 'sprotty/lib/features/expand/model'; +export * from 'sprotty/lib/features/expand/views'; +export { ExportSvgCommand, ExportSvgKeyListener, ExportSvgPostprocessor, RequestExportSvgAction } from 'sprotty/lib/features/export/export'; +export * from 'sprotty/lib/features/export/model'; +export { SvgExporter } from 'sprotty/lib/features/export/svg-exporter'; +export * from 'sprotty/lib/features/fade/fade'; +export * from 'sprotty/lib/features/fade/model'; +export { + AbstractHoverMouseListener, + ClosePopupActionHandler, + HoverFeedbackAction, + HoverFeedbackCommand, + HoverKeyListener, + HoverMouseListener, + HoverState, + PopupHoverMouseListener, + SetPopupModelCommand +} from 'sprotty/lib/features/hover/hover'; +export * from 'sprotty/lib/features/hover/model'; +export * from 'sprotty/lib/features/move/model'; +export * from 'sprotty/lib/features/move/move'; +export * from 'sprotty/lib/features/move/snap'; +export * from 'sprotty/lib/features/nameable/model'; +export * from 'sprotty/lib/features/open/model'; +export * from 'sprotty/lib/features/open/open'; +export * from 'sprotty/lib/features/projection/model'; +export * from 'sprotty/lib/features/projection/views'; +export * from 'sprotty/lib/features/routing/abstract-edge-router'; +export * from 'sprotty/lib/features/routing/anchor'; +export * from 'sprotty/lib/features/routing/bezier-anchors'; +export * from 'sprotty/lib/features/routing/bezier-edge-router'; +export * from 'sprotty/lib/features/routing/manhattan-anchors'; +export * from 'sprotty/lib/features/routing/manhattan-edge-router'; +export * from 'sprotty/lib/features/routing/model'; +export * from 'sprotty/lib/features/routing/polyline-anchors'; +export * from 'sprotty/lib/features/routing/polyline-edge-router'; +export * from 'sprotty/lib/features/routing/routing'; +export * from 'sprotty/lib/features/routing/views'; +export * from 'sprotty/lib/features/select/model'; +export { + GetSelectionAction, + GetSelectionCommand, + SelectAllCommand, + SelectCommand, + SelectionResult, + SelectKeyboardListener, + SelectMouseListener +} from 'sprotty/lib/features/select/select'; +export * from 'sprotty/lib/features/undo-redo/undo-redo'; +export * from 'sprotty/lib/features/update/model-matching'; +export { UpdateAnimationData, UpdateModelCommand } from 'sprotty/lib/features/update/update-model'; +export { + BoundsAwareViewportCommand, + CenterCommand, + CenterKeyboardListener, + FitToScreenCommand +} from 'sprotty/lib/features/viewport/center-fit'; +export * from 'sprotty/lib/features/viewport/model'; +export * from 'sprotty/lib/features/viewport/scroll'; +export { + GetViewportAction, + GetViewportCommand, + SetViewportCommand, + ViewportAnimation, + ViewportResult +} from 'sprotty/lib/features/viewport/viewport'; +export * from 'sprotty/lib/features/viewport/viewport-root'; +export * from 'sprotty/lib/features/viewport/zoom'; +export * from 'sprotty/lib/features/zorder/zorder'; +export { SCompartment, SEdge, SGraph, SGraphIndex, SLabel, SNode, SPort } from 'sprotty/lib/graph/sgraph'; +// ------------------ Graph ------------------ +export * from 'sprotty/lib/graph/sgraph-factory'; +export * from 'sprotty/lib/graph/views'; +export * from 'sprotty/lib/lib/generic-views'; +export * from 'sprotty/lib/lib/html-views'; +export * from 'sprotty/lib/lib/jsx'; +export { + CircularNode, + CircularPort, + DiamondNode, + ForeignObjectElement, + HtmlRoot, + PreRenderedElement, + RectangularNode, + RectangularPort, + ShapedPreRenderedElement +} from 'sprotty/lib/lib/model'; +// ------------------ Library ------------------ +export * from 'sprotty/lib/lib/modules'; +export * from 'sprotty/lib/lib/svg-views'; +// ------------------ Model Source ------------------ +export * from 'sprotty/lib/model-source/commit-model'; +export { DiagramServerProxy } from 'sprotty/lib/model-source/diagram-server'; +export * from 'sprotty/lib/model-source/local-model-source'; +export * from 'sprotty/lib/model-source/logging'; +export * from 'sprotty/lib/model-source/model-source'; +export * from 'sprotty/lib/model-source/websocket'; +// ------------------ Utilities ------------------ +export * from 'sprotty/lib/utils/browser'; +export * from 'sprotty/lib/utils/codicon'; +export * from 'sprotty/lib/utils/color'; +export { Diamond, Insets, intersection, Line, Orientation, PointToPointLine } from 'sprotty/lib/utils/geometry'; +export * from 'sprotty/lib/utils/inversify'; +export * from 'sprotty/lib/utils/logging'; +export * from 'sprotty/lib/utils/registry'; +export { defaultModule }; +export { + graphModule, + boundsModule, + buttonModule, + commandPaletteModule, + contextMenuModule, + decorationModule, + edgeIntersectionModule, + edgeLayoutModule, + expandModule, + exportModule, + fadeModule, + hoverModule, + moveModule, + openModule, + routingModule, + selectModule, + undoRedoModule, + updateModule, + viewportModule, + zorderModule +}; +export { modelSourceModule }; + +import defaultModule from 'sprotty/lib/base/di.config'; +import boundsModule from 'sprotty/lib/features/bounds/di.config'; +import buttonModule from 'sprotty/lib/features/button/di.config'; +import commandPaletteModule from 'sprotty/lib/features/command-palette/di.config'; +import contextMenuModule from 'sprotty/lib/features/context-menu/di.config'; +import decorationModule from 'sprotty/lib/features/decoration/di.config'; +import edgeIntersectionModule from 'sprotty/lib/features/edge-intersection/di.config'; +import edgeLayoutModule from 'sprotty/lib/features/edge-layout/di.config'; +import expandModule from 'sprotty/lib/features/expand/di.config'; +import exportModule from 'sprotty/lib/features/export/di.config'; +import fadeModule from 'sprotty/lib/features/fade/di.config'; +import hoverModule from 'sprotty/lib/features/hover/di.config'; +import moveModule from 'sprotty/lib/features/move/di.config'; +import openModule from 'sprotty/lib/features/open/di.config'; +import routingModule from 'sprotty/lib/features/routing/di.config'; +import selectModule from 'sprotty/lib/features/select/di.config'; +import undoRedoModule from 'sprotty/lib/features/undo-redo/di.config'; +import updateModule from 'sprotty/lib/features/update/di.config'; +import viewportModule from 'sprotty/lib/features/viewport/di.config'; +import zorderModule from 'sprotty/lib/features/zorder/di.config'; +import graphModule from 'sprotty/lib/graph/di.config'; +import modelSourceModule from 'sprotty/lib/model-source/di.config'; diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index bb8fb81..f83af08 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; -import { BoundsAware, Dimension, ElementAndBounds, ElementMove, ModelLayoutOptions, SModelElement } from 'sprotty'; +import { Dimension, ElementAndBounds, Point } from '@eclipse-glsp/protocol'; +import { BoundsAware, ElementMove, ModelLayoutOptions, SModelElement } from 'sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; export function minWidth(element: SModelElement & BoundsAware): number { diff --git a/packages/client/src/utils/smodel-util.ts b/packages/client/src/utils/smodel-util.ts index 9c70abd..853d6f8 100644 --- a/packages/client/src/utils/smodel-util.ts +++ b/packages/client/src/utils/smodel-util.ts @@ -13,18 +13,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { distinctAdd, ElementAndRoutingPoints, remove, TypeGuard } from '@eclipse-glsp/protocol'; +import { distinctAdd, ElementAndBounds, ElementAndRoutingPoints, remove, SModelElementSchema, TypeGuard } from '@eclipse-glsp/protocol'; import { BoundsAware, - ElementAndBounds, isBoundsAware, isMoveable, isSelectable, isSelected, + ModelIndexImpl, Selectable, SModelElement, - SModelElementSchema, - SModelIndex, SRoutableElement, SRoutingHandle } from 'sprotty'; @@ -32,31 +30,31 @@ import { FluentIterable } from 'sprotty/lib/utils/iterable'; /** * Helper type to represent a filter predicate for {@link SModelElement}s. This is used to retrieve - * elements from the {@link SModelIndex} that also conform to a second type `T`. Its mainly used for + * elements from the {@link ModelIndexImpl} that also conform to a second type `T`. Its mainly used for * retrieving elements that also implement a certain model features (e.g. selectable) */ export type ModelFilterPredicate = (modelElement: SModelElement) => modelElement is SModelElement & T; /** - * Retrieves all elements from the given {@link SModelIndex} that match the given {@link ModelFilterPredicate} - * @param index The {@link SModelIndex}. + * Retrieves all elements from the given {@link ModelIndexImpl} that match the given {@link ModelFilterPredicate} + * @param index The {@link ModelIndexImpl}. * @param predicate The {@link ModelFilterPredicate} that should be used. * @returns A {@link FluentIterable} of all indexed element that match the predicate * (correctly casted to also include the additional type of the predicate). */ -export function filter(index: SModelIndex, predicate: ModelFilterPredicate): FluentIterable { +export function filter(index: ModelIndexImpl, predicate: ModelFilterPredicate): FluentIterable { return index.all().filter(predicate) as FluentIterable; } /** - * Retrieves all elements from the given {@link SModelIndex} that match the given {@link ModelFilterPredicate} and executes + * Retrieves all elements from the given {@link ModelIndexImpl} that match the given {@link ModelFilterPredicate} and executes * the given runnable for each element of the result set. - * @param index The {@link SModelIndex}. + * @param index The {@link ModelIndexImpl}. * @param predicate The {@link ModelFilterPredicate} that should be used. * @param runnable The runnable that should be executed for each matching element. */ export function forEachElement( - index: SModelIndex, + index: ModelIndexImpl, predicate: ModelFilterPredicate, runnable: (modelElement: SModelElement & T) => void ): void { @@ -64,13 +62,13 @@ export function forEachElement( } /** - * Retrieves an array of all elements that match the given {@link ModelFilterPredicate} from the given {@link SModelIndex}. - * @param index The {@link SModelIndex}. + * Retrieves an array of all elements that match the given {@link ModelFilterPredicate} from the given {@link ModelIndexImpl}. + * @param index The {@link ModelIndexImpl}. * @param predicate The {@link ModelFilterPredicate} that should be used. * @returns An array of all indexed element that match the predicate * (correctly casted to also include the additional type of the predicate). */ -export function getMatchingElements(index: SModelIndex, predicate: ModelFilterPredicate): (SModelElement & T)[] { +export function getMatchingElements(index: ModelIndexImpl, predicate: ModelFilterPredicate): (SModelElement & T)[] { return Array.from(filter(index, predicate)); } @@ -82,7 +80,7 @@ export function getMatchingElements(index: SModelIndex, predic * @param guard Optional typeguard. If defined only elements that match the guard will be returned. * @returns An array of the model elements that correspond to the given ids and filter predicate. */ -export function getElements(index: SModelIndex, elementsIDs: string[], guard?: TypeGuard): S[] { +export function getElements(index: ModelIndexImpl, elementsIDs: string[], guard?: TypeGuard): S[] { // Internal filter function that filters out undefined model elements and runs an optional typeguard check. const filterFn = (element?: SModelElement): element is S => { if (element !== undefined) { @@ -93,22 +91,22 @@ export function getElements(index: SModelIndex index.getById(id)).filter(filterFn); } /** - * Retrieves the amount of currently selected elements in the given {@link SModelIndex}. - * @param index The {@link SModelIndex}. + * Retrieves the amount of currently selected elements in the given {@link ModelIndexImpl}. + * @param index The {@link ModelIndexImpl}. * @returns The amount of selected elements. */ -export function getSelectedElementCount(index: SModelIndex): number { +export function getSelectedElementCount(index: ModelIndexImpl): number { let selected = 0; forEachElement(index, isSelected, element => selected++); return selected; } /** - * Helper function to check wether an any element is selected in the given {@link SModelIndex}. - * @param index The {@link SModelIndex}. + * Helper function to check wether an any element is selected in the given {@link ModelIndexImpl}. + * @param index The {@link ModelIndexImpl}. * @returns `true` if at least one element is selected, `false` otherwise. */ -export function hasSelectedElements(index: SModelIndex): boolean { +export function hasSelectedElements(index: ModelIndexImpl): boolean { return getSelectedElementCount(index) > 0; } diff --git a/packages/client/src/utils/viewpoint-util.ts b/packages/client/src/utils/viewpoint-util.ts index f9ba8df..75f16f5 100644 --- a/packages/client/src/utils/viewpoint-util.ts +++ b/packages/client/src/utils/viewpoint-util.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,18 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Bounds, Point } from '@eclipse-glsp/protocol'; -import { - BoundsAware, - Dimension, - findParentByFeature, - isAlignable, - isViewport, - ORIGIN_POINT, - SModelElement, - translateBounds, - Viewport -} from 'sprotty'; + +import { Bounds, Dimension, Point, Viewport } from '@eclipse-glsp/protocol'; +import { BoundsAware, findParentByFeature, isAlignable, isViewport, SModelElement, translateBounds } from 'sprotty'; /** * Return the position corresponding to this mouse event (Browser coordinates) @@ -70,7 +61,7 @@ export function getAbsolutePosition(target: SModelElement, mouseEvent: MouseEven * @param target A bounds-aware element from the diagram */ export function toAbsoluteBounds(element: SModelElement & BoundsAware): Bounds { - const location = isAlignable(element) ? element.alignment : ORIGIN_POINT; + const location = isAlignable(element) ? element.alignment : Point.ORIGIN; const x = location.x; const y = location.y; const width = element.bounds.width; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 3fed2b4..6e56290 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -6,5 +6,6 @@ "reactNamespace": "JSX", "baseUrl": "." }, + "include": ["src"] } diff --git a/packages/protocol/.eslintrc.js b/packages/protocol/.eslintrc.js index 727bf62..c649a32 100644 --- a/packages/protocol/.eslintrc.js +++ b/packages/protocol/.eslintrc.js @@ -4,5 +4,29 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, project: 'tsconfig.json' + }, + rules: { + 'no-restricted-imports': [ + 'warn', + { + name: 'sprotty', + message: "The protocol package should not have any direct 'sprotty' dependencies. Try to use 'sprotty-protocol' instead" + }, + { + name: 'sprotty/*', + message: "The protocol package should not have any direct 'sprotty' dependencies. Try to use 'sprotty-protocol' instead" + }, + 'error', + { + name: '@eclipse-glsp/client', + message: + "Circular dependency! This package is consumed by '@eclipse-glsp' client. Consider moving the conflicting file into this package." + }, + { + name: '@eclipse-glsp/client/*', + message: + "Circular dependency! This package is consumed by '@eclipse-glsp' client. Consider moving the conflicting file into this package." + } + ] } }; diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 9a9a2df..5b07e91 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -33,9 +33,9 @@ "src" ], "dependencies": { - "inversify": "^5.1.1", "uuid": "7.0.3", - "vscode-ws-jsonrpc": "0.2.0" + "vscode-ws-jsonrpc": "0.2.0", + "sprotty-protocol": "next" }, "devDependencies": { "@types/uuid": "3.4.5" @@ -45,6 +45,8 @@ "clean": "rimraf lib tsconfig.tsbuildinfo ", "build": "tsc", "lint": "eslint -c ./.eslintrc.js --ext .ts ./src", + "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", + "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", "watch": "tsc -w" }, "publishConfig": { diff --git a/packages/protocol/src/action-protocol/base-protocol.spec.ts b/packages/protocol/src/action-protocol/base-protocol.spec.ts new file mode 100644 index 0000000..782bb69 --- /dev/null +++ b/packages/protocol/src/action-protocol/base-protocol.spec.ts @@ -0,0 +1,236 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Action, ActionMessage, CompoundOperation, Operation, RejectAction, RequestAction, ResponseAction } from './base-protocol'; + +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Base Protocol Actions', () => { + const customAction: SomeCustomAction = { kind: 'custom' }; + + describe('Action', () => { + describe('is', () => { + it('should return true for an object having a `kind` property with string type', () => { + const action = { kind: 'myAction' }; + expect(Action.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(Action.is(undefined)).to.be.false; + }); + it('should return false for an object having a `kind` property with incorrect type', () => { + const notAnAction = { kind: 5 }; + expect(Action.is(notAnAction)).to.be.false; + }); + }); + describe('hasKind', () => { + it('should return true for an object having a `kind` property that matches the given value', () => { + const action = { kind: 'myAction' }; + expect(Action.hasKind(action, 'myAction')).to.be.true; + }); + it('should return false for undefined', () => { + expect(Action.hasKind(undefined, '')).to.be.false; + }); + it('should return false for an object having a "kind" property that does not match the given value', () => { + const action = { kind: 'myAction' }; + expect(Action.hasKind(action, 'someOtherKind')).to.be.false; + }); + it('should return false for an object not having a `kind` property', () => { + expect(Action.hasKind({ I: 'm not an action' }, '')).to.be.false; + }); + }); + }); + + describe('ActionMessage', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const message: ActionMessage = { action: { kind: 'myAction' }, clientId: 'someId' }; + expect(ActionMessage.is(message)).to.be.true; + }); + it('should return false for undefined', () => { + expect(ActionMessage.is(undefined)).to.be.false; + }); + it('should return false for an object that does have all required interface properties', () => { + const notAnActionMessage = 'notAnActionMessage'; + expect(ActionMessage.is(notAnActionMessage)).to.be.false; + }); + it('should return true for an object that has all required interface properties an `action` property that passes the typeguard check', () => { + const message: ActionMessage = { action: customAction, clientId: 'someId' }; + expect(ActionMessage.is(message, isSomeCustomAction)).to.be.true; + }); + it('should return false for an object that has all required interface properties but does not have an `action` property that passes the typeguard check ', () => { + const message: ActionMessage = { action: { kind: 'myAction' }, clientId: 'someId' }; + expect(ActionMessage.is(message, isSomeCustomAction)).to.be.false; + }); + }); + }); + + const requestAction: SomeRequestAction = { kind: 'someRequest', requestId: '' }; + + describe('RequestAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(RequestAction.is(requestAction)).to.be.true; + }); + it('should return false for undefined', () => { + expect(RequestAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestAction.is(customAction)).to.be.false; + }); + }); + + describe('hasKind', () => { + it('should return true for an object having the correct type and a value for all required interface properties and a `kind` property that matches the given value', () => { + expect(RequestAction.hasKind(requestAction, 'someRequest')).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestAction.hasKind(undefined, '')).to.be.false; + }); + it('should return false for an object having the correct type and a value for all required interface properties but having a `kind` property that does not match the given value', () => { + expect(RequestAction.hasKind(requestAction, 'someOtherKind')).to.be.false; + }); + it('should return for an object not having the correct type and value for all required interface properties', () => { + expect(RequestAction.hasKind({ I: 'm not an action' }, '')).to.be.false; + }); + }); + }); + + const responseAction: SomeResponseAction = { kind: 'someResponse', responseId: '' }; + describe('ResponseAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(ResponseAction.is(responseAction)).to.be.true; + }); + it('should return false for undefined', () => { + expect(ResponseAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ResponseAction.is(customAction)).to.be.false; + }); + }); + + describe('hasValidResponseId', () => { + it('should return true for an object having the correct type and a value for all required interface properties and a non-empty value for `requestId`', () => { + const nonEmptyResponse = { ...responseAction, responseId: 'nonempty' }; + expect(ResponseAction.hasValidResponseId(nonEmptyResponse)).to.be.true; + }); + it('should return false for an object having the correct type and a value for all required interface properties and an empty value for `requestId`', () => { + expect(ResponseAction.hasValidResponseId(responseAction)).to.be.false; + }); + it('should return false for `undefined`', () => { + expect(ResponseAction.hasValidResponseId(undefined)).to.be.false; + }); + }); + }); + + const rejectAction: RejectAction = { kind: 'rejectRequest', message: '', responseId: '' }; + describe('RejectAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(RejectAction.is(rejectAction)).to.be.true; + }); + it('should return false for undefined', () => { + expect(RejectAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RejectAction.is(customAction)).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected = { kind: RejectAction.KIND, responseId: '', message: 'someMessage' }; + const { message } = expected; + expect(RejectAction.create(message)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected = { kind: RejectAction.KIND, responseId: 'someId', message: 'someMessage', detail: 'details' }; + const { detail, responseId, message } = expected; + expect(RejectAction.create(message, { detail, responseId })).to.deep.equals(expected); + }); + }); + }); + + const operation: Operation = { kind: 'someOperation', isOperation: true }; + describe('Operation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(Operation.is(operation)).to.be.true; + }); + it('should return false for undefined', () => { + expect(Operation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(Operation.is(customAction)).to.be.false; + }); + }); + + describe('hasKind', () => { + it('should return true for an object having the correct type and a value for all required interface properties and a `kind` property that matches the given value', () => { + expect(Operation.hasKind(operation, operation.kind)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(Operation.hasKind(undefined, '')).to.be.false; + }); + it('should return false for an object having the correct type and a value for all required interface properties but having a `kind` property that does not match the given value', () => { + expect(Operation.hasKind(operation, 'someOtherKind')).to.be.false; + }); + it('should return false for an object not having the correct type and value for all required interface properties', () => { + expect(Operation.hasKind({ I: 'm not an action' }, '')).to.be.false; + }); + }); + }); + + const compoundOperation: CompoundOperation = { kind: 'compound', isOperation: true, operationList: [] }; + describe('CompoundOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(CompoundOperation.is(compoundOperation)).to.be.true; + }); + it('should return false for undefined', () => { + expect(CompoundOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CompoundOperation.is(customAction)).to.be.false; + }); + }); + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const operationList = [operation]; + const expected = { kind: CompoundOperation.KIND, isOperation: true, operationList }; + expect(CompoundOperation.create(operationList)).to.deep.equals(expected); + }); + }); + }); +}); + +/** Collection of simple custom action types used for testing the action type guards */ + +interface SomeCustomAction extends Action { + kind: 'custom'; +} + +interface SomeRequestAction extends RequestAction { + kind: 'someRequest'; +} + +interface SomeResponseAction extends ResponseAction { + kind: 'someResponse'; +} +const isSomeCustomAction = (object: any): object is SomeCustomAction => object !== undefined && object.kind === 'custom'; diff --git a/packages/protocol/src/action-protocol/base-protocol.ts b/packages/protocol/src/action-protocol/base-protocol.ts index 2754d8e..b8ab642 100644 --- a/packages/protocol/src/action-protocol/base-protocol.ts +++ b/packages/protocol/src/action-protocol/base-protocol.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,114 +13,218 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import * as uuid from 'uuid'; -import { isArray, isString } from '../utils/typeguard-util'; -import { JsonAny } from './types'; +import { JsonPrimitive } from 'sprotty-protocol'; +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { AnyObject, hasArrayProp, hasStringProp, TypeGuard } from '../utils/type-util'; /** - * A general message serves as an envelope carrying an action to be transmitted between the client and the server via a DiagramServer. + * An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. + * It is a plain data structure, and as such transferable between server and client. An action must never contain actual + * SModelElement instances, but either refer to them via their ids or contain serializable schema for model elements. + * Additional typeguard functions are provided via the corresponding namespace. */ -export interface ActionMessage
{ - /** - * Used to identify a specific client session. - */ - clientId: string; +export interface Action extends sprotty.Action { /** - * The action to execute. + * Unique identifier specifying the kind of action to process. */ - action: A; + kind: string; } -export function isActionMessage(object: any): object is ActionMessage { - return object !== undefined && isString(object, 'clientId') && isAction(object['action']); -} - -export function isActionMessageOfType(object: any, guard: (action: any) => action is A): object is ActionMessage { - return isActionMessage(object) && guard(object.action); +export namespace Action { + export function is(object: any): object is Action { + return AnyObject.is(object) && hasStringProp(object, 'kind'); + } + /** + * Typeguard function to check wether the given object is an {@link Action} with the given `kind`. + * @param object The object to check. + * @param kind The expected action kind. + * @returns A type literal indicating wether the given object is an action with the given kind. + */ + export function hasKind(object: any, kind: string): object is Action { + return Action.is(object) && object.kind === kind; + } } /** - * An action is a declarative description of a behavior that shall be invoked by the receiver upon receipt of the action. - * It is a plain data structure, and as such transferable between server and client. An action must never contain actual - * SModelElement instances, but either refer to them via their ids or contain serializable schema for model elements. + * A general message serves as an envelope carrying an action to be transmitted between the client and the server via a DiagramServer. + * @typeParam A the {@link Action} type that is contained by this message. + * A typeguard function is provided via the corresponding namespace. */ -export interface Action { +export interface ActionMessage extends sprotty.ActionMessage { /** - * Unique identifier specifying the kind of action to process. - */ - readonly kind: string; -} + * The unique client id + * */ + clientId: string; -export function isAction(action: any): action is Action { - return action !== undefined && isString(action, 'kind'); + /** + * The action to execute. + */ + action: A; } -export function isActionKind(action: any, kind: string): action is Action { - return isAction(action) && action.kind === kind; +export namespace ActionMessage { + export function is(object: any, typeguard?: TypeGuard): object is ActionMessage { + const actionGuard = typeguard ?? Action.is; + return AnyObject.is(object) && hasStringProp(object, 'clientId') && actionGuard(object.action); + } } /** * A request action is tied to the expectation of receiving a corresponding response action. The requestId property is used to match the - * received response with the original request. + * received response with the original request. Typically its not necessary to create an explicit requestId. The requestId can be set + * to an empty string. The action dispatcher than auto generates an request id if needed (i.e. the action was dispatched as request). + * + + * A typeguard function, and a generic helper function to generate a request id are provided via the corresponding namespace. */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface RequestAction extends Action { +export interface RequestAction extends Action, sprotty.RequestAction { /** * Unique id for this request. In order to match a response to this request, the response needs to have the same id. */ - readonly requestId: string; + requestId: string; } -export function isRequestAction(action?: any): action is RequestAction { - return isAction(action) && isString(action, 'requestId'); -} +export namespace RequestAction { + export function is(object: any): object is RequestAction { + return Action.is(object) && hasStringProp(object, 'requestId'); + } -export function generateRequestId(): string { - return uuid.v4(); + /** + * Typeguard function to check wether the given object is an {@link RequestAction} with the given `kind`. + * @param object The object to check. + * @param kind The expected action kind. + * @returns A type literal indicating wether the given object is a request action with the given kind. + */ + export function hasKind(object: any, kind: string): object is Action { + return RequestAction.is(object) && object.kind === kind; + } + + export function generateRequestId(): string { + return sprotty.generateRequestId(); + } } + /** * A response action is sent to respond to a request action. The responseId must match the requestId of the preceding request. - * In case the responseId is empty or undefined, the action is handled as standalone, i.e. it was fired without a preceding request. + * In case the responseId is empty, the action is handled as standalone, i.e. it was fired without a preceding request. + * The action dispatcher of the GLSP server has a special handling for {@link RequestAction} handlers + * and automatically sets the `responseId` of the corresponding responseAction. So on the server side its typically enough + * to set the `responseId` to an empty string and rely on the `ActionDispatcher` for assigning the correct `responseId. + * Additional typeguard functions are provided via the corresponding namespace. */ -export interface ResponseAction extends Action { +export interface ResponseAction extends Action, sprotty.ResponseAction { /** * Id corresponding to the request this action responds to. */ - readonly responseId: string; + responseId: string; } -export function isResponseAction(action?: any): action is ResponseAction { - return isAction(action) && 'responseId' in action && typeof action['responseId'] === 'string' && action['responseId'] !== ''; +export namespace ResponseAction { + export function is(object: any): object is ResponseAction { + return Action.is(object) && hasStringProp(object, 'responseId'); + } + + /** + * Typeguard function to check wether the given object is an {@link ResponseAction} with a non-empty response id. + * @param object The object to check. + * @returns A type literal indicating wether the given object is a response action with a non-empty response id. + */ + export function hasValidResponseId(object: any): object is ResponseAction { + return ResponseAction.is(object) && object.responseId !== ''; + } } /** - * A reject action is a response fired to indicate that a request must be rejected. + * A reject action is a {@link ResponseAction} fired to indicate that a certain {@link ResponseAction} + * has been rejected. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RejectActions`. */ -export class RejectAction implements ResponseAction { - static readonly KIND = 'rejectRequest'; - readonly kind = RejectAction.KIND; - - constructor(public readonly message: string, public readonly responseId: string, public readonly detail?: JsonAny) {} +export interface RejectAction extends ResponseAction, sprotty.RejectAction { + kind: typeof RejectAction.KIND; + /** + * A human-readable description of the reject reason. Typically this is an error message + * that has been thrown when handling the corresponding {@link RequestAction}. + */ + message: string; + /** + * Optional additional details. + */ + detail?: JsonPrimitive; } -export function isRejectAction(action: any): action is RejectAction { - return isResponseAction(action) && action.kind === RejectAction.KIND && isString(action, 'message'); +export namespace RejectAction { + export const KIND = 'rejectRequest'; + + export function is(object: any): object is RejectAction { + return Action.hasKind(object, RejectAction.KIND) && hasStringProp(object, 'message'); + } + + export function create(message: string, options: { detail?: JsonPrimitive; responseId?: string } = {}): RejectAction { + return { + kind: KIND, + responseId: '', + message, + ...options + }; + } } /** * Operations are actions that denote requests from the client to _modify_ the model. Model modifications are always performed by the * server. After a successful modification, the server sends the updated model back to the client using the `UpdateModelAction`. + * An operation contains a dedicated `isOperation` property that is used as a discriminator. This is necessary so that the server + * can distinguish between plain actions and operations. + * The corresponding namespace offers a helper function for type guard checks. */ -export interface Operation extends Action {} +export interface Operation extends Action { + /** + * Discriminator property to make operations distinguishable from plain {@link Action}s. + */ + isOperation: true; +} + +export namespace Operation { + export function is(object: any): object is Operation { + return Action.is(object) && (object as any).isOperation === true; + } + + /** + * Typeguard function to check wether the given object is an {@link Operation} with the given `kind`. + * @param object The object to check. + * @param kind The expected operation kind. + * @returns A type literal indicating wether the given object is an operation with the given kind. + */ + export function hasKind(object: any, kind: string): object is Operation { + return Operation.is(object) && object.kind === kind; + } +} /** * An operation that executes a list of sub-operations. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CompoundOperations`. */ -export class CompoundOperation implements Operation { - static readonly KIND = 'compound'; - constructor(public operationList: Operation[], public readonly kind: string = CompoundOperation.KIND) {} +export interface CompoundOperation extends Operation { + kind: typeof CompoundOperation.KIND; + /** + * List of operations that should be executed. + */ + operationList: Operation[]; } -export function isCompoundOperation(operation?: any): operation is CompoundOperation { - return isActionKind(operation, CompoundOperation.KIND) && isArray(operation, 'operationList'); +export namespace CompoundOperation { + export const KIND = 'compound'; + + export function is(object: any): object is CompoundOperation { + return Operation.hasKind(object, KIND) && hasArrayProp(object, 'operationList'); + } + + export function create(operationList: Operation[]): CompoundOperation { + return { + kind: KIND, + isOperation: true, + operationList + }; + } } diff --git a/packages/protocol/src/action-protocol/client-notification.spec.ts b/packages/protocol/src/action-protocol/client-notification.spec.ts new file mode 100644 index 0000000..4ad2652 --- /dev/null +++ b/packages/protocol/src/action-protocol/client-notification.spec.ts @@ -0,0 +1,90 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { ServerMessageAction, ServerStatusAction } from './client-notification'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Client notification actions', () => { + describe('ServerStatusAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const statusAction: ServerStatusAction = { kind: 'serverStatus', message: 'Some', severity: 'INFO' }; + expect(ServerStatusAction.is(statusAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ServerStatusAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ServerStatusAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const message = 'someMessage'; + const expected: ServerStatusAction = { kind: ServerStatusAction.KIND, message, severity: 'INFO' }; + expect(ServerStatusAction.create(message)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ServerStatusAction = { + kind: ServerStatusAction.KIND, + message: 'someMessage', + severity: 'ERROR', + timeout: 5 + }; + const { message, severity, timeout } = expected; + expect(ServerStatusAction.create(message, { severity, timeout })).to.deep.equals(expected); + }); + }); + }); + + describe('ServerMessageAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const messageAction: ServerMessageAction = { kind: 'serverMessage', message: '', severity: 'INFO' }; + expect(ServerMessageAction.is(messageAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ServerMessageAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ServerMessageAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const message = 'someMessage'; + const expected: ServerMessageAction = { kind: ServerMessageAction.KIND, message, severity: 'INFO' }; + expect(ServerMessageAction.create(message)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ServerMessageAction = { + kind: ServerMessageAction.KIND, + message: 'someMessage', + details: 'details', + severity: 'ERROR', + timeout: 5 + }; + const { message, severity, timeout, details } = expected; + expect(ServerMessageAction.create(message, { severity, timeout, details })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/client-notification.ts b/packages/protocol/src/action-protocol/client-notification.ts index 4fd0e84..ce2c3b1 100644 --- a/packages/protocol/src/action-protocol/client-notification.ts +++ b/packages/protocol/src/action-protocol/client-notification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,53 +13,104 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isNumber, isString } from '../utils/typeguard-util'; -import { Action, isActionKind } from './base-protocol'; +import { hasStringProp } from '../utils/type-util'; +import { Action } from './base-protocol'; -export type ServerSeverity = 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL' | 'OK'; /** - * This action is typically sent by the server to signal a state change. This action extends the corresponding Sprotty action to include - * a timeout. If a timeout is given the respective status should disappear after the timeout is reached. + * Sent by the server to signal a state change. + * If a timeout is given the respective status should disappear after the timeout is reached. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ServerStatusActions`. */ -export class GLSPServerStatusAction implements Action { - static KIND = 'serverStatus'; - - constructor( - public severity: ServerSeverity, - public message: string, - public timeout = -1, - readonly kind = GLSPServerStatusAction.KIND - ) {} +export interface ServerStatusAction extends Action { + kind: typeof ServerStatusAction.KIND; + /** + * The severity of the status. + */ + severity: ServerSeverity; + + /** + * The user-facing message describing the status. + */ + message: string; + + /** + * Timeout after which a displayed status should disappear. + */ + timeout?: number; } -export function isGLSPServerStatusAction(action: any): action is GLSPServerStatusAction { - return ( - isActionKind(action, GLSPServerStatusAction.KIND) && - isString(action, 'severity') && - isString(action, 'message') && - isNumber(action, 'timeout') - ); +export namespace ServerStatusAction { + export const KIND = 'serverStatus'; + + export function is(object: any): object is ServerStatusAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'severity') && hasStringProp(object, 'message'); + } + + export function create(message: string, options: { severity?: ServerSeverity; timeout?: number } = {}): ServerStatusAction { + return { + kind: KIND, + severity: 'INFO', + message, + ...options + }; + } } + /** - * This action is typically sent by the server to notify the user about something of interest. + * The possible server status severity levels. */ -export class ServerMessageAction implements Action { - static KIND = 'serverMessage'; - - constructor( - public severity: ServerSeverity, - public message: string, - public details?: string, - public timeout = -1, - readonly kind = ServerMessageAction.KIND - ) {} + +export type ServerSeverity = 'NONE' | 'INFO' | 'WARNING' | 'ERROR' | 'FATAL' | 'OK'; + +/** + * Sent by the server to notify the user about something of interest. Typically this message is handled by + * the client by showing a message to the user with the application's message service. + * If a timeout is given the respective message should disappear after the timeout is reached. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ServerMessageActions`. + */ +export interface ServerMessageAction extends Action { + kind: typeof ServerMessageAction.KIND; + + severity: ServerSeverity; + + /** + * The message that shall be shown to the user. + */ + message: string; + + /** + * Further details on the message. + */ + details?: string; + + /** + * Timeout after which a displayed message disappears. + */ + timeout?: number; } -export function isServerMessageAction(action?: any): action is ServerMessageAction { - return ( - isActionKind(action, ServerMessageAction.KIND) && - isString(action, 'severity') && - isString(action, 'message') && - isNumber(action, 'timeout') - ); +export namespace ServerMessageAction { + export const KIND = 'serverMessage'; + + export function is(object: any): object is ServerMessageAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'message') && hasStringProp(object, 'severity'); + } + + export function create( + message: string, + options: { + severity?: ServerSeverity; + details?: string; + timeout?: number; + } = {} + ): ServerMessageAction { + return { + kind: KIND, + message, + severity: 'INFO', + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/clipboard.spec.ts b/packages/protocol/src/action-protocol/clipboard.spec.ts new file mode 100644 index 0000000..74c4c2c --- /dev/null +++ b/packages/protocol/src/action-protocol/clipboard.spec.ts @@ -0,0 +1,168 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { CutOperation, PasteOperation, RequestClipboardDataAction, SetClipboardDataAction } from './clipboard'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Clipboard actions', () => { + describe('RequestClipboardDataAction', () => { + const requestClipboardDataAction: RequestClipboardDataAction = { + kind: 'requestClipboardData', + requestId: '', + editorContext: { selectedElementIds: [] } + }; + + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + expect(RequestClipboardDataAction.is(requestClipboardDataAction)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestClipboardDataAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestClipboardDataAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestClipboardDataAction = { + kind: RequestClipboardDataAction.KIND, + editorContext: { selectedElementIds: ['element1'] }, + requestId: '' + }; + const { editorContext } = expected; + expect(RequestClipboardDataAction.create(editorContext)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestClipboardDataAction = { + kind: RequestClipboardDataAction.KIND, + editorContext: { selectedElementIds: ['element1'] }, + requestId: '100' + }; + const { editorContext, requestId } = expected; + expect(RequestClipboardDataAction.create(editorContext, { requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetClipboardDataAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetClipboardDataAction = { + clipboardData: { format: '' }, + kind: 'setClipboardData', + responseId: '' + }; + expect(SetClipboardDataAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetClipboardDataAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetClipboardDataAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetClipboardDataAction = { + clipboardData: { format: 'json' }, + kind: 'setClipboardData', + responseId: '' + }; + const { clipboardData } = expected; + expect(SetClipboardDataAction.create(clipboardData)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetClipboardDataAction = { + clipboardData: { format: 'json' }, + kind: 'setClipboardData', + responseId: '600' + }; + const { clipboardData, responseId } = expected; + expect(SetClipboardDataAction.create(clipboardData, { responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('CutOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: CutOperation = { + kind: 'cut', + isOperation: true, + editorContext: { selectedElementIds: [] } + }; + expect(CutOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CutOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CutOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: CutOperation = { + kind: 'cut', + isOperation: true, + editorContext: { selectedElementIds: ['element1'] } + }; + const { editorContext } = expected; + expect(CutOperation.create(editorContext)).to.deep.equals(expected); + }); + }); + }); + + describe('PasteOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: PasteOperation = { + kind: 'paste', + isOperation: true, + editorContext: { selectedElementIds: [] }, + clipboardData: { format: '' } + }; + expect(PasteOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(PasteOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(PasteOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: PasteOperation = { + kind: 'paste', + isOperation: true, + clipboardData: { format: 'string' }, + editorContext: { selectedElementIds: ['element1'] } + }; + const { editorContext, clipboardData } = expected; + expect(PasteOperation.create({ clipboardData, editorContext })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/clipboard.ts b/packages/protocol/src/action-protocol/clipboard.ts index fe7e997..45b6c38 100644 --- a/packages/protocol/src/action-protocol/clipboard.ts +++ b/packages/protocol/src/action-protocol/clipboard.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,80 +14,133 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isObject, isString } from '../utils/typeguard-util'; -import { generateRequestId, isActionKind, Operation, RequestAction, ResponseAction } from './base-protocol'; +import { hasObjectProp } from '../utils/type-util'; +import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; import { EditorContext } from './types'; /** * Requests the clipboard data for the current editor context, i.e., the selected elements, in a clipboard-compatible format. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestClipboardDataActions`. */ -export class RequestClipboardDataAction implements RequestAction { - static readonly KIND = 'requestClipboardData'; +export interface RequestClipboardDataAction extends RequestAction { + kind: typeof RequestClipboardDataAction.KIND; - constructor( - public readonly editorContext: EditorContext, - public readonly requestId: string = generateRequestId(), - public readonly kind: string = RequestClipboardDataAction.KIND - ) {} + editorContext: EditorContext; +} + +export namespace RequestClipboardDataAction { + export const KIND = 'requestClipboardData'; - static create(editorContext: EditorContext): RequestAction { - return new RequestClipboardDataAction(editorContext); + export function is(object: any): object is RequestClipboardDataAction { + return RequestAction.hasKind(object, KIND) && hasObjectProp(object, 'editorContext'); } -} -export function isRequestClipboardDataAction(action: any): action is RequestClipboardDataAction { - return isActionKind(action, RequestClipboardDataAction.KIND) && isObject(action, 'editorContext') && isString(action, 'requestId'); + export function create(editorContext: EditorContext, options: { requestId?: string } = {}): RequestClipboardDataAction { + return { + kind: KIND, + requestId: '', + editorContext, + ...options + }; + } } /** * Server response to a {@link RequestClipboardDataAction} containing the selected elements as clipboard-compatible format. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetClipboardDataActions`. */ -export class SetClipboardDataAction implements ResponseAction { - static readonly KIND = 'setClipboardData'; - - constructor( - public readonly clipboardData: ClipboardData, - public readonly responseId: string = '', - public readonly kind: string = SetClipboardDataAction.KIND - ) {} +export interface SetClipboardDataAction extends ResponseAction { + kind: typeof SetClipboardDataAction.KIND; + + /** + * The data to be added into the clipboard. This data will be sent back to the server on paste. + */ + clipboardData: ClipboardData; } -export function isSetClipboardDataAction(action: any): action is SetClipboardDataAction { - return isActionKind(action, SetClipboardDataAction.KIND) && isObject(action, 'clipboardData') && isString(action, 'responseId'); +export namespace SetClipboardDataAction { + export const KIND = 'setClipboardData'; + + export function is(object: any): object is SetClipboardDataAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'clipboardData'); + } + + export function create(clipboardData: ClipboardData, options: { responseId?: string } = {}): SetClipboardDataAction { + return { + kind: KIND, + responseId: '', + clipboardData, + ...options + }; + } } /** * Requests a cut operation from the server, i.e., deleting the selected elements from the model. Before submitting a `CutOperation` * a client should ensure that the cut elements are put into the clipboard. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CutOperations`. */ -export class CutOperation implements Operation { - static readonly KIND = 'cut'; +export interface CutOperation extends Operation { + kind: typeof CutOperation.KIND; - constructor(public readonly editorContext: EditorContext, public readonly kind: string = CutOperation.KIND) {} + editorContext: EditorContext; } -export function isCutOperation(action: any): action is CutOperation { - return isActionKind(action, CutOperation.KIND) && isObject(action, 'editorContext'); +export namespace CutOperation { + export const KIND = 'cut'; + + export function is(object: any): object is CutOperation { + return Operation.hasKind(object, KIND) && hasObjectProp(object, 'editorContext'); + } + + export function create(editorContext: EditorContext): CutOperation { + return { + kind: KIND, + isOperation: true, + editorContext + }; + } } /** * Requests a paste operation from the server by providing the current clipboard data. Typically this means that elements should be created * based on the data in the clipboard. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `PasteOperations`. */ -export class PasteOperation implements Operation { - static readonly KIND = 'paste'; - - constructor( - public readonly clipboardData: ClipboardData, - public readonly editorContext: EditorContext, - public readonly kind: string = PasteOperation.KIND - ) {} -} +export interface PasteOperation extends Operation { + kind: typeof PasteOperation.KIND; -export function isPasteOperation(action: any): action is PasteOperation { - return isActionKind(action, PasteOperation.KIND) && isObject(action, 'clipboardData') && isObject(action, 'editorContext'); + editorContext: EditorContext; + + /** + * The clipboard data that should be pasted to the editor's last recorded mouse position (see `editorContext`). + */ + clipboardData: ClipboardData; } +export namespace PasteOperation { + export const KIND = 'paste'; + + export function is(object: any): object is PasteOperation { + return Operation.hasKind(object, KIND) && hasObjectProp(object, 'clipboardData') && hasObjectProp(object, 'editorContext'); + } + + export function create(options: { editorContext: EditorContext; clipboardData: ClipboardData }): PasteOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } +} +/** + * In GLSP the clipboard needs to be managed by the client but the conversion from the selection to be copied into a + * clipboard-compatible format is handled by the server. By default, GLSP use application/json as exchange format. + */ export interface ClipboardData { [format: string]: string; } diff --git a/packages/protocol/src/action-protocol/contexts.spec..ts b/packages/protocol/src/action-protocol/contexts.spec..ts new file mode 100644 index 0000000..5d7d491 --- /dev/null +++ b/packages/protocol/src/action-protocol/contexts.spec..ts @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { RequestContextActions, SetContextActions } from './contexts'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Context Actions', () => { + describe('RequestContextActions', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestContextActions = { + contextId: '', + editorContext: { selectedElementIds: [] }, + kind: 'requestContextActions', + requestId: '' + }; + expect(RequestContextActions.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestContextActions.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestContextActions.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestContextActions = { + kind: 'requestContextActions', + contextId: 'someId', + editorContext: { selectedElementIds: ['element1'] }, + requestId: '' + }; + const { contextId, editorContext } = expected; + expect(RequestContextActions.create({ contextId, editorContext })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestContextActions = { + kind: 'requestContextActions', + contextId: 'someId', + editorContext: { selectedElementIds: ['element1'] }, + requestId: 'myRequest' + }; + const { contextId, editorContext, requestId } = expected; + expect(RequestContextActions.create({ contextId, editorContext, requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetContextActions', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetContextActions = { + kind: 'setContextActions', + actions: [], + responseId: '' + }; + expect(SetContextActions.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetContextActions.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetContextActions.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetContextActions = { + kind: 'setContextActions', + actions: [{ label: 'label', actions: [{ kind: 'kind' }] }], + responseId: '' + }; + const { actions } = expected; + expect(SetContextActions.create(actions)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetContextActions = { + kind: 'setContextActions', + actions: [{ label: 'label', actions: [{ kind: 'kind' }] }], + responseId: 'someResponse', + args: { some: 'args' } + }; + const { actions, args, responseId } = expected; + expect(SetContextActions.create(actions, { args, responseId })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/contexts.ts b/packages/protocol/src/action-protocol/contexts.ts index 5213f0f..8082eb4 100644 --- a/packages/protocol/src/action-protocol/contexts.ts +++ b/packages/protocol/src/action-protocol/contexts.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,45 +14,74 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isObject, isString } from '../utils/typeguard-util'; -import { generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import { hasArrayProp, hasObjectProp, hasStringProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; import { Args, EditorContext, LabeledAction } from './types'; /** * The `RequestContextActions` is sent from the client to the server to request the available actions for the context with id contextId. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestContextActions`. */ -export class RequestContextActions implements RequestAction { - static readonly KIND = 'requestContextActions'; - constructor( - public readonly contextId: string, - public readonly editorContext: EditorContext, - public readonly requestId: string = generateRequestId(), - public readonly kind: string = RequestContextActions.KIND - ) {} +export interface RequestContextActions extends RequestAction { + kind: typeof RequestContextActions.KIND; + + /** + * The identifier for the context. + */ + contextId: string; + + editorContext: EditorContext; } -export function isRequestContextActions(action: any): action is RequestContextActions { - return ( - isActionKind(action, RequestContextActions.KIND) && - isString(action, 'contextId') && - isObject(action, 'editorContext') && - isString(action, 'requestId') - ); +export namespace RequestContextActions { + export const KIND = 'requestContextActions'; + + export function is(object: any): object is RequestContextActions { + return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'contextId') && hasObjectProp(object, 'editorContext'); + } + + export function create(options: { contextId: string; editorContext: EditorContext; requestId?: string }): RequestContextActions { + return { + kind: KIND, + requestId: '', + ...options + }; + } } /** * The `SetContextActions` is the response to a {@link RequestContextActions} containing all actions for the queried context. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetContextsActions`. */ -export class SetContextActions implements ResponseAction { - static readonly KIND = 'setContextActions'; - constructor( - public readonly actions: LabeledAction[], - public readonly responseId: string = '', - public readonly args?: Args, - public readonly kind: string = SetContextActions.KIND - ) {} +export interface SetContextActions extends ResponseAction { + kind: typeof SetContextActions.KIND; + + /** + * The actions available in the queried context. + */ + readonly actions: LabeledAction[]; + + /** + * Custom arguments. + */ + args?: Args; } -export function isSetContextActionsAction(action: any): action is SetContextActions { - return isActionKind(action, SetContextActions.KIND) && isArray(action, 'actions') && isString(action, 'responseId'); +export namespace SetContextActions { + export const KIND = 'setContextActions'; + + export function is(object: any): object is SetContextActions { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'actions'); + } + + export function create(actions: LabeledAction[], options: { args?: Args; responseId?: string } = {}): SetContextActions { + return { + kind: KIND, + responseId: '', + actions, + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/edge-modification.spec.ts b/packages/protocol/src/action-protocol/edge-modification.spec.ts new file mode 100644 index 0000000..23dff75 --- /dev/null +++ b/packages/protocol/src/action-protocol/edge-modification.spec.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Point } from 'sprotty-protocol'; +import { ChangeRoutingPointsOperation, ReconnectEdgeOperation } from './edge-modification'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Edge modification operations', () => { + describe('ReconnectEdgeOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: ReconnectEdgeOperation = { + kind: 'reconnectEdge', + isOperation: true, + edgeElementId: '1', + sourceElementId: '2', + targetElementId: '3' + }; + expect(ReconnectEdgeOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ReconnectEdgeOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ReconnectEdgeOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ReconnectEdgeOperation = { + kind: 'reconnectEdge', + isOperation: true, + edgeElementId: 'edgeElement', + sourceElementId: 'source', + targetElementId: 'target' + }; + const { edgeElementId, sourceElementId, targetElementId } = expected; + expect(ReconnectEdgeOperation.create({ edgeElementId, sourceElementId, targetElementId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ReconnectEdgeOperation = { + kind: 'reconnectEdge', + isOperation: true, + edgeElementId: 'edgeElement', + sourceElementId: 'source', + targetElementId: 'target', + args: { some: 'args' } + }; + const { edgeElementId, sourceElementId, targetElementId, args } = expected; + expect(ReconnectEdgeOperation.create({ edgeElementId, sourceElementId, targetElementId, args })).to.deep.equals(expected); + }); + }); + }); + + describe('ChangeRoutingPointsOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: ChangeRoutingPointsOperation = { + kind: 'changeRoutingPoints', + isOperation: true, + newRoutingPoints: [] + }; + expect(ChangeRoutingPointsOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ChangeRoutingPointsOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ChangeRoutingPointsOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ReconnectEdgeOperation = { + kind: 'reconnectEdge', + isOperation: true, + edgeElementId: 'edgeElement', + sourceElementId: 'source', + targetElementId: 'target' + }; + const { edgeElementId, sourceElementId, targetElementId } = expected; + expect(ReconnectEdgeOperation.create({ edgeElementId, sourceElementId, targetElementId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required args', () => { + const expected: ChangeRoutingPointsOperation = { + kind: 'changeRoutingPoints', + isOperation: true, + newRoutingPoints: [{ elementId: 'element', newRoutingPoints: [Point.ORIGIN] }] + }; + const { newRoutingPoints } = expected; + expect(ChangeRoutingPointsOperation.create(newRoutingPoints)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/edge-modification.ts b/packages/protocol/src/action-protocol/edge-modification.ts index 6e0e703..dd90162 100644 --- a/packages/protocol/src/action-protocol/edge-modification.ts +++ b/packages/protocol/src/action-protocol/edge-modification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,41 +14,92 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isString } from '../utils/typeguard-util'; -import { isActionKind, Operation } from './base-protocol'; -import { ElementAndRoutingPoints } from './types'; +import { hasArrayProp, hasStringProp } from '../utils/type-util'; +import { Operation } from './base-protocol'; +import { Args, ElementAndRoutingPoints } from './types'; /** * If the source and/or target element of an edge should be adapted, the client can send a `ReconnectEdgeOperation` to the server. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ReconnectEdgeOperations`. */ -export class ReconnectEdgeOperation implements Operation { - static readonly KIND = 'reconnectEdge'; - constructor( - public readonly edgeElementId: string, - public readonly sourceElementId: string, - public readonly targetElementId: string, - public readonly kind: string = ReconnectEdgeOperation.KIND - ) {} +export interface ReconnectEdgeOperation extends Operation { + kind: typeof ReconnectEdgeOperation.KIND; + + /** + * The edge element that should be reconnected. + */ + edgeElementId: string; + + /** + * The (new) source element of the edge. + */ + sourceElementId: string; + + /** + * The (new) target element of the edge. + */ + targetElementId: string; + + /* + * Additional arguments for custom behavior. + */ + args?: Args; } -export function isReconnectEdgeOperation(action: any): action is ReconnectEdgeOperation { - return ( - isActionKind(action, ReconnectEdgeOperation.KIND) && - isString(action, 'edgeElementId') && - isString(action, 'sourceElementId') && - isString(action, 'targetElementId') - ); +export namespace ReconnectEdgeOperation { + export const KIND = 'reconnectEdge'; + + export function is(object: any): object is ReconnectEdgeOperation { + return ( + Operation.hasKind(object, KIND) && + hasStringProp(object, 'edgeElementId') && + hasStringProp(object, 'sourceElementId') && + hasStringProp(object, 'targetElementId') + ); + } + + export function create(options: { + edgeElementId: string; + sourceElementId: string; + targetElementId: string; + args?: Args; + }): ReconnectEdgeOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } } /** * An edge may have zero or more routing points that "re-direct" the edge between the source and the target element. In order to set these * routing points the client may send a `ChangeRoutingPointsOperation`. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ChangeRoutingPointsOperations`. */ -export class ChangeRoutingPointsOperation implements Operation { - static readonly KIND = 'changeRoutingPoints'; - constructor(public newRoutingPoints: ElementAndRoutingPoints[], public readonly kind: string = ChangeRoutingPointsOperation.KIND) {} +export interface ChangeRoutingPointsOperation extends Operation { + kind: typeof ChangeRoutingPointsOperation.KIND; + + /** + * The routing points of the edge (may be empty). + */ + newRoutingPoints: ElementAndRoutingPoints[]; } -export function isChangeRoutingsPointsOperation(action: any): action is ChangeRoutingPointsOperation { - return isActionKind(action, ChangeRoutingPointsOperation.KIND) && isArray(action, 'newRoutingPoints'); +export namespace ChangeRoutingPointsOperation { + export const KIND = 'changeRoutingPoints'; + + export function is(object: any): object is ChangeRoutingPointsOperation { + return Operation.hasKind(object, KIND) && hasArrayProp(object, 'newRoutingPoints'); + } + + export function create(newRoutingPoints: ElementAndRoutingPoints[]): ChangeRoutingPointsOperation { + return { + kind: KIND, + isOperation: true, + newRoutingPoints + }; + } } diff --git a/packages/protocol/src/action-protocol/element-creation.spec.ts b/packages/protocol/src/action-protocol/element-creation.spec.ts new file mode 100644 index 0000000..4fb6986 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-creation.spec.ts @@ -0,0 +1,165 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Point } from 'sprotty-protocol'; +import { CreateEdgeOperation, CreateNodeOperation, CreateOperation, DeleteElementOperation } from './element-creation'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element creation operations', () => { + describe('CreateOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: CreateOperation = { + kind: 'CreateEdgeOperation', + isOperation: true, + elementTypeId: 'someType' + }; + expect(CreateOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CreateOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CreateOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + }); + + describe('CreateNodeOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: CreateNodeOperation = { + kind: 'createNode', + isOperation: true, + elementTypeId: '' + }; + expect(CreateNodeOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CreateNodeOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CreateNodeOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: CreateNodeOperation = { + kind: 'createNode', + isOperation: true, + elementTypeId: 'someNode' + }; + const { elementTypeId } = expected; + expect(CreateNodeOperation.create(elementTypeId)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: CreateNodeOperation = { + kind: 'createNode', + elementTypeId: 'elementType', + containerId: 'container', + location: Point.ORIGIN, + isOperation: true, + args: { some: 'args' } + }; + const { elementTypeId, containerId, location, args } = expected; + expect(CreateNodeOperation.create(elementTypeId, { args, location, containerId })).to.deep.equals(expected); + }); + }); + }); + + describe('CreateEdgeOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: CreateEdgeOperation = { + kind: 'createEdge', + isOperation: true, + elementTypeId: '', + sourceElementId: '', + targetElementId: '' + }; + expect(CreateEdgeOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CreateEdgeOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CreateEdgeOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: CreateEdgeOperation = { + kind: 'createEdge', + isOperation: true, + elementTypeId: 'someNode', + sourceElementId: 'source', + targetElementId: 'target' + }; + const { elementTypeId, sourceElementId, targetElementId } = expected; + expect(CreateEdgeOperation.create({ elementTypeId, sourceElementId, targetElementId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: CreateEdgeOperation = { + kind: 'createEdge', + isOperation: true, + elementTypeId: 'someNode', + sourceElementId: 'source', + targetElementId: 'target', + args: { some: 'args' } + }; + const { elementTypeId, sourceElementId, targetElementId, args } = expected; + expect(CreateEdgeOperation.create({ elementTypeId, sourceElementId, targetElementId, args })).to.deep.equals(expected); + }); + }); + }); + + describe('DeleteElementOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: DeleteElementOperation = { + kind: 'deleteElement', + isOperation: true, + elementIds: [] + }; + expect(DeleteElementOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(DeleteElementOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(DeleteElementOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: DeleteElementOperation = { + kind: 'deleteElement', + isOperation: true, + elementIds: ['deleteMe'] + }; + const { elementIds } = expected; + expect(DeleteElementOperation.create(elementIds)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-creation.ts b/packages/protocol/src/action-protocol/element-creation.ts index e4e9293..c7545e9 100644 --- a/packages/protocol/src/action-protocol/element-creation.ts +++ b/packages/protocol/src/action-protocol/element-creation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,69 +14,144 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isString } from '../utils/typeguard-util'; -import { isAction, isActionKind, Operation } from './base-protocol'; -import { Args, Point } from './types'; +import { Point } from 'sprotty-protocol'; +import { hasArrayProp, hasStringProp } from '../utils/type-util'; +import { Operation } from './base-protocol'; +import { Args } from './types'; +/** + * Common interface for all create {@link Operation}s in GLSP. + * The corresponding namespace offers a helper function for type guard checks. + */ export interface CreateOperation extends Operation { + /** + * The type of the element that should be created. + */ elementTypeId: string; + /** + * Optional additional arguments for the server to execute the create operation. + */ args?: Args; } -export function isCreateOperation(action: any): action is CreateOperation { - return isAction(action) && isString(action, 'elementTypeId'); +export namespace CreateOperation { + export function is(object: any): object is CreateOperation { + return Operation.is(object) && hasStringProp(object, 'elementTypeId'); + } + + /** + * Typeguard function to check wether the given object is a {@link CreateOperation} with the given `kind`. + * @param object The object to check. + * @param kind The expected operation kind. + * @returns A type literal indicating wether the given object is a create operation with the given kind. + */ + export function hasKind(object: any, kind: string): object is CreateOperation { + return CreateOperation.is(object) && object.kind === kind; + } } + /** - * In order to create a node in the model the client can send a CreateNodeOperation with the necessary information to create that node. + * In order to create a node in the model the client can send a CreateNodeOperation with the necessary information to create + * the element that corresponds to that node in the source model. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CreateNodeOperations`. */ -export class CreateNodeOperation implements CreateOperation { - static readonly KIND = 'createNode'; - - constructor( - public readonly elementTypeId: string, - public location?: Point, - public containerId?: string, - public args?: Args, - public readonly kind: string = CreateNodeOperation.KIND - ) {} -} +export interface CreateNodeOperation extends CreateOperation { + kind: typeof CreateNodeOperation.KIND; -export function isCreateNodeOperation(action: any): action is CreateNodeOperation { - return isActionKind(action, CreateNodeOperation.KIND) && isString(action, 'elementTypeId'); + /* + * The location at which the operation shall be executed. + */ + location?: Point; + + /* + * The id of container element in which the node should be created. If not defined + * the root element will be used. + */ + containerId?: string; } +export namespace CreateNodeOperation { + export const KIND = 'createNode'; + + export function is(object: any): object is CreateNodeOperation { + return CreateOperation.hasKind(object, KIND); + } + + export function create( + elementTypeId: string, + options: { location?: Point; containerId?: string; args?: Args } = {} + ): CreateNodeOperation { + return { + kind: KIND, + isOperation: true, + elementTypeId, + ...options + }; + } +} /** - * In order to create an edge in the model the client can send a `CreateEdgeOperation` with the necessary information to create that edge. + * In order to create an edge in the model the client can send a `CreateEdgeOperation` with the necessary information to create + * the element that corresponds to that edge in the source model. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CreateEdgeOperations`. */ -export class CreateEdgeOperation implements CreateOperation { - static readonly KIND = 'createEdge'; - - constructor( - public readonly elementTypeId: string, - public sourceElementId: string, - public targetElementId: string, - public args?: Args, - public readonly kind: string = CreateEdgeOperation.KIND - ) {} -} +export interface CreateEdgeOperation extends CreateOperation { + kind: typeof CreateEdgeOperation.KIND; + + sourceElementId: string; -export function isCreateEdgeOperation(action: any): action is CreateEdgeOperation { - return ( - isActionKind(action, CreateEdgeOperation.KIND) && - isString(action, 'elementTypeId') && - isString(action, 'sourceElementId') && - isString(action, 'targetElementId') - ); + targetElementId: string; } +export namespace CreateEdgeOperation { + export const KIND = 'createEdge'; + + export function is(object: any): object is CreateEdgeOperation { + return ( + CreateOperation.hasKind(object, KIND) && hasStringProp(object, 'sourceElementId') && hasStringProp(object, 'targetElementId') + ); + } + + export function create(options: { + elementTypeId: string; + sourceElementId: string; + targetElementId: string; + args?: Args; + }): CreateEdgeOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } +} /** - * The client sends a `DeleteElementOperation` to the server to request the deletion of an element from the model. + * The client sends a `DeleteElementOperation` to the server to request the deletion of an element from the source model. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `DeleteElementOperations`. */ -export class DeleteElementOperation implements Operation { - static readonly KIND = 'deleteElement'; - constructor(readonly elementIds: string[], public readonly kind: string = DeleteElementOperation.KIND) {} +export interface DeleteElementOperation extends Operation { + kind: typeof DeleteElementOperation.KIND; + + /** + * The ids of the elements to be deleted. + */ + elementIds: string[]; } -export function isDeleteElementOperation(action: any): action is DeleteElementOperation { - return isActionKind(action, DeleteElementOperation.KIND) && isArray(action, 'elementIds'); +export namespace DeleteElementOperation { + export const KIND = 'deleteElement'; + + export function is(object: any): object is DeleteElementOperation { + return Operation.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); + } + + export function create(elementIds: string[]): DeleteElementOperation { + return { + kind: KIND, + isOperation: true, + elementIds + }; + } } diff --git a/packages/protocol/src/action-protocol/element-hover.spec.ts b/packages/protocol/src/action-protocol/element-hover.spec.ts new file mode 100644 index 0000000..4e809c7 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-hover.spec.ts @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Bounds } from 'sprotty-protocol'; +import { RequestPopupModelAction, SetPopupModelAction } from './element-hover'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element hover actions', () => { + describe('RequestPopupModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestPopupModelAction = { + kind: 'requestPopupModel', + requestId: '', + elementId: '', + bounds: Bounds.EMPTY + }; + expect(RequestPopupModelAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestPopupModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestPopupModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestPopupModelAction = { + kind: 'requestPopupModel', + requestId: '', + elementId: 'someId', + bounds: Bounds.EMPTY + }; + const { elementId, bounds } = expected; + expect(RequestPopupModelAction.create({ bounds, elementId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestPopupModelAction = { + kind: 'requestPopupModel', + requestId: 'someRequest', + elementId: 'someId', + bounds: Bounds.EMPTY + }; + const { elementId, bounds, requestId } = expected; + expect(RequestPopupModelAction.create({ bounds, elementId, requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetPopupModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetPopupModelAction = { + kind: 'setPopupModel', + responseId: '', + newRoot: { id: '', type: '' } + }; + expect(SetPopupModelAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetPopupModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetPopupModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetPopupModelAction = { + kind: 'setPopupModel', + responseId: '', + newRoot: { id: '', type: '' } + }; + const { newRoot } = expected; + expect(SetPopupModelAction.create(newRoot)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetPopupModelAction = { + kind: 'setPopupModel', + responseId: 'someResponse', + newRoot: { id: '', type: '' } + }; + const { newRoot, responseId } = expected; + expect(SetPopupModelAction.create(newRoot, { responseId })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-hover.ts b/packages/protocol/src/action-protocol/element-hover.ts index 8b2430e..46231cd 100644 --- a/packages/protocol/src/action-protocol/element-hover.ts +++ b/packages/protocol/src/action-protocol/element-hover.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,47 +13,73 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isObject, isString } from '../utils/typeguard-util'; -import { generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import { Bounds } from 'sprotty-protocol'; +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { hasObjectProp, hasStringProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; import { SModelRootSchema } from './model-structure'; -import { Bounds } from './types'; /** * Triggered when the user hovers the mouse pointer over an element to get a popup with details on that element. * This action is sent from the client to the server. The response is a `SetPopupModelAction`. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestPopupModelActions`. */ -export class RequestPopupModelAction implements RequestAction { - static readonly KIND = 'requestPopupModel'; - readonly kind = RequestPopupModelAction.KIND; +export interface RequestPopupModelAction extends RequestAction, sprotty.RequestPopupModelAction { + kind: typeof RequestPopupModelAction.KIND; - constructor(public readonly elementId: string, public readonly bounds: Bounds, public readonly requestId = '') {} + /** + * The identifier of the elements for which a popup is requested. + */ + elementId: string; - /** Factory function to dispatch a request with the `IActionDispatcher` */ - static create(elementId: string, bounds: Bounds): RequestAction { - return new RequestPopupModelAction(elementId, bounds, generateRequestId()); - } + /** + * The popup bounds declaring the position of the popup. Optionally the desired dimension. + */ + bounds: Bounds; } -export function isRequestPopupModelAction(action: any): action is RequestPopupModelAction { - return ( - isActionKind(action, RequestPopupModelAction.KIND) && - isString(action, 'elementId') && - isObject(action, 'bounds') && - isString(action, 'requestId') - ); +export namespace RequestPopupModelAction { + export const KIND = 'requestPopupModel'; + + export function is(object: any): object is RequestPopupModelAction { + return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'elementId') && hasObjectProp(object, 'bounds'); + } + + export function create(options: { elementId: string; bounds: Bounds; requestId?: string }): RequestPopupModelAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } } /** * Sent from the server to the client to display a popup in response to a RequestPopupModelAction. This action can also be used to remove * any existing popup by choosing EMPTY_ROOT as root element. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetPopupModelActions`. */ -export class SetPopupModelAction implements ResponseAction { - static readonly KIND = 'setPopupModel'; - readonly kind = SetPopupModelAction.KIND; +export interface SetPopupModelAction extends ResponseAction, sprotty.SetPopupModelAction { + kind: typeof SetPopupModelAction.KIND; - constructor(public readonly newRoot: SModelRootSchema, public readonly responseId = '') {} + newRoot: SModelRootSchema; } -export function isSetPopupModelAction(action: any): action is SetPopupModelAction { - return isActionKind(action, SetPopupModelAction.KIND) && isObject(action, 'newRoot') && isString(action, 'responseId'); +export namespace SetPopupModelAction { + export const KIND = 'setPopupModel'; + + export function is(object: any): object is SetPopupModelAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'newRoot'); + } + + export function create(newRoot: SModelRootSchema, options: { responseId?: string } = {}): SetPopupModelAction { + return { + kind: KIND, + responseId: '', + newRoot, + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/element-navigation.spec.ts b/packages/protocol/src/action-protocol/element-navigation.spec.ts new file mode 100644 index 0000000..13de8bc --- /dev/null +++ b/packages/protocol/src/action-protocol/element-navigation.spec.ts @@ -0,0 +1,347 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { + NavigateToExternalTargetAction, + NavigateToTargetAction, + NavigationTarget, + RequestNavigationTargetsAction, + ResolveNavigationTargetAction, + SetNavigationTargetsAction, + SetResolvedNavigationTargetAction +} from './element-navigation'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element navigation actions', () => { + describe('NavigationTarget', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const target: NavigationTarget = { + uri: '' + }; + expect(NavigationTarget.is(target)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(NavigationTarget.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(NavigationTarget.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('hasArguments', () => { + it('should return true for an object having the correct type and a value for all required interface properties and a defined `args` property', () => { + const target: NavigationTarget = { uri: 'myuri', args: { some: 'args' } }; + expect(NavigationTarget.hasArguments(target)).to.be.true; + }); + it('should return false for an object having the correct type and a value for all required interface properties and an undefined `args property', () => { + const target: NavigationTarget = { uri: 'myuri' }; + expect(NavigationTarget.hasArguments(target)).to.be.false; + }); + }); + + describe('addArgument', () => { + it('should assign a new `args` property to the given target and add the given key-value pair to the args object', () => { + const target: NavigationTarget = { uri: 'myuri' }; + NavigationTarget.addArgument(target, 'some', 'argument'); + expect(target.args?.some).to.be.equal('argument'); + }); + }); + describe('getElementIds', () => { + it('should return the value for the ELEMENT_IDS key of the args object of the given target as a string array', () => { + const id1 = 'someId'; + const id2 = 'anotherId'; + const target: NavigationTarget = { uri: 'myuri', args: { [NavigationTarget.ELEMENT_IDS]: `${id1}&${id2}` } }; + const elementIds = NavigationTarget.getElementIds(target); + expect(elementIds.length).to.be.equal(2); + expect(elementIds).to.include(id1); + expect(elementIds).to.include(id2); + }); + it('should return an empty array for the given target with an undefined args property', () => { + const target: NavigationTarget = { uri: 'myuri' }; + const elementIds = NavigationTarget.getElementIds(target); + expect(elementIds.length).to.be.equal(0); + }); + }); + + describe('setElementIds', () => { + it('(should store the given element id array as a joined string value for the ELEMENT_IDS key', () => { + const target: NavigationTarget = { uri: 'myuri' }; + const id1 = 'someId'; + const id2 = 'anotherId'; + NavigationTarget.setElementIds(target, id1, id2); + expect(target.args?.[NavigationTarget.ELEMENT_IDS]).to.be.equal(`${id1}&${id2}`); + }); + }); + + describe('setTextPosition', () => { + it('(should store the given text position in the args property of the given target with separate keys for column and line.', () => { + const target: NavigationTarget = { uri: 'myuri' }; + const pos: NavigationTarget.TextPosition = { character: 5, line: 2 }; + const { character: column, line } = pos; + NavigationTarget.setTextPosition(target, pos); + expect(target.args?.[NavigationTarget.TEXT_COLUMN]).to.be.equal(column); + expect(target.args?.[NavigationTarget.TEXT_LINE]).to.be.equal(line); + }); + }); + + describe('getTextPosition', () => { + it('should return the text position composed of its correspond keys in the args object of the given target', () => { + const position: NavigationTarget.TextPosition = { + character: 5, + line: 2 + }; + const target: NavigationTarget = { + uri: 'myuri', + args: { [NavigationTarget.TEXT_COLUMN]: position.character, [NavigationTarget.TEXT_LINE]: position.line } + }; + const result = NavigationTarget.getTextPosition(target); + expect(result).to.be.not.undefined; + expect(result).to.be.deep.equal(position); + }); + it('should return undefined for the given target with an undefined args property', () => { + const target: NavigationTarget = { uri: 'myuri' }; + const result = NavigationTarget.getTextPosition(target); + expect(result).to.be.undefined; + }); + }); + }); + + describe('RequestNavigationTargetsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestNavigationTargetsAction = { + kind: 'requestNavigationTargets', + requestId: '', + editorContext: { selectedElementIds: [] }, + targetTypeId: '' + }; + expect(RequestNavigationTargetsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestNavigationTargetsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestNavigationTargetsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestNavigationTargetsAction = { + kind: 'requestNavigationTargets', + requestId: '', + editorContext: { selectedElementIds: ['selected'] }, + targetTypeId: 'someId' + }; + const { editorContext, targetTypeId } = expected; + expect(RequestNavigationTargetsAction.create({ editorContext, targetTypeId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestNavigationTargetsAction = { + kind: 'requestNavigationTargets', + requestId: 'myRequest', + editorContext: { selectedElementIds: ['selected'] }, + targetTypeId: 'someId' + }; + const { editorContext, targetTypeId, requestId } = expected; + expect(RequestNavigationTargetsAction.create({ editorContext, targetTypeId, requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetNavigationTargetsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetNavigationTargetsAction = { + kind: 'setNavigationTargets', + responseId: '', + targets: [] + }; + expect(SetNavigationTargetsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetNavigationTargetsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetNavigationTargetsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetNavigationTargetsAction = { + kind: 'setNavigationTargets', + responseId: '', + targets: [{ uri: 'someUri' }] + }; + const { targets } = expected; + expect(SetNavigationTargetsAction.create(targets)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetNavigationTargetsAction = { + kind: 'setNavigationTargets', + responseId: 'myResponse', + targets: [{ uri: 'someUri' }] + }; + const { targets, responseId } = expected; + expect(SetNavigationTargetsAction.create(targets, { responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('NavigateToTargetAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: NavigateToTargetAction = { + kind: 'navigateToTarget', + target: { uri: '' } + }; + expect(NavigateToTargetAction.is(action)).to.be.true; + }); + }); + it('should return false for `undefined`', () => { + expect(NavigateToTargetAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(NavigateToTargetAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: NavigateToTargetAction = { + kind: 'navigateToTarget', + target: { uri: 'myUri' } + }; + const { target } = expected; + expect(NavigateToTargetAction.create(target)).to.deep.equals(expected); + }); + }); + }); + + describe('ResolveNavigationTargetAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ResolveNavigationTargetAction = { + kind: 'resolveNavigationTarget', + requestId: '', + navigationTarget: { uri: '' } + }; + expect(ResolveNavigationTargetAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ResolveNavigationTargetAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ResolveNavigationTargetAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ResolveNavigationTargetAction = { + kind: 'resolveNavigationTarget', + requestId: '', + navigationTarget: { uri: '' } + }; + const { navigationTarget } = expected; + expect(ResolveNavigationTargetAction.create(navigationTarget)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ResolveNavigationTargetAction = { + kind: 'resolveNavigationTarget', + requestId: 'myRequest', + navigationTarget: { uri: '' } + }; + const { navigationTarget, requestId } = expected; + expect(ResolveNavigationTargetAction.create(navigationTarget, { requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetResolvedNavigationTargetAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetResolvedNavigationTargetAction = { + kind: 'setResolvedNavigationTarget', + responseId: '', + elementIds: [''] + }; + expect(SetResolvedNavigationTargetAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetResolvedNavigationTargetAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetResolvedNavigationTargetAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetResolvedNavigationTargetAction = { + kind: 'setResolvedNavigationTarget', + responseId: '', + elementIds: [''] + }; + const { elementIds } = expected; + expect(SetResolvedNavigationTargetAction.create(elementIds)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetResolvedNavigationTargetAction = { + kind: 'setResolvedNavigationTarget', + responseId: '', + elementIds: [''] + }; + const { elementIds, responseId } = expected; + expect(SetResolvedNavigationTargetAction.create(elementIds, { responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('NavigateToExternalTargetAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: NavigateToExternalTargetAction = { + kind: 'navigateToExternalTarget', + target: { uri: '' } + }; + expect(NavigateToExternalTargetAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(NavigateToExternalTargetAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(NavigateToExternalTargetAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: NavigateToExternalTargetAction = { + kind: 'navigateToExternalTarget', + target: { uri: '' } + }; + const { target } = expected; + expect(NavigateToExternalTargetAction.create(target)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-navigation.ts b/packages/protocol/src/action-protocol/element-navigation.ts index 2c4c53d..6c5037c 100644 --- a/packages/protocol/src/action-protocol/element-navigation.ts +++ b/packages/protocol/src/action-protocol/element-navigation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,56 +13,120 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isObject, isString } from '../utils/typeguard-util'; -import { Action, generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import { JsonPrimitive } from 'sprotty-protocol'; +import { hasArrayProp, hasObjectProp, hasStringProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; import { Args, EditorContext } from './types'; /** - * A NavigationTarget identifies the object we want to navigate to via its uri and may further provide a label to display for the client. + * A `NavigationTarget` identifies the object we want to navigate to via its uri and may further provide a label to display to the user. + * The corresponding namespace offers a set of utility function to interact with `NavigationTargets`. */ export interface NavigationTarget { + /** + * URI to identify the object we want to navigate to. + */ uri: string; + + /** + * Optional label to display to the user. + */ label?: string; + + /** + * Domain-specific arguments that may be interpreted directly or resolved further. + */ args?: Args; } export namespace NavigationTarget { + export function is(object: any): object is NavigationTarget { + return object !== undefined && hasStringProp(object, 'uri'); + } + /** + * Generic key to store element ids as additional argument + */ export const ELEMENT_IDS = 'elementIds'; + /** + * The separator that is used to store he values for the {@link ELEMENT_IDS} as a single string. + */ export const ELEMENT_IDS_SEPARATOR = '&'; + /** + * Generic key ot store the line property of a {@link TextPosition} as additional argument. + */ export const TEXT_LINE = 'line'; + /** + * Generic key ot store the character property of a {@link TextPosition} as additional argument. + */ export const TEXT_COLUMN = 'column'; + /** + * Denotes the position of the cursor in a text element. + */ export interface TextPosition { line: number; + /** + * The character number within the line. Also refereed to as column. + */ character: number; } + /** + * Utility function to check wether the given {@link NavigationTarget} has additional arguments defined. + * @param target The navigation target to check. + * @returns `true` if the navigation target has a non-empty `args` property, `false` + */ export function hasArguments(target: NavigationTarget): boolean { return target.args !== undefined && Object.keys(target.args).length > 0; } - export function addArgument(target: NavigationTarget, key: string, value: string | number | boolean): void { - if (target.args === undefined) { + /** + * Adds a new key-value pair to the additional arguments of the given {@link NavigationTarget}. + * @param target The navigation target. + * @param key The key of the new argument. + * @param value The (primitive) value of the new argument. + */ + export function addArgument(target: NavigationTarget, key: string, value: JsonPrimitive): void { + if (!target.args) { target.args = {}; } target.args[key] = value; } + /** + * Retrieves the element ids that have been stored with the generic {@link ELEMENT_IDS} key from the args of the + * given target. + * @param target The navigation target. + * @returns An array with the parsed element ids. The array is empty if no {@link ELEMENT_IDS} key is present in the args + * of the navigation target. + */ export function getElementIds(target: NavigationTarget): string[] { - if (target.args === undefined || target.args[ELEMENT_IDS] === undefined) { + if (!target?.args?.[ELEMENT_IDS]) { return []; } const elementIdsValue = target.args[ELEMENT_IDS].toString(); return elementIdsValue.split(ELEMENT_IDS_SEPARATOR); } - export function setElementIds(target: NavigationTarget, elementIds: string[]): string { + /** + * Stores the given element ids in the given {@link NavigationTarget} as additional arguments using the generic {@link ELEMENT_IDS} key. + * @param target The navigation target. + * @param elementIds The element ids that should be stored. + * @returns the value of the {@link ELEMENT_IDS} key after storing the given element ids. + */ + export function setElementIds(target: NavigationTarget, ...elementIds: string[]): string { if (target.args === undefined) { target.args = {}; } return (target.args[ELEMENT_IDS] = elementIds.join(ELEMENT_IDS_SEPARATOR)); } + /** + * Stores the given {@link TextPosition} in the given {@link NavigationTarget} as additional arguments using + * the generic {@link TEXT_LINE} & {@link TEXT_COLUMN} keys. + * @param target The navigation target. + * @param position The text position that should be stored. + */ export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void { if (position) { if (target.args === undefined) { @@ -73,8 +137,15 @@ export namespace NavigationTarget { } } + /** + * Retrieves the {@link TextPosition} that have been stored with the generic {@link TEXT_LINE} & {@link TEXT_COLUMN} keys + * from the args of the given target. + * @param target The navigation target. + * @returns The parsed text position or `undefined` if one of the generic text keys is not present in the args + * of the navigation target. + */ export function getTextPosition(target: NavigationTarget): TextPosition | undefined { - if (target.args === undefined || target.args[TEXT_LINE] === undefined || target.args[TEXT_COLUMN] === undefined) { + if (!target.args || !target.args[TEXT_LINE] || !target.args[TEXT_COLUMN]) { return undefined; } return { @@ -86,91 +157,199 @@ export namespace NavigationTarget { /** * Action that is usually sent from the client to the server to request navigation targets for a specific navigation type such as - * documentation or implementation in the given editor context. + * `documentation` or `implementation` in the given editor context. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestNavigationTargetsActions`. */ -export class RequestNavigationTargetsAction implements RequestAction { - static readonly KIND = 'requestNavigationTargets'; - kind = RequestNavigationTargetsAction.KIND; - constructor(readonly targetTypeId: string, readonly editorContext: EditorContext, readonly requestId: string = generateRequestId()) {} +export interface RequestNavigationTargetsAction extends RequestAction { + kind: typeof RequestNavigationTargetsAction.KIND; + + /** + * Identifier of the type of navigation targets we want to retrieve, e.g., 'documentation', 'implementation', etc. + */ + targetTypeId: string; + + editorContext: EditorContext; } -export function isRequestNavigationTargetsAction(action: any): action is RequestNavigationTargetsAction { - return ( - isActionKind(action, RequestNavigationTargetsAction.KIND) && - isString(action, 'targetTypeId') && - isObject(action, 'editorContext') && - isString(action, 'requestId') - ); +export namespace RequestNavigationTargetsAction { + export const KIND = 'requestNavigationTargets'; + + export function is(object: any): object is RequestNavigationTargetsAction { + return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'targetTypeId') && hasObjectProp(object, 'editorContext'); + } + + export function create(options: { + targetTypeId: string; + editorContext: EditorContext; + requestId?: string; + }): RequestNavigationTargetsAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } } /** * Response action from the server following a {@link RequestNavigationTargetsAction}. It contains all available navigation targets for the * queried target type in the provided editor context. The server may also provide additional information using the arguments, e.g., - * warnings, that can be interpreted by the client. + * warnings, that can be interpreted by the client. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetNavigationTargetsActions`. */ -export class SetNavigationTargetsAction implements ResponseAction { - static readonly KIND = 'setNavigationTargets'; - kind = SetNavigationTargetsAction.KIND; - constructor(readonly targets: NavigationTarget[], readonly responseId: string = '', readonly args?: Args) {} +export interface SetNavigationTargetsAction extends ResponseAction { + kind: typeof SetNavigationTargetsAction.KIND; + + targets: NavigationTarget[]; + + /** + * Custom arguments that may be interpreted by the client. + */ + args?: Args; } -export function isSetNavigationTargetsAction(action: any): action is SetNavigationTargetsAction { - return isActionKind(action, SetNavigationTargetsAction.KIND) && isArray(action, 'targets') && isString(action, 'responseId'); +export namespace SetNavigationTargetsAction { + export const KIND = 'setNavigationTargets'; + + export function is(object: any): object is SetNavigationTargetsAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'targets'); + } + + export function create(targets: NavigationTarget[], options: { args?: Args; responseId?: string } = {}): SetNavigationTargetsAction { + return { + kind: KIND, + responseId: '', + targets, + ...options + }; + } } -/** Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. */ -export class NavigateToTargetAction implements Action { - static readonly KIND = 'navigateToTarget'; - readonly kind = NavigateToTargetAction.KIND; - constructor(readonly target: NavigationTarget) {} +/** + * Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc.. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `NavigateToTargetActions`. + */ +export interface NavigateToTargetAction extends Action { + kind: typeof NavigateToTargetAction.KIND; + + target: NavigationTarget; } -export function isNavigateToTargetAction(action: any): action is NavigateToTargetAction { - return isActionKind(action, NavigateToTargetAction.KIND) && isObject(action, 'target'); +export namespace NavigateToTargetAction { + export const KIND = 'navigateToTarget'; + + export function is(object: any): object is NavigateToTargetAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'target'); + } + + export function create(target: NavigationTarget): NavigateToTargetAction { + return { + kind: KIND, + target + }; + } } /** * If a client cannot navigate to a target directly, a {@link ResolveNavigationTargetAction} may be sent to the server to resolve the * navigation target to one or more model elements. This may be useful in cases where the resolution of each target is expensive or the * client architecture requires an indirection. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ResolveNavigationTargetActions`. */ -export class ResolveNavigationTargetAction implements RequestAction { - static readonly KIND = 'resolveNavigationTarget'; - kind = ResolveNavigationTargetAction.KIND; - constructor(readonly navigationTarget: NavigationTarget, public readonly requestId: string = generateRequestId()) {} +export interface ResolveNavigationTargetAction extends RequestAction { + kind: typeof ResolveNavigationTargetAction.KIND; + + /** + * The navigation target to resolve. + */ + navigationTarget: NavigationTarget; } -export function isResolveNavigationTargetAction(action: any): action is ResolveNavigationTargetAction { - return ( - isActionKind(action, ResolveNavigationTargetAction.KIND) && isObject(action, 'navigationTarget') && isString(action, 'requestId') - ); +export namespace ResolveNavigationTargetAction { + export const KIND = 'resolveNavigationTarget'; + + export function is(object: any): object is ResolveNavigationTargetAction { + return RequestAction.hasKind(object, KIND) && hasObjectProp(object, 'navigationTarget'); + } + + export function create(navigationTarget: NavigationTarget, options: { requestId?: string } = {}): ResolveNavigationTargetAction { + return { + kind: KIND, + requestId: '', + navigationTarget, + ...options + }; + } } /** * An action sent from the server in response to a {@link ResolveNavigationTargetAction}. The response contains the resolved element ids * for the given target and may contain additional information in the args property. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetResolvedNavigationTargetActions`. */ -export class SetResolvedNavigationTargetAction implements ResponseAction { - static readonly KIND = 'setResolvedNavigationTarget'; - kind = SetResolvedNavigationTargetAction.KIND; - constructor(readonly elementIds: string[] = [], readonly args?: Args, readonly responseId: string = '') {} +export interface SetResolvedNavigationTargetAction extends ResponseAction { + kind: typeof SetResolvedNavigationTargetAction.KIND; + + /** + * The element ids of the resolved navigation target. + */ + elementIds: string[]; + + /** + * Custom arguments that may be interpreted by the client. + */ + args?: Args; } -export function isSetResolvedNavigationTargets(action: any): action is SetResolvedNavigationTargetAction { - return isActionKind(action, SetResolvedNavigationTargetAction.KIND) && isArray(action, 'elementIds') && isString(action, 'responseId'); +export namespace SetResolvedNavigationTargetAction { + export const KIND = 'setResolvedNavigationTarget'; + + export function is(object: any): object is SetResolvedNavigationTargetAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); + } + + export function create(elementIds: string[], options: { args?: Args; responseId?: string } = {}): SetResolvedNavigationTargetAction { + return { + kind: KIND, + responseId: '', + elementIds, + ...options + }; + } } /** * If a {@link NavigationTarget} cannot be resolved or the resolved target is something that is not part of our model source, e.g., * a separate documentation file, a {@link NavigateToExternalTargetAction} may be sent. Since the target it outside of the model scope such * an action would be typically handled by an integration layer (such as the surrounding IDE). + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `NavigateToExternalTargetActions`. */ -export class NavigateToExternalTargetAction implements Action { - static readonly KIND = 'navigateToExternalTarget'; - readonly kind = NavigateToExternalTargetAction.KIND; - constructor(readonly target: NavigationTarget) {} +export interface NavigateToExternalTargetAction extends Action { + kind: typeof NavigateToExternalTargetAction.KIND; + + /** + * The diagram-external target to which the client shall navigate. + */ + target: NavigationTarget; } -export function isNavigateToExternalTargetAction(action: any): action is NavigateToExternalTargetAction { - return isActionKind(action, NavigateToExternalTargetAction.KIND) && isObject(action, 'target'); +export namespace NavigateToExternalTargetAction { + export const KIND = 'navigateToExternalTarget'; + + export function is(object: any): object is NavigateToExternalTargetAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'target'); + } + + export function create(target: NavigationTarget): NavigateToExternalTargetAction { + return { + kind: KIND, + target + }; + } } diff --git a/packages/protocol/src/action-protocol/element-selection.spec.ts b/packages/protocol/src/action-protocol/element-selection.spec.ts new file mode 100644 index 0000000..1e2e208 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-selection.spec.ts @@ -0,0 +1,99 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { SelectAction, SelectAllAction } from './element-selection'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element selection actions', () => { + describe('SelectAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: [], + deselectedElementsIDs: [] + }; + expect(SelectAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SelectAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SelectAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: [], + deselectedElementsIDs: [] + }; + expect(SelectAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SelectAction = { + kind: 'elementSelected', + selectedElementsIDs: ['selected'], + deselectedElementsIDs: ['deselected'] + }; + const { selectedElementsIDs, deselectedElementsIDs } = expected; + expect(SelectAction.create({ deselectedElementsIDs, selectedElementsIDs })).to.deep.equals(expected); + }); + }); + }); + + describe('SelectAllAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SelectAllAction = { + kind: 'allSelected', + select: true + }; + expect(SelectAllAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SelectAllAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SelectAllAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SelectAllAction = { + kind: 'allSelected', + select: true + }; + expect(SelectAllAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SelectAllAction = { + kind: 'allSelected', + select: false + }; + const { select } = expected; + expect(SelectAllAction.create(select)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-selection.ts b/packages/protocol/src/action-protocol/element-selection.ts index 9a23ed1..e64891b 100644 --- a/packages/protocol/src/action-protocol/element-selection.ts +++ b/packages/protocol/src/action-protocol/element-selection.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,38 +13,73 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isBoolean } from '../utils/typeguard-util'; -import { Action, isActionKind } from './base-protocol'; +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { hasArrayProp, hasBooleanProp } from '../utils/type-util'; +import { Action } from './base-protocol'; /** * Triggered when the user changes the selection, e.g. by clicking on a selectable element. The action should trigger a change in the * selected state accordingly, so the elements can be rendered differently. The server can send such an action to the client in order to - * change the selection remotely. + * change the selection remotely. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SelectActions`. */ -export class SelectAction implements Action { - static readonly KIND = 'elementSelected'; - kind = SelectAction.KIND; +export interface SelectAction extends Action, sprotty.SelectAction { + kind: typeof SelectAction.KIND; - constructor(public readonly selectedElementsIDs: string[] = [], public readonly deselectedElementsIDs: string[] = []) {} + /** + * The identifiers of the elements to mark as selected. + */ + selectedElementsIDs: string[]; + + /** + * The identifiers of the elements to mark as not selected. + */ + deselectedElementsIDs: string[]; } -export function isSelectAction(action: any): action is SelectAction { - return isActionKind(action, SelectAction.KIND) && isArray(action, 'selectedElementsIDs') && isArray(action, 'deselectedElementsIDs'); +export namespace SelectAction { + export const KIND = 'elementSelected'; + + export function is(object: any): object is SelectAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs'); + } + + export function create(options: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] } = {}): SelectAction { + return { + kind: KIND, + selectedElementsIDs: [], + deselectedElementsIDs: [], + ...options + }; + } } /** * Programmatic action for selecting or deselecting all elements. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SelectAllActions`. */ -export class SelectAllAction implements Action { - static readonly KIND = 'allSelected'; - kind = SelectAllAction.KIND; +export interface SelectAllAction extends Action, sprotty.SelectAllAction { + kind: typeof SelectAllAction.KIND; /** * If `select` is true, all elements are selected, otherwise they are deselected. */ - constructor(public readonly select: boolean = true) {} + select: boolean; } -export function isSelectAllAction(action: any): action is SelectAllAction { - return isActionKind(action, SelectAllAction.KIND) && isBoolean(action, 'select'); +export namespace SelectAllAction { + export const KIND = 'allSelected'; + + export function is(object: any): object is SelectAllAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'select'); + } + + export function create(select = true): SelectAllAction { + return { + kind: KIND, + select + }; + } } diff --git a/packages/protocol/src/action-protocol/element-text-editing.spec.ts b/packages/protocol/src/action-protocol/element-text-editing.spec.ts new file mode 100644 index 0000000..f31214d --- /dev/null +++ b/packages/protocol/src/action-protocol/element-text-editing.spec.ts @@ -0,0 +1,149 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { + ApplyLabelEditOperation, + RequestEditValidationAction, + SetEditValidationResultAction, + ValidationStatus +} from './element-text-editing'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element text editing actions', () => { + describe('RequestEditValidationAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestEditValidationAction = { + kind: 'requestEditValidation', + requestId: '', + contextId: '', + modelElementId: '', + text: '' + }; + expect(RequestEditValidationAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestEditValidationAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestEditValidationAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestEditValidationAction = { + kind: 'requestEditValidation', + requestId: '', + contextId: 'myContext', + modelElementId: 'myModelElement', + text: 'someText' + }; + const { contextId, modelElementId, text } = expected; + expect(RequestEditValidationAction.create({ contextId, modelElementId, text })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestEditValidationAction = { + kind: 'requestEditValidation', + requestId: 'myRequest', + contextId: 'myContext', + modelElementId: 'myModelElement', + text: 'someText' + }; + const { contextId, modelElementId, text, requestId } = expected; + expect(RequestEditValidationAction.create({ contextId, modelElementId, text, requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetEditValidationResultAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetEditValidationResultAction = { + kind: 'setEditValidationResult', + responseId: '', + status: { severity: ValidationStatus.Severity.OK } + }; + expect(SetEditValidationResultAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetEditValidationResultAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetEditValidationResultAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetEditValidationResultAction = { + kind: 'setEditValidationResult', + responseId: '', + status: { severity: ValidationStatus.Severity.OK } + }; + const { status } = expected; + expect(SetEditValidationResultAction.create(status)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetEditValidationResultAction = { + kind: 'setEditValidationResult', + responseId: 'myRespsone', + args: { some: 'args' }, + status: { severity: ValidationStatus.Severity.OK } + }; + const { status, args, responseId } = expected; + expect(SetEditValidationResultAction.create(status, { args, responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('ApplyLabelEditOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: ApplyLabelEditOperation = { + kind: 'applyLabelEdit', + isOperation: true, + labelId: '', + text: '' + }; + expect(ApplyLabelEditOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ApplyLabelEditOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ApplyLabelEditOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: ApplyLabelEditOperation = { + kind: 'applyLabelEdit', + isOperation: true, + labelId: 'myLabel', + text: 'myText' + }; + const { labelId, text } = expected; + expect(ApplyLabelEditOperation.create({ labelId, text })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-text-editing.ts b/packages/protocol/src/action-protocol/element-text-editing.ts index 5f5cec6..3507ed5 100644 --- a/packages/protocol/src/action-protocol/element-text-editing.ts +++ b/packages/protocol/src/action-protocol/element-text-editing.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,61 +13,135 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isObject, isString } from '../utils/typeguard-util'; -import { Action, generateRequestId, isActionKind, isRequestAction, Operation, RequestAction, ResponseAction } from './base-protocol'; +import { hasObjectProp, hasStringProp } from '../utils/type-util'; +import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; import { Args } from './types'; /** * Requests the validation of the given text in the context of the provided model element. Typically sent from the client to the server. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `NewActions`. */ -export class RequestEditValidationAction implements RequestAction { - static readonly KIND = 'requestEditValidation'; - constructor( - public readonly contextId: string, - public readonly modelElementId: string, - public readonly text: string, - public readonly requestId: string = generateRequestId(), - public readonly kind: string = RequestEditValidationAction.KIND - ) {} +export interface RequestEditValidationAction extends RequestAction { + kind: typeof RequestEditValidationAction.KIND; + + /** + * Context in which the text is validated, e.g., 'label-edit'. + */ + contextId: string; + + /** + * Model element that is being edited. + */ + modelElementId: string; + + /** + * Text that should be validated for the given context and the model element. + */ + text: string; } -export function isRequestEditValidationAction(action: any): action is RequestEditValidationAction { - return ( - isRequestAction(action) && - action.kind === RequestEditValidationAction.KIND && - isString(action, 'contextId') && - isString(action, 'modelElementId') && - isString(action, 'text') - ); +export namespace RequestEditValidationAction { + export const KIND = 'requestEditValidation'; + + export function is(object: any): object is RequestEditValidationAction { + return ( + RequestAction.hasKind(object, KIND) && + hasStringProp(object, 'contextId') && + hasStringProp(object, 'modelElementId') && + hasStringProp(object, 'text') + ); + } + + export function create(options: { + contextId: string; + modelElementId: string; + text: string; + requestId?: string; + }): RequestEditValidationAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } } /** * Response to a {@link RequestEditValidationAction} containing the validation result for applying a text on a certain model element. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetEditValidationResultActions`. */ -export class SetEditValidationResultAction implements ResponseAction { - static readonly KIND = 'setEditValidationResult'; - constructor( - public readonly status: ValidationStatus, - public readonly responseId: string = '', - public readonly args?: Args, - public readonly kind: string = SetEditValidationResultAction.KIND - ) {} +export interface SetEditValidationResultAction extends ResponseAction { + kind: typeof SetEditValidationResultAction.KIND; + + status: ValidationStatus; + + /* + * Additional arguments for custom behavior. + */ + args?: Args; } -export function isSetEditValidationResultAction(action: Action): action is SetEditValidationResultAction { - return isActionKind(action, SetEditValidationResultAction.KIND) && isObject(action, 'status') && isString(action, 'responseId'); +export namespace SetEditValidationResultAction { + export const KIND = 'setEditValidationResult'; + + export function is(object: any): object is SetEditValidationResultAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'status'); + } + + export function create(status: ValidationStatus, options: { args?: Args; responseId?: string } = {}): SetEditValidationResultAction { + return { + kind: KIND, + responseId: '', + status, + ...options + }; + } } -export class ApplyLabelEditOperation implements Operation { - static KIND = 'applyLabelEdit'; - kind = ApplyLabelEditOperation.KIND; - constructor(readonly labelId: string, readonly text: string) {} +/** + * A very common use case in domain models is the support of labels that display textual information to the user. + * To apply new text to such a label element the client may send an ApplyLabelEditOperation to the server. Typically this is + * done by the client after it has received a error free validation result via {@link SetEditValidationResultAction} from the server. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ApplyLabelEditOperations`. + */ +export interface ApplyLabelEditOperation extends Operation { + kind: typeof ApplyLabelEditOperation.KIND; + + /** + * Identifier of the label model element. + */ + labelId: string; + + /** + * Text that should be applied on the label. + */ + text: string; } -export function isApplyLabelEditOperation(action: any): action is ApplyLabelEditOperation { - return isActionKind(action, ApplyLabelEditOperation.KIND) && isString(action, 'labelId') && isString(action, 'text'); +export namespace ApplyLabelEditOperation { + export const KIND = 'applyLabelEdit'; + + export function is(object: any): object is ApplyLabelEditOperation { + return Operation.hasKind(object, KIND) && hasStringProp(object, 'labelId') && hasStringProp(object, 'text'); + } + + export function create(options: { labelId: string; text: string }): ApplyLabelEditOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } } +/** + * The serializable result of an an validation request. + * Tje corresponding namespace offers the default severity values + * and other utility functions. + */ export interface ValidationStatus { /** * The severity of the validation returned by the server. @@ -85,24 +159,10 @@ export interface ValidationStatus { readonly error?: ResponseError; } -export interface ResponseError { - /** - * Code identifying the error kind. - */ - readonly code: number; - - /** - * Error message. - */ - readonly message: string; - +export namespace ValidationStatus { /** - * Additional custom data, e.g., a serialized stacktrace. + * The default {@link ValidationStatus} severity levels used in GLSP. */ - readonly data: Record; -} - -export namespace ValidationStatus { // eslint-disable-next-line no-shadow export enum Severity { FATAL, @@ -114,12 +174,21 @@ export namespace ValidationStatus { NONE } + /** + * An empty {@link ValidationStatus}. + */ export const NONE: ValidationStatus = { severity: Severity.NONE, message: '', error: { code: -1, message: '', data: {} } }; + /** + * Utility function to check wether the given {@link ValidationStatus} has + * a severity that is considered to be OK. + * @param validationStatus The validation status to check. + * @returns `true` if the given status has a non critical severity, `false` otherwise. + */ export function isOk(validationStatus: ValidationStatus): boolean { return ( validationStatus.severity === Severity.OK || @@ -128,11 +197,43 @@ export namespace ValidationStatus { ); } + /** + * Utility function to check wether the given {@link ValidationStatus} has + * a `warning` severity. + * @param validationStatus The validation status to check. + * @returns `true` if the given status has a `warning` severity, `false` otherwise. + */ export function isWarning(validationStatus: ValidationStatus): boolean { return validationStatus.severity === Severity.WARNING; } + /** + * Utility function to check wether the given {@link ValidationStatus} has + * an `error` or `fatal` severity. + * @param validationStatus The validation status to check. + * @returns `true` if the given status has a `error` or `fatal` severity, `false` otherwise. + */ export function isError(validationStatus: ValidationStatus): boolean { return validationStatus.severity === Severity.ERROR || validationStatus.severity === Severity.FATAL; } } + +/** + * The serializable format of an error that occurred during validation. + */ +export interface ResponseError { + /** + * Code identifying the error kind. + */ + readonly code: number; + + /** + * Error message. + */ + readonly message: string; + + /** + * Additional custom data, e.g., a serialized stacktrace. + */ + readonly data: Record; +} diff --git a/packages/protocol/src/action-protocol/element-type-hints.spec.ts b/packages/protocol/src/action-protocol/element-type-hints.spec.ts new file mode 100644 index 0000000..e6d6f37 --- /dev/null +++ b/packages/protocol/src/action-protocol/element-type-hints.spec.ts @@ -0,0 +1,139 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { RequestTypeHintsAction, SetTypeHintsAction } from './element-type-hints'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Element type hints actions', () => { + describe('RequestTypeHintsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestTypeHintsAction = { + kind: 'requestTypeHints', + requestId: '' + }; + expect(RequestTypeHintsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestTypeHintsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestTypeHintsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestTypeHintsAction = { + kind: 'requestTypeHints', + requestId: '' + }; + + expect(RequestTypeHintsAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestTypeHintsAction = { + kind: 'requestTypeHints', + requestId: 'myRequest' + }; + const { requestId } = expected; + expect(RequestTypeHintsAction.create({ requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetTypeHintsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetTypeHintsAction = { + kind: 'setTypeHints', + responseId: '', + edgeHints: [], + shapeHints: [] + }; + expect(SetTypeHintsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetTypeHintsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetTypeHintsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetTypeHintsAction = { + kind: 'setTypeHints', + responseId: '', + edgeHints: [ + { + deletable: false, + elementTypeId: 'myShape', + repositionable: true, + routable: true, + sourceElementTypeIds: [], + targetElementTypeIds: [] + } + ], + shapeHints: [ + { + deletable: true, + elementTypeId: 'myEdge', + reparentable: true, + repositionable: true, + resizable: true, + containableElementTypeIds: [] + } + ] + }; + const { edgeHints, shapeHints } = expected; + expect(SetTypeHintsAction.create({ edgeHints, shapeHints })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetTypeHintsAction = { + kind: 'setTypeHints', + responseId: 'myResponse', + edgeHints: [ + { + deletable: false, + elementTypeId: 'myShape', + repositionable: true, + routable: true, + sourceElementTypeIds: [], + targetElementTypeIds: [] + } + ], + shapeHints: [ + { + deletable: true, + elementTypeId: 'myEdge', + reparentable: true, + repositionable: true, + resizable: true, + containableElementTypeIds: [] + } + ] + }; + const { edgeHints, shapeHints, responseId } = expected; + expect(SetTypeHintsAction.create({ edgeHints, shapeHints, responseId })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-type-hints.ts b/packages/protocol/src/action-protocol/element-type-hints.ts index 6984645..a0bbebe 100644 --- a/packages/protocol/src/action-protocol/element-type-hints.ts +++ b/packages/protocol/src/action-protocol/element-type-hints.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2019-2021 EclipseSource and others. + * Copyright (c) 2019-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { RequestAction, ResponseAction } from '.'; -import { isArray } from '../utils/typeguard-util'; -import { isActionKind } from './base-protocol'; +import { hasArrayProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; + +/** + * Type hints are used to define what modifications are supported on the different element types. + * The rationale is to avoid a client-server round-trip for user feedback of each synchronous user interaction. + */ export interface TypeHint { /** * The id of the element. @@ -34,6 +38,9 @@ export interface TypeHint { readonly deletable: boolean; } +/** + * A {@link TypeHint} with additional modification properties for shape elements. + */ export interface ShapeTypeHint extends TypeHint { /** * Specifies whether the element can be resized. @@ -51,6 +58,9 @@ export interface ShapeTypeHint extends TypeHint { readonly containableElementTypeIds?: string[]; } +/** + * A {@link TypeHint} with additional modification properties for edge elements. + */ export interface EdgeTypeHint extends TypeHint { /** * Specifies whether the routing of this element can be changed. @@ -72,29 +82,54 @@ export interface EdgeTypeHint extends TypeHint { * Sent from the client to the server in order to request hints on whether certain modifications are allowed for a specific element type. * The `RequestTypeHintsAction` is optional, but should usually be among the first messages sent from the client to the server after * receiving the model via RequestModelAction. The response is a {@link SetTypeHintsAction}. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestTypeHintsActions`. */ -export class RequestTypeHintsAction implements RequestAction { - static readonly KIND = 'requestTypeHints'; - constructor(public readonly requestId = '', public readonly kind: string = RequestTypeHintsAction.KIND) {} +export interface RequestTypeHintsAction extends RequestAction { + kind: typeof RequestTypeHintsAction.KIND; } -export function isRequestTypeHintsAction(action: any): action is RequestTypeHintsAction { - return isActionKind(action, RequestTypeHintsAction.KIND); +export namespace RequestTypeHintsAction { + export const KIND = 'requestTypeHints'; + + export function is(object: any): object is RequestTypeHintsAction { + return RequestAction.hasKind(object, KIND); + } + + export function create(options: { requestId?: string } = {}): RequestTypeHintsAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } } /** * Sent from the server to the client in order to provide hints certain modifications are allowed for a specific element type. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetTypeHintsActions`. */ -export class SetTypeHintsAction implements ResponseAction { - static readonly KIND = 'setTypeHints'; - constructor( - public readonly shapeHints: ShapeTypeHint[], - public readonly edgeHints: EdgeTypeHint[], - public readonly responseId = '', - public readonly kind: string = SetTypeHintsAction.KIND - ) {} +export interface SetTypeHintsAction extends ResponseAction { + kind: typeof SetTypeHintsAction.KIND; + + shapeHints: ShapeTypeHint[]; + + edgeHints: EdgeTypeHint[]; } -export function isSetTypeHintsAction(action: any): action is SetTypeHintsAction { - return isActionKind(action, SetTypeHintsAction.KIND) && isArray(action, 'shapeHints') && isArray(action, 'edgeHints'); +export namespace SetTypeHintsAction { + export const KIND = 'setTypeHints'; + + export function is(object: any): object is SetTypeHintsAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'shapeHints') && hasArrayProp(object, 'edgeHints'); + } + + export function create(options: { shapeHints: ShapeTypeHint[]; edgeHints: EdgeTypeHint[]; responseId?: string }): SetTypeHintsAction { + return { + kind: KIND, + responseId: '', + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/element-validation.spec.ts b/packages/protocol/src/action-protocol/element-validation.spec.ts new file mode 100644 index 0000000..71de59a --- /dev/null +++ b/packages/protocol/src/action-protocol/element-validation.spec.ts @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { DeleteMarkersAction, RequestMarkersAction, SetMarkersAction } from './element-validation'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Element validation actions', () => { + describe('RequestMarkersAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestMarkersAction = { + kind: 'requestMarkers', + requestId: '', + elementsIDs: [] + }; + expect(RequestMarkersAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestMarkersAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestMarkersAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestMarkersAction = { + kind: 'requestMarkers', + requestId: '', + elementsIDs: ['myIds'] + }; + const { elementsIDs } = expected; + expect(RequestMarkersAction.create(elementsIDs)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestMarkersAction = { + kind: 'requestMarkers', + requestId: 'myRequest', + elementsIDs: ['myIds'] + }; + const { elementsIDs, requestId } = expected; + expect(RequestMarkersAction.create(elementsIDs, { requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetMarkersAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetMarkersAction = { + kind: 'setMarkers', + responseId: '', + markers: [] + }; + expect(SetMarkersAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetMarkersAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetMarkersAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetMarkersAction = { + kind: 'setMarkers', + responseId: '', + markers: [{ description: 'desc', elementId: 'myId', kind: 'info', label: 'string' }] + }; + const { markers } = expected; + expect(SetMarkersAction.create(markers)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetMarkersAction = { + kind: 'setMarkers', + responseId: 'myResponse', + markers: [{ description: 'desc', elementId: 'myId', kind: 'info', label: 'string' }] + }; + const { markers, responseId } = expected; + expect(SetMarkersAction.create(markers, { responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('DeleteMarkersAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: DeleteMarkersAction = { + kind: 'deleteMarkers', + markers: [] + }; + expect(DeleteMarkersAction.is(action)).to.be.true; + it('should return false for `undefined`', () => { + expect(DeleteMarkersAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(DeleteMarkersAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: DeleteMarkersAction = { + kind: 'deleteMarkers', + markers: [{ description: 'desc', elementId: 'myId', kind: 'info', label: 'string' }] + }; + const { markers } = expected; + expect(DeleteMarkersAction.create(markers)).to.deep.equals(expected); + }); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/element-validation.ts b/packages/protocol/src/action-protocol/element-validation.ts index 51102e6..1a0087b 100644 --- a/packages/protocol/src/action-protocol/element-validation.ts +++ b/packages/protocol/src/action-protocol/element-validation.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { RequestAction, ResponseAction } from '.'; -import { isArray } from '../utils/typeguard-util'; -import { Action, isActionKind } from './base-protocol'; +import { hasArrayProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; /** * Validation in GLSP is performed by using validation markers. A marker represents the validation result for a single model element @@ -40,6 +38,9 @@ export interface Marker { readonly kind: string; } +/** + * The default marker kinds used in GLSP + */ export namespace MarkerKind { export const INFO = 'info'; export const WARNING = 'warning'; @@ -48,41 +49,93 @@ export namespace MarkerKind { /** * Action to retrieve markers for the specified model elements. Sent from the client to the server. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestMarkersActions`. */ -export class RequestMarkersAction implements RequestAction { - static readonly KIND = 'requestMarkers'; - constructor( - public readonly elementsIDs: string[] = [], - public readonly requestId = '', - public readonly kind = RequestMarkersAction.KIND - ) {} +export interface RequestMarkersAction extends RequestAction { + kind: typeof RequestMarkersAction.KIND; + + /** + * The elements for which markers are requested, may be just the root element. + */ + elementsIDs: string[]; } -export function isRequestMarkersAction(action: any): action is RequestMarkersAction { - return isActionKind(action, RequestMarkersAction.KIND) && isArray(action, 'elementsIDs'); +export namespace RequestMarkersAction { + export const KIND = 'requestMarkers'; + + export function is(object: any): object is RequestMarkersAction { + return RequestAction.hasKind(object, KIND) && hasArrayProp(object, 'elementsIDs'); + } + + export function create(elementsIDs: string[], options: { requestId?: string } = {}): RequestMarkersAction { + return { + kind: KIND, + requestId: '', + elementsIDs, + ...options + }; + } } /** - * Response to the {@link RequestMarkersAction} containing all validation markers. Sent from the server to the client. + * Instructs the client to add markers to the diagram. + * Typically, this is a response to the {@link RequestMarkersAction} containing all validation markers, but can be sent by the server at + * any time. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetMarkersActions`. */ -export class SetMarkersAction implements ResponseAction { - static readonly KIND = 'setMarkers'; - constructor(public readonly markers: Marker[], public readonly responseId = '', public readonly kind = SetMarkersAction.KIND) {} +export interface SetMarkersAction extends ResponseAction { + kind: typeof SetMarkersAction.KIND; + + /** + * The list of markers to be added to the diagram. + */ + readonly markers: Marker[]; } -export function isSetMarkersAction(action: any): action is SetMarkersAction { - return isActionKind(action, SetMarkersAction.KIND) && isArray(action, 'markers'); +export namespace SetMarkersAction { + export const KIND = 'setMarkers'; + + export function is(object: any): object is SetMarkersAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); + } + + export function create(markers: Marker[], options: { responseId?: string } = {}): SetMarkersAction { + return { + kind: KIND, + responseId: '', + markers, + ...options + }; + } } /** * Action for clearing makers of a model + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `DeleteMarkersActions`. Can be sent by either the client or the server. */ -@injectable() -export class DeleteMarkersAction implements Action { - static readonly KIND = 'deleteMarkers'; - constructor(public readonly markers: Marker[], public readonly kind = DeleteMarkersAction.KIND) {} +export interface DeleteMarkersAction extends Action { + kind: typeof DeleteMarkersAction.KIND; + + /** + * The list of markers that has been requested by the `RequestMarkersAction`. + */ + markers: Marker[]; } -export function isDeleteMarkersAction(action: any): action is DeleteMarkersAction { - return isActionKind(action, DeleteMarkersAction.KIND) && isArray(action, 'markers'); +export namespace DeleteMarkersAction { + export const KIND = 'deleteMarkers'; + + export function is(object: any): object is DeleteMarkersAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'markers'); + } + + export function create(markers: Marker[]): DeleteMarkersAction { + return { + kind: KIND, + markers + }; + } } diff --git a/packages/protocol/src/action-protocol/index.ts b/packages/protocol/src/action-protocol/index.ts index 5270b97..ad23bda 100644 --- a/packages/protocol/src/action-protocol/index.ts +++ b/packages/protocol/src/action-protocol/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,9 +25,9 @@ export * from './element-selection'; export * from './element-text-editing'; export * from './element-type-hints'; export * from './element-validation'; -export * from './mode-layout'; export * from './model-data'; export * from './model-edit-mode'; +export * from './model-layout'; export * from './model-saving'; export * from './model-structure'; export * from './node-modification'; diff --git a/packages/protocol/src/action-protocol/mode-layout.ts b/packages/protocol/src/action-protocol/mode-layout.ts deleted file mode 100644 index 57fa470..0000000 --- a/packages/protocol/src/action-protocol/mode-layout.ts +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { isArray, isObject, isString } from '../utils/typeguard-util'; -import { generateRequestId, isActionKind, Operation, RequestAction, ResponseAction } from './base-protocol'; -import { SModelRootSchema } from './model-structure'; -import { ElementAndAlignment, ElementAndBounds } from './types'; - -/** Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can - * derived from the DOM. The response is a ComputedBoundsAction. This hidden rendering round-trip is necessary if the client is responsible - * for parts of the layout. - */ -export class RequestBoundsAction implements RequestAction { - static readonly KIND: string = 'requestBounds'; - readonly kind = RequestBoundsAction.KIND; - - constructor(public readonly newRoot: SModelRootSchema, public readonly requestId: string = '') {} - - /** Factory function to dispatch a request with the `IActionDispatcher` */ - static create(newRoot: SModelRootSchema): RequestAction { - return new RequestBoundsAction(newRoot, generateRequestId()); - } -} - -export function isRequestBoundsAction(action: any): action is RequestBoundsAction { - return isActionKind(action, RequestBoundsAction.KIND) && isObject(action, 'newRoot') && isString(action, 'requestId'); -} - -/** - * Sent from the client to the server to transmit the result of bounds computation as a response to a RequestBoundsAction. If the server is - * responsible for parts of the layout, it can do so after applying the computed bounds received with this action. Otherwise there is no - * need to send the computed bounds to the server, so they can be processed locally by the client. - */ -export class ComputedBoundsAction implements ResponseAction { - static readonly KIND = 'computedBounds'; - readonly kind = ComputedBoundsAction.KIND; - - constructor( - public readonly bounds: ElementAndBounds[], - public readonly revision?: number, - public readonly alignments?: ElementAndAlignment[], - public readonly responseId = '' - ) {} -} - -export function isComputedBoundsAction(action: any): action is ComputedBoundsAction { - return isActionKind(action, ComputedBoundsAction.KIND) && isArray(action, 'bounds') && isString(action, 'responseId'); -} - -/** - * Request a layout of the diagram or the selected elements only. - */ -export class LayoutOperation implements Operation { - static readonly KIND = 'layout'; - - constructor(public readonly elementIds: string[], public readonly kind = LayoutOperation.KIND) {} -} - -export function isLayoutOperation(action: any): action is LayoutOperation { - return isActionKind(action, LayoutOperation.KIND) && isArray(action, 'elementIds'); -} diff --git a/packages/protocol/src/action-protocol/model-data.spec.ts b/packages/protocol/src/action-protocol/model-data.spec.ts new file mode 100644 index 0000000..65d6186 --- /dev/null +++ b/packages/protocol/src/action-protocol/model-data.spec.ts @@ -0,0 +1,171 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { ModelSourceChangedAction, RequestModelAction, SetModelAction, UpdateModelAction } from './model-data'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('Model data actions', () => { + describe('RequestModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + it('RequestModelAction.is with valid action type', () => { + const action: RequestModelAction = { + kind: 'requestModel', + requestId: '' + }; + expect(RequestModelAction.is(action)).to.be.true; + }); + }); + it('should return false for `undefined`', () => { + expect(RequestModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestModelAction = { + kind: 'requestModel', + requestId: '' + }; + + expect(RequestModelAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestModelAction = { + kind: 'requestModel', + requestId: 'myRequestId', + options: { some: 'option' } + }; + const { requestId, options } = expected; + expect(RequestModelAction.create({ options, requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('SetModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetModelAction = { + kind: 'setModel', + responseId: '', + newRoot: { id: '', type: '' } + }; + expect(SetModelAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetModelAction = { + kind: 'setModel', + responseId: '', + newRoot: { id: 'myId', type: 'myType' } + }; + const { newRoot } = expected; + expect(SetModelAction.create(newRoot)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetModelAction = { + kind: 'setModel', + responseId: 'myResponse', + newRoot: { id: '', type: '' } + }; + const { newRoot, responseId } = expected; + expect(SetModelAction.create(newRoot, { responseId })).to.deep.equals(expected); + }); + }); + }); + + describe('UpdateModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: UpdateModelAction = { + kind: 'updateModel', + newRoot: { id: '', type: '' } + }; + expect(UpdateModelAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(UpdateModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(UpdateModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: UpdateModelAction = { + kind: 'updateModel', + animate: true, + newRoot: { id: 'myId', type: 'myType' } + }; + const { newRoot } = expected; + expect(UpdateModelAction.create(newRoot)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: UpdateModelAction = { + kind: 'updateModel', + newRoot: { id: 'myId', type: 'myType' }, + animate: false + }; + const { newRoot, animate } = expected; + expect(UpdateModelAction.create(newRoot, { animate })).to.deep.equals(expected); + }); + }); + }); + + describe('ModelSourceChangedAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ModelSourceChangedAction = { + kind: 'modelSourceChanged', + modelSourceName: '' + }; + expect(ModelSourceChangedAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ModelSourceChangedAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ModelSourceChangedAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: ModelSourceChangedAction = { + kind: 'modelSourceChanged', + modelSourceName: 'myModelSource' + }; + const { modelSourceName } = expected; + expect(ModelSourceChangedAction.create(modelSourceName)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/model-data.ts b/packages/protocol/src/action-protocol/model-data.ts index c2b4d06..33800a9 100644 --- a/packages/protocol/src/action-protocol/model-data.ts +++ b/packages/protocol/src/action-protocol/model-data.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,64 +13,131 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isBoolean, isObject, isString } from '../utils/typeguard-util'; -import { Action, generateRequestId, isActionKind, RequestAction, ResponseAction } from './base-protocol'; +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { hasObjectProp, hasStringProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; import { SModelRootSchema } from './model-structure'; -import { JsonPrimitive } from './types'; +import { Args } from './types'; /** * Sent from the server to the client in order to set the model. If a model is already present, it is replaced. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestModelActions`. */ -export class RequestModelAction implements RequestAction { - static readonly KIND = 'requestModel'; - readonly kind = RequestModelAction.KIND; +export interface RequestModelAction extends RequestAction, sprotty.RequestModelAction { + kind: typeof RequestModelAction.KIND; - constructor(public readonly options?: { [key: string]: JsonPrimitive }, public readonly requestId = '') {} + /** + * Additional options used to compute the graphical model. + */ + options?: Args; +} + +export namespace RequestModelAction { + export const KIND = 'requestModel'; - /** Factory function to dispatch a request with the `IActionDispatcher` */ - static create(options?: { [key: string]: JsonPrimitive }): RequestAction { - return new RequestModelAction(options, generateRequestId()); + export function is(object: any): object is RequestModelAction { + return RequestAction.hasKind(object, KIND); } -} -export function isRequestModelAction(action: any): action is RequestModelAction { - return isActionKind(action, RequestModelAction.KIND); + export function create(options: { options?: Args; requestId?: string } = {}): RequestModelAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } } + /** * Sent from the model source to the client in order to set the model. If a model is already present, it is replaced. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetModelActions`. */ -export class SetModelAction implements ResponseAction { - static readonly KIND = 'setModel'; - readonly kind = SetModelAction.KIND; - - constructor(public readonly newRoot: SModelRootSchema, public readonly responseId = '') {} +export interface SetModelAction extends ResponseAction, sprotty.SetModelAction { + kind: typeof SetModelAction.KIND; + /** + * The new graphical model root. + */ + newRoot: SModelRootSchema; } -export function isSetModelAction(action: any): action is SetModelAction { - return isActionKind(action, SetModelAction.KIND) && isObject(action, 'newRoot'); +export namespace SetModelAction { + export const KIND = 'setModel'; + + export function is(object: any): object is SetModelAction { + return Action.hasKind(object, KIND) && hasObjectProp(object, 'newRoot'); + } + + export function create(newRoot: SModelRootSchema, options: { responseId?: string } = {}): SetModelAction { + return { + kind: KIND, + responseId: '', + newRoot, + ...options + }; + } } /** - * Sent from the server to the client in order to update the model. If no model is present yet, this behaves the same as a SetModelAction. - * The transition from the old model to the new one can be animated. + * Sent from the server to the client in order to update the model. If no model is present yet, this behaves the same as + * a {@link SetModelAction}. The transition from the old model to the new one can be animated. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `UpdateModelActions`. */ -export class UpdateModelAction implements Action { - static readonly KIND = 'updateModel'; - readonly kind = UpdateModelAction.KIND; +export interface UpdateModelAction extends Action, Omit { + kind: typeof UpdateModelAction.KIND; - constructor(public readonly newRoot: SModelRootSchema, public readonly animate: boolean = true) {} + newRoot: SModelRootSchema; + /** + * Boolean flag to indicate wether updated/changed elements should be animated in the diagram. + */ + animate?: boolean; } -export function isUpdateModelAction(action: any): action is UpdateModelAction { - return isActionKind(action, UpdateModelAction.KIND) && isObject(action, 'newRoot') && isBoolean(action, 'animate'); +export namespace UpdateModelAction { + export const KIND = 'updateModel'; + + export function is(action: any): action is UpdateModelAction { + return Action.hasKind(action, KIND) && hasObjectProp(action, 'newRoot'); + } + + export function create(newRoot: SModelRootSchema, options: { animate?: boolean } = {}): UpdateModelAction { + return { + kind: KIND, + newRoot, + animate: true, + ...options + }; + } } -export class ModelSourceChangedAction implements Action { - static KIND = 'modelSourceChanged'; - readonly kind = ModelSourceChangedAction.KIND; - constructor(public readonly modelSourceName: string) {} +/** + * Sent from the server to the client in order to indicate that the model source has changed. + * The model source denotes the data source from which the diagram has been originally derived (such as a file, a database, etc.). + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ModelSourceChangedActions`. + */ +export interface ModelSourceChangedAction extends Action { + kind: typeof ModelSourceChangedAction.KIND; + + /** + * A human readable name of the model source (e.g. the file name). + */ + modelSourceName: string; } -export function isModelSourceChangedAction(action?: any): action is ModelSourceChangedAction { - return isActionKind(action, ModelSourceChangedAction.KIND) && isString(action, 'modelSourceName'); +export namespace ModelSourceChangedAction { + export const KIND = 'modelSourceChanged'; + + export function is(object: any): object is ModelSourceChangedAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'modelSourceName'); + } + + export function create(modelSourceName: string): ModelSourceChangedAction { + return { + kind: KIND, + modelSourceName + }; + } } diff --git a/packages/protocol/src/action-protocol/model-edit-mode.spec.ts b/packages/protocol/src/action-protocol/model-edit-mode.spec.ts new file mode 100644 index 0000000..cc0c02e --- /dev/null +++ b/packages/protocol/src/action-protocol/model-edit-mode.spec.ts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { SetEditModeAction } from './model-edit-mode'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ +describe('SetEditModeAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetEditModeAction = { + kind: 'setEditMode', + editMode: '' + }; + expect(SetEditModeAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetEditModeAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetEditModeAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: SetEditModeAction = { + kind: 'setEditMode', + editMode: 'myMode' + }; + const { editMode } = expected; + expect(SetEditModeAction.create(editMode)).to.deep.equals(expected); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/model-edit-mode.ts b/packages/protocol/src/action-protocol/model-edit-mode.ts index c1b54de..bf8d07c 100644 --- a/packages/protocol/src/action-protocol/model-edit-mode.ts +++ b/packages/protocol/src/action-protocol/model-edit-mode.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,21 +13,42 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { Action } from 'sprotty'; -import { isString } from '../utils/typeguard-util'; -import { isActionKind } from './base-protocol'; +import { hasStringProp } from '../utils/type-util'; +import { Action } from './base-protocol'; -@injectable() -export class SetEditModeAction implements Action { - static readonly KIND = 'setEditMode'; - constructor(public readonly editMode: string = EditMode.EDITABLE, public readonly kind: string = SetEditModeAction.KIND) {} +/** + * Sent from the client to the server to set the model into a specific editor mode, allowing the server to react to certain + * requests differently depending on the mode. A client may also listen to this action to prevent certain user interactions preemptively. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetEditModeActions`. + */ +export interface SetEditModeAction extends Action { + kind: typeof SetEditModeAction.KIND; + + /** + * The new edit mode of the diagram. + */ + editMode: string; } -export function isSetEditModeAction(action: Action): action is SetEditModeAction { - return isActionKind(action, SetEditModeAction.KIND) && isString(action, 'editMode'); +export namespace SetEditModeAction { + export const KIND = 'setEditMode'; + + export function is(object: any): object is SetEditModeAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'editMode'); + } + + export function create(editMode: string): SetEditModeAction { + return { + kind: KIND, + editMode + }; + } } +/** + * The potential default values for the `editMode` property of a {@link SetEditModeAction}. + */ export namespace EditMode { export const READONLY = 'readonly'; export const EDITABLE = 'editable'; diff --git a/packages/protocol/src/action-protocol/model-layout.spec.ts b/packages/protocol/src/action-protocol/model-layout.spec.ts new file mode 100644 index 0000000..5e5f9c1 --- /dev/null +++ b/packages/protocol/src/action-protocol/model-layout.spec.ts @@ -0,0 +1,138 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Dimension, Point } from 'sprotty-protocol'; +import { ComputedBoundsAction, LayoutOperation, RequestBoundsAction } from './model-layout'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Model layout actions', () => { + describe('RequestBoundsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestBoundsAction = { + kind: 'requestBounds', + requestId: '', + newRoot: { id: '', type: '' } + }; + expect(RequestBoundsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestBoundsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestBoundsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestBoundsAction = { + kind: 'requestBounds', + requestId: '', + newRoot: { id: 'myId', type: 'myType' } + }; + const { newRoot } = expected; + expect(RequestBoundsAction.create(newRoot)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestBoundsAction = { + kind: 'requestBounds', + requestId: 'myRequest', + newRoot: { id: 'myId', type: 'myType' } + }; + const { newRoot, requestId } = expected; + expect(RequestBoundsAction.create(newRoot, { requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('ComputedBoundsAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ComputedBoundsAction = { + kind: 'computedBounds', + responseId: '', + bounds: [] + }; + expect(ComputedBoundsAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ComputedBoundsAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ComputedBoundsAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ComputedBoundsAction = { + kind: 'computedBounds', + responseId: '', + bounds: [{ elementId: '', newSize: Dimension.EMPTY, newPosition: Point.ORIGIN }] + }; + const { bounds } = expected; + expect(ComputedBoundsAction.create(bounds)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ComputedBoundsAction = { + kind: 'computedBounds', + responseId: 'myResponse', + alignments: [{ elementId: 'myElement', newAlignment: Point.ORIGIN }], + revision: 5, + bounds: [{ elementId: '', newSize: Dimension.EMPTY, newPosition: Point.ORIGIN }] + }; + const { bounds, responseId, alignments, revision } = expected; + expect(ComputedBoundsAction.create(bounds, { responseId, alignments, revision })).to.deep.equals(expected); + }); + }); + }); + + describe('LayoutOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: LayoutOperation = { + kind: 'layout', + isOperation: true, + elementIds: [] + }; + expect(LayoutOperation.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(LayoutOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(LayoutOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: LayoutOperation = { + kind: 'layout', + isOperation: true, + elementIds: ['myElements'] + }; + const { elementIds } = expected; + expect(LayoutOperation.create(elementIds)).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/model-layout.ts b/packages/protocol/src/action-protocol/model-layout.ts new file mode 100644 index 0000000..5df0d05 --- /dev/null +++ b/packages/protocol/src/action-protocol/model-layout.ts @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2021-2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { hasArrayProp, hasObjectProp } from '../utils/type-util'; +import { Action, Operation, RequestAction, ResponseAction } from './base-protocol'; +import { SModelRootSchema } from './model-structure'; +import { ElementAndAlignment, ElementAndBounds } from './types'; + +/** + * Sent from the server to the client to request bounds for the given model. The model is rendered invisibly so the bounds can + * derived from the DOM. The response is a ComputedBoundsAction. This hidden rendering round-trip is necessary if the client is responsible + * for parts of the layout. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RequestBoundsActions`. + */ +export interface RequestBoundsAction extends RequestAction, sprotty.RequestBoundsAction { + kind: typeof RequestBoundsAction.KIND; + + /** + * The model root element for which to compute the bounds. + */ + newRoot: SModelRootSchema; +} + +export namespace RequestBoundsAction { + export const KIND = 'requestBounds'; + + export function is(object: any): object is RequestBoundsAction { + return RequestAction.hasKind(object, KIND) && hasObjectProp(object, 'newRoot'); + } + + export function create(newRoot: SModelRootSchema, options: { requestId?: string } = {}): RequestBoundsAction { + return { + kind: KIND, + requestId: '', + newRoot, + ...options + }; + } +} + +/** + * Sent from the client to the server to transmit the result of bounds computation as a response to a {@link RequestBoundsAction}. + * If the server is responsible for parts of the layout, it can do so after applying the computed bounds received with this action. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ComputedBoundsActions`. + */ +export interface ComputedBoundsAction extends ResponseAction, sprotty.ComputedBoundsAction { + kind: typeof ComputedBoundsAction.KIND; + /** + * The new bounds of the model elements. + */ + bounds: ElementAndBounds[]; + + /* + * The revision number. + */ + revision?: number; + + /** + * The new alignment of the model elements. + */ + alignments?: ElementAndAlignment[]; +} + +export namespace ComputedBoundsAction { + export const KIND = 'computedBounds'; + + export function is(object: any): object is ComputedBoundsAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'bounds'); + } + + export function create( + bounds: ElementAndBounds[], + options: { + revision?: number; + responseId?: string; + alignments?: ElementAndAlignment[]; + } = {} + ): ComputedBoundsAction { + return { + kind: KIND, + responseId: '', + bounds, + ...options + }; + } +} + +/** + * Request a layout of the diagram or the selected elements only. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `LayoutOperations`. + */ +export interface LayoutOperation extends Operation, Omit { + kind: typeof LayoutOperation.KIND; + + /** + * The identifiers of the elements that should be layouted, may be just the root element. + */ + elementIds: string[]; +} + +export namespace LayoutOperation { + export const KIND = 'layout'; + + export function is(object: any): object is LayoutOperation { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds'); + } + + export function create(elementIds: string[]): LayoutOperation { + return { + kind: KIND, + isOperation: true, + elementIds + }; + } +} diff --git a/packages/protocol/src/action-protocol/model-saving.spec.ts b/packages/protocol/src/action-protocol/model-saving.spec.ts new file mode 100644 index 0000000..0aa1f61 --- /dev/null +++ b/packages/protocol/src/action-protocol/model-saving.spec.ts @@ -0,0 +1,174 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ + +import { expect } from 'chai'; +import { ExportSvgAction, RequestExportSvgAction, SaveModelAction, SetDirtyStateAction } from './model-saving'; +/** + * Tests for the utility functions declared in the namespaces of the protocol + * action definitions. + */ + +describe('Model saving actions', () => { + describe('SaveModelAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SaveModelAction = { + kind: 'saveModel' + }; + expect(SaveModelAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SaveModelAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SaveModelAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SaveModelAction = { + kind: 'saveModel' + }; + + expect(SaveModelAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SaveModelAction = { + kind: 'saveModel', + fileUri: 'myUri' + }; + const { fileUri } = expected; + expect(SaveModelAction.create({ fileUri })).to.deep.equals(expected); + }); + }); + }); + + describe('SetDirtyStateAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: SetDirtyStateAction = { + kind: 'setDirtyState', + isDirty: true + }; + expect(SetDirtyStateAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(SetDirtyStateAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(SetDirtyStateAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: SetDirtyStateAction = { + kind: 'setDirtyState', + isDirty: true + }; + const { isDirty } = expected; + expect(SetDirtyStateAction.create(isDirty)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: SetDirtyStateAction = { + kind: 'setDirtyState', + isDirty: false, + reason: 'save' + }; + const { isDirty, reason } = expected; + expect(SetDirtyStateAction.create(isDirty, { reason })).to.deep.equals(expected); + }); + }); + }); + + describe('RequestExportSvgAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RequestExportSvgAction = { + kind: 'requestExportSvg', + requestId: '' + }; + expect(RequestExportSvgAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RequestExportSvgAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RequestExportSvgAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: RequestExportSvgAction = { + kind: 'requestExportSvg', + requestId: '' + }; + expect(RequestExportSvgAction.create()).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: RequestExportSvgAction = { + kind: 'requestExportSvg', + requestId: 'myRequest' + }; + const { requestId } = expected; + expect(RequestExportSvgAction.create({ requestId })).to.deep.equals(expected); + }); + }); + }); + + describe('ExportSvgAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ExportSvgAction = { + kind: 'exportSvg', + responseId: '', + svg: '' + }; + expect(ExportSvgAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ExportSvgAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ExportSvgAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ExportSvgAction = { + kind: 'exportSvg', + responseId: '', + svg: 'someSvg' + }; + const { svg } = expected; + expect(ExportSvgAction.create(svg)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ExportSvgAction = { + kind: 'exportSvg', + responseId: 'responseId', + svg: 'someSvg' + }; + const { svg, responseId } = expected; + expect(ExportSvgAction.create(svg, { responseId })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index d29b821..ac90508 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,42 +13,124 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isBoolean, isString } from '../utils/typeguard-util'; -import { Action, isActionKind, ResponseAction } from './base-protocol'; +import { hasBooleanProp, hasStringProp } from '../utils/type-util'; +import { Action, RequestAction, ResponseAction } from './base-protocol'; -export class SaveModelAction implements Action { - static readonly KIND = 'saveModel'; - constructor(public readonly fileUri?: string, public readonly kind: string = SaveModelAction.KIND) {} +/** + * Sent from the client to the server in order to persist the current model state back to the model source. + * A new fileUri can be defined to save the model to a new destination different from its original model source. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SaveModelActions`. + */ +export interface SaveModelAction extends Action { + kind: typeof SaveModelAction.KIND; + /** + * The optional destination file uri. + */ + fileUri?: string; } -export function isSaveModelAction(action?: any): action is SaveModelAction { - return isActionKind(action, SaveModelAction.KIND); +export namespace SaveModelAction { + export const KIND = 'saveModel'; + + export function is(object: any): object is SaveModelAction { + return Action.hasKind(object, KIND); + } + + export function create(options: { fileUri?: string } = {}): SaveModelAction { + return { + kind: KIND, + ...options + }; + } } -export class SetDirtyStateAction implements Action { - static readonly KIND = 'setDirtyState'; - constructor(public readonly isDirty: boolean, public readonly reason?: string, public readonly kind = SetDirtyStateAction.KIND) {} +/** + * The server sends this action to indicate to the client that the current model state on the server does not correspond + * to the persisted model state of the model source. A client may ignore such an action or use it to indicate to the user the dirty state. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `SetDirtyStateActions`. + */ +export interface SetDirtyStateAction extends Action { + kind: typeof SetDirtyStateAction.KIND; + /** + * True if the current model state is dirty + */ + isDirty: boolean; + + /** + * A string indicating the reason for the dirty state change e.g 'operation', 'undo',... + */ + reason?: DirtyStateChangeReason; } -export namespace DirtyStateChangeReason { - export const OPERATION = 'operation'; - export const UNDO = 'undo'; - export const REDO = 'redo'; - export const SAVE = 'save'; - export const EXTERNAL = 'external'; +export type DirtyStateChangeReason = 'operation' | 'undo' | 'redo' | 'save' | 'external'; + +export namespace SetDirtyStateAction { + export const KIND = 'setDirtyState'; + + export function is(object: any): object is SetDirtyStateAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'isDirty'); + } + + export function create(isDirty: boolean, options: { reason?: DirtyStateChangeReason } = {}): SetDirtyStateAction { + return { + kind: KIND, + isDirty, + ...options + }; + } } -export function isSetDirtyStateAction(action?: any): action is SetDirtyStateAction { - return isActionKind(action, SetDirtyStateAction.KIND) && isBoolean(action, 'isDirty'); +/** + * A `RequestExportSvgAction` is sent by the client (or the server) to initiate the SVG export of the current diagram. + * The handler of this action is expected to retrieve the diagram SVG and should send a {@link ExportSvgAction} as response. + * Typically the {@link ExportSvgAction} is handled directly on client side. + */ +export interface RequestExportSvgAction extends RequestAction { + kind: typeof RequestExportSvgAction.KIND; } +export namespace RequestExportSvgAction { + export const KIND = 'requestExportSvg'; + + export function is(object: any): object is RequestExportSvgAction { + return RequestAction.hasKind(object, KIND); + } -export class ExportSvgAction implements ResponseAction { - static KIND = 'exportSvg'; - kind = ExportSvgAction.KIND; + export function create(options: { requestId?: string } = {}): RequestExportSvgAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } +} - constructor(public readonly svg: string, public readonly responseId: string = '') {} +/** + * The client sends an `ExportSvgAction` to indicate that the diagram, which represents the current model state, + * should be exported in SVG format. The action only provides the diagram SVG as plain string. The expected result of executing + * an `ExportSvgAction` is a new file in SVG-format on the underlying filesystem. However, other details like the target destination, + * concrete file name, file extension etc. are not specified in the protocol. So it is the responsibility of the action handler to + * process this information accordingly and export the result to the underlying filesystem. + */ +export interface ExportSvgAction extends ResponseAction { + kind: typeof ExportSvgAction.KIND; + svg: string; + responseId: string; } +export namespace ExportSvgAction { + export const KIND = 'exportSvg'; + + export function is(object: any): object is RequestExportSvgAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'svg'); + } -export function isExportSvgAction(action?: any): action is ExportSvgAction { - return isActionKind(action, ExportSvgAction.KIND) && isString(action, 'svg'); + export function create(svg: string, options: { responseId?: string } = {}): ExportSvgAction { + return { + kind: KIND, + svg, + responseId: '', + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/model-structure.ts b/packages/protocol/src/action-protocol/model-structure.ts index ddaa1ab..97b8e70 100644 --- a/packages/protocol/src/action-protocol/model-structure.ts +++ b/packages/protocol/src/action-protocol/model-structure.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,33 +13,52 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isString } from '../utils/typeguard-util'; -import { Bounds } from './types'; +import { Bounds } from 'sprotty-protocol'; +import * as sprotty from 'sprotty-protocol/lib/model'; +import { hasStringProp } from '../utils/type-util'; /** - * The schema of an SModelElement describes its serializable form. The actual model is created from - * its schema with an IModelFactory. - * Each model element must have a unique ID and a type that is used to look up its view. + * The schema of an SModelElement describes its serializable form. The actual class-based model is derived + * its schema whenever the client or server deserializes a received schema`. + * Each model element must have a unique ID and a type that is used on the client to look up its view. */ -export interface SModelElementSchema { - type: string; +export interface SModelElementSchema extends sprotty.SModelElement { id: string; + /** + * Type to look up the graphical representation of this element. + */ + type: string; + children?: SModelElementSchema[]; + + /** + * CSS classes that should be applied on the rendered SVG element representing this element. + */ cssClasses?: string[]; } -export function isSModelElementSchema(schema: any): schema is SModelElementSchema { - return schema !== undefined && typeof schema === 'object' && isString(schema, 'type') && isString(schema, 'id'); +export namespace SModelElementSchema { + /** + * Typeguard function to check wether the given object is an {@link SModelElementSchema}. + * @param object The object to check. + * @returns A type literal indicating wether the given object is of type {@link SModelElementSchema}. + */ + export function is(object: any): object is SModelElementSchema { + return typeof object === 'object' && hasStringProp(object, 'type') && hasStringProp(object, 'id'); + } } /** * Serializable schema for the root element of the model tree. */ export interface SModelRootSchema extends SModelElementSchema { + /** + * Bounds of this element in the canvas. + */ canvasBounds?: Bounds; - revision?: number; -} -export function isSModelRootSchema(schema: any): schema is SModelRootSchema { - return isSModelElementSchema(schema) && !('parent' in schema); + /** + * The revision number identifies single versions of the models sent by the server. + */ + revision?: number; } diff --git a/packages/protocol/src/action-protocol/node-modification.spec.ts b/packages/protocol/src/action-protocol/node-modification.spec.ts new file mode 100644 index 0000000..fa8213a --- /dev/null +++ b/packages/protocol/src/action-protocol/node-modification.spec.ts @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { Dimension, Point } from 'sprotty-protocol'; +import { ChangeBoundsOperation, ChangeContainerOperation } from './node-modification'; + +/** + * The schema of an SModelElement describes its serializable form. The actual class-based model is derived + * its schema whenever the client or server deserializes a received schema`. + * Each model element must have a unique ID and a type that is used on the client to look up its view. + */ + +describe('Node modification actions', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const operation: ChangeBoundsOperation = { + kind: 'changeBounds', + isOperation: true, + newBounds: [] + }; + expect(ChangeBoundsOperation.is(operation)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ChangeBoundsOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ChangeBoundsOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: ChangeBoundsOperation = { + kind: 'changeBounds', + isOperation: true, + newBounds: [{ elementId: 'someElement', newSize: Dimension.EMPTY, newPosition: Point.ORIGIN }] + }; + const { newBounds } = expected; + expect(ChangeBoundsOperation.create(newBounds)).to.deep.equals(expected); + }); + }); +}); + +describe('ChangeContainerOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: ChangeContainerOperation = { + kind: 'changeContainer', + isOperation: true, + elementId: '', + targetContainerId: '' + }; + expect(ChangeContainerOperation.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(ChangeContainerOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(ChangeContainerOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: ChangeContainerOperation = { + kind: 'changeContainer', + isOperation: true, + elementId: 'myElement', + targetContainerId: 'myContainer' + }; + const { elementId, targetContainerId } = expected; + expect(ChangeContainerOperation.create({ elementId, targetContainerId })).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: ChangeContainerOperation = { + kind: 'changeContainer', + isOperation: true, + elementId: 'myElement', + targetContainerId: 'myContainer', + location: Point.ORIGIN + }; + const { elementId, targetContainerId, location } = expected; + expect(ChangeContainerOperation.create({ elementId, targetContainerId, location })).to.deep.equals(expected); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/node-modification.ts b/packages/protocol/src/action-protocol/node-modification.ts index 94077d8..5eb014e 100644 --- a/packages/protocol/src/action-protocol/node-modification.ts +++ b/packages/protocol/src/action-protocol/node-modification.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,37 +14,77 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isString } from '../utils/typeguard-util'; -import { isActionKind, Operation } from './base-protocol'; -import { ElementAndBounds, Point } from './types'; +import { Point } from 'sprotty-protocol'; +import { hasArrayProp, hasStringProp } from '../utils/type-util'; +import { Operation } from './base-protocol'; +import { ElementAndBounds } from './types'; /** * Triggers the position or size change of elements. This action concerns only the element's graphical size and position. * Whether an element can be resized or repositioned may be specified by the server with a `TypeHint` to allow for immediate user feedback * before resizing or repositioning. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ChangeBoundsOperations`. */ -export class ChangeBoundsOperation implements Operation { - static readonly KIND = 'changeBounds'; - constructor(public newBounds: ElementAndBounds[], public readonly kind: string = ChangeBoundsOperation.KIND) {} +export interface ChangeBoundsOperation extends Operation { + kind: typeof ChangeBoundsOperation.KIND; + + newBounds: ElementAndBounds[]; } -export function isChangeBoundsOperation(action: any): action is ChangeBoundsOperation { - return isActionKind(action, ChangeBoundsOperation.KIND) && isArray(action, 'newBounds'); +export namespace ChangeBoundsOperation { + export const KIND = 'changeBounds'; + + export function is(object: any): object is ChangeBoundsOperation { + return Operation.hasKind(object, KIND) && hasArrayProp(object, 'newBounds'); + } + + export function create(newBounds: ElementAndBounds[]): ChangeBoundsOperation { + return { + kind: KIND, + isOperation: true, + newBounds + }; + } } /** - * The client sends a `ChangeContainerOperation` to the server to request the execution of a changeContainer operation. + * The client sends a `ChangeContainerOperation` to the server to request the a semantic move i.e. the corresponding element + * should be moved form its current container to the target container. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `ChangeContainerOperations`. */ -export class ChangeContainerOperation implements Operation { - static readonly KIND = 'changeContainer'; - constructor( - public readonly elementId: string, - public readonly targetContainerId: string, - public readonly location?: Point, - public readonly kind: string = ChangeContainerOperation.KIND - ) {} +export interface ChangeContainerOperation extends Operation { + kind: typeof ChangeContainerOperation.KIND; + + /** + * The element to be changed. + */ + elementId: string; + + /** + * The element container of the changeContainer operation. + */ + targetContainerId: string; + + /** + * The graphical location. + */ + location?: Point; } -export function isChangeContainerOperation(action: any): action is ChangeContainerOperation { - return isActionKind(action, ChangeContainerOperation.KIND) && isString(action, 'elementId') && isString(action, 'targetContainerId'); +export namespace ChangeContainerOperation { + export const KIND = 'changeContainer'; + + export function is(object: any): object is ChangeContainerOperation { + return Operation.hasKind(object, KIND) && hasStringProp(object, 'elementId') && hasStringProp(object, 'targetContainerId'); + } + + export function create(options: { elementId: string; targetContainerId: string; location?: Point }): ChangeContainerOperation { + return { + kind: KIND, + isOperation: true, + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/tool-palette.spec.ts b/packages/protocol/src/action-protocol/tool-palette.spec.ts new file mode 100644 index 0000000..b0e0fdb --- /dev/null +++ b/packages/protocol/src/action-protocol/tool-palette.spec.ts @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from './tool-palette'; + +describe('Tool palette Actions', () => { + describe('TrigerNodeCreationAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: TriggerNodeCreationAction = { + kind: 'triggerNodeCreation', + elementTypeId: '' + }; + expect(TriggerNodeCreationAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(TriggerNodeCreationAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(TriggerNodeCreationAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: TriggerNodeCreationAction = { + kind: 'triggerNodeCreation', + elementTypeId: 'myElementType' + }; + const { elementTypeId } = expected; + expect(TriggerNodeCreationAction.create(elementTypeId)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: TriggerNodeCreationAction = { + kind: 'triggerNodeCreation', + elementTypeId: 'myElementType', + args: { some: 'args' } + }; + const { elementTypeId, args } = expected; + expect(TriggerNodeCreationAction.create(elementTypeId, { args })).to.deep.equals(expected); + }); + }); + }); + + describe('TriggerEdgeCreationAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: TriggerEdgeCreationAction = { + kind: 'triggerEdgeCreation', + elementTypeId: '' + }; + expect(TriggerEdgeCreationAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(TriggerEdgeCreationAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(TriggerEdgeCreationAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: TriggerEdgeCreationAction = { + kind: 'triggerEdgeCreation', + elementTypeId: 'myEdge' + }; + const { elementTypeId } = expected; + expect(TriggerEdgeCreationAction.create(elementTypeId)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: TriggerEdgeCreationAction = { + kind: 'triggerEdgeCreation', + elementTypeId: 'myEdge', + args: { some: 'args' } + }; + const { elementTypeId, args } = expected; + expect(TriggerEdgeCreationAction.create(elementTypeId, { args })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/tool-palette.ts b/packages/protocol/src/action-protocol/tool-palette.ts index 3dfc6ab..f1d38db 100644 --- a/packages/protocol/src/action-protocol/tool-palette.ts +++ b/packages/protocol/src/action-protocol/tool-palette.ts @@ -14,54 +14,79 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isString } from '../utils/typeguard-util'; -import { Action, isAction } from './base-protocol'; +import { hasStringProp } from '../utils/type-util'; +import { Action } from './base-protocol'; import { Args } from './types'; -export abstract class TriggerElementCreationAction implements Action { - constructor(public readonly elementTypeId: string, readonly args?: Args, public readonly kind: string = 'unknown') {} -} - -export function isTriggerElementTypeCreationAction(action: any): action is TriggerElementCreationAction { - return isAction(action) && isString(action, 'elementTypeId'); -} - /** * Triggers the enablement of the tool that is responsible for creating nodes and initializes it with the creation of nodes of the given * `elementTypeId`. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `TriggerNodeCreationActions`. */ -export class TriggerNodeCreationAction extends TriggerElementCreationAction { - static readonly KIND = 'triggerNodeCreation'; +export interface TriggerNodeCreationAction extends Action { + kind: typeof TriggerNodeCreationAction.KIND; - constructor( - override readonly elementTypeId: string, - override readonly args?: Args, - override readonly kind = TriggerNodeCreationAction.KIND - ) { - super(elementTypeId, args, kind); - } + /** + * The type of edge that should be created by the nodes creation tool. + */ + elementTypeId: string; + + /** + * Custom arguments. + */ + args?: Args; } -export function isTriggerNodeCreationAction(action: any): action is TriggerNodeCreationAction { - return isTriggerElementTypeCreationAction(action) && action.kind === TriggerNodeCreationAction.KIND; +export namespace TriggerNodeCreationAction { + export const KIND = 'triggerNodeCreation'; + + export function is(object: any): object is TriggerNodeCreationAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'elementTypeId'); + } + + export function create(elementTypeId: string, options: { args?: Args } = {}): TriggerNodeCreationAction { + return { + kind: KIND, + elementTypeId, + ...options + }; + } } /** * Triggers the enablement of the tool that is responsible for creating edges and initializes it with the creation of edges of the given * `elementTypeId`. + * + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `TriggerEdgeCreationActions`. */ -export class TriggerEdgeCreationAction extends TriggerElementCreationAction { - static readonly KIND = 'triggerEdgeCreation'; +export interface TriggerEdgeCreationAction extends Action { + kind: typeof TriggerEdgeCreationAction.KIND; - constructor( - override readonly elementTypeId: string, - override readonly args?: Args, - override readonly kind: string = TriggerEdgeCreationAction.KIND - ) { - super(elementTypeId, args, kind); - } + /** + * The type of edge that should be created by the edge creation tool. + */ + elementTypeId: string; + + /** + * Custom arguments. + */ + args?: Args; } -export function isTriggerEdgeCreationAction(action: any): action is TriggerEdgeCreationAction { - return isTriggerElementTypeCreationAction(action) && action.kind === TriggerEdgeCreationAction.KIND; +export namespace TriggerEdgeCreationAction { + export const KIND = 'triggerEdgeCreation'; + + export function is(object: any): object is TriggerEdgeCreationAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'elementTypeId'); + } + + export function create(elementTypeId: string, options: { args?: Args } = {}): TriggerEdgeCreationAction { + return { + kind: KIND, + elementTypeId, + ...options + }; + } } diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index 378415f..c21b61c 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,55 +13,50 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as sprotty from 'sprotty-protocol'; +import { Dimension, Point } from 'sprotty-protocol'; +import { AnyObject, hasArrayProp, hasStringProp } from '../utils/type-util'; import { Action } from './base-protocol'; - -export type JsonPrimitive = string | number | boolean; +import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from './tool-palette'; +// A collection of convenience and utility types that are used in the GLSP action protocol. /** * A key-value pair structure for primitive typed custom arguments. */ export interface Args { - [key: string]: JsonPrimitive; -} - -export interface Point { - readonly x: number; - readonly y: number; -} - -export const ORIGIN_POINT: Point = Object.freeze({ - x: 0, - y: 0 -}); - -export interface Dimension { - readonly width: number; - readonly height: number; + [key: string]: sprotty.JsonPrimitive; } -export const EMPTY_DIMENSION: Dimension = Object.freeze({ - width: -1, - height: -1 -}); - /** - * The bounds are the position (x, y) and dimension (width, height) of an object. + * The ElementAndBounds type is used to associate new bounds with a model element, which is referenced via its id. */ -export interface Bounds extends Point, Dimension {} - -export const EMPTY_BOUNDS: Bounds = Object.freeze({ ...EMPTY_DIMENSION, ...ORIGIN_POINT }); - -export interface ElementAndBounds { +export interface ElementAndBounds extends sprotty.ElementAndBounds { + /** + * The identifier of the element. + */ elementId: string; - newPosition?: Point; + /** + * The new size of the element. + */ newSize: Dimension; + /** + * The new position of the element. + */ + newPosition?: Point; } /** - * Associates a new alignment with a model element, which is referenced via its id. + * The `ElementAndAlignment` type is used to associate a new alignment with a model element, which is referenced via its id. + * Typically used to align label relative to their parent element. */ -export interface ElementAndAlignment { +export interface ElementAndAlignment extends sprotty.ElementAndAlignment { + /** + * The identifier of the element. + */ elementId: string; + /** + * The new alignment of the element. + */ newAlignment: Point; } @@ -102,18 +97,100 @@ export interface EditorContext { readonly args?: Args; } +export namespace EditorContext { + export function is(object: any): object is EditorContext { + return object !== undefined && hasArrayProp(object, 'selectedElementIds'); + } +} /** - *Labeled actions are used to denote a group of actions in a user-interface context, e.g., to define an entry in the command palette or - *in the context menu. + * Labeled actions are used to denote a group of actions in a user-interface context, e.g., to define an entry in the command palette or + * in the context menu. + * The corresponding namespace offers a helper function for type guard checks. */ -export class LabeledAction { - constructor(readonly label: string, readonly actions: Action[], readonly icon?: string) {} +export interface LabeledAction { + /** + * Group label. + */ + label: string; + + /** + * Actions in the group. + */ + actions: Action[]; + /** + * Optional group icon. + */ + icon?: string; } -export function isLabeledAction(element: any): element is LabeledAction { - return element !== undefined && typeof element === 'object' && 'label' in element && 'actions' in element; +export namespace LabeledAction { + export function is(object: any): object is LabeledAction { + return AnyObject.is(object) && hasStringProp(object, 'label') && hasArrayProp(object, 'actions'); + } + + export function toActionArray(input: LabeledAction | Action[] | Action): Action[] { + if (Array.isArray(input)) { + return input; + } else if (LabeledAction.is(input)) { + return input.actions; + } + return [input]; + } } -export type JsonAny = JsonPrimitive | Args | JsonArray | null; +/** + * A special {@link LabeledAction} that is used to denote actions that should be triggered when the user + * click a tool palette item (e.g. a button to create a new node)., + */ +export interface PaletteItem extends LabeledAction { + /** Technical id of the palette item. */ + readonly id: string; + /** String indicating the order. */ + readonly sortString: string; + /** Children of this item. If this item has children, the client will know to render them as sub group. */ + readonly children?: PaletteItem[]; +} -export interface JsonArray extends Array {} +export namespace PaletteItem { + export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { + if (item) { + const initialActions = item.actions + .filter(a => isTriggerElementCreationAction(a)) + .map(action => action as TriggerElementCreationAction); + return initialActions.length > 0 ? initialActions[0] : undefined; + } + return undefined; + } + + export type TriggerElementCreationAction = TriggerEdgeCreationAction | TriggerNodeCreationAction; + + export function isTriggerElementCreationAction(object: any): object is TriggerElementCreationAction { + return TriggerNodeCreationAction.is(object) || TriggerEdgeCreationAction.is(object); + } +} + +/** + * A special {@link LabeledAction} that is used to denote items in a menu. + */ +export interface MenuItem extends LabeledAction { + /** Technical id of the menu item. */ + readonly id: string; + /** String indicating the order. */ + readonly sortString?: string; + /** String indicating the grouping (separators). Items with equal group will be in the same group. */ + readonly group?: string; + /** + * The optional parent id can be used to add this element as a child of another element provided by another menu provider. + * The `parentId` must be fully qualified in the form of `a.b.c`, whereas `a`, `b` and `c` are referring to the IDs of other elements. + * Note that this attribute will only be considered for root items of a provider and not for children of provided items. + */ + readonly parentId?: string; + /** Function determining whether the element is enabled. */ + readonly isEnabled?: () => boolean; + /** Function determining whether the element is visible. */ + readonly isVisible?: () => boolean; + /** Function determining whether the element is toggled on or off. */ + readonly isToggled?: () => boolean; + /** Children of this item. If this item has children, they will be added into a submenu of this item. */ + children?: MenuItem[]; +} diff --git a/packages/protocol/src/action-protocol/undo-redo.spec.ts b/packages/protocol/src/action-protocol/undo-redo.spec.ts new file mode 100644 index 0000000..36ee65d --- /dev/null +++ b/packages/protocol/src/action-protocol/undo-redo.spec.ts @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { RedoOperation, UndoOperation } from './undo-redo'; + +describe('Undo & Redo Actions', () => { + describe('UndoOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: UndoOperation = { + kind: 'glspUndo', + isOperation: true + }; + expect(UndoOperation.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(UndoOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(UndoOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: UndoOperation = { + kind: 'glspUndo', + isOperation: true + }; + expect(UndoOperation.create()).to.deep.equals(expected); + }); + }); + }); + + describe('RedoOperation', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: RedoOperation = { + kind: 'glspRedo', + isOperation: true + }; + expect(RedoOperation.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(RedoOperation.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(RedoOperation.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments', () => { + const expected: RedoOperation = { + kind: 'glspRedo', + isOperation: true + }; + expect(RedoOperation.create()).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/undo-redo.ts b/packages/protocol/src/action-protocol/undo-redo.ts index a998a47..c997518 100644 --- a/packages/protocol/src/action-protocol/undo-redo.ts +++ b/packages/protocol/src/action-protocol/undo-redo.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,28 +14,52 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isActionKind, Operation } from './base-protocol'; +import { Operation } from './base-protocol'; /** * Trigger an undo of the latest executed command. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `UndoOperations`. */ -export class UndoOperation implements Operation { - static readonly KIND = 'glspUndo'; - constructor(public readonly kind = UndoOperation.KIND) {} +export interface UndoOperation extends Operation { + kind: typeof UndoOperation.KIND; } -export function isUndoOperation(action: any): action is UndoOperation { - return isActionKind(action, UndoOperation.KIND); +export namespace UndoOperation { + export const KIND = 'glspUndo'; + + export function is(object: any): object is UndoOperation { + return Operation.hasKind(object, KIND); + } + + export function create(): UndoOperation { + return { + kind: KIND, + isOperation: true + }; + } } /** * Trigger a redo of the latest undone command. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `RedoOperations`. */ -export class RedoOperation implements Operation { - static readonly KIND = 'glspRedo'; - constructor(public readonly kind = RedoOperation.KIND) {} +export interface RedoOperation extends Operation { + kind: typeof RedoOperation.KIND; } -export function isRedoOperation(action: any): action is RedoOperation { - return isActionKind(action, RedoOperation.KIND); +export namespace RedoOperation { + export const KIND = 'glspRedo'; + + export function is(object: any): object is RedoOperation { + return Operation.hasKind(object, KIND); + } + + export function create(): RedoOperation { + return { + kind: KIND, + isOperation: true + }; + } } diff --git a/packages/protocol/src/action-protocol/viewport.spec.ts b/packages/protocol/src/action-protocol/viewport.spec.ts new file mode 100644 index 0000000..f5afb2b --- /dev/null +++ b/packages/protocol/src/action-protocol/viewport.spec.ts @@ -0,0 +1,105 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/* eslint-disable max-len */ +import { expect } from 'chai'; +import { CenterAction, FitToScreenAction } from './viewport'; + +describe('Viewport Actions', () => { + describe('CenterAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: CenterAction = { + kind: 'center', + elementIds: [], + animate: true, + retainZoom: true + }; + expect(CenterAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(CenterAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(CenterAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: CenterAction = { + kind: 'center', + elementIds: ['my', 'elements'], + animate: true, + retainZoom: false + }; + const { elementIds } = expected; + expect(CenterAction.create(elementIds)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: CenterAction = { + kind: 'center', + elementIds: ['my', 'elements'], + animate: false, + retainZoom: true + }; + const { elementIds, animate, retainZoom } = expected; + expect(CenterAction.create(elementIds, { animate, retainZoom })).to.deep.equals(expected); + }); + }); + }); + + describe('FitToScreenAction', () => { + describe('is', () => { + it('should return true for an object having the correct type and a value for all required interface properties', () => { + const action: FitToScreenAction = { + kind: 'fit', + elementIds: [], + animate: true + }; + expect(FitToScreenAction.is(action)).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(FitToScreenAction.is(undefined)).to.be.false; + }); + it('should return false for an object that does not have all required interface properties', () => { + expect(FitToScreenAction.is({ kind: 'notTheRightOne' })).to.be.false; + }); + }); + + describe('create', () => { + it('should return an object conforming to the interface with matching properties for the given required arguments and default values for the optional arguments', () => { + const expected: FitToScreenAction = { + kind: 'fit', + elementIds: ['my', 'elements'], + animate: true + }; + const { elementIds } = expected; + expect(FitToScreenAction.create(elementIds)).to.deep.equals(expected); + }); + it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => { + const expected: FitToScreenAction = { + kind: 'fit', + elementIds: ['my', 'elements'], + animate: false, + maxZoom: 4, + padding: 12 + }; + const { elementIds, maxZoom, padding, animate } = expected; + expect(FitToScreenAction.create(elementIds, { maxZoom, padding, animate })).to.deep.equals(expected); + }); + }); + }); +}); diff --git a/packages/protocol/src/action-protocol/viewport.ts b/packages/protocol/src/action-protocol/viewport.ts index 819cab3..de5da5e 100644 --- a/packages/protocol/src/action-protocol/viewport.ts +++ b/packages/protocol/src/action-protocol/viewport.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 STMicroelectronics and others. + * Copyright (c) 2021-2022 STMicroelectronics and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,41 +13,100 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { isArray, isBoolean } from '../utils/typeguard-util'; -import { Action, isActionKind } from './base-protocol'; +import * as sprotty from 'sprotty-protocol/lib/actions'; +import { hasArrayProp, hasBooleanProp } from '../utils/type-util'; +import { Action } from './base-protocol'; /** * Centers the viewport on the elements with the given identifiers. It changes the scroll setting of the viewport accordingly and resets - * the zoom to its default. This action can also be created on the client but it can also be sent by the server in order to perform such - * a viewport change remotely. + * the zoom to its default. This action is usually be created on the client but it can also be sent by the server in order to perform such + * a viewport change remotely. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CenterActions`. */ -export class CenterAction implements Action { - static readonly KIND = 'center'; - readonly kind = CenterAction.KIND; - - constructor( - public readonly elementIds: string[], - public readonly animate: boolean = true, - public readonly retainZoom: boolean = false - ) {} +export interface CenterAction extends Action, sprotty.CenterAction { + kind: typeof CenterAction.KIND; + + /** + * The identifier of the elements on which the viewport should be centered. + * If empty the root element will be used. + */ + elementIds: string[]; + + /** + * Indicate if the modification of the viewport should be realized with or without support of animations. + */ + animate: boolean; + + /** + * Indicates whether the zoom level should be kept. + */ + retainZoom: boolean; } -export function isCenterAction(action: any): action is CenterAction { - return isActionKind(action, CenterAction.KIND) && isBoolean(action, 'animate') && isBoolean(action, 'retainZoom'); +export namespace CenterAction { + export const KIND = 'center'; + + export function is(object: any): object is CenterAction { + return Action.hasKind(object, KIND) && hasBooleanProp(object, 'animate') && hasBooleanProp(object, 'retainZoom'); + } + + export function create(elementIds: string[], options: { animate?: boolean; retainZoom?: boolean } = {}): CenterAction { + return { + kind: KIND, + animate: true, + retainZoom: false, + elementIds, + ...options + }; + } } -export class FitToScreenAction implements Action { - static readonly KIND = 'fit'; - readonly kind = FitToScreenAction.KIND; +/** + * Triggers to fit all or a list of elements into the available diagram canvas. + * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks + * and creating new `CenterActions`. + */ +export interface FitToScreenAction extends Action, sprotty.FitToScreenAction { + kind: typeof FitToScreenAction.KIND; + + /** + * The identifier of the elements to fit on screen. + */ + elementIds: string[]; + + /** + * The padding that should be visible on the viewport. + */ + padding?: number; - constructor( - public readonly elementIds: string[], - public readonly padding?: number, - public readonly maxZoom?: number, - public readonly animate: boolean = true - ) {} + /** + * The max zoom level authorized. + */ + maxZoom?: number; + + /** + * Indicate if the action should be performed with animation support or not. + */ + animate: boolean; } -export function isFitToScreenAction(action: any): action is FitToScreenAction { - return isActionKind(action, FitToScreenAction.KIND) && isArray(action, 'elementIds') && isBoolean(action, 'animate'); +export namespace FitToScreenAction { + export const KIND = 'fit'; + + export function is(object: any): object is FitToScreenAction { + return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds') && hasBooleanProp(object, 'animate'); + } + + export function create( + elementIds: string[], + options: { padding?: number; maxZoom?: number; animate?: boolean } = {} + ): FitToScreenAction { + return { + kind: KIND, + animate: true, + elementIds, + ...options + }; + } } diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index e433980..09dea73 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,11 +13,45 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { SetBoundsAction, SetViewportAction } from 'sprotty-protocol/lib/actions'; +import { Action } from './action-protocol'; +import { hasBooleanProp, hasObjectProp, hasStringProp } from './utils/type-util'; + +// Add the is() function to the namespace declarations of sprotty-protocol actions +declare module 'sprotty-protocol/lib/actions' { + // eslint-disable-next-line no-shadow + namespace SetViewportAction { + export function is(object: any): object is SetViewportAction; + } + + // eslint-disable-next-line no-shadow + namespace SetBoundsAction { + export function is(object: any): object is SetBoundsAction; + } +} + +SetViewportAction.is = (object: any): object is SetViewportAction => + Action.hasKind(object, SetViewportAction.KIND) && + hasStringProp(object, 'elementId') && + hasObjectProp(object, 'newViewport') && + hasBooleanProp(object, 'animate'); + +SetBoundsAction.is = (object: any): object is SetBoundsAction => + Action.hasKind(object, SetBoundsAction.KIND) && hasObjectProp(object, 'bounds'); + +// Partial reexport of sprotty-protocol +export { Viewport } from 'sprotty-protocol/lib/model'; +export * from 'sprotty-protocol/lib/utils/async'; +export * from 'sprotty-protocol/lib/utils/geometry'; +export * from 'sprotty-protocol/lib/utils/json'; +export * from 'sprotty-protocol/lib/utils/model-utils'; +// Default export of @eclipse-glsp/protocol export * from './action-protocol'; export * from './glsp-client'; export * from './jsonrpc/base-jsonrpc-glsp-client'; export * from './jsonrpc/glsp-jsonrpc-client'; -export * from './types/default-types'; +export * from './model/default-types'; +export * from './model/model-schema'; export * from './utils/array-util'; -export * from './utils/launch-util'; -export * from './utils/typeguard-util'; +export * from './utils/type-util'; +export { SetBoundsAction, SetViewportAction }; diff --git a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts index e522f95..6a5cef7 100644 --- a/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts +++ b/packages/protocol/src/jsonrpc/base-jsonrpc-glsp-client.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from 'sprotty'; import { Message, MessageConnection } from 'vscode-ws-jsonrpc'; +import { ActionMessage } from '../action-protocol'; import { ActionMessageHandler, ClientState, diff --git a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts index 47461e7..d0b84c9 100644 --- a/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts +++ b/packages/protocol/src/jsonrpc/glsp-jsonrpc-client.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020-2021 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ActionMessage } from 'sprotty'; import { MessageConnection, NotificationType, RequestType } from 'vscode-jsonrpc'; import { createMessageConnection, @@ -23,6 +22,7 @@ import { WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; +import { ActionMessage } from '../action-protocol/base-protocol'; import { DisposeClientSessionParameters, GLSPClient, diff --git a/packages/protocol/src/types/default-types.ts b/packages/protocol/src/model/default-types.ts similarity index 93% rename from packages/protocol/src/types/default-types.ts rename to packages/protocol/src/model/default-types.ts index 9f327db..ce60376 100644 --- a/packages/protocol/src/types/default-types.ts +++ b/packages/protocol/src/model/default-types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2021 EclipseSource and others. + * Copyright (c) 2021-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,6 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** + * The default types for graphical model elements available in GLSP. + */ export namespace DefaultTypes { // HTML elements export const HTML = 'html'; diff --git a/packages/protocol/src/model/model-schema.ts b/packages/protocol/src/model/model-schema.ts new file mode 100644 index 0000000..e0f3939 --- /dev/null +++ b/packages/protocol/src/model/model-schema.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/** + * Reexport of the sprotty-protocol model definitions with a `Schema` suffix. We use a class based + * model representation on client and server side. The `Schema` suffix is used for type that represent + * the serializable description of these graphical model elements. + * The basic types `SModelElementSchema` and `SRootElementSchema` are already exported by the base action protocol so + * we only have to reexport the advanced model element interfaces from sprotty-protocol. + */ +export { + HtmlRoot as HtmlRootSchema, + PreRenderedElement as PreRenderedElementSchema, + SCompartment as SCompartmentSchema, + SEdge as SEdgeSchema, + SGraph as SGraphSchema, + ShapedPreRenderedElement as ShapedPreRenderedElementSchema, + SLabel as SLabelSchema, + SNode as SNodeSchema, + SPort as SPortSchema, + SShapeElement as SShapeElementSchema +} from 'sprotty-protocol/lib/model'; diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index d04cd70..1e21d78 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -18,6 +18,26 @@ */ export type MaybeArray = T | T[]; +/** + * Returns the first element of the given array. + * @param array the array. + * @returns the element at index 0. + */ +export function first(array: T[]): number; +/** + * Returns the first n elements of the given array. + * @param array the array. + * @param n the number of elements that should be returned + * @returns the first n elements of the array + */ +export function first(array: T[], n: number): T[]; +export function first(array: T[], n?: number): T[] | T { + if (n) { + return array.filter((_, index) => index < n); + } + return array[0]; +} + /** * Removes the given values from the given array (if present). * @param array The array to execute the remove operation on. diff --git a/packages/protocol/src/utils/type-util.spec.ts b/packages/protocol/src/utils/type-util.spec.ts new file mode 100644 index 0000000..c839d3d --- /dev/null +++ b/packages/protocol/src/utils/type-util.spec.ts @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { AnyObject, hasArrayProp, hasBooleanProp, hasNumberProp, hasObjectProp, hasStringProp } from './type-util'; + +describe('TypeUtil', () => { + describe('AnyObject', () => { + describe('is', () => { + it('should return true for an empty object', () => { + expect(AnyObject.is({})).to.be.true; + }); + it('should return true for an object with arbitrary properties', () => { + expect(AnyObject.is({ a: 'a', b: 5 })).to.be.true; + }); + it('should return false for `undefined`', () => { + expect(AnyObject.is(undefined)).to.be.false; + }); + it('should return false for `null`', () => { + // eslint-disable-next-line no-null/no-null + expect(AnyObject.is(null)).to.be.false; + }); + it('should return false for any object of primitive type', () => { + expect(AnyObject.is('')).to.be.false; + expect(AnyObject.is(5)).to.be.false; + expect(AnyObject.is(true)).to.be.false; + + expect( + AnyObject.is(() => { + /**/ + }) + ).to.be.false; + expect(AnyObject.is(Symbol('Symbol'))); + }); + }); + }); + + describe('hasStringProp', () => { + it('should return true for an object that has a property that matches the given key and type', () => { + expect(hasStringProp({ someProp: 'someKey' }, 'someProp')).to.be.true; + }); + it('should return false for an object that has a property that matches the given but not the given type', () => { + expect(hasStringProp({ someProp: 123 }, 'someProp')).to.be.false; + }); + it('should return false for an object that does not have a property that matches the given key.', () => { + expect(hasStringProp({ anotherProp: 123 }, 'someProp')).to.be.false; + }); + }); + + describe('hasBooleanProp', () => { + it('should return true for an object that has a property that matches the given key and type', () => { + expect(hasBooleanProp({ someProp: true }, 'someProp')).to.be.true; + }); + it('should return false for an object that has a property that matches the given but not the given type', () => { + expect(hasBooleanProp({ someProp: 123 }, 'someProp')).to.be.false; + }); + it('should return false for an object that does not have a property that matches the given key.', () => { + expect(hasBooleanProp({ anotherProp: 123 }, 'someProp')).to.be.false; + }); + }); + + describe('hasNumberProp', () => { + it('should return true for an object that has a property that matches the given key and type', () => { + expect(hasNumberProp({ someProp: 123 }, 'someProp')).to.be.true; + }); + it('should return false for an object that has a property that matches the given but not the given type', () => { + expect(hasNumberProp({ someProp: '123' }, 'someProp')).to.be.false; + }); + it('should return false for an object that does not have a property that matches the given key.', () => { + expect(hasBooleanProp({ anotherProp: 123 }, 'someProp')).to.be.false; + }); + }); + + describe('hasObjectProp', () => { + it('should return true for an object that has a property that matches the given key and type', () => { + expect(hasObjectProp({ someProp: { value: 'someKey' } }, 'someProp')).to.be.true; + }); + it('should return false for an object that has a property that matches the given but not the given type', () => { + expect(hasObjectProp({ someProp: '123' }, 'someProp')).to.be.false; + }); + it('should return false for an object that does not have a property that matches the given key.', () => { + expect(hasObjectProp({ anotherProp: 123 }, 'someProp')).to.be.false; + }); + }); + + describe('hasArrayProp', () => { + it('should return true for an object that has a property that matches the given key and type', () => { + expect(hasArrayProp({ someProp: ['some', 'prop'] }, 'someProp')).to.be.true; + }); + it('should return false for an object that has a property that matches the given but not the given type', () => { + expect(hasArrayProp({ someProp: '123' }, 'someProp')).to.be.false; + }); + it('should return false for an object that does not have a property that matches the given key.', () => { + expect(hasArrayProp({ anotherProp: 123 }, 'someProp')).to.be.false; + }); + }); +}); diff --git a/packages/protocol/src/utils/typeguard-util.ts b/packages/protocol/src/utils/type-util.ts similarity index 70% rename from packages/protocol/src/utils/typeguard-util.ts rename to packages/protocol/src/utils/type-util.ts index 48e0414..a2df90d 100644 --- a/packages/protocol/src/utils/typeguard-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -14,6 +14,25 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** + * The built-in 'object' & 'Object' types are currently hard to use + * an should be avoided. It's recommended to use Record instead to describe the + * type meaning of "any object"; + */ +export type AnyObject = Record; + +export namespace AnyObject { + /** + * Type guard to check wether a given object is of type {@link AnyObject}. + * @param object The object to check. + * @returns The given object as {@link AnyObject} or `false`. + */ + export function is(object: unknown): object is AnyObject { + // eslint-disable-next-line no-null/no-null + return object !== null && typeof object === 'object'; + } +} + /** * Utility type to describe typeguard functions. */ @@ -25,7 +44,7 @@ export type TypeGuard = (element: any, ...args: any[]) => element is T; * @param propertyKey The key of the property * @returns `true` if the object has property with matching key of type `string`. */ -export function isString(object: any, propertyKey: string): boolean { +export function hasStringProp(object: AnyObject, propertyKey: string): boolean { return propertyKey in object && typeof object[propertyKey] === 'string'; } @@ -35,7 +54,7 @@ export function isString(object: any, propertyKey: string): boolean { * @param propertyKey The key of the property * @returns `true` if the object has property with matching key of type `boolean`. */ -export function isBoolean(object: any, propertyKey: string): boolean { +export function hasBooleanProp(object: AnyObject, propertyKey: string): boolean { return propertyKey in object && typeof object[propertyKey] === 'boolean'; } @@ -45,7 +64,7 @@ export function isBoolean(object: any, propertyKey: string): boolean { * @param propertyKey The key of the property * @returns `true` if the object has property with matching key of type `number`. */ -export function isNumber(object: any, propertyKey: string): boolean { +export function hasNumberProp(object: AnyObject, propertyKey: string): boolean { return propertyKey in object && typeof object[propertyKey] === 'number'; } @@ -55,8 +74,8 @@ export function isNumber(object: any, propertyKey: string): boolean { * @param propertyKey The key of the property * @returns `true` if the object has property with matching key of type `object`. */ -export function isObject(object: any, propertyKey: string): boolean { - return propertyKey in object && typeof object[propertyKey] === 'object'; +export function hasObjectProp(object: AnyObject, propertyKey: string): boolean { + return propertyKey in object && AnyObject.is(object[propertyKey]); } /** @@ -65,6 +84,6 @@ export function isObject(object: any, propertyKey: string): boolean { * @param propertyKey The key of the property * @returns `true` if the object has property with matching key of type `Array`. */ -export function isArray(object: any, propertyKey: string): boolean { +export function hasArrayProp(object: AnyObject, propertyKey: string): boolean { return propertyKey in object && Array.isArray(object[propertyKey]); } From 8d578064288ed763f4465651b7613c9a7c43819a Mon Sep 17 00:00:00 2001 From: Simon Graband Date: Wed, 30 Mar 2022 12:54:21 +0200 Subject: [PATCH 090/566] Fix multi binding type for context edit validators (#9) Co-authored-by: Tobias Ortmayr --- packages/server-node/src/di/diagram-module.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index 5d52ea1..f9fd164 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -52,6 +52,7 @@ import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-act import { ContextActionsProvider } from '../features/contextactions/context-actions-provider'; import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry'; import { RequestContextActionsHandler } from '../features/contextactions/request-context-actions-handler'; +import { ContextEditValidator } from '../features/directediting/context-edit-validator'; import { ContextEditValidatorRegistry, DefaultContextEditValidatorRegistry @@ -174,7 +175,7 @@ export abstract class DiagramModule extends GLSPModule { this.configureMultiBinding(new ClassMultiBinding(ContextActionsProviders), binding => this.configureContextActionProviders(binding) ); - this.configureMultiBinding(new ClassMultiBinding(ContextEditValidators), binding => + this.configureMultiBinding(new ClassMultiBinding(ContextEditValidators), binding => this.configureContextEditValidators(binding) ); } @@ -205,7 +206,7 @@ export abstract class DiagramModule extends GLSPModule { // empty as default } - protected configureContextEditValidators(binding: ClassMultiBinding): void { + protected configureContextEditValidators(binding: ClassMultiBinding): void { // empty as default } From 629d0a2146da0d55553dc15814863dbfc7adc2b7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 30 Mar 2022 05:53:02 -0700 Subject: [PATCH 091/566] Add typeguard functions for menu and palette items (#172) --- packages/protocol/src/action-protocol/types.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/protocol/src/action-protocol/types.ts b/packages/protocol/src/action-protocol/types.ts index c21b61c..d4c7847 100644 --- a/packages/protocol/src/action-protocol/types.ts +++ b/packages/protocol/src/action-protocol/types.ts @@ -152,6 +152,10 @@ export interface PaletteItem extends LabeledAction { } export namespace PaletteItem { + export function is(object: any): object is PaletteItem { + return LabeledAction.is(object) && hasStringProp(object, 'id') && hasStringProp(object, 'sortString'); + } + export function getTriggerAction(item?: PaletteItem): TriggerElementCreationAction | undefined { if (item) { const initialActions = item.actions @@ -194,3 +198,9 @@ export interface MenuItem extends LabeledAction { /** Children of this item. If this item has children, they will be added into a submenu of this item. */ children?: MenuItem[]; } + +export namespace MenuItem { + export function is(object: any): object is MenuItem { + return LabeledAction.is(object) && hasStringProp(object, 'id'); + } +} From a9692931f18922edc718160a3a6766c876b8df22 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 30 Mar 2022 07:48:28 -0700 Subject: [PATCH 092/566] Consume new protocol version & update TS (#12) #472 - Consume the new glsp-protocol version and update the code base to comply to the new action definitions #559 - Consume new @eclipse-glsp/config version and update to typescript 4.5.5. - Adapt code to conform to new "strict" mode and "noImplicitOverride" - Cleanup dev dependencies and move common dependencies into package root #492 Update workflow builders to provide the same improved styled components as the Java GLSP server Part of eclipse-glsp/glsp/issues/472 Part of eclipse-glsp/glsp/issues/559 Part of eclipse-glsp/glsp/issues/492 Requires https://github.com/eclipse-glsp/glsp-client/pull/171 Contributed on behalf of STMicroelectronics --- examples/workflow-server/package.json | 5 +- .../workflow-server/src/graph-extension.ts | 37 ++------ .../handler/create-activity-node-handler.ts | 6 +- .../handler/create-automated-task-handler.ts | 8 +- .../src/handler/create-category-handler.ts | 5 +- .../handler/create-decision-node-handler.ts | 8 +- .../create-fork-or-join-node-handler.ts | 6 +- .../src/handler/create-manual-task-handler.ts | 8 +- .../src/handler/create-merge-node-handler.ts | 8 +- .../src/handler/create-task-handler.ts | 8 +- .../create-workflow-node-operation-handler.ts | 8 +- .../layout/workflow-layout-configurator.ts | 2 +- ...orkflow-command-palette-action-provider.ts | 86 +++++++++++-------- .../workflow-context-menu-item-provider.ts | 25 ++++-- .../src/workflow-diagram-module.ts | 13 ++- .../src/workflow-glsp-server.ts | 4 +- packages/graph/package.json | 4 +- packages/graph/src/gbutton.ts | 2 +- packages/graph/src/gcompartment.ts | 12 +-- packages/graph/src/gedge.ts | 2 +- packages/graph/src/ggraph.ts | 8 +- packages/graph/src/gissue-marker.ts | 2 +- packages/graph/src/glabel.ts | 6 +- packages/graph/src/gmodel-element.ts | 15 ++-- packages/graph/src/gnode.ts | 2 +- packages/graph/src/gport.ts | 2 +- packages/layout-elk/package.json | 4 +- packages/layout-elk/src/di.config.spec.ts | 8 +- .../layout-elk/src/layout-configurator.ts | 2 +- packages/server-node/package.json | 6 +- .../src/actions/action-dispatcher.spec.ts | 8 +- .../src/actions/action-dispatcher.ts | 12 +-- .../src/actions/action-handler-registry.ts | 2 +- .../src/actions/save-model-action-handler.ts | 6 +- .../computed-bounds-action-handler.ts | 4 +- .../src/base-impl/gmodel-diagram-module.ts | 11 ++- .../src/base-impl/gmodel-storage.ts | 6 +- packages/server-node/src/di/diagram-module.ts | 4 +- packages/server-node/src/di/multi-binding.ts | 25 +++--- .../server-node/src/di/multi-bindings.spec.ts | 14 +-- .../request-type-hints-action-handler.spec.ts | 21 +---- .../request-type-hints-action-handler.ts | 14 +-- .../request-clipboard-data-action-handler.ts | 10 +-- .../context-menu-item-provider.ts | 34 +------- .../request-context-actions-handler.spec.ts | 24 +++--- .../request-context-actions-handler.ts | 2 +- .../tool-palette-item-provider.spec.ts | 8 +- .../tool-palette-item-provider.ts | 31 ++----- ...apply-label-edit-operation-handler.spec.ts | 6 +- .../request-edit-validation-handler.spec.ts | 34 +++++--- .../request-edit-validation-handler.ts | 6 +- .../features/model/gmodel-serializer.spec.ts | 16 ++-- .../model/model-submission-handler.ts | 25 ++++-- .../model/request-model-action-handler.ts | 28 +++--- ...quest-navigation-targets-action-handler.ts | 2 +- ...solve-navigation-targets-action-handler.ts | 4 +- .../request-popup-model-action-handler.ts | 2 +- .../validation/request-markers-handler.ts | 8 +- .../src/launch/socket-server-launcher.ts | 2 +- .../operations/create-operation-handler.ts | 48 +++++++++-- .../src/operations/cut-operation-handler.ts | 2 +- .../operations/operation-action-handler.ts | 12 ++- .../operation-handler-registry.spec.ts | 4 +- .../src/operations/paste-operation-handler.ts | 21 +++-- .../src/protocol/glsp-server.spec.ts | 21 +++-- .../server-node/src/protocol/glsp-server.ts | 9 +- .../session/client-session-factory.spec.ts | 4 +- .../src/session/client-session-manager.ts | 2 +- .../server-node/src/session/client-session.ts | 2 +- .../src/test/integration-test.spec.ts | 33 ++++--- packages/server-node/src/utils/di-utilts.ts | 19 ---- .../server-node/src/utils/disposable.spec.ts | 4 +- .../server-node/src/utils/geometry-util.ts | 61 ------------- .../src/utils/glsp-server-error.ts | 2 +- packages/server-node/src/utils/registry.ts | 3 +- .../server-node/src/utils/winston-logger.ts | 2 +- packages/server-node/tsconfig.json | 3 +- 77 files changed, 441 insertions(+), 492 deletions(-) delete mode 100644 packages/server-node/src/utils/di-utilts.ts delete mode 100644 packages/server-node/src/utils/geometry-util.ts diff --git a/examples/workflow-server/package.json b/examples/workflow-server/package.json index 6b64cd5..8e66312 100644 --- a/examples/workflow-server/package.json +++ b/examples/workflow-server/package.json @@ -40,11 +40,8 @@ "@eclipse-glsp/server-node": "0.9.0", "@eclipse-glsp/layout-elk": "0.9.0" }, - "devDependencies": { - "typescript": "^3.9.2" - }, "scripts": { - "clean": "rimraf lib", + "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "yarn run clean && tsc", "test": "", "lint": "eslint -c ../../.eslintrc.js --ext .ts,.tsx ./src", diff --git a/examples/workflow-server/src/graph-extension.ts b/examples/workflow-server/src/graph-extension.ts index bffd57a..893ac3e 100644 --- a/examples/workflow-server/src/graph-extension.ts +++ b/examples/workflow-server/src/graph-extension.ts @@ -30,7 +30,7 @@ import { ModelTypes } from './util/model-types'; export class ActivityNode extends GNode { nodeType: string; - static builder(): ActivityNodeBuilder { + static override builder(): ActivityNodeBuilder { return new ActivityNodeBuilder(ActivityNode); } } @@ -48,7 +48,7 @@ export class TaskNode extends GNode { taskType: string; references: string; - static builder(): TaskNodeBuilder { + static override builder(): TaskNodeBuilder { return new TaskNodeBuilder(TaskNode).layout('vbox').addArgs(ArgsUtil.cornerRadius(5)); } } @@ -75,20 +75,12 @@ export class TaskNodeBuilder extends GNodeBuilder } children(): this { - this.proxy.children.push(this.createCompartment()); return this; } - protected createCompartment(): GCompartment { - const layoutOptions: Args = {}; - return new GCompartmentBuilder(GCompartment) - .type(ModelTypes.COMP_HEADER) - .id(this.proxy.id + '_header') - .layout('hbox') - .addLayoutOptions(layoutOptions) - .add(this.createCompartmentIcon()) - .add(this.createCompartmentHeader()) - .build(); + override build(): T { + this.layout('hbox').addLayoutOption('paddingRight', 10).add(this.createCompartmentIcon()).add(this.createCompartmentHeader()); + return super.build(); } protected createCompartmentHeader(): GLabel { @@ -100,20 +92,9 @@ export class TaskNodeBuilder extends GNodeBuilder } protected createCompartmentIcon(): GCompartment { - return new GCompartmentBuilder(GCompartment) - .type(ModelTypes.ICON) + return GCompartment.builder() .id(this.proxy.id + '_icon') - .layout('stack') - .addLayoutOptions({ hAlign: 'center', resizeContainer: false }) - .add(this.createCompartmentIconLabel()) - .build(); - } - - protected createCompartmentIconLabel(): GLabel { - return new GLabelBuilder(GLabel) - .type(ModelTypes.LABEL_ICON) - .id(this.proxy.id + '_ticon') - .text(`${this.proxy.taskType.toUpperCase().charAt(0)}`) + .type(ModelTypes.ICON) .build(); } } @@ -121,7 +102,7 @@ export class TaskNodeBuilder extends GNodeBuilder export class WeightedEdge extends GEdge { probability?: string; - static builder(): WeightedEdgeBuilder { + static override builder(): WeightedEdgeBuilder { return new WeightedEdgeBuilder(WeightedEdge).type(ModelTypes.WEIGHTED_EDGE); } } @@ -136,7 +117,7 @@ export class WeightedEdgeBuilder extends export class Category extends ActivityNode { name: string; - static builder(): CategoryNodeBuilder { + static override builder(): CategoryNodeBuilder { return new CategoryNodeBuilder(Category) .layout('vbox') .addLayoutOptions({ hAlign: 'center', hGrab: false, vGrab: false }) diff --git a/examples/workflow-server/src/handler/create-activity-node-handler.ts b/examples/workflow-server/src/handler/create-activity-node-handler.ts index 28dbf5f..8fdd979 100644 --- a/examples/workflow-server/src/handler/create-activity-node-handler.ts +++ b/examples/workflow-server/src/handler/create-activity-node-handler.ts @@ -13,11 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point, Args, GNode, GModelState, ORIGIN_POINT } from '@eclipse-glsp/server-node'; -import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; +import { Args, GModelState, GNode, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNode, ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; +import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @injectable() export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperationHandler { @@ -27,7 +27,7 @@ export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperat protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { return ActivityNode.builder() - .position(point ?? ORIGIN_POINT) + .position(point ?? Point.ORIGIN) .type(this.elementTypeIds[0]) .nodeType(ModelTypes.toNodeType(this.elementTypeIds[0])); } diff --git a/examples/workflow-server/src/handler/create-automated-task-handler.ts b/examples/workflow-server/src/handler/create-automated-task-handler.ts index 2dc20a3..7175c5e 100644 --- a/examples/workflow-server/src/handler/create-automated-task-handler.ts +++ b/examples/workflow-server/src/handler/create-automated-task-handler.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelTypes } from '../util/model-types'; -import { CreateTaskHandler } from './create-task-handler'; +import { GModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; -import { Point, GModelState } from '@eclipse-glsp/server-node'; import { TaskNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { CreateTaskHandler } from './create-task-handler'; @injectable() export class CreateAutomatedTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.AUTOMATED_TASK]; label = 'Automated Task'; - protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + protected override builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { return super.builder(point, modelState).addCssClass('automated'); } } diff --git a/examples/workflow-server/src/handler/create-category-handler.ts b/examples/workflow-server/src/handler/create-category-handler.ts index cd4fd99..ec63b54 100644 --- a/examples/workflow-server/src/handler/create-category-handler.ts +++ b/examples/workflow-server/src/handler/create-category-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point, Args, GNode, GModelState, ORIGIN_POINT } from '@eclipse-glsp/server-node'; +import { Args, ArgsUtil, GModelState, GNode, Point } from '@eclipse-glsp/server-node'; import { Category, CategoryNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @@ -29,8 +29,9 @@ export class CreateCategoryHandler extends CreateWorkflowNodeOperationHandler { protected builder(point: Point | undefined, modelState: GModelState): CategoryNodeBuilder { return Category.builder() .type(this.elementTypeIds[0]) - .position(point ?? ORIGIN_POINT) + .position(point ?? Point.ORIGIN) .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(Category).length) + .addArgs(ArgsUtil.cornerRadius(5)) .children(); } } diff --git a/examples/workflow-server/src/handler/create-decision-node-handler.ts b/examples/workflow-server/src/handler/create-decision-node-handler.ts index 743db96..63b12a4 100644 --- a/examples/workflow-server/src/handler/create-decision-node-handler.ts +++ b/examples/workflow-server/src/handler/create-decision-node-handler.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelTypes } from '../util/model-types'; -import { injectable } from 'inversify'; -import { CreateActivityNodeHandler } from './create-activity-node-handler'; import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; @injectable() export class CreateDecisionNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.DECISION_NODE]; label = 'Decision Node'; - protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('decision'); } } diff --git a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts index 379cedb..535fd21 100644 --- a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts +++ b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point, GModelState } from '@eclipse-glsp/server-node'; -import { CreateActivityNodeHandler } from './create-activity-node-handler'; +import { GModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; @injectable() export abstract class CreateForkOrJoinNodeHandler extends CreateActivityNodeHandler { - protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('forkOrJoin').size(10, 50); } } diff --git a/examples/workflow-server/src/handler/create-manual-task-handler.ts b/examples/workflow-server/src/handler/create-manual-task-handler.ts index b6a14f2..0cce665 100644 --- a/examples/workflow-server/src/handler/create-manual-task-handler.ts +++ b/examples/workflow-server/src/handler/create-manual-task-handler.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelTypes } from '../util/model-types'; -import { CreateTaskHandler } from './create-task-handler'; +import { GModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; -import { Point, GModelState } from '@eclipse-glsp/server-node'; import { TaskNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { CreateTaskHandler } from './create-task-handler'; @injectable() export class CreateManualTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.MANUAL_TASK]; label = 'Manual Task'; - protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + protected override builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { return super.builder(point, modelState).addCssClass('manual'); } } diff --git a/examples/workflow-server/src/handler/create-merge-node-handler.ts b/examples/workflow-server/src/handler/create-merge-node-handler.ts index 0e42258..c88ffae 100644 --- a/examples/workflow-server/src/handler/create-merge-node-handler.ts +++ b/examples/workflow-server/src/handler/create-merge-node-handler.ts @@ -13,18 +13,18 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelTypes } from '../util/model-types'; -import { injectable } from 'inversify'; -import { CreateActivityNodeHandler } from './create-activity-node-handler'; import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; +import { ModelTypes } from '../util/model-types'; +import { CreateActivityNodeHandler } from './create-activity-node-handler'; @injectable() export class CreateMergeNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.MERGE_NODE]; label = 'Merge Node'; - protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('merge'); } } diff --git a/examples/workflow-server/src/handler/create-task-handler.ts b/examples/workflow-server/src/handler/create-task-handler.ts index 2c468c8..f4c23d0 100644 --- a/examples/workflow-server/src/handler/create-task-handler.ts +++ b/examples/workflow-server/src/handler/create-task-handler.ts @@ -13,12 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; -import { injectable } from 'inversify'; -import { Args, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { Args, Point } from '@eclipse-glsp/protocol'; import { GModelState, GNode } from '@eclipse-glsp/server-node'; +import { injectable } from 'inversify'; import { TaskNode, TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; +import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @injectable() export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandler { @@ -28,7 +28,7 @@ export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandl protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { return TaskNode.builder() - .position(point ?? ORIGIN_POINT) + .position(point ?? Point.ORIGIN) .addCssClass('task') .name(this.label.replace(' ', '') + this.modelState.index.getAllByClass(TaskNode).length) .type(this.elementTypeIds[0]) diff --git a/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts index ef1133d..0d1feea 100644 --- a/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts +++ b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts @@ -21,7 +21,7 @@ import { GModelState, Point } from '@eclipse-glsp/server-node'; -import { injectable, inject } from 'inversify'; +import { inject, injectable } from 'inversify'; import { Category } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; import { GridSnapper } from './grid-snapper'; @@ -29,13 +29,13 @@ import { GridSnapper } from './grid-snapper'; @injectable() export abstract class CreateWorkflowNodeOperationHandler extends CreateNodeOperationHandler { @inject(GModelState) - protected modelState: GModelState; + protected override modelState: GModelState; - getLocation(operation: CreateNodeOperation): Point | undefined { + override getLocation(operation: CreateNodeOperation): Point | undefined { return GridSnapper.snap(operation.location); } - getContainer(operation: CreateNodeOperation): GModelElement | undefined { + override getContainer(operation: CreateNodeOperation): GModelElement | undefined { const container = super.getContainer(operation); if (container instanceof Category) { diff --git a/examples/workflow-server/src/layout/workflow-layout-configurator.ts b/examples/workflow-server/src/layout/workflow-layout-configurator.ts index 12d62ce..0feac48 100644 --- a/examples/workflow-server/src/layout/workflow-layout-configurator.ts +++ b/examples/workflow-server/src/layout/workflow-layout-configurator.ts @@ -19,7 +19,7 @@ import { injectable } from 'inversify'; @injectable() export class WorkflowLayoutConfigurator extends AbstractLayoutConfigurator { - protected graphOptions(graph: GGraph): LayoutOptions | undefined { + protected override graphOptions(graph: GGraph): LayoutOptions | undefined { return { 'elk.algorithm': 'layered' }; diff --git a/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts index b25c182..770186c 100644 --- a/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts +++ b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts @@ -24,7 +24,6 @@ import { GModelState, GNode, LabeledAction, - ORIGIN_POINT, Point } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; @@ -34,7 +33,7 @@ import { ModelTypes } from '../util/model-types'; @injectable() export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionProvider { @inject(GModelState) - protected modelState: GModelState; + protected override modelState: GModelState; getPaletteActions(selectedElementIds: string[], selectedElements: GModelElement[], position: Point, args?: Args): LabeledAction[] { const actions: LabeledAction[] = []; @@ -43,28 +42,33 @@ export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionPr } const index = this.modelState.index; // Create actions + const location = position ?? Point.ORIGIN; actions.push( - new LabeledAction( - 'Create Automated Task', - [new CreateNodeOperation(ModelTypes.AUTOMATED_TASK, position ?? ORIGIN_POINT)], - 'fa-plus-square' - ), - new LabeledAction( - 'Create Manual Task', - [new CreateNodeOperation(ModelTypes.MANUAL_TASK, position ?? ORIGIN_POINT)], - 'fa-plus-square' - ), - new LabeledAction( - 'Create Merge Node', - [new CreateNodeOperation(ModelTypes.MERGE_NODE, position ?? ORIGIN_POINT)], - 'fa-plus-square' - ), - new LabeledAction( - 'Create Decision Node', - [new CreateNodeOperation(ModelTypes.DECISION_NODE, position ?? ORIGIN_POINT)], - 'fa-plus-square' - ), - new LabeledAction('Create Category', [new CreateNodeOperation(ModelTypes.CATEGORY, position ?? ORIGIN_POINT)], 'fa-plus-square') + { + label: 'Create Automated Task', + actions: [CreateNodeOperation.create(ModelTypes.AUTOMATED_TASK, { location })], + icon: 'fa-plus-square' + }, + { + label: 'Create Manual Task', + actions: [CreateNodeOperation.create(ModelTypes.MANUAL_TASK, { location })], + icon: 'fa-plus-square' + }, + { + label: 'Create Merge Node', + actions: [CreateNodeOperation.create(ModelTypes.MERGE_NODE, { location })], + icon: 'fa-plus-square' + }, + { + label: 'Create Decision Node', + actions: [CreateNodeOperation.create(ModelTypes.DECISION_NODE, { location })], + icon: 'fa-plus-square' + }, + { + label: 'Create Category', + actions: [CreateNodeOperation.create(ModelTypes.CATEGORY, { location })], + icon: 'fa-plus-square' + } ); // Create edge action between two nodes if (selectedElements.length === 1) { @@ -89,16 +93,14 @@ export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionPr } } // Delete action - if (selectedElements.length === 1) { - actions.push(new LabeledAction('Delete', [new DeleteElementOperation(selectedElementIds)], 'fa-minus-square')); - } else if (selectedElements.length > 1) { - actions.push(new LabeledAction('Delete All', [new DeleteElementOperation(selectedElementIds)], 'fa-minus-square')); - } + + const label = selectedElementIds.length === 1 ? 'Delete' : 'Delete All'; + actions.push({ label, actions: [DeleteElementOperation.create(selectedElementIds)], icon: 'fa-minus-square' }); return actions; } - private createEdgeActions(source: GNode, targets: GNode[]): LabeledAction[] { + protected createEdgeActions(source: GNode, targets: GNode[]): LabeledAction[] { const actions: LabeledAction[] = []; targets.forEach(node => actions.push(this.createEdgeAction(`Create Edge to ${this.getLabel(node)}`, source, node))); targets.forEach(node => @@ -107,15 +109,31 @@ export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionPr return actions; } - private createWeightedEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { - return new LabeledAction(label, [new CreateEdgeOperation(ModelTypes.WEIGHTED_EDGE, source.id, node.id)], 'fa-plus-square'); + protected createWeightedEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { + return { + label, + actions: [ + CreateEdgeOperation.create({ + elementTypeId: ModelTypes.WEIGHTED_EDGE, + sourceElementId: source.id, + targetElementId: node.id + }) + ], + icon: 'fa-plus-square' + }; } - private createEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { - return new LabeledAction(label, [new CreateEdgeOperation(DefaultTypes.EDGE, source.id, node.id)], 'fa-plus-square'); + protected createEdgeAction(label: string, source: GNode, node: GNode): LabeledAction { + return { + label, + actions: [ + CreateEdgeOperation.create({ elementTypeId: DefaultTypes.EDGE, sourceElementId: source.id, targetElementId: node.id }) + ], + icon: 'fa-plus-square' + }; } - private getLabel(node: GNode): string { + protected getLabel(node: GNode): string { if (node instanceof TaskNode) { return node.name; } diff --git a/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts index 831f203..cbd8995 100644 --- a/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts +++ b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts @@ -24,15 +24,28 @@ export class WorkflowContextMenuItemProvider extends ContextMenuItemProvider { protected modelState: GModelState; getItems(selectedElementIds: string[], position: Point, args?: Args): MenuItem[] { - if (this.modelState.isReadonly) { + if (this.modelState.isReadonly || selectedElementIds.length !== 0) { return []; } const snappedPosition = GridSnapper.snap(position); - const newAutTask = new MenuItem('newAutoTask', 'Automated Task', [ - new CreateNodeOperation(ModelTypes.AUTOMATED_TASK, snappedPosition) - ]); - const newManTask = new MenuItem('newManualTask', 'Manual Task', [new CreateNodeOperation(ModelTypes.MANUAL_TASK, snappedPosition)]); - const newChildMenu = new MenuItem('new', 'New', undefined, [newAutTask, newManTask], 'add', '0_new'); + const newAutTask: MenuItem = { + id: 'newAutoTask', + label: 'Automated Task', + actions: [CreateNodeOperation.create(ModelTypes.AUTOMATED_TASK, { location: snappedPosition })] + }; + const newManTask: MenuItem = { + id: 'newManualTask', + label: 'Manual Task', + actions: [CreateNodeOperation.create(ModelTypes.MANUAL_TASK, { location: snappedPosition })] + }; + const newChildMenu: MenuItem = { + id: 'new', + label: 'New', + actions: [], + children: [newAutTask, newManTask], + icon: 'add', + group: '0_new' + }; return [newChildMenu]; } } diff --git a/examples/workflow-server/src/workflow-diagram-module.ts b/examples/workflow-server/src/workflow-diagram-module.ts index a6d091c..ab44e4f 100644 --- a/examples/workflow-server/src/workflow-diagram-module.ts +++ b/examples/workflow-server/src/workflow-diagram-module.ts @@ -56,7 +56,7 @@ import { WorkflowPopupFactory } from './workflow-popup-factory'; @injectable() export class WorkflowServerModule extends ServerModule { - configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + override configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { super.configure(bind, unbind, isBound, rebind); bind(WorkflowGLSPServer).toSelf().inSingletonScope(); rebind(GLSPServer).toService(WorkflowGLSPServer); @@ -70,7 +70,12 @@ export class WorkflowDiagramModule extends GModelDiagramModule { return 'workflow-diagram'; } - protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + protected override configure( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ): void { super.configure(bind, unbind, isBound, rebind); bind(DiagramConfiguration).to(WorkflowDiagramConfiguration).inSingletonScope(); bind(NavigationTargetResolver).to(WorkflowNavigationTargetResolver).inSingletonScope(); @@ -82,7 +87,7 @@ export class WorkflowDiagramModule extends GModelDiagramModule { bind(ToolPaletteItemProvider).to(DefaultToolPaletteItemProvider).inSingletonScope(); } - protected configureOperationHandlers(binding: InstanceMultiBinding): void { + protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(CreateAutomatedTaskHandler); binding.add(CreateManualTaskHandler); @@ -95,7 +100,7 @@ export class WorkflowDiagramModule extends GModelDiagramModule { binding.add(CreateCategoryHandler); } - protected configureNavigationTargetProviders(binding: ClassMultiBinding): void { + protected override configureNavigationTargetProviders(binding: ClassMultiBinding): void { super.configureNavigationTargetProviders(binding); binding.add(NextNodeNavigationTargetProvider); binding.add(PreviousNodeNavigationTargetProvider); diff --git a/examples/workflow-server/src/workflow-glsp-server.ts b/examples/workflow-server/src/workflow-glsp-server.ts index 982f49f..d3b5141 100644 --- a/examples/workflow-server/src/workflow-glsp-server.ts +++ b/examples/workflow-server/src/workflow-glsp-server.ts @@ -22,9 +22,9 @@ export class WorkflowGLSPServer extends DefaultGLSPServer { TIMESTAMP_KEY = 'timestamp'; @inject(Logger) - protected logger: Logger; + protected override logger: Logger; - protected handleInitializeArgs(result: InitializeResult, args: Args | undefined): MaybePromise { + protected override handleInitializeArgs(result: InitializeResult, args: Args | undefined): MaybePromise { if (!args) { return result; } diff --git a/packages/graph/package.json b/packages/graph/package.json index faf20ac..2c343f7 100644 --- a/packages/graph/package.json +++ b/packages/graph/package.json @@ -41,12 +41,10 @@ "@eclipse-glsp/protocol": "next" }, "devDependencies": { - "@types/ws": "^8.2.0", - "typescript": "^3.9.2", "@types/uuid": "8.3.1" }, "scripts": { - "clean": "rimraf lib", + "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "yarn run clean && tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/graph/src/gbutton.ts b/packages/graph/src/gbutton.ts index 7ad7c5b..b0fc93d 100644 --- a/packages/graph/src/gbutton.ts +++ b/packages/graph/src/gbutton.ts @@ -22,7 +22,7 @@ export class GButton extends GShapeElement { return new GButtonBuilder(constructor ?? GButton).type(DefaultTypes.BUTTON_EXPAND); } - type = DefaultTypes.BUTTON_EXPAND; + override type = DefaultTypes.BUTTON_EXPAND; enabled = true; } diff --git a/packages/graph/src/gcompartment.ts b/packages/graph/src/gcompartment.ts index 12725d8..37200b3 100644 --- a/packages/graph/src/gcompartment.ts +++ b/packages/graph/src/gcompartment.ts @@ -23,9 +23,9 @@ export class GCompartment extends GShapeElement implements GLayoutContainer, GLa return new GCompartmentBuilder(GCompartment).type(DefaultTypes.COMPARTMENT); } - type = DefaultTypes.COMPARTMENT; + override type = DefaultTypes.COMPARTMENT; layout?: string; - layoutOptions?: Args; + override layoutOptions?: Args; [GLayoutContainer] = true; [GLayoutable] = true; } @@ -35,13 +35,13 @@ export class GCompartmentBuilder extends return GLayoutContainerBuilder.layout(this, layout); } - addLayoutOption(key: string, value: JsonPrimitive): this { + override addLayoutOption(key: string, value: JsonPrimitive): this { return GLayoutableBuilder.addLayoutOption(this, key, value); } - addLayoutOptions(layoutOptions: Args): this; - addLayoutOptions(layoutOptions: Map): this; - addLayoutOptions(layoutOptions: Args | Map): this { + override addLayoutOptions(layoutOptions: Args): this; + override addLayoutOptions(layoutOptions: Map): this; + override addLayoutOptions(layoutOptions: Args | Map): this { return GLayoutableBuilder.addLayoutOptions(this, layoutOptions); } } diff --git a/packages/graph/src/gedge.ts b/packages/graph/src/gedge.ts index 17950ea..945c409 100644 --- a/packages/graph/src/gedge.ts +++ b/packages/graph/src/gedge.ts @@ -21,7 +21,7 @@ export class GEdge extends GModelElement { return new GEdgeBuilder(GEdge).type(DefaultTypes.EDGE); } - type = DefaultTypes.EDGE; + override type = DefaultTypes.EDGE; routingPoints: Point[] = []; sourceId: string; targetId: string; diff --git a/packages/graph/src/ggraph.ts b/packages/graph/src/ggraph.ts index 30e7f46..571b846 100644 --- a/packages/graph/src/ggraph.ts +++ b/packages/graph/src/ggraph.ts @@ -19,17 +19,17 @@ * Parts of the implementation is derived from Sprotty's SModel API * (https://github.com/eclipse/sprotty/blob/master/packages/sprotty/src/base/model/smodel.ts) */ -import { DefaultTypes as types, Dimension, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { DefaultTypes as types, Dimension, Point } from '@eclipse-glsp/protocol'; import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; import { GModelRoot, GModelRootBuilder } from './gmodel-element'; export class GGraph extends GModelRoot implements GBoundsAware { - static builder(): GGraphBuilder { + static override builder(): GGraphBuilder { return new GGraphBuilder(GGraph); } - type = types.GRAPH; - position: Point = ORIGIN_POINT; + override type = types.GRAPH; + position: Point = Point.ORIGIN; size?: Dimension; [GBoundsAware] = true; } diff --git a/packages/graph/src/gissue-marker.ts b/packages/graph/src/gissue-marker.ts index 9dbe03d..0a10422 100644 --- a/packages/graph/src/gissue-marker.ts +++ b/packages/graph/src/gissue-marker.ts @@ -24,7 +24,7 @@ export class GIssue { } export class GIssueMarker extends GShapeElement { - type = DefaultTypes.ISSUE_MARKER; + override type = DefaultTypes.ISSUE_MARKER; issues: GIssue[] = []; } diff --git a/packages/graph/src/glabel.ts b/packages/graph/src/glabel.ts index 6724f35..6f0c6d1 100644 --- a/packages/graph/src/glabel.ts +++ b/packages/graph/src/glabel.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { DefaultTypes, ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; +import { DefaultTypes, Point } from '@eclipse-glsp/protocol'; import { GAlignable, GAlignableBuilder } from './galignable'; import { EdgePlacement, GEdgeLayoutable, GEdgeLayoutableBuilder } from './gedge-layoutable'; import { GModelElementConstructor } from './gmodel-element'; @@ -24,9 +24,9 @@ export class GLabel extends GShapeElement implements GAlignable, GEdgeLayoutable return new GLabelBuilder(constructor ?? GLabel).type(DefaultTypes.LABEL); } - type = DefaultTypes.LABEL; + override type = DefaultTypes.LABEL; text: string; - alignment: Point = ORIGIN_POINT; + alignment: Point = Point.ORIGIN; edgePlacement?: EdgePlacement; [GAlignable] = true; [GEdgeLayoutable] = true; diff --git a/packages/graph/src/gmodel-element.ts b/packages/graph/src/gmodel-element.ts index 97afd26..cf586e2 100644 --- a/packages/graph/src/gmodel-element.ts +++ b/packages/graph/src/gmodel-element.ts @@ -18,7 +18,6 @@ import { Bounds, Dimension, flatPush, - isSModelElementSchema, JsonPrimitive, MaybeArray, Point, @@ -26,14 +25,20 @@ import { SModelRootSchema } from '@eclipse-glsp/protocol'; import * as uuid from 'uuid'; + export type GModelElementConstructor = new () => G; /** * Represents a `GModeElement` serialized as plain JSON object. */ export type GModelElementSchema = SModelElementSchema; -export function isGModelElementSchema(schema: any): schema is GModelElementSchema { - return isSModelElementSchema(schema); +export namespace GModelElementSchema { + /** + * Typeguard function to check wether the given object is an {@link GModelElementSchema}. + * @param object The object to check. + * @returns A type literal indicating wether the given object is of type {@link GModelElementSchema}. + */ + export const is = SModelElementSchema.is; } /** @@ -183,10 +188,6 @@ export abstract class GModelElementBuilder { export type GModelRootSchema = SModelRootSchema; -export function isGModelRootSchema(schema: any): schema is GModelRootSchema { - return isSModelElementSchema(schema); -} - export class GModelRoot extends GModelElement implements SModelRootSchema { static builder(): GModelRootBuilder { return new GModelRootBuilder(GModelRoot); diff --git a/packages/graph/src/gnode.ts b/packages/graph/src/gnode.ts index 1480a57..b59e1a6 100644 --- a/packages/graph/src/gnode.ts +++ b/packages/graph/src/gnode.ts @@ -22,7 +22,7 @@ export class GNode extends GShapeElement implements GLayoutContainer { return new GNodeBuilder(GNode).type(DefaultTypes.NODE); } - type = DefaultTypes.NODE; + override type = DefaultTypes.NODE; layout?: string; [GLayoutContainer] = true; } diff --git a/packages/graph/src/gport.ts b/packages/graph/src/gport.ts index ba12566..751b6cc 100644 --- a/packages/graph/src/gport.ts +++ b/packages/graph/src/gport.ts @@ -22,5 +22,5 @@ export class GPort extends GShapeElement { return new GShapeElementBuilder(GPort).type(DefaultTypes.PORT); } - type = DefaultTypes.PORT; + override type = DefaultTypes.PORT; } diff --git a/packages/layout-elk/package.json b/packages/layout-elk/package.json index e262206..f7d01ce 100644 --- a/packages/layout-elk/package.json +++ b/packages/layout-elk/package.json @@ -39,12 +39,12 @@ ], "dependencies": { "sprotty-elk": "next", - "inversify": "^5.0.1", + "inversify": "^5.1.1", "@eclipse-glsp/server-node": "0.9.0", "elkjs": "^0.7.1" }, "scripts": { - "clean": "rimraf lib", + "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "yarn run clean && tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/layout-elk/src/di.config.spec.ts b/packages/layout-elk/src/di.config.spec.ts index 53e00ec..39a68c5 100644 --- a/packages/layout-elk/src/di.config.spec.ts +++ b/packages/layout-elk/src/di.config.spec.ts @@ -46,9 +46,9 @@ describe('test configureELKLayoutModule', () => { const container = new Container(); container.load(baseModule, elkModule); const filter = container.get(ElementFilter); - expect(filter).to.be.instanceOf(DefaultElementFilter); + expect(filter).to.be.an.instanceOf(DefaultElementFilter); const configurator = container.get(LayoutConfigurator); - expect(configurator).to.be.instanceOf(FallbackLayoutConfigurator); + expect(configurator).to.be.an.instanceOf(FallbackLayoutConfigurator); const graphOptions = configurator.apply(new GGraph()); expect(graphOptions).not.to.be.undefined; expect(graphOptions!['elk.algorithm']).to.equal(algorithm); @@ -75,7 +75,7 @@ describe('test configureELKLayoutModule', () => { const container = new Container(); container.load(baseModule, elkModule); const configurator = container.get(LayoutConfigurator); - expect(configurator).to.be.instanceOf(CustomLayoutConfigurator); + expect(configurator).to.be.an.instanceOf(CustomLayoutConfigurator); }); it('configure with custom element filter', () => { @@ -84,6 +84,6 @@ describe('test configureELKLayoutModule', () => { const container = new Container(); container.load(baseModule, elkModule); const filter = container.get(ElementFilter); - expect(filter).to.be.instanceOf(CustomElementFilter); + expect(filter).to.be.an.instanceOf(CustomElementFilter); }); }); diff --git a/packages/layout-elk/src/layout-configurator.ts b/packages/layout-elk/src/layout-configurator.ts index fc34347..303fea9 100644 --- a/packages/layout-elk/src/layout-configurator.ts +++ b/packages/layout-elk/src/layout-configurator.ts @@ -95,7 +95,7 @@ export class FallbackLayoutConfigurator extends AbstractLayoutConfigurator { }; } - protected graphOptions(sgraph: GGraph): LayoutOptions | undefined { + protected override graphOptions(sgraph: GGraph): LayoutOptions | undefined { return this.fallbackGraphOptions; } } diff --git a/packages/server-node/package.json b/packages/server-node/package.json index d4f3485..1ef2088 100644 --- a/packages/server-node/package.json +++ b/packages/server-node/package.json @@ -45,12 +45,10 @@ "@types/uuid": "8.3.1" }, "devDependencies": { - "@types/fs-extra": "^9.0.13", - "reflect-metadata": "^0.1.13", - "typescript": "^3.9.2" + "@types/fs-extra": "^9.0.13" }, "scripts": { - "clean": "rimraf lib", + "clean": "rimraf tsconfig.tsbuildinfo lib", "build": "yarn run clean && tsc", "test": "mocha --config ../../.mocharc.json \"./src/**/*.spec.?(ts|tsx)\"", "test:ci": "export JUNIT_REPORT_PATH=./report.xml && yarn test --reporter mocha-jenkins-reporter", diff --git a/packages/server-node/src/actions/action-dispatcher.spec.ts b/packages/server-node/src/actions/action-dispatcher.spec.ts index dff7ece..bf1e5bc 100644 --- a/packages/server-node/src/actions/action-dispatcher.spec.ts +++ b/packages/server-node/src/actions/action-dispatcher.spec.ts @@ -13,7 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { Action, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; +import * as sinon from 'sinon'; import { ClientActionKinds, ClientId } from '../di/service-identifiers'; import { ClientSessionManager } from '../session/client-session-manager'; import * as mock from '../test/mock-util'; @@ -21,9 +24,6 @@ import { Logger } from '../utils/logger'; import { DefaultActionDispatcher } from './action-dispatcher'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; -import { Action, UpdateModelAction } from '@eclipse-glsp/protocol'; function waitSync(timeInMillis: number): void { const start = Date.now(); @@ -256,7 +256,7 @@ describe('test DefaultActionDispatcher', () => { describe('test dispatch after next update', () => { it('dispatchAfterNextUpdate', async () => { // Mock setup - const updateModelAction = new UpdateModelAction({ id: 'newRoot', type: 'myType' }); + const updateModelAction = UpdateModelAction.create({ id: 'newRoot', type: 'myType' }); const intermediateAction = 'intermediate'; const postUpdateAction = 'postUpdate'; const handler = new mock.StubActionHandler([updateModelAction.kind, intermediateAction]); diff --git a/packages/server-node/src/actions/action-dispatcher.ts b/packages/server-node/src/actions/action-dispatcher.ts index 5a9764d..153a436 100644 --- a/packages/server-node/src/actions/action-dispatcher.ts +++ b/packages/server-node/src/actions/action-dispatcher.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, flatPush, isAction, isRequestAction, isUpdateModelAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol'; +import { Action, flatPush, MaybeArray, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { ClientActionKinds, ClientId } from '../di/service-identifiers'; @@ -96,7 +96,7 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc responses.push(...response); } - if (isUpdateModelAction(action) && this.postUpdateQueue.length > 0) { + if (UpdateModelAction.is(action) && this.postUpdateQueue.length > 0) { responses.push(...this.postUpdateQueue); this.postUpdateQueue = []; } @@ -133,7 +133,7 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc } } - doDispose(): void { + override doDispose(): void { this.actionQueue.clear(); } } @@ -146,12 +146,8 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc * @returns given response action with id set if applicable */ export function respond(request: Action, response: Action): Action { - if (isRequestAction(request) && isResponseAction(response)) { + if (RequestAction.is(request) && ResponseAction.is(response)) { (response as any).responseId = request.requestId; } return response; } - -function isResponseAction(action?: any): action is ResponseAction { - return isAction(action) && 'responseId' in action && typeof action['responseId'] === 'string'; -} diff --git a/packages/server-node/src/actions/action-handler-registry.ts b/packages/server-node/src/actions/action-handler-registry.ts index 060e6e9..7061d4b 100644 --- a/packages/server-node/src/actions/action-handler-registry.ts +++ b/packages/server-node/src/actions/action-handler-registry.ts @@ -35,7 +35,7 @@ export class ActionHandlerRegistry extends MultiRegistry * @param action The action * @returns A list of all registered handlers that can handle the given action. */ - get(key: string): ActionHandler[] { + override get(key: string): ActionHandler[] { const result = super.get(key); return result.sort((a, b) => a.priority ?? 0 - (b.priority ?? 0)); } diff --git a/packages/server-node/src/actions/save-model-action-handler.ts b/packages/server-node/src/actions/save-model-action-handler.ts index 55effa3..cd4fa77 100644 --- a/packages/server-node/src/actions/save-model-action-handler.ts +++ b/packages/server-node/src/actions/save-model-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, DirtyStateChangeReason, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; +import { Action, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { GModelState } from '../base-impl/gmodel-state'; import { SourceModelStorage } from '../features/model/source-model-storage'; @@ -30,13 +30,13 @@ export class SaveModelActionHandler implements ActionHandler { @inject(SourceModelStorage) protected sourceModelStorage: SourceModelStorage; - execute(action: Action): MaybePromise { + execute(action: SaveModelAction): MaybePromise { try { this.sourceModelStorage.saveSourceModel(action); this.modelState.isDirty = false; // TODO: call save is done when available } catch (err) { throw new GLSPServerError(`An error occurred during save process: ${err}`); } - return [new SetDirtyStateAction(this.modelState.isDirty, DirtyStateChangeReason.SAVE)]; + return [SetDirtyStateAction.create(this.modelState.isDirty, { reason: 'save' })]; } } diff --git a/packages/server-node/src/base-impl/computed-bounds-action-handler.ts b/packages/server-node/src/base-impl/computed-bounds-action-handler.ts index 6c1df4f..4ae979a 100644 --- a/packages/server-node/src/base-impl/computed-bounds-action-handler.ts +++ b/packages/server-node/src/base-impl/computed-bounds-action-handler.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph'; -import { Action, ComputedBoundsAction, isComputedBoundsAction } from '@eclipse-glsp/protocol'; +import { Action, ComputedBoundsAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { GModelIndex } from '../features/model/gmodel-index'; @@ -34,7 +34,7 @@ export class ComputedBoundsActionHandler implements ActionHandler { actionKinds = [ComputedBoundsAction.KIND]; execute(action: Action): Action[] { - if (isComputedBoundsAction(action)) { + if (ComputedBoundsAction.is(action)) { const model = this.modelState.root; if (action.revision === model.revision) { this.applyBounds(model, action); diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/base-impl/gmodel-diagram-module.ts index ab3cdd4..4c82667 100644 --- a/packages/server-node/src/base-impl/gmodel-diagram-module.ts +++ b/packages/server-node/src/base-impl/gmodel-diagram-module.ts @@ -56,7 +56,12 @@ import { GModelStorage } from './gmodel-storage'; */ @injectable() export abstract class GModelDiagramModule extends DiagramModule { - protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + protected override configure( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ): void { super.configure(bind, unbind, isBound, rebind); bind(SourceModelStorage).to(GModelStorage); @@ -70,14 +75,14 @@ export abstract class GModelDiagramModule extends DiagramModule { bind(GModelIndex).toSelf().inSingletonScope(); } - protected configureActionHandlers(binding: InstanceMultiBinding): void { + protected override configureActionHandlers(binding: InstanceMultiBinding): void { super.configureActionHandlers(binding); binding.add(ComputedBoundsActionHandler); binding.add(SaveModelActionHandler); binding.add(RequestClipboardDataActionHandler); } - protected configureOperationHandlers(binding: InstanceMultiBinding): void { + protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(ApplyLabelEditOperationHandler); binding.add(ChangeBoundsOperationHandler); diff --git a/packages/server-node/src/base-impl/gmodel-storage.ts b/packages/server-node/src/base-impl/gmodel-storage.ts index f399669..04f30b3 100644 --- a/packages/server-node/src/base-impl/gmodel-storage.ts +++ b/packages/server-node/src/base-impl/gmodel-storage.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GGraph, GModelRoot } from '@eclipse-glsp/graph'; -import { isSModelRootSchema, MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; +import { GGraph, GModelElementSchema, GModelRoot } from '@eclipse-glsp/graph'; +import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; import { writeFileSync } from 'fs'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; @@ -57,7 +57,7 @@ export class GModelStorage implements SourceModelStorage { if (!fileContent) { return EMPTY_ROOT; } - if (!isSModelRootSchema(fileContent)) { + if (!GModelElementSchema.is(fileContent)) { throw new Error('The loaded root object is not of type SModelRootSchema'); } return this.modelSerializer.createRoot(fileContent); diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index f9fd164..39d372f 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -20,7 +20,6 @@ import { DeleteMarkersAction, ExportSvgAction, FitToScreenAction, - GLSPServerStatusAction, ModelSourceChangedAction, NavigateToExternalTargetAction, NavigateToTargetAction, @@ -28,6 +27,7 @@ import { SelectAction, SelectAllAction, ServerMessageAction, + ServerStatusAction, SetClipboardDataAction, SetContextActions, SetDirtyStateAction, @@ -239,7 +239,7 @@ export abstract class DiagramModule extends GLSPModule { binding.add(SetResolvedNavigationTargetAction.KIND); binding.add(SetTypeHintsAction.KIND); // binding.add(SetViewportAction.KIND); TODO: Add missing action to protocol - binding.add(GLSPServerStatusAction.KIND); + binding.add(ServerStatusAction.KIND); binding.add(TriggerNodeCreationAction.KIND); binding.add(TriggerEdgeCreationAction.KIND); binding.add(UpdateModelAction.KIND); diff --git a/packages/server-node/src/di/multi-binding.ts b/packages/server-node/src/di/multi-binding.ts index 440fe70..8c25a5b 100644 --- a/packages/server-node/src/di/multi-binding.ts +++ b/packages/server-node/src/di/multi-binding.ts @@ -31,30 +31,29 @@ export abstract class MultiBinding { abstract applyBindings(context: ModuleContext): void; - add(newBinding: T): boolean { - return distinctAdd(this.bindings, newBinding); + add(newBinding: T): void { + distinctAdd(this.bindings, newBinding); } - addAll(newBindings: T[]): boolean; - addAll(...newBindings: T[]): boolean; - addAll(...newBindings: MaybeArray[]): boolean { + addAll(newBindings: T[]): void; + addAll(...newBindings: T[]): void; + addAll(...newBindings: MaybeArray[]): void { const result: T[] = []; flatPush(result, newBindings); - return result.every(newBinding => this.add(newBinding)); + result.forEach(newBinding => this.add(newBinding)); } - remove(toRemove: T): boolean { - return remove(this.bindings, toRemove); + remove(toRemove: T): void { + remove(this.bindings, toRemove); } - removeAll(toRemove: T[]): boolean { - return toRemove.every(binding => this.remove(binding)); + removeAll(toRemove: T[]): void { + return toRemove.forEach(binding => this.remove(binding)); } rebind(oldBinding: T, newBinding: T): void { - if (this.remove(oldBinding)) { - this.add(newBinding); - } + this.remove(oldBinding); + this.add(newBinding); } contains(binding: T): boolean { diff --git a/packages/server-node/src/di/multi-bindings.spec.ts b/packages/server-node/src/di/multi-bindings.spec.ts index aee7aee..d823ddd 100644 --- a/packages/server-node/src/di/multi-bindings.spec.ts +++ b/packages/server-node/src/di/multi-bindings.spec.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; -import { expect } from 'chai'; @injectable() class TestClass {} @@ -33,7 +33,7 @@ describe('test implementations of MultiBinding', () => { describe('test basic functionaly (ClassMultiBinding) ', () => { const binding = new ClassMultiBinding('TestClass'); it('add - new binding', () => { - expect(binding.add(TestClass1)).true; + binding.add(TestClass1); expect(binding.contains(TestClass1)).true; const result = binding.getAll(); @@ -42,26 +42,26 @@ describe('test implementations of MultiBinding', () => { }); it('add - existing binding', () => { - expect(binding.add(TestClass1)).false; + binding.add(TestClass1); expect(binding.getAll().length).to.be.equal(1); expect(binding.getAll()[0]).to.be.equal(TestClass1); }); it('remove - existing binding', () => { expect(binding.contains(TestClass1)).true; - expect(binding.remove(TestClass1)).true; + binding.remove(TestClass1); expect(binding.contains(TestClass1)).false; expect(binding.getAll()).to.have.length(0); }); it('remove - non-existing binding', () => { const previousSize = binding.getAll().length; - expect(binding.remove(TestClass2)).false; + binding.remove(TestClass2); expect(binding.getAll()).to.have.length(previousSize); }); it('addAll - TestClass1 & TestClass2', () => { - expect(binding.addAll([TestClass1, TestClass2])).true; + binding.addAll([TestClass1, TestClass2]); const result = binding.getAll(); expect(result.length).to.be.equal(2); @@ -86,7 +86,7 @@ describe('test implementations of MultiBinding', () => { }); it('removeAll- TestClass1 & TestClass3', () => { - expect(binding.removeAll([TestClass1, TestClass3])).true; + binding.removeAll([TestClass1, TestClass3]); const result = binding.getAll(); expect(result).to.have.length(0); }); diff --git a/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts b/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts index 9da5099..a426ee3 100644 --- a/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts +++ b/packages/server-node/src/diagram/request-type-hints-action-handler.spec.ts @@ -13,19 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { - EdgeTypeHint, - isSetTypeHintsAction, - RequestModelAction, - RequestTypeHintsAction, - SetTypeHintsAction, - ShapeTypeHint -} from '@eclipse-glsp/protocol'; +import { EdgeTypeHint, RequestTypeHintsAction, SetTypeHintsAction, ShapeTypeHint } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import * as mock from '../test/mock-util'; import { DiagramConfiguration } from './diagram-configuration'; import { RequestTypeHintsActionHandler } from './request-type-hints-action-handler'; -import { expect } from 'chai'; describe('test RequestTypeHintsActionHandler', () => { const container = new Container(); @@ -55,18 +48,12 @@ describe('test RequestTypeHintsActionHandler', () => { const handler = container.resolve(RequestTypeHintsActionHandler); it('execute with correct action', () => { - const result = handler.execute(new RequestTypeHintsAction()); + const result = handler.execute(RequestTypeHintsAction.create()); expect(result).to.have.length(1); - expect(isSetTypeHintsAction(result[0])).true; + expect(SetTypeHintsAction.is(result[0])).true; expect(result).to.be.deep.equal([ { edgeHints: [edgeTypeHint], kind: 'setTypeHints', shapeHints: [shapeTypeHint], responseId: '' } as SetTypeHintsAction ]); }); - - it('execute with wrong action', () => { - const result = handler.execute(new RequestModelAction()); - - expect(result).to.be.deep.equal([]); - }); }); diff --git a/packages/server-node/src/diagram/request-type-hints-action-handler.ts b/packages/server-node/src/diagram/request-type-hints-action-handler.ts index 062babf..df061c6 100644 --- a/packages/server-node/src/diagram/request-type-hints-action-handler.ts +++ b/packages/server-node/src/diagram/request-type-hints-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isRequestTypeHintsAction, RequestTypeHintsAction, SetTypeHintsAction } from '@eclipse-glsp/protocol'; +import { Action, RequestTypeHintsAction, SetTypeHintsAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { DiagramConfiguration } from './diagram-configuration'; @@ -23,11 +23,13 @@ export class RequestTypeHintsActionHandler implements ActionHandler { @inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration; static KINDS = [RequestTypeHintsAction.KIND]; - execute(action: Action): Action[] { - if (isRequestTypeHintsAction(action)) { - return [new SetTypeHintsAction(this.diagramConfiguration.shapeTypeHints, this.diagramConfiguration.edgeTypeHints)]; - } - return []; + execute(action: RequestTypeHintsAction): Action[] { + return [ + SetTypeHintsAction.create({ + shapeHints: this.diagramConfiguration.shapeTypeHints, + edgeHints: this.diagramConfiguration.edgeTypeHints + }) + ]; } readonly actionKinds = [RequestTypeHintsAction.KIND]; diff --git a/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts index a6c1e81..2ad863c 100644 --- a/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts +++ b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts @@ -37,15 +37,15 @@ export class RequestClipboardDataActionHandler implements ActionHandler { protected modelSerializer: GModelSerializer; execute(action: RequestClipboardDataAction): MaybePromise { - const jsonArray: SModelElementSchema[] = []; + const schemas: SModelElementSchema[] = []; const index = this.modelState.index; const selectedElements = index.getAll(action.editorContext.selectedElementIds); - const clipboardData: ClipboardData = {}; + const clipboardData: ClipboardData = { format: 'application/json' }; selectedElements.forEach(element => { - jsonArray.push(this.modelSerializer.createSchema(element)); + schemas.push(this.modelSerializer.createSchema(element)); }); // eslint-disable-next-line no-null/no-null - clipboardData['application/json'] = JSON.stringify(jsonArray, null, 2); - return [new SetClipboardDataAction(clipboardData)]; + clipboardData['application/json'] = JSON.stringify(schemas, undefined, 2); + return [SetClipboardDataAction.create(clipboardData)]; } } diff --git a/packages/server-node/src/features/contextactions/context-menu-item-provider.ts b/packages/server-node/src/features/contextactions/context-menu-item-provider.ts index 6cf1199..b1a6037 100644 --- a/packages/server-node/src/features/contextactions/context-menu-item-provider.ts +++ b/packages/server-node/src/features/contextactions/context-menu-item-provider.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, Args, EditorContext, LabeledAction, Point } from '@eclipse-glsp/protocol'; +import { Args, EditorContext, LabeledAction, MenuItem, Point } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { ContextActionsProvider } from './context-actions-provider'; @@ -50,35 +50,3 @@ export abstract class ContextMenuItemProvider implements ContextActionsProvider return this.getItems(editorContext.selectedElementIds, position, editorContext.args); } } - -export class MenuItem extends LabeledAction { - readonly id: string; - readonly sortString?: string; - readonly group?: string; - readonly parentId?: string; - readonly children?: MenuItem[]; - readonly isEnabled?: boolean; - readonly isToggled?: boolean; - - constructor( - id: string, - label: string, - actions?: Action[], - children?: MenuItem[], - icon?: string, - sortString?: string, - group?: string, - parentId?: string, - isEnabled = true, - isToggled = false - ) { - super(label, actions ? actions : [], icon); - this.id = id; - this.sortString = sortString; - this.group = group; - this.parentId = parentId; - this.children = children; - this.isEnabled = isEnabled; - this.isToggled = isToggled; - } -} diff --git a/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts b/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts index b522e5f..7062d62 100644 --- a/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts +++ b/packages/server-node/src/features/contextactions/request-context-actions-handler.spec.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; +import { PaletteItem, RequestContextActions, SetContextActions } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import { OperationHandlerRegistry } from '../../operations/operation-handler-registry'; import * as mock from '../../test/mock-util'; import { ContextActionsProvider } from './context-actions-provider'; import { ContextActionsProviderRegistry } from './context-actions-provider-registry'; import { RequestContextActionsHandler } from './request-context-actions-handler'; -import { DefaultToolPaletteItemProvider, PaletteItem } from './tool-palette-item-provider'; -import { expect } from 'chai'; +import { DefaultToolPaletteItemProvider } from './tool-palette-item-provider'; describe('Test RequestContextActionsHandler', () => { const operationHandlerRegistry = new OperationHandlerRegistry(); @@ -48,26 +48,28 @@ describe('Test RequestContextActionsHandler', () => { requestContextActionsHandler['contextActionsProviderRegistry'] = contextActionsProviderRegistry; it('request ToolPaletteContextActions', async () => { - const actions = await requestContextActionsHandler.execute(new RequestContextActions('tool-palette', { selectedElementIds: [] })); + const actions = await requestContextActionsHandler.execute( + RequestContextActions.create({ contextId: 'tool-palette', editorContext: { selectedElementIds: [] } }) + ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetContextActions); + expect(SetContextActions.is(action)).to.be.true; const setContextActions = action as SetContextActions; expect(setContextActions.actions).to.have.length(2); - expect(setContextActions.actions[0]).instanceOf(PaletteItem); + expect(PaletteItem.is(setContextActions.actions[0])).to.be.true; const firstPaletteItem = setContextActions.actions[0] as PaletteItem; expect(firstPaletteItem.label).to.be.equal('Nodes'); expect(firstPaletteItem.children).to.have.length(2); - expect(firstPaletteItem.children[0].label).to.be.equal('ANode'); - expect(firstPaletteItem.children[1].label).to.be.equal('BNode'); + expect(firstPaletteItem.children?.[0].label).to.be.equal('ANode'); + expect(firstPaletteItem.children?.[1].label).to.be.equal('BNode'); - expect(setContextActions.actions[1]).instanceOf(PaletteItem); + expect(PaletteItem.is(setContextActions.actions[1])).to.be.true; const secondPaletteItem = setContextActions.actions[1] as PaletteItem; expect(secondPaletteItem.label).to.be.equal('Edges'); expect(secondPaletteItem.children).to.have.length(2); - expect(secondPaletteItem.children[0].label).to.be.equal('AEdge'); - expect(secondPaletteItem.children[1].label).to.be.equal('BEdge'); + expect(secondPaletteItem.children?.[0].label).to.be.equal('AEdge'); + expect(secondPaletteItem.children?.[1].label).to.be.equal('BEdge'); }); }); diff --git a/packages/server-node/src/features/contextactions/request-context-actions-handler.ts b/packages/server-node/src/features/contextactions/request-context-actions-handler.ts index 120e8ec..9334ae3 100644 --- a/packages/server-node/src/features/contextactions/request-context-actions-handler.ts +++ b/packages/server-node/src/features/contextactions/request-context-actions-handler.ts @@ -32,6 +32,6 @@ export class RequestContextActionsHandler implements ActionHandler { const provider = this.contextActionsProviderRegistry.get(action.contextId)!; (await provider.getActions(editorContext)).forEach(returnAction => actions.push(returnAction)); } - return [new SetContextActions(actions, undefined, action.editorContext.args)]; + return [SetContextActions.create(actions, { args: action.editorContext.args })]; } } diff --git a/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts b/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts index b42defb..eba9a6b 100644 --- a/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts +++ b/packages/server-node/src/features/contextactions/tool-palette-item-provider.spec.ts @@ -36,11 +36,11 @@ describe('Test DefaultToolPaletteItemProvider', () => { expect(items).to.have.length(2); expect(items[0].label).to.be.equal('Nodes'); expect(items[0].children).to.have.length(2); - expect(items[0].children[0].label).to.be.equal('ANode'); - expect(items[0].children[1].label).to.be.equal('BNode'); + expect(items[0].children?.[0].label).to.be.equal('ANode'); + expect(items[0].children?.[1].label).to.be.equal('BNode'); expect(items[1].label).to.be.equal('Edges'); expect(items[1].children).to.have.length(2); - expect(items[1].children[0].label).to.be.equal('AEdge'); - expect(items[1].children[1].label).to.be.equal('BEdge'); + expect(items[1].children?.[0].label).to.be.equal('AEdge'); + expect(items[1].children?.[1].label).to.be.equal('BEdge'); }); }); diff --git a/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts b/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts index 2573034..6812216 100644 --- a/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts +++ b/packages/server-node/src/features/contextactions/tool-palette-item-provider.ts @@ -20,7 +20,7 @@ import { EditorContext, LabeledAction, MaybePromise, - TriggerElementCreationAction + PaletteItem } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { CreateOperationHandler } from '../../operations/create-operation-handler'; @@ -71,8 +71,8 @@ export class DefaultToolPaletteItemProvider extends ToolPaletteItemProvider { const nodes = this.createPaletteItem(handlers, CreateNodeOperation.KIND); const edges = this.createPaletteItem(handlers, CreateEdgeOperation.KIND); return [ - PaletteItem.createPaletteGroup('node-group', 'Nodes', nodes, 'symbol-property', 'A'), - PaletteItem.createPaletteGroup('edge-group', 'Edges', edges, 'symbol-property', 'B') + { id: 'node-group', label: 'Nodes', actions: [], children: nodes, icon: 'symbol-property', sortString: 'A' }, + { id: 'edge-group', label: 'Edges', actions: [], children: edges, icon: 'symbol-property', sortString: 'B' } ]; } @@ -84,28 +84,7 @@ export class DefaultToolPaletteItemProvider extends ToolPaletteItemProvider { .sort((a, b) => a.sortString.localeCompare(b.sortString)); } - create(action: TriggerElementCreationAction, label: string): PaletteItem { - return new PaletteItem(`palette-item${this.counter}`, label, action); - } -} - -export class PaletteItem extends LabeledAction { - id: string; - sortString: string; - children: PaletteItem[]; - - constructor(id: string, label: string, initializeAction?: TriggerElementCreationAction, icon?: string) { - super(label, initializeAction ? [initializeAction] : [], icon); - this.sortString = label.charAt(0); - this.id = id; - } - - static createPaletteGroup(id: string, label: string, children: PaletteItem[], icon?: string, sortString?: string): PaletteItem { - const item = new PaletteItem(id, label, undefined, icon); - item.children = children; - if (sortString) { - item.sortString = sortString; - } - return item; + create(action: PaletteItem.TriggerElementCreationAction, label: string): PaletteItem { + return { id: `palette-item${this.counter}`, sortString: label.charAt(0), label, actions: [action] }; } } diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts index 48e5138..1275510 100644 --- a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts +++ b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts @@ -15,11 +15,11 @@ ********************************************************************************/ import { GLabel } from '@eclipse-glsp/graph'; import { ApplyLabelEditOperation } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; import { GModelState } from '../../base-impl/gmodel-state'; import { GModelIndex } from '../model/gmodel-index'; import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; describe('Test ApplyLabelEditOperationHandler', () => { const label = new GLabel(); @@ -30,7 +30,7 @@ describe('Test ApplyLabelEditOperationHandler', () => { Object.defineProperty(applyLabelEditOperationHandler, 'modelState', { value: modelState }); it('text is changed after ApplyLabelEditOperation', async () => { - expect(applyLabelEditOperationHandler.execute(new ApplyLabelEditOperation('undefined', 'test'))).to.not.throw; + expect(applyLabelEditOperationHandler.execute(ApplyLabelEditOperation.create({ labelId: 'myId', text: 'test' }))).to.not.throw; expect(label.text).to.be.equal('test'); }); }); diff --git a/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts index d59e112..3d583db 100644 --- a/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts +++ b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts @@ -15,6 +15,8 @@ ********************************************************************************/ import { GNode } from '@eclipse-glsp/graph'; import { RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; import { GModelState } from '../../base-impl/gmodel-state'; import { TestContextEditValidator, TestLabelEditValidator } from '../../test/mock-util'; import { GModelIndex } from '../model/gmodel-index'; @@ -22,8 +24,6 @@ import { ContextEditValidator } from './context-edit-validator'; import { DefaultContextEditValidatorRegistry } from './context-edit-validator-registry'; import { LabelEditValidator } from './label-edit-validator'; import { RequestEditValidationHandler } from './request-edit-validation-handler'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; describe('Test RequestEditValidationHandler', () => { const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; @@ -40,11 +40,13 @@ describe('Test RequestEditValidationHandler', () => { requestEditValidationHandler['contextEditValidatorRegistry'] = contextEditValidatorsRegistry; it('requestContextEditValidation with ok result', async () => { - const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'ok')); + const actions = await requestEditValidationHandler.execute( + RequestEditValidationAction.create({ contextId: 'test', modelElementId: '', text: 'ok' }) + ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; @@ -53,11 +55,13 @@ describe('Test RequestEditValidationHandler', () => { }); it('requestContextEditValidation with warning result', async () => { - const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'warning')); + const actions = await requestEditValidationHandler.execute( + RequestEditValidationAction.create({ contextId: 'test', modelElementId: '', text: 'warning' }) + ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; @@ -66,11 +70,13 @@ describe('Test RequestEditValidationHandler', () => { }); it('requestContextEditValidation with error result', async () => { - const actions = await requestEditValidationHandler.execute(new RequestEditValidationAction('test', 'undefined', 'error')); + const actions = await requestEditValidationHandler.execute( + RequestEditValidationAction.create({ contextId: 'test', modelElementId: '', text: 'error' }) + ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; @@ -80,12 +86,12 @@ describe('Test RequestEditValidationHandler', () => { it('requestLabelEditValidation with ok result', async () => { const actions = await requestEditValidationHandler.execute( - new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'ok') + RequestEditValidationAction.create({ contextId: LabelEditValidator.CONTEXT_ID, modelElementId: '', text: 'ok' }) ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; @@ -95,12 +101,12 @@ describe('Test RequestEditValidationHandler', () => { it('requestLabelEditValidation with warning result', async () => { const actions = await requestEditValidationHandler.execute( - new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'warning') + RequestEditValidationAction.create({ contextId: LabelEditValidator.CONTEXT_ID, modelElementId: '', text: 'warning' }) ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; @@ -110,12 +116,12 @@ describe('Test RequestEditValidationHandler', () => { it('requestLabelEditValidation with error result', async () => { const actions = await requestEditValidationHandler.execute( - new RequestEditValidationAction(LabelEditValidator.CONTEXT_ID, 'undefined', 'error') + RequestEditValidationAction.create({ contextId: LabelEditValidator.CONTEXT_ID, modelElementId: '', text: 'error' }) ); expect(actions).to.have.length(1); const action = actions[0]; - expect(action).instanceOf(SetEditValidationResultAction); + expect(SetEditValidationResultAction.is(action)).to.be.true; const setEditValidationResultAction = action as SetEditValidationResultAction; const status = setEditValidationResultAction.status; diff --git a/packages/server-node/src/features/directediting/request-edit-validation-handler.ts b/packages/server-node/src/features/directediting/request-edit-validation-handler.ts index dcc7f28..426c1b7 100644 --- a/packages/server-node/src/features/directediting/request-edit-validation-handler.ts +++ b/packages/server-node/src/features/directediting/request-edit-validation-handler.ts @@ -15,8 +15,8 @@ ********************************************************************************/ import { Action, MaybePromise, RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ContextEditValidatorRegistry } from '../../features/directediting/context-edit-validator-registry'; import { ActionHandler } from '../../actions/action-handler'; +import { ContextEditValidatorRegistry } from '../../features/directediting/context-edit-validator-registry'; import { Logger } from '../../utils/logger'; @injectable() @@ -33,11 +33,11 @@ export class RequestEditValidationHandler implements ActionHandler { const validator = this.contextEditValidatorRegistry.get(action.contextId); if (validator) { const validationStatus = validator.validate(action); - return [new SetEditValidationResultAction(validationStatus)]; + return [SetEditValidationResultAction.create(validationStatus)]; } else { const message = `No validator registered for the context '${action.contextId}'`; this.logger.warn(message); - return [new SetEditValidationResultAction({ severity: ValidationStatus.Severity.WARNING, message: message })]; + return [SetEditValidationResultAction.create({ severity: ValidationStatus.Severity.WARNING, message: message })]; } } } diff --git a/packages/server-node/src/features/model/gmodel-serializer.spec.ts b/packages/server-node/src/features/model/gmodel-serializer.spec.ts index 7f88e64..bf49dc3 100644 --- a/packages/server-node/src/features/model/gmodel-serializer.spec.ts +++ b/packages/server-node/src/features/model/gmodel-serializer.spec.ts @@ -14,13 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GEdge, getDefaultMapping, GGraph, GNode } from '@eclipse-glsp/graph'; +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import { DiagramConfiguration } from '../../diagram/diagram-configuration'; import * as mock from '../../test/mock-util'; import { GLSPServerError } from '../../utils/glsp-server-error'; import { Logger } from '../../utils/logger'; import { DefaultGModelSerializer } from './gmodel-serializer'; -import { expect } from 'chai'; class TestNode extends GNode { foo(): void { @@ -81,7 +81,7 @@ describe('test DefaultGModelSerializer', () => { it('createElement - with node schema', () => { const node = serializer.createElement(testNodeSchema); - expect(node instanceof TestNode).true; + expect(node).to.be.an.instanceOf(TestNode); expect(node).to.be.deep.equal(testNodeSchema); expect((node as TestNode).foo).to.not.be.undefined; }); @@ -89,7 +89,7 @@ describe('test DefaultGModelSerializer', () => { it('createElement- with sub type of registered schema', () => { testNodeSchema.type = 'node:rectangular'; const node = serializer.createElement(testNodeSchema); - expect(node instanceof TestNode).true; + expect(node).to.be.an.instanceOf(TestNode); expect(node).to.be.deep.equal(testNodeSchema); expect((node as TestNode).foo).to.not.be.undefined; }); @@ -97,7 +97,7 @@ describe('test DefaultGModelSerializer', () => { it('createElement - with parent', () => { const parent = new GNode(); const child = serializer.createElement(testNodeSchema, parent); - expect(child instanceof TestNode).true; + expect(child).to.be.an.instanceOf(TestNode); expect(child).to.be.deep.equal(testNodeSchemaWithParent); expect(child.parent).to.be.equal(parent); }); @@ -113,13 +113,13 @@ describe('test DefaultGModelSerializer', () => { it('createRoot - with registered root schema', () => { const root = serializer.createRoot(testRootSchema); - expect(root instanceof GGraph).true; + expect(root).to.be.an.instanceOf(GGraph); expect(root.children.length).to.be.equal(3); const node1 = root.children[0]; - expect(node1 instanceof TestNode).true; + expect(node1).to.be.an.instanceOf(TestNode); const node2 = root.children[1]; - expect(node2 instanceof TestNode).true; - expect(root.children[2] instanceof GEdge).true; + expect(node2).to.be.an.instanceOf(TestNode); + expect(root.children[2]).to.be.an.instanceOf(GEdge); const edge = root.children[2] as GEdge; expect(edge.sourceId).to.be.equal(node1.id); expect(edge.targetId).to.be.equal(node2.id); diff --git a/packages/server-node/src/features/model/model-submission-handler.ts b/packages/server-node/src/features/model/model-submission-handler.ts index 9143948..27f1ae5 100644 --- a/packages/server-node/src/features/model/model-submission-handler.ts +++ b/packages/server-node/src/features/model/model-submission-handler.ts @@ -14,7 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GModelRootSchema } from '@eclipse-glsp/graph'; -import { Action, RequestBoundsAction, SetDirtyStateAction, SetModelAction, UpdateModelAction } from '@eclipse-glsp/protocol'; +import { + Action, + DirtyStateChangeReason, + RequestBoundsAction, + SetDirtyStateAction, + SetModelAction, + UpdateModelAction +} from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration'; import { LayoutEngine } from '../layout/layout-engine'; @@ -49,15 +56,15 @@ export class ModelSubmissionHandler { * @param reason The optional reason that caused the model update. * @returns A list of actions to be processed in order to submit the model. */ - submitModel(reason?: string): Action[] { + submitModel(reason?: DirtyStateChangeReason): Action[] { this.modelFactory.createModel(); this.modelState.root.revision = (this.modelState.root.revision ?? 0) + 1; const root = this.serializeGModel(); if (this.diagramConfiguration.needsClientLayout) { - return [new RequestBoundsAction(root), new SetDirtyStateAction(this.modelState.isDirty, reason)]; + return [RequestBoundsAction.create(root), SetDirtyStateAction.create(this.modelState.isDirty, { reason })]; } - return [new SetModelAction(root)]; + return [SetModelAction.create(root)]; } /** @@ -74,16 +81,20 @@ export class ModelSubmissionHandler { * @param reason The optional reason that caused the model update. * @returns A list of actions to be processed in order to submit the model. */ - submitModelDirectly(reason?: string): Action[] { + submitModelDirectly(reason?: DirtyStateChangeReason): Action[] { const root = this.serializeGModel(); if (this.diagramConfiguration.layoutKind === ServerLayoutKind.AUTOMATIC && this.layoutEngine) { this.layoutEngine.layout(); } const result: Action[] = []; - result.push(root.revision === 0 ? new SetModelAction(root) : new UpdateModelAction(root, this.diagramConfiguration.animatedUpdate)); + result.push( + root.revision === 0 + ? SetModelAction.create(root) + : UpdateModelAction.create(root, { animate: this.diagramConfiguration.animatedUpdate }) + ); if (!this.diagramConfiguration.needsClientLayout) { - result.push(new SetDirtyStateAction(this.modelState.isDirty, reason)); + result.push(SetDirtyStateAction.create(this.modelState.isDirty, { reason })); } return result; } diff --git a/packages/server-node/src/features/model/request-model-action-handler.ts b/packages/server-node/src/features/model/request-model-action-handler.ts index 245e53a..077405d 100644 --- a/packages/server-node/src/features/model/request-model-action-handler.ts +++ b/packages/server-node/src/features/model/request-model-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, GLSPServerStatusAction, isRequestModelAction, RequestModelAction, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { Action, RequestModelAction, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../../actions/action-dispatcher'; import { ActionHandler } from '../../actions/action-handler'; @@ -41,23 +41,27 @@ export class RequestModelActionHandler implements ActionHandler { @inject(ModelSubmissionHandler) protected submissionHandler: ModelSubmissionHandler; - async execute(action: Action): Promise { + async execute(action: RequestModelAction): Promise { this.logger.debug('Execute RequestModelAction:', action); - if (isRequestModelAction(action)) { - this.modelState.setAll(action.options ?? {}); + this.modelState.setAll(action.options ?? {}); - this.notifyClient('Model loading in progress'); - await this.sourceModelStorage.loadSourceModel(action); - this.notifyClient(); - } + this.notifyClient('Model loading in progress'); + await this.sourceModelStorage.loadSourceModel(action); + // Clear the previous notification. + this.notifyClient(); return this.submissionHandler.submitModel(); } - protected notifyClient(message?: string): void { - const severity = message ? 'INFO' : 'NONE'; + /** + * Send a message and status notification with the given message to the client. + * An empty message is an indication for the client to clear previously received notifications. + * @param message The message that should be sent to the client + */ + protected notifyClient(message = ''): void { + const severity = message.length > 0 ? 'INFO' : 'NONE'; this.actionDispatcher.dispatchAll( - new GLSPServerStatusAction(severity, message ?? ''), - new ServerMessageAction(severity, message ?? '') + ServerMessageAction.create(message, { severity }), + ServerStatusAction.create(message, { severity }) ); } } diff --git a/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts b/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts index 889b32c..7323114 100644 --- a/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts +++ b/packages/server-node/src/features/navigation/request-navigation-targets-action-handler.ts @@ -34,6 +34,6 @@ export class RequestNavigationTargetsActionHandler implements ActionHandler { const targets = provider.getTargets(editorContext); targets.forEach(target => allTargets.push(target)); } - return [new SetNavigationTargetsAction(allTargets, action.requestId, editorContext.args)]; + return [SetNavigationTargetsAction.create(allTargets, { args: editorContext.args })]; } } diff --git a/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts b/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts index e928b6f..b13ea82 100644 --- a/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts +++ b/packages/server-node/src/features/navigation/resolve-navigation-targets-action-handler.ts @@ -16,8 +16,8 @@ import { Action, ResolveNavigationTargetAction, SetResolvedNavigationTargetAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; -import { NavigationTargetResolver } from './navigation-target-resolver'; import { Logger } from '../../utils/logger'; +import { NavigationTargetResolver } from './navigation-target-resolver'; @injectable() export class ResolveNavigationTargetsActionHandler implements ActionHandler { @@ -37,6 +37,6 @@ export class ResolveNavigationTargetsActionHandler implements ActionHandler { } const target = action.navigationTarget; const resolution = await this.navigationTargetResolver.resolve(target); - return [new SetResolvedNavigationTargetAction(resolution.elementIds, resolution.args)]; + return [SetResolvedNavigationTargetAction.create(resolution.elementIds, { args: resolution.args })]; } } diff --git a/packages/server-node/src/features/popup/request-popup-model-action-handler.ts b/packages/server-node/src/features/popup/request-popup-model-action-handler.ts index 501d339..c72078c 100644 --- a/packages/server-node/src/features/popup/request-popup-model-action-handler.ts +++ b/packages/server-node/src/features/popup/request-popup-model-action-handler.ts @@ -46,7 +46,7 @@ export class RequestPopupModelActionHandler implements ActionHandler { const popupModel = this.popupModelFactory.createPopupModel(hoverElement, action); if (popupModel) { const modelSchema = this.modelSerializer.createSchema(popupModel); - return [new SetPopupModelAction(modelSchema)]; + return [SetPopupModelAction.create(modelSchema)]; } return []; } diff --git a/packages/server-node/src/features/validation/request-markers-handler.ts b/packages/server-node/src/features/validation/request-markers-handler.ts index 879a59b..84f7656 100644 --- a/packages/server-node/src/features/validation/request-markers-handler.ts +++ b/packages/server-node/src/features/validation/request-markers-handler.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GGraph } from '@eclipse-glsp/graph'; import { Action, Marker, RequestMarkersAction, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { GModelState } from '../../base-impl/gmodel-state'; import { ActionHandler } from '../../actions/action-handler'; -import { ModelValidator } from './model-validator'; +import { GModelState } from '../../base-impl/gmodel-state'; import { GLSPServerError } from '../../utils/glsp-server-error'; -import { GGraph } from '@eclipse-glsp/graph'; +import { ModelValidator } from './model-validator'; @injectable() export class RequestMarkersHandler implements ActionHandler { @@ -46,6 +46,6 @@ export class RequestMarkersHandler implements ActionHandler { markers = markers.concat(await this.validator.validate([modelElement])); } } - return [new SetMarkersAction(markers)]; + return [SetMarkersAction.create(markers)]; } } diff --git a/packages/server-node/src/launch/socket-server-launcher.ts b/packages/server-node/src/launch/socket-server-launcher.ts index ab9e50c..fdf9aaf 100644 --- a/packages/server-node/src/launch/socket-server-launcher.ts +++ b/packages/server-node/src/launch/socket-server-launcher.ts @@ -23,7 +23,7 @@ const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed'; @injectable() export class SocketServerLauncher extends GLSPServerLauncher { - @inject(Logger) protected logger: Logger; + @inject(Logger) protected override logger: Logger; protected currentConnections: jsonrpc.MessageConnection[] = []; protected startupCompleteMessage = START_UP_COMPLETE_MSG; diff --git a/packages/server-node/src/operations/create-operation-handler.ts b/packages/server-node/src/operations/create-operation-handler.ts index 6463f65..ee98729 100644 --- a/packages/server-node/src/operations/create-operation-handler.ts +++ b/packages/server-node/src/operations/create-operation-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GEdge, GGraph, GModelElement, GNode, GPort, isGBoundsAware } from '@eclipse-glsp/graph'; +import { GBoundsAware, GEdge, GGraph, GModelElement, GModelRoot, GNode, GPort, isGBoundsAware } from '@eclipse-glsp/graph'; import { Args, CreateEdgeOperation, @@ -23,13 +23,11 @@ import { Point, SelectAction, TriggerEdgeCreationAction, - TriggerElementCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { GModelState } from '../base-impl/gmodel-state'; -import { absoluteToRelative } from '../utils/geometry-util'; import { OperationHandler } from './operation-handler'; /** @@ -48,11 +46,11 @@ export abstract class CreateOperationHandler implements OperationHandler { * * @returns A list of {@link TriggerElementCreationAction}s. */ - getTriggerActions(): TriggerElementCreationAction[] { + getTriggerActions(): (TriggerEdgeCreationAction | TriggerNodeCreationAction)[] { if (this.operationType === CreateNodeOperation.KIND) { - return this.elementTypeIds.map(typeId => new TriggerNodeCreationAction(typeId)); + return this.elementTypeIds.map(typeId => TriggerNodeCreationAction.create(typeId)); } else if (this.operationType === CreateEdgeOperation.KIND) { - return this.elementTypeIds.map(typeId => new TriggerEdgeCreationAction(typeId)); + return this.elementTypeIds.map(typeId => TriggerEdgeCreationAction.create(typeId)); } return []; } @@ -74,7 +72,7 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler return CreateNodeOperation.KIND; } - abstract elementTypeIds: string[]; + abstract override elementTypeIds: string[]; execute(operation: CreateNodeOperation): void { const container = this.getContainer(operation) ?? this.modelState.root; @@ -85,7 +83,7 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler if (element) { container.children.push(element); element.parent = container; - this.actionDispatcher.dispatchAfterNextUpdate(new SelectAction(), new SelectAction([element.id])); + this.actionDispatcher.dispatchAfterNextUpdate(SelectAction.create({ selectedElementsIDs: [element.id] })); } } @@ -145,3 +143,37 @@ export abstract class CreateEdgeOperationHandler extends CreateOperationHandler abstract createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined; } + +/** + * Convert a point in absolute coordinates to a point relative to the specified GBoundsAware. + * Note: this method only works if the specified {@link GBoundsAware} is part of a + * hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement + * {@link GBoundsAware}, this method will throw an exception. + * + * @param absolutePoint + * @param modelElement + * @returns + * A new point, relative to the coordinates space of the specified {@link GBoundsAware} + * @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy + */ +export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point { + let parentElement; + if (!(modelElement instanceof GModelRoot)) { + parentElement = modelElement.parent; + } + + let relativeToParent: Point; + if (!parentElement) { + relativeToParent = { x: absolutePoint.x, y: absolutePoint.y }; + } else { + if (!isGBoundsAware(parentElement)) { + throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`); + } + relativeToParent = absoluteToRelative(absolutePoint, parentElement); + } + + const x = modelElement.position?.x ?? 0; + const y = modelElement.position?.y ?? 0; + + return { x: relativeToParent.x - x, y: relativeToParent.y - y }; +} diff --git a/packages/server-node/src/operations/cut-operation-handler.ts b/packages/server-node/src/operations/cut-operation-handler.ts index ee33c76..c3d47af 100644 --- a/packages/server-node/src/operations/cut-operation-handler.ts +++ b/packages/server-node/src/operations/cut-operation-handler.ts @@ -28,7 +28,7 @@ export class CutOperationHandler implements OperationHandler { execute(operation: CutOperation): void { const cutableElementIds = this.getElementToCut(operation); if (cutableElementIds.length > 0) { - this.actionDispatcher.dispatch(new DeleteElementOperation(cutableElementIds)); + this.actionDispatcher.dispatch(DeleteElementOperation.create(cutableElementIds)); } } diff --git a/packages/server-node/src/operations/operation-action-handler.ts b/packages/server-node/src/operations/operation-action-handler.ts index 9263610..7dd5316 100644 --- a/packages/server-node/src/operations/operation-action-handler.ts +++ b/packages/server-node/src/operations/operation-action-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, isCreateOperation, MaybePromise, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; +import { Action, CreateOperation, MaybePromise, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; import { GModelState } from '../base-impl/gmodel-state'; @@ -30,10 +30,14 @@ export class OperationActionHandler implements ActionHandler { constructor(@inject(Operations) readonly actionKinds: string[]) {} - execute(action: Action): MaybePromise { + execute(action: Operation): MaybePromise { if (this.handles(action)) { if (this.modelState.isReadonly) { - return [new ServerMessageAction('WARNING', `Server is in readonly-mode! Could not execute operation: ${action.kind}`)]; + return [ + ServerMessageAction.create(`Server is in readonly-mode! Could not execute operation: ${action.kind}`, { + severity: 'WARNING' + }) + ]; } const operationHandler = OperationActionHandler.getOperationHandler(action, this.operationHandlerRegistry); if (operationHandler) { @@ -60,6 +64,6 @@ export class OperationActionHandler implements ActionHandler { } static getOperationHandler(operation: Operation, registry: OperationHandlerRegistry): OperationHandler | undefined { - return isCreateOperation(operation) ? registry.get(`${operation.kind}_${operation.elementTypeId}`) : registry.get(operation.kind); + return CreateOperation.is(operation) ? registry.get(`${operation.kind}_${operation.elementTypeId}`) : registry.get(operation.kind); } } diff --git a/packages/server-node/src/operations/operation-handler-registry.spec.ts b/packages/server-node/src/operations/operation-handler-registry.spec.ts index dea5442..4183385 100644 --- a/packages/server-node/src/operations/operation-handler-registry.spec.ts +++ b/packages/server-node/src/operations/operation-handler-registry.spec.ts @@ -14,10 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { CompoundOperation, CreateEdgeOperation, CreateNodeOperation } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import * as mock from '../test/mock-util'; import { CompoundOperationHandler } from './compound-operation-handler'; import { OperationHandlerRegistry } from './operation-handler-registry'; -import { expect } from 'chai'; describe('Test OperationHandlerRegistry', () => { const stubANode = new mock.StubCreateNodeOperationHandler('ANode'); @@ -30,7 +30,7 @@ describe('Test OperationHandlerRegistry', () => { operationHandlerRegistry.registerHandler(new CompoundOperationHandler()); expect(operationHandlerRegistry.keys()).to.have.length(1); expect(operationHandlerRegistry.keys()).to.contain(CompoundOperation.KIND); - expect(operationHandlerRegistry.get(CompoundOperation.KIND)).instanceOf(CompoundOperationHandler); + expect(operationHandlerRegistry.get(CompoundOperation.KIND)).to.be.an.instanceOf(CompoundOperationHandler); }); it('register CreateOperationActionHandlers', () => { diff --git a/packages/server-node/src/operations/paste-operation-handler.ts b/packages/server-node/src/operations/paste-operation-handler.ts index bfc31cb..5f29d26 100644 --- a/packages/server-node/src/operations/paste-operation-handler.ts +++ b/packages/server-node/src/operations/paste-operation-handler.ts @@ -14,13 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GBoundsAware, GModelElement, isGBoundsAware } from '@eclipse-glsp/graph'; -import { ORIGIN_POINT, PasteOperation, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; +import { PasteOperation, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; +import * as uuid from 'uuid'; import { GModelState } from '../base-impl/gmodel-state'; import { GModelSerializer } from '../features/model/gmodel-serializer'; import { OperationHandler } from '../operations/operation-handler'; -import { shift } from '../utils/geometry-util'; -import * as uuid from 'uuid'; @injectable() export class PasteOperationHandler implements OperationHandler { @@ -50,7 +49,7 @@ export class PasteOperationHandler implements OperationHandler { if (lastMousePosition) { const referenceElement = this.getReferenceElementForPasteOffset(elements); if (referenceElement) { - const position = referenceElement.position ?? ORIGIN_POINT; + const position = referenceElement.position ?? Point.ORIGIN; offset.x = lastMousePosition.x - position.x; offset.y = lastMousePosition.y - position.y; } @@ -62,7 +61,7 @@ export class PasteOperationHandler implements OperationHandler { let minY = Number.MAX_VALUE; for (const element of elements) { if (isGBoundsAware(element)) { - const position = element.position ?? ORIGIN_POINT; + const position = element.position ?? Point.ORIGIN; if (minY > position.y) { minY = position.y; return element; @@ -109,8 +108,12 @@ export class PasteOperationHandler implements OperationHandler { } } -export type TypeGuard = (object: unknown) => object is T; - -export function filterByType(elements: GModelElement[], guard: TypeGuard): T[] { - return elements.filter(element => guard(element)) as T[]; +export function shift(elements: GModelElement[], offset: Point): void { + elements + .filter(element => isGBoundsAware(element)) + .map(element => element as unknown as GBoundsAware) + .forEach(gBoundsAware => { + const position = gBoundsAware.position ?? Point.ORIGIN; + gBoundsAware.position = { x: position.x + offset.x, y: position.y + offset.y }; + }); } diff --git a/packages/server-node/src/protocol/glsp-server.spec.ts b/packages/server-node/src/protocol/glsp-server.spec.ts index d18e753..c9524bf 100644 --- a/packages/server-node/src/protocol/glsp-server.spec.ts +++ b/packages/server-node/src/protocol/glsp-server.spec.ts @@ -14,7 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; +import * as sinon from 'sinon'; import { GlobalActionProvider } from '../actions/global-action-provider'; import { ClientSessionManager } from '../session/client-session-manager'; import * as mock from '../test/mock-util'; @@ -22,8 +24,6 @@ import { Logger } from '../utils/logger'; import { GLSPClientProxy, JsonRpcGLSPClientProxy } from './glsp-client-proxy'; import { DefaultGLSPServer } from './glsp-server'; import { GLSPServerListener } from './glsp-server-listener'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; describe('test DefaultGLSPServer', () => { const container = new Container(); @@ -72,19 +72,28 @@ describe('test DefaultGLSPServer', () => { }); it('addListener - add existing listener', () => { - expect(glspServer.addListener(listener1)).false; + const originalSize = glspServer['serverListeners'].length; + glspServer.addListener(listener1); + expect(glspServer['serverListeners'].length).to.be.equal(originalSize); }); it('addListener - add new listener', () => { - expect(glspServer.addListener(listener2)).true; + const originalSize = glspServer['serverListeners'].length; + glspServer.addListener(listener2); + expect(glspServer['serverListeners']).to.include(listener2); + expect(glspServer['serverListeners'].length).to.be.equal(originalSize + 1); }); it('removeListener - remove non-existing listener', () => { - expect(glspServer.removeListener({})).false; + const originalSize = glspServer['serverListeners'].length; + glspServer.removeListener({}); + expect(glspServer['serverListeners'].length).to.be.equal(originalSize); }); it('removeListener - remove existing listener', () => { - expect(glspServer.removeListener(listener2)).true; + const originalSize = glspServer['serverListeners'].length; + glspServer.removeListener(listener2); + expect(glspServer['serverListeners'].length).to.be.equal(originalSize - 1); }); it('initialize - with wrong protocol version', async () => { diff --git a/packages/server-node/src/protocol/glsp-server.ts b/packages/server-node/src/protocol/glsp-server.ts index 23c03b3..81c4c1c 100644 --- a/packages/server-node/src/protocol/glsp-server.ts +++ b/packages/server-node/src/protocol/glsp-server.ts @@ -273,8 +273,9 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { protected handleProcessError(message: ActionMessage, reason: any): void | PromiseLike { const errorMsg = `Could not process action: '${message.action.kind}`; this.logger.error(errorMsg, reason); + const details = reason?.cause?.toString() ?? ''; if (reason instanceof GLSPServerError) { - const errorAction = new ServerMessageAction('ERROR', errorMsg, reason.cause?.toString()); + const errorAction = ServerMessageAction.create(errorMsg, { severity: 'ERROR', details }); this.glspClient.process({ clientId: message.clientId, action: errorAction }); } } @@ -300,11 +301,13 @@ export class DefaultGLSPServer implements JsonRpcGLSPServer { } addListener(listener: GLSPServerListener): boolean { - return distinctAdd(this.serverListeners, listener); + distinctAdd(this.serverListeners, listener); + return true; } removeListener(listener: GLSPServerListener): boolean { - return remove(this.serverListeners, listener); + remove(this.serverListeners, listener); + return true; } protected getListenersToNotify(method: keyof GLSPServerListener): GLSPServerListener[] { diff --git a/packages/server-node/src/session/client-session-factory.spec.ts b/packages/server-node/src/session/client-session-factory.spec.ts index b76aa85..398964e 100644 --- a/packages/server-node/src/session/client-session-factory.spec.ts +++ b/packages/server-node/src/session/client-session-factory.spec.ts @@ -13,6 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import { Container, ContainerModule } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { DiagramModules, InjectionContainer } from '../di/service-identifiers'; @@ -22,7 +23,6 @@ import { GLSPServerError } from '../utils/glsp-server-error'; import { Logger } from '../utils/logger'; import { DefaultClientSessionFactory } from './client-session-factory'; import { ClientSessionInitializer } from './client-session-initializer'; -import { expect } from 'chai'; describe('test DefaultClientSessionFactory', () => { const clientSessionId = 'myClientId'; @@ -51,7 +51,7 @@ describe('test DefaultClientSessionFactory', () => { expect(session.id).to.be.equal(clientSessionId); expect(session.diagramType).to.be.equal(diagramType); expect(session.container.parent).to.be.equal(container); - expect(session.actionDispatcher instanceof mock.StubActionDispatcher).true; + expect(session.actionDispatcher).to.be.an.instanceOf(mock.StubActionDispatcher); }); it('create - unknown diagram type', () => { diff --git a/packages/server-node/src/session/client-session-manager.ts b/packages/server-node/src/session/client-session-manager.ts index 42645f2..4e3e6da 100644 --- a/packages/server-node/src/session/client-session-manager.ts +++ b/packages/server-node/src/session/client-session-manager.ts @@ -180,7 +180,7 @@ export class DefaultClientSessionManager extends Disposable implements ClientSes this.dispose(); } - protected doDispose(): void { + protected override doDispose(): void { Array.from(this.clientSessions.keys()).forEach(id => this.disposeClientSession(id)); this.listeners.clear(); } diff --git a/packages/server-node/src/session/client-session.ts b/packages/server-node/src/session/client-session.ts index 0487afa..c4c4357 100644 --- a/packages/server-node/src/session/client-session.ts +++ b/packages/server-node/src/session/client-session.ts @@ -56,7 +56,7 @@ export class DefaultClientSession extends Disposable implements ClientSession { super(); } - protected doDispose(): void { + protected override doDispose(): void { if (isIDisposable(this.actionDispatcher)) { this.actionDispatcher.dispose(); } diff --git a/packages/server-node/src/test/integration-test.spec.ts b/packages/server-node/src/test/integration-test.spec.ts index 6e93b05..e27fb63 100644 --- a/packages/server-node/src/test/integration-test.spec.ts +++ b/packages/server-node/src/test/integration-test.spec.ts @@ -19,12 +19,14 @@ import { CompoundOperation, CreateNodeOperation, InitializeResult, - isRequestBoundsAction, Point, + RequestBoundsAction, RequestModelAction } from '@eclipse-glsp/protocol'; +import { expect } from 'chai'; import { Container, ContainerModule, injectable, interfaces } from 'inversify'; import * as path from 'path'; +import * as sinon from 'sinon'; import { GModelDiagramModule } from '../base-impl/gmodel-diagram-module'; import { InstanceMultiBinding } from '../di/multi-binding'; import { ServerModule } from '../di/server-module'; @@ -44,22 +46,25 @@ const protocolVersion = '0.9.0'; const diagramType = 'testDiagram'; const clientId = 'session1'; const sourceUri = path.resolve(__dirname, 'minimal.json'); -import * as sinon from 'sinon'; -import { expect } from 'chai'; class TestDiagramModule extends GModelDiagramModule { diagramType = diagramType; - protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { + protected override configure( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ): void { super.configure(bind, unbind, isBound, rebind); bind(DiagramConfiguration).toConstantValue( new (class extends mock.StubDiagramConfiguration { - typeMapping = getDefaultMapping(); + override typeMapping = getDefaultMapping(); })() ); } - configureOperationHandlers(binding: InstanceMultiBinding): void { + override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(CreateANodeOperationHandler); } @@ -106,8 +111,10 @@ describe('Integration tests for a glsp server created by SocketServerLauncher', expect(() => glspServer.process({ clientId, - action: new RequestModelAction({ - ['sourceUri']: sourceUri + action: RequestModelAction.create({ + options: { + ['sourceUri']: sourceUri + } }) }) ).to.throw; @@ -137,27 +144,27 @@ describe('Integration tests for a glsp server created by SocketServerLauncher', }); it('process - RequestModelAction should send RequestBoundsAction to client', async () => { - glspServer.process({ clientId, action: new RequestModelAction({ ['sourceUri']: sourceUri }) }); + glspServer.process({ clientId, action: RequestModelAction.create({ options: { ['sourceUri']: sourceUri } }) }); // Action handling is done async. We have to add a delay to ensure that we receive all responses await mock.delay(200); const result = spy_client_process.getCalls().map(call => call.args); expect(spy_client_process.calledOnce); expect(result[result.length - 1][0].clientId).to.be.equals(clientId); - expect(isRequestBoundsAction(result[result.length - 2][0].action)).true; + expect(RequestBoundsAction.is(result[result.length - 2][0].action)).true; }); it('check OperationHandlerRegistry for defined operations', () => { expect(operationHandlerRegistry.keys()).to.contain(`${CreateNodeOperation.KIND}_ANode`); - expect(operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)).instanceOf(CreateANodeOperationHandler); + expect(operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)).to.be.an.instanceOf(CreateANodeOperationHandler); expect(operationHandlerRegistry.keys()).to.contain(CompoundOperation.KIND); - expect(operationHandlerRegistry.get(CompoundOperation.KIND)).instanceOf(CompoundOperationHandler); + expect(operationHandlerRegistry.get(CompoundOperation.KIND)).to.be.an.instanceOf(CompoundOperationHandler); }); it('correct handler is triggered on process', () => { const handler = operationHandlerRegistry.get(`${CreateNodeOperation.KIND}_ANode`)!; const spy_operationHandler_execute = sinon.stub(handler, 'execute'); - glspServer.process({ clientId, action: new CreateNodeOperation('ANode') }); + glspServer.process({ clientId, action: CreateNodeOperation.create('ANode') }); expect(spy_operationHandler_execute.calledOnce); }); }); diff --git a/packages/server-node/src/utils/di-utilts.ts b/packages/server-node/src/utils/di-utilts.ts deleted file mode 100644 index bec6c27..0000000 --- a/packages/server-node/src/utils/di-utilts.ts +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -export function isInjectable(constr: new (...args: unknown[]) => unknown): boolean { - return Reflect.getMetadata('inversify:paramtypes', constr) !== undefined; -} diff --git a/packages/server-node/src/utils/disposable.spec.ts b/packages/server-node/src/utils/disposable.spec.ts index 4d9eab3..1581da3 100644 --- a/packages/server-node/src/utils/disposable.spec.ts +++ b/packages/server-node/src/utils/disposable.spec.ts @@ -14,13 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Disposable } from './disposable'; import { expect } from 'chai'; +import { Disposable } from './disposable'; describe('test Disposable', () => { let doDisposeCalled: boolean; const disposable = new (class extends Disposable { - protected doDispose(): void { + protected override doDispose(): void { doDisposeCalled = true; } })(); diff --git a/packages/server-node/src/utils/geometry-util.ts b/packages/server-node/src/utils/geometry-util.ts deleted file mode 100644 index 75810ca..0000000 --- a/packages/server-node/src/utils/geometry-util.ts +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { GBoundsAware, GModelElement, GModelRoot, isGBoundsAware } from '@eclipse-glsp/graph'; -import { ORIGIN_POINT, Point } from '@eclipse-glsp/protocol'; - -/** - * Convert a point in absolute coordinates to a point relative to the specified GBoundsAware. - * Note: this method only works if the specified {@link GBoundsAware} is part of a - * hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement - * {@link GBoundsAware}, this method will throw an exception. - * - * @param absolutePoint - * @param modelElement - * @returns - * A new point, relative to the coordinates space of the specified {@link GBoundsAware} - * @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy - */ -export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point { - let parentElement; - if (!(modelElement instanceof GModelRoot)) { - parentElement = modelElement.parent; - } - - let relativeToParent: Point; - if (!parentElement) { - relativeToParent = { x: absolutePoint.x, y: absolutePoint.y }; - } else { - if (!isGBoundsAware(parentElement)) { - throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`); - } - relativeToParent = absoluteToRelative(absolutePoint, parentElement); - } - - const x = modelElement.position?.x ?? 0; - const y = modelElement.position?.y ?? 0; - - return { x: relativeToParent.x - x, y: relativeToParent.y - y }; -} - -export function shift(elements: GModelElement[], offset: Point): void { - elements - .filter(element => isGBoundsAware(element)) - .map(element => element as unknown as GBoundsAware) - .forEach(gBoundsAware => { - const position = gBoundsAware.position ?? ORIGIN_POINT; - gBoundsAware.position = { x: position.x + offset.x, y: position.y + offset.y }; - }); -} diff --git a/packages/server-node/src/utils/glsp-server-error.ts b/packages/server-node/src/utils/glsp-server-error.ts index 2ee8194..01de79a 100644 --- a/packages/server-node/src/utils/glsp-server-error.ts +++ b/packages/server-node/src/utils/glsp-server-error.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ export class GLSPServerError extends Error { - constructor(message: string, readonly cause?: Record) { + constructor(message: string, readonly cause?: unknown) { super(message); } } diff --git a/packages/server-node/src/utils/registry.ts b/packages/server-node/src/utils/registry.ts index 2b9a7d0..cc4c015 100644 --- a/packages/server-node/src/utils/registry.ts +++ b/packages/server-node/src/utils/registry.ts @@ -119,7 +119,8 @@ export class MultiRegistry { deregister(key: K, element: V): boolean { const instances = this.elements.get(key); if (instances) { - return remove(instances, element); + remove(instances, element); + return true; } return false; } diff --git a/packages/server-node/src/utils/winston-logger.ts b/packages/server-node/src/utils/winston-logger.ts index fac2085..16a554a 100644 --- a/packages/server-node/src/utils/winston-logger.ts +++ b/packages/server-node/src/utils/winston-logger.ts @@ -66,7 +66,7 @@ export class WinstonLogger extends Logger { return params.map(param => this.toString(param)).join(',\n'); } - toString(param: unknown): string { + override toString(param: unknown): string { if (param instanceof Error) { return `${param.message} ${param.stack || ''}`; diff --git a/packages/server-node/tsconfig.json b/packages/server-node/tsconfig.json index c28991c..1fe0813 100644 --- a/packages/server-node/tsconfig.json +++ b/packages/server-node/tsconfig.json @@ -7,6 +7,5 @@ "baseUrl": "src", "types": ["node", "mocha", "reflect-metadata"] }, - "include": ["src"], - "exclude": ["**/*.spec.ts"] + "include": ["src"] } From e30fcc0a17bf4ceaa520e3e0efaa3814128da52c Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 30 Mar 2022 20:36:18 +0200 Subject: [PATCH 093/566] Add typedoc for AutoCompleteWidget and EditorContextService --- .../src/base/auto-complete/auto-complete-widget.ts | 7 +++++++ packages/client/src/base/editor-context-service.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/client/src/base/auto-complete/auto-complete-widget.ts b/packages/client/src/base/auto-complete/auto-complete-widget.ts index 7e41b57..35d4746 100644 --- a/packages/client/src/base/auto-complete/auto-complete-widget.ts +++ b/packages/client/src/base/auto-complete/auto-complete-widget.ts @@ -51,6 +51,13 @@ export interface TextSubmitHandler { // eslint-disable-next-line @typescript-eslint/no-var-requires const configureAutocomplete: (settings: AutocompleteSettings) => AutocompleteResult = require('autocompleter'); +/** + * The `AutoCompleteWidget` is a reusable UI element that provides a text input supporting auto-completion, + * validation, validation messages, etc. + * + * An example for using it is available in the workflow diagram: + * https://github.com/eclipse-glsp/glsp-client/blob/master/examples/workflow-glsp/src/direct-task-editing/direct-task-editor.ts + */ export class AutoCompleteWidget { loadingIndicatorClasses = codiconCSSClasses('loading', false, true, ['loading']); diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index 31a8403..e186433 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -24,6 +24,18 @@ export interface EditModeListener { editModeChanged(newValue: string, oldvalue: string): void; } +/** + * The `EditorContextService` is a central injectable component that gives read-only access to + * certain aspects of the diagram, such as the currently selected elements, the model root, + * the edit mode, the latest position of the mouse in the diagram. + * + * It has been introduced for two main reasons: + * 1. to simplify accessing the model root and the current selection from components that are + * not commands, + * 2. to conveniently create an EditorContext, which is a context object sent as part of several + * actions to the server to describe the current state of the editor (selection, last mouse + * position, etc.). + */ @injectable() export class EditorContextService implements IActionHandler { @inject(TYPES.SelectionService) protected selectionService: SelectionService; From dce37c21c6390464c071ac9b5e3f4e20a6e9f424 Mon Sep 17 00:00:00 2001 From: Camille Letavernier Date: Wed, 6 Apr 2022 15:16:47 +0200 Subject: [PATCH 094/566] 443: Implement extended HBox layouter (#174) - Implement HBoxLayouterExt, similar to the existing VBoxLayouterExt --- .../client/src/features/layout/di.config.ts | 4 +- .../client/src/features/layout/hbox-layout.ts | 256 ++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/features/layout/hbox-layout.ts diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 93f59e8..9d99aa7 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -14,8 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureCommand, configureLayout, VBoxLayouter } from 'sprotty'; +import { configureCommand, configureLayout, HBoxLayouter, VBoxLayouter } from 'sprotty'; import { FreeFormLayouter } from './freeform-layout'; +import { HBoxLayouterExt } from './hbox-layout'; import { AlignElementsCommand, ResizeElementsCommand } from './layout-commands'; import { VBoxLayouterExt } from './vbox-layout'; @@ -24,6 +25,7 @@ const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound, rebind configureCommand({ bind, isBound }, AlignElementsCommand); rebind(VBoxLayouter).to(VBoxLayouterExt); + rebind(HBoxLayouter).to(HBoxLayouterExt); configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); }); diff --git a/packages/client/src/features/layout/hbox-layout.ts b/packages/client/src/features/layout/hbox-layout.ts new file mode 100644 index 0000000..9775333 --- /dev/null +++ b/packages/client/src/features/layout/hbox-layout.ts @@ -0,0 +1,256 @@ +/******************************************************************************** + * Copyright (c) 2021-2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Bounds, Dimension, Point } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { + BoundsData, + isBoundsAware, + isLayoutableChild, + LayoutContainer, + SChildElement, + SModelElement, + SParentElement, + StatefulLayouter, + HBoxLayouter, + HBoxLayoutOptions +} from 'sprotty'; + +export interface HBoxLayoutOptionsExt extends HBoxLayoutOptions { + hGrab: boolean; + vGrab: boolean; + prefWidth: number | null; + prefHeight: number | null; +} + +/** + * Extends HBoxLayouter to support additional layout options + */ +@injectable() +export class HBoxLayouterExt extends HBoxLayouter { + static override KIND = HBoxLayouter.KIND; + + override layout(container: SParentElement & LayoutContainer, layouter: StatefulLayouter): void { + const boundsData = layouter.getBoundsData(container); + const options = this.getLayoutOptions(container); + const childrenSize = this.getChildrenSize(container, options, layouter); + + const fixedSize = this.getFixedContainerBounds(container, options, layouter); + + const maxWidth = + options.paddingFactor * + (options.resizeContainer + ? Math.max(fixedSize.width - options.paddingLeft - options.paddingRight, childrenSize.width) + : Math.max(0, fixedSize.width - options.paddingLeft - options.paddingRight)); + const maxHeight = + options.paddingFactor * + (options.resizeContainer + ? Math.max(fixedSize.height - options.paddingTop - options.paddingBottom, childrenSize.height) + : Math.max(0, fixedSize.height - options.paddingTop - options.paddingBottom)); + + // Remaining size that can be grabbed by children with the hGrab option + const grabWidth: number = maxWidth - childrenSize.width; + // Number of children that request hGrab + // FIXME: This approach works fine when only 1 child uses HGrab, but may cause rounding issues + // when the grabHeight can't be equally shared by all children. + const grabbingChildren = container.children + .map(child => this.getChildLayoutOptions(child, options)) + .filter(opt => opt.hGrab).length; + + if (maxWidth > 0 && maxHeight > 0) { + const offset = this.layoutChildren(container, layouter, options, maxWidth, maxHeight, grabWidth, grabbingChildren); + boundsData.bounds = this.getFinalContainerBounds(container, offset, options, childrenSize.width, childrenSize.height); + boundsData.boundsChanged = true; + } + } + + protected override getChildrenSize( + container: SParentElement & LayoutContainer, + containerOptions: HBoxLayoutOptionsExt, + layouter: StatefulLayouter + ): Dimension { + let maxWidth = 0; + let maxHeight = -1; + let isFirst = true; + container.children.forEach(child => { + if (isLayoutableChild(child)) { + const bounds = layouter.getBoundsData(child).bounds; + if (bounds !== undefined && Dimension.isValid(bounds)) { + maxWidth += bounds.width; + if (isFirst) { + isFirst = false; + } else { + maxWidth += containerOptions.hGap; + } + maxHeight = Math.max(maxHeight, bounds.height); + } + } + }); + const result = { + width: maxWidth, + height: maxHeight + }; + return result; + } + + protected override layoutChildren( + container: SParentElement & LayoutContainer, + layouter: StatefulLayouter, + containerOptions: HBoxLayoutOptionsExt, + maxWidth: number, + maxHeight: number, + grabWidth?: number, + grabbingChildren?: number + ): Point { + let currentOffset: Point = { + x: containerOptions.paddingLeft + 0.5 * (maxWidth - maxWidth / containerOptions.paddingFactor), + y: containerOptions.paddingTop + 0.5 * (maxHeight - maxHeight / containerOptions.paddingFactor) + }; + + container.children.forEach(child => { + if (isLayoutableChild(child)) { + const boundsData = layouter.getBoundsData(child); + const bounds = boundsData.bounds; + const childOptions = this.getChildLayoutOptions(child, containerOptions); + if (bounds !== undefined && Dimension.isValid(bounds)) { + currentOffset = this.layoutChild( + child, + boundsData, + bounds, + childOptions, + containerOptions, + currentOffset, + maxWidth, + maxHeight, + grabWidth, + grabbingChildren + ); + } + } + }); + return currentOffset; + } + + protected override layoutChild( + child: SChildElement, + boundsData: BoundsData, + bounds: Bounds, + childOptions: HBoxLayoutOptionsExt, + containerOptions: HBoxLayoutOptionsExt, + currentOffset: Point, + maxWidth: number, + maxHeight: number, + grabWidth?: number, + grabbingChildren?: number + ): Point { + let offset = super.layoutChild(child, boundsData, bounds, childOptions, containerOptions, currentOffset, maxWidth, maxHeight); + if (childOptions.hGrab) { + boundsData.bounds = { + x: boundsData.bounds!.x, + y: boundsData.bounds!.y, + width: boundsData.bounds!.width, + height: maxHeight + }; + boundsData.boundsChanged = true; + } + if (childOptions.hGrab && grabWidth && grabbingChildren) { + const width = boundsData.bounds!.width + grabWidth / grabbingChildren; + boundsData.bounds = { + x: boundsData.bounds!.x, + y: boundsData.bounds!.y, + width: width, + height: boundsData.bounds!.height + }; + boundsData.boundsChanged = true; + offset = { x: currentOffset.x + width, y: currentOffset.y }; + } + return offset; + } + + protected override getFixedContainerBounds( + container: SModelElement, + layoutOptions: HBoxLayoutOptionsExt, + layouter: StatefulLayouter + ): Bounds { + const currentContainer = container; + // eslint-disable-next-line no-constant-condition + if (isBoundsAware(currentContainer)) { + const bounds = currentContainer.bounds; + const elementOptions = this.getElementLayoutOptions(currentContainer); + const width = elementOptions?.prefWidth ?? 0; + const height = elementOptions?.prefHeight ?? 0; + return { ...bounds, width, height }; + } + return Bounds.EMPTY; + } + + protected override getChildLayoutOptions(child: SChildElement, containerOptions: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { + return super.getChildLayoutOptions(child, containerOptions) as HBoxLayoutOptionsExt; + } + + protected override getLayoutOptions(element: SModelElement): HBoxLayoutOptionsExt { + return super.getLayoutOptions(element) as HBoxLayoutOptionsExt; + } + + protected getElementLayoutOptions(element: SModelElement): HBoxLayoutOptionsExt | undefined { + return (element as any).layoutOptions; + } + + protected override getFinalContainerBounds( + container: SParentElement & LayoutContainer, + lastOffset: Point, + options: HBoxLayoutOptionsExt, + maxWidth: number, + maxHeight: number + ): Bounds { + const elementOptions = this.getElementLayoutOptions(container); + const width = elementOptions?.prefWidth ?? options.minWidth; + const height = elementOptions?.prefHeight ?? options.minHeight; + + const result = { + x: container.bounds.x, + y: container.bounds.y, + width: Math.max(width, maxWidth + options.paddingLeft + options.paddingRight), + height: Math.max(height, maxHeight + options.paddingTop + options.paddingBottom) + }; + + return result; + } + + protected override getDefaultLayoutOptions(): HBoxLayoutOptionsExt { + return { + resizeContainer: true, + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 5, + paddingRight: 5, + paddingFactor: 1, + hGap: 1, + vAlign: 'center', + minWidth: 0, + minHeight: 0, + hGrab: false, + vGrab: false, + // eslint-disable-next-line no-null/no-null + prefHeight: null, + // eslint-disable-next-line no-null/no-null + prefWidth: null + }; + } + + protected override spread(a: HBoxLayoutOptionsExt, b: HBoxLayoutOptionsExt): HBoxLayoutOptionsExt { + return { ...a, ...b }; + } +} From bc7a2b8db1794ef488694d66fd29d4850d5e887e Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Thu, 31 Mar 2022 17:55:12 +0200 Subject: [PATCH 095/566] Enable default tools on focus loss Fixes https://github.com/eclipse-glsp/glsp/issues/547 Contributed on behalf of STMicroelectronics --- packages/client/src/base/container-modules.ts | 3 +- .../client/src/features/tools/di.config.ts | 15 +++++++-- .../enable-default-tools-on-focus-loss.ts | 33 +++++++++++++++++++ packages/client/src/index.ts | 3 +- 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 2731a5e..2e0f287 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -44,7 +44,7 @@ import { navigationModule } from '../features/navigation/di.config'; import glspSelectModule from '../features/select/di.config'; import toolFeedbackModule from '../features/tool-feedback/di.config'; import toolPaletteModule from '../features/tool-palette/di.config'; -import toolsModule from '../features/tools/di.config'; +import { enableDefaultToolsOnFocusLossModule, toolsModule } from '../features/tools/di.config'; import { markerNavigatorModule, validationModule } from '../features/validation/di.config'; import glspViewportModule from '../features/viewport/di.config'; import defaultGLSPModule from './di.config'; @@ -80,6 +80,7 @@ export const DEFAULT_MODULES = [ routingModule, toolFeedbackModule, toolsModule, + enableDefaultToolsOnFocusLossModule, validationModule, zorderModule ]; diff --git a/packages/client/src/features/tools/di.config.ts b/packages/client/src/features/tools/di.config.ts index 4879af0..d782f94 100644 --- a/packages/client/src/features/tools/di.config.ts +++ b/packages/client/src/features/tools/di.config.ts @@ -16,19 +16,25 @@ import { TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { ContainerModule, interfaces } from 'inversify'; import { configureActionHandler, configureModelElement } from 'sprotty'; +import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; import { TYPES } from '../../base/types'; import { MARQUEE } from '../tool-feedback/marquee-tool-feedback'; import { ChangeBoundsTool } from './change-bounds-tool'; import { DelKeyDeleteTool, MouseDeleteTool } from './delete-tool'; import { EdgeCreationTool } from './edge-creation-tool'; import { EdgeEditTool } from './edge-edit-tool'; +import { EnableDefaultToolsOnFocusLossHandler } from './enable-default-tools-on-focus-loss'; import { MarqueeMouseTool } from './marquee-mouse-tool'; import { MarqueeTool } from './marquee-tool'; import { MarqueeNode } from './model'; import { NodeCreationTool } from './node-creation-tool'; import { MarqueeView } from './view'; -const toolsModule = new ContainerModule((bind, _unbind, isBound) => { +/** + * Registers the default tools of GLSP (node and edge creation, changing bounds, edge editing, deletion) + * and adds the marquee selection tool. + */ +export const toolsModule = new ContainerModule((bind, _unbind, isBound) => { // Register default tools bind(TYPES.IDefaultTool).to(ChangeBoundsTool); bind(TYPES.IDefaultTool).to(EdgeEditTool); @@ -52,4 +58,9 @@ export function configureMarqueeTool(context: { bind: interfaces.Bind; isBound: context.bind(TYPES.ITool).to(MarqueeMouseTool); } -export default toolsModule; +/** + * Enables the default tools in the tool manager if the diagram looses focus. + */ +export const enableDefaultToolsOnFocusLossModule = new ContainerModule((bind, _unbind, isBound) => { + configureActionHandler({ bind, isBound }, FocusStateChangedAction.KIND, EnableDefaultToolsOnFocusLossHandler); +}); diff --git a/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts new file mode 100644 index 0000000..b2a98d7 --- /dev/null +++ b/packages/client/src/features/tools/enable-default-tools-on-focus-loss.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action } from '@eclipse-glsp/protocol'; +import { injectable } from 'inversify'; +import { EnableDefaultToolsAction, IActionHandler, ICommand } from 'sprotty'; +import { FocusStateChangedAction } from '../../base/actions/focus-change-action'; + +/** + * Action handler that enables the default tools when the diagram lost focus. + * @see FocusTracker + * @see FocusStateChangedAction + */ +@injectable() +export class EnableDefaultToolsOnFocusLossHandler implements IActionHandler { + handle(action: Action): void | Action | ICommand { + if (FocusStateChangedAction.is(action) && !action.hasFocus) { + return EnableDefaultToolsAction.create(); + } + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 75de6bb..b8aaf9e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -29,7 +29,7 @@ import saveModule from './features/save/di.config'; import glspSelectModule from './features/select/di.config'; import toolFeedbackModule from './features/tool-feedback/di.config'; import paletteModule from './features/tool-palette/di.config'; -import toolsModule from './features/tools/di.config'; +import { enableDefaultToolsOnFocusLossModule, toolsModule } from './features/tools/di.config'; import { markerNavigatorModule, validationModule } from './features/validation/di.config'; import glspViewportModule from './features/viewport/di.config'; @@ -117,6 +117,7 @@ export { glspEditLabelModule, glspHoverModule, toolsModule, + enableDefaultToolsOnFocusLossModule, navigationModule, markerNavigatorModule, glspDecorationModule, From f35d009aa48811319ec59aa734975cbc7b2c385f Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 8 Apr 2022 08:13:22 -0700 Subject: [PATCH 096/566] #609 Cleanup/Refactor commands and action handlers (#176) * #609 Cleanup/Refactor commands and action handlers Refactor all command action handlers that don't necessarily need to be an command-based action handler to pure action handlers. Commands should only be used if the client model is modified (i.e. for feedback actions) or in other justified corner cases. - Change handling of `ResizeElement` and `AlignElement` actions to pure action handlers instead of commands. Also clean up "wrong" action definitions which did declare function properties which makes them not serializable. -- Refactor corresponding tests - Change handling of `NavigateToMarkersAction` to a pure action handler instead of commands. - Refactor handler for `SetMarkersAction``to a standalone action handler instead of an command - Cleanup command handler in `selection-service.ts`. Here commands are still necessary to ensure that the select actions are handled before the next model render tick. Also: - Refactor `SourceUriAware` API to an interface instead of an abstract class. Also change `getSourceUri()` to property as there is no benefit in using an arg less getter function over a property. - Enable synchronous `sourceURI` retrieval from the `EditorContext` using postConstruct. - Refactor `MovmentRestrictor` API so that the validate method can also handle newPositions that are potentially undefined. aLSO add typedoc. - Extends type-utils -- Introduce union type for all primitive types -- Introduce a common type for class constructors -- Add utility function to derive a typeguard function from a class def (i.e. constructor) -- Introduce a `Writable` utility type which removes all readonly modifiers -- Replace all occurences of `WriteableXYZ` classes with the new `Writable` declaration. - Extend array-utils -- Add method to retrieve the last n elements of an array -- Add method to pluck (i.e. extract) a property from an Barry Closes eclipse-glsp/glsp/issues/609 * Fix selection-service.spec.ts * Address review feedback --- packages/client/src/base/container-modules.ts | 4 +- .../client/src/base/editor-context-service.ts | 32 +- packages/client/src/base/source-uri-aware.ts | 9 +- .../change-bounds/movement-restrictor.ts | 116 +++-- .../client/src/features/change-bounds/snap.ts | 5 +- .../client/src/features/layout/di.config.ts | 14 +- ...spec.ts => layout-elements-action.spec.ts} | 104 ++--- ...-commands.ts => layout-elements-action.ts} | 402 +++++++++--------- .../navigation/navigation-target-resolver.ts | 2 +- .../features/select/selection-service.spec.ts | 2 +- .../src/features/select/selection-service.ts | 87 ++-- .../change-bounds-tool-feedback.ts | 2 +- .../src/features/validation/di.config.ts | 17 +- .../features/validation/marker-navigator.ts | 95 ++--- .../src/features/validation/validate.ts | 97 ++--- packages/client/src/index.ts | 6 +- .../src/model-source/glsp-diagram-server.ts | 2 +- packages/client/src/utils/layout-utils.ts | 34 +- packages/protocol/src/utils/array-util.ts | 49 ++- packages/protocol/src/utils/type-util.ts | 30 ++ 20 files changed, 550 insertions(+), 559 deletions(-) rename packages/client/src/features/layout/{layout-commands.spec.ts => layout-elements-action.spec.ts} (86%) rename packages/client/src/features/layout/{layout-commands.ts => layout-elements-action.ts} (57%) diff --git a/packages/client/src/base/container-modules.ts b/packages/client/src/base/container-modules.ts index 2e0f287..5453e2d 100644 --- a/packages/client/src/base/container-modules.ts +++ b/packages/client/src/base/container-modules.ts @@ -37,7 +37,7 @@ import glspDecorationModule from '../features/decoration/di.config'; import glspEditLabelModule from '../features/edit-label/di.config'; import modelHintsModule from '../features/hints/di.config'; import glspHoverModule from '../features/hover/di.config'; -import layoutCommandsModule from '../features/layout/di.config'; +import layoutModule from '../features/layout/di.config'; import modelSourceWatcherModule from '../features/model-source-watcher/di.config'; import glspMouseToolModule from '../features/mouse-tool/di.config'; import { navigationModule } from '../features/navigation/di.config'; @@ -69,7 +69,7 @@ export const DEFAULT_MODULES = [ glspServerCopyPasteModule, glspViewportModule, labelEditUiModule, - layoutCommandsModule, + layoutModule, markerNavigatorModule, modelHintsModule, modelSourceModule, diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index e186433..d151489 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Action, Args, distinctAdd, EditMode, EditorContext, remove, SetEditModeAction } from '@eclipse-glsp/protocol'; -import { inject, injectable, multiInject, optional } from 'inversify'; +import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot } from 'sprotty'; import { SelectionService } from '../features/select/selection-service'; import { isSourceUriAware } from './source-uri-aware'; @@ -38,12 +38,26 @@ export interface EditModeListener { */ @injectable() export class EditorContextService implements IActionHandler { - @inject(TYPES.SelectionService) protected selectionService: SelectionService; - @inject(MousePositionTracker) protected mousePositionTracker: MousePositionTracker; - @inject(TYPES.ModelSourceProvider) protected modelSource: () => Promise; + @inject(TYPES.SelectionService) + protected selectionService: SelectionService; + + @inject(MousePositionTracker) + protected mousePositionTracker: MousePositionTracker; + + @multiInject(TYPES.IEditModeListener) + @optional() + protected editModeListeners: EditModeListener[] = []; + + @inject(TYPES.ModelSourceProvider) + protected modelSourceProvider: () => Promise; + protected _editMode: string; + protected modelSource: ModelSource | undefined; - constructor(@multiInject(TYPES.IEditModeListener) @optional() protected editModeListeners: EditModeListener[] = []) {} + @postConstruct() + async initialize(): Promise { + this.modelSource = await this.modelSourceProvider(); + } register(editModeListener: EditModeListener): void { distinctAdd(this.editModeListeners, editModeListener); @@ -81,12 +95,8 @@ export class EditorContextService implements IActionHandler { this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); } - async getSourceUri(): Promise { - const modelSource = await this.modelSource(); - if (isSourceUriAware(modelSource)) { - return modelSource.getSourceURI(); - } - return undefined; + get sourceUri(): string | undefined { + return this.modelSource && isSourceUriAware(this.modelSource) ? this.modelSource.sourceURI : undefined; } get editMode(): string { diff --git a/packages/client/src/base/source-uri-aware.ts b/packages/client/src/base/source-uri-aware.ts index c7a28b9..4233e52 100644 --- a/packages/client/src/base/source-uri-aware.ts +++ b/packages/client/src/base/source-uri-aware.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 EclipseSource and others. + * Copyright (c) 2020-2022 EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,10 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -export abstract class SourceUriAware { - abstract getSourceURI(): string; +import { AnyObject, hasStringProp } from '@eclipse-glsp/protocol'; +export interface SourceUriAware { + sourceURI: string; } export function isSourceUriAware(obj: any): obj is SourceUriAware { - return obj !== undefined && typeof obj.getSourceURI === 'function'; + return AnyObject.is(obj) && hasStringProp(obj, 'sourceURI'); } diff --git a/packages/client/src/features/change-bounds/movement-restrictor.ts b/packages/client/src/features/change-bounds/movement-restrictor.ts index fb8857e..e4c16b6 100644 --- a/packages/client/src/features/change-bounds/movement-restrictor.ts +++ b/packages/client/src/features/change-bounds/movement-restrictor.ts @@ -20,40 +20,35 @@ import { toAbsoluteBounds } from '../../utils/viewpoint-util'; import { ModifyCSSFeedbackAction } from '../tool-feedback/css-feedback'; import { isBoundsAwareMoveable, SResizeHandle } from './model'; +/** + * A `MovementRestrictor` is an optional service that can be used by tools to validate + * whether a certain move operation (e.g. `ChangeBoundsOperation`) in the diagram is valid. + */ export interface IMovementRestrictor { - validate(newLocation: Point, element: SModelElement): boolean; + /** + * Validate whether moving the given element to a new given location is allowed. + * @param element The element that should be moved. + * @param newLocation The new location of the element. + * @returns `true` if the the element is movable and moving to the given location is allowed, `false` otherwise. + * Should also return `false` if the newLocation is `undefined`: + */ + validate(element: SModelElement, newLocation?: Point): boolean; + /** + * Feedback css-classes. Can be applied to elements that did fail the validation. + */ cssClasses?: string[]; } -export function createMovementRestrictionFeedback( - element: SModelElement, - movementRestrictor: IMovementRestrictor -): ModifyCSSFeedbackAction { - const elements: SModelElement[] = [element]; - if (element instanceof SParentElement) { - element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); - } - return ModifyCSSFeedbackAction.create({ elements, add: movementRestrictor.cssClasses }); -} - -export function removeMovementRestrictionFeedback( - element: SModelElement, - movementRestrictor: IMovementRestrictor -): ModifyCSSFeedbackAction { - const elements: SModelElement[] = [element]; - if (element instanceof SParentElement) { - element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); - } - - return ModifyCSSFeedbackAction.create({ elements, remove: movementRestrictor.cssClasses }); -} - +/** + * A `IMovementRestrictor` that checks for overlapping elements. Move operations + * are only valid if the element does not collide with another element after moving. + */ @injectable() -export class NoOverlapMovmentRestrictor implements IMovementRestrictor { +export class NoOverlapMovementRestrictor implements IMovementRestrictor { cssClasses = ['movement-not-allowed']; - validate(newLocation: Point, element: SModelElement): boolean { - if (!isBoundsAwareMoveable(element)) { + validate(element: SModelElement, newLocation?: Point): boolean { + if (!isBoundsAwareMoveable(element) || !newLocation) { return false; } // Create ghost element at the newLocation @@ -71,27 +66,62 @@ export class NoOverlapMovmentRestrictor implements IMovementRestrictor { .all() .filter(e => e.id !== ghostElement.id && e !== ghostElement.root && e instanceof SNode) .map(e => e as SModelElement & BoundsAware) - ).some(e => areOverlapping(e, ghostElement)); + ).some(e => this.areOverlapping(e, ghostElement)); } -} -export function areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware): boolean { - const b1 = toAbsoluteBounds(element1); - const b2 = toAbsoluteBounds(element2); - const r1TopLeft: Point = b1; - const r1BottomRight = { x: b1.x + b1.width, y: b1.y + b1.height }; - const r2TopLeft: Point = b2; - const r2BottomRight = { x: b2.x + b2.width, y: b2.y + b2.height }; + protected areOverlapping(element1: SModelElement & BoundsAware, element2: SModelElement & BoundsAware): boolean { + const b1 = toAbsoluteBounds(element1); + const b2 = toAbsoluteBounds(element2); + const r1TopLeft: Point = b1; + const r1BottomRight = { x: b1.x + b1.width, y: b1.y + b1.height }; + const r2TopLeft: Point = b2; + const r2BottomRight = { x: b2.x + b2.width, y: b2.y + b2.height }; + + // If one rectangle is on left side of other + if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) { + return false; + } - // If one rectangle is on left side of other - if (r1TopLeft.x > r2BottomRight.x || r2TopLeft.x > r1BottomRight.x) { - return false; + // If one rectangle is above other + if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) { + return false; + } + + return true; } +} - // If one rectangle is above other - if (r1BottomRight.y < r2TopLeft.y || r2BottomRight.y < r1TopLeft.y) { - return false; +/** + * Utility function to create an action that applies the given {@link IMovementRestrictor.cssClasses} to the given element. + * @param element The element on which the css classes should be applied. + * @param movementRestrictor The movement restrictor whose cssClasses should be applied. + * @returns The corresponding {@link ModifyCSSFeedbackAction} + */ +export function createMovementRestrictionFeedback( + element: SModelElement, + movementRestrictor: IMovementRestrictor +): ModifyCSSFeedbackAction { + const elements: SModelElement[] = [element]; + if (element instanceof SParentElement) { + element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); + } + return ModifyCSSFeedbackAction.create({ elements, add: movementRestrictor.cssClasses }); +} + +/** + * Utility function to create an action that removes the given {@link IMovementRestrictor.cssClasses} from the given element. + * @param element The element from which the css classes should be removed. + * @param movementRestrictor The movement restrictor whose cssClasses should be removed. + * @returns The corresponding {@link ModifyCSSFeedbackAction} + */ +export function removeMovementRestrictionFeedback( + element: SModelElement, + movementRestrictor: IMovementRestrictor +): ModifyCSSFeedbackAction { + const elements: SModelElement[] = [element]; + if (element instanceof SParentElement) { + element.children.filter(child => child instanceof SResizeHandle).forEach(e => elements.push(e)); } - return true; + return ModifyCSSFeedbackAction.create({ elements, remove: movementRestrictor.cssClasses }); } diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 1141e7e..47dca86 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -13,10 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Point } from '@eclipse-glsp/protocol'; +import { Point, Writable } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { findParentByFeature, ISnapper, isViewport, SModelElement } from 'sprotty'; -import { WriteablePoint } from '../../utils/layout-utils'; @injectable() export class GridSnapper implements ISnapper { @@ -46,7 +45,7 @@ export class GridSnapper implements ISnapper { */ export class PointPositionUpdater { protected lastDragPosition?: Point; - protected positionDelta: WriteablePoint = { x: 0, y: 0 }; + protected positionDelta: Writable = { x: 0, y: 0 }; constructor(protected snapper?: ISnapper) {} diff --git a/packages/client/src/features/layout/di.config.ts b/packages/client/src/features/layout/di.config.ts index 9d99aa7..5704ca4 100644 --- a/packages/client/src/features/layout/di.config.ts +++ b/packages/client/src/features/layout/di.config.ts @@ -14,19 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { configureCommand, configureLayout, HBoxLayouter, VBoxLayouter } from 'sprotty'; +import { configureActionHandler, configureLayout, HBoxLayouter, VBoxLayouter } from 'sprotty'; +import { AlignElementsActionHandler } from '../..'; import { FreeFormLayouter } from './freeform-layout'; import { HBoxLayouterExt } from './hbox-layout'; -import { AlignElementsCommand, ResizeElementsCommand } from './layout-commands'; +import { AlignElementsAction, ResizeElementsAction, ResizeElementsActionHandler } from './layout-elements-action'; import { VBoxLayouterExt } from './vbox-layout'; -const layoutCommandsModule = new ContainerModule((bind, _unbind, isBound, rebind) => { - configureCommand({ bind, isBound }, ResizeElementsCommand); - configureCommand({ bind, isBound }, AlignElementsCommand); +const layoutModule = new ContainerModule((bind, _unbind, isBound, rebind) => { + const context = { bind, isBound }; + configureActionHandler(context, ResizeElementsAction.KIND, ResizeElementsActionHandler); + configureActionHandler(context, AlignElementsAction.KIND, AlignElementsActionHandler); rebind(VBoxLayouter).to(VBoxLayouterExt); rebind(HBoxLayouter).to(HBoxLayouterExt); configureLayout({ bind, isBound }, FreeFormLayouter.KIND, FreeFormLayouter); }); -export default layoutCommandsModule; +export default layoutModule; diff --git a/packages/client/src/features/layout/layout-commands.spec.ts b/packages/client/src/features/layout/layout-elements-action.spec.ts similarity index 86% rename from packages/client/src/features/layout/layout-commands.spec.ts rename to packages/client/src/features/layout/layout-elements-action.spec.ts index d2500ab..e2c57f2 100644 --- a/packages/client/src/features/layout/layout-commands.spec.ts +++ b/packages/client/src/features/layout/layout-elements-action.spec.ts @@ -48,13 +48,12 @@ import { SelectionService } from '../select/selection-service'; import { FeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { AlignElementsAction, - AlignElementsCommand, + AlignElementsActionHandler, Alignment, - Reduce, ResizeDimension, ResizeElementsAction, - ResizeElementsCommand -} from './layout-commands'; + ResizeElementsActionHandler +} from './layout-elements-action'; class MockActionDispatcher implements IActionDispatcher { constructor(public dispatchedActions: Action[] = []) {} @@ -71,14 +70,21 @@ class MockActionDispatcher implements IActionDispatcher { } } +class MockSelectionService extends SelectionService { + constructor(public modelRoot: SModelRoot) { + super(); + } + override getModelRoot(): Readonly { + return this.modelRoot; + } +} + +// Generic Test setup const container = new Container(); container.load(defaultModule); container.bind(TYPES.IFeedbackActionDispatcher).to(FeedbackActionDispatcher).inSingletonScope(); -container.bind(SelectionService).toSelf().inSingletonScope(); -container.bind(TYPES.SelectionService).toService(SelectionService); container.rebind(TYPES.IModelFactory).to(SGraphFactory).inSingletonScope(); const graphFactory = container.get(TYPES.IModelFactory); -const selectionService = container.get(TYPES.SelectionService); const actionDispatcher = new MockActionDispatcher(); @@ -125,16 +131,26 @@ const context: CommandExecutionContext = { const defaultSize = { height: 10, width: 10 }; describe('AlignElementsCommand', () => { - it('should align all elements left', () => { + let handler: AlignElementsActionHandler; + const setModel = (newModel: SModelRoot): void => { + handler['selectionService'] = new MockSelectionService(newModel); + }; + + beforeEach(() => { actionDispatcher.dispatchedActions = []; + + handler = new AlignElementsActionHandler(); + handler['actionDispatcher'] = actionDispatcher; + }); + it('should align all elements left', () => { const newModel = initModel([ { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Left }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ @@ -146,16 +162,14 @@ describe('AlignElementsCommand', () => { }); it('should align all elements right', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Right }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); - + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ ['node1', { x: 333, y: 111, width: defaultSize.width, height: defaultSize.height }], @@ -173,8 +187,8 @@ describe('AlignElementsCommand', () => { { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Center }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ @@ -186,15 +200,14 @@ describe('AlignElementsCommand', () => { }); it('should align all elements top', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Top }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ @@ -206,15 +219,14 @@ describe('AlignElementsCommand', () => { }); it('should align all elements bottom', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Bottom }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ @@ -226,15 +238,14 @@ describe('AlignElementsCommand', () => { }); it('should align all elements middle', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 111, y: 111 }, newSize: defaultSize }, { elementId: 'node2', newPosition: { x: 222, y: 222 }, newSize: defaultSize }, { elementId: 'node3', newPosition: { x: 333, y: 333 }, newSize: defaultSize } ]); const action = AlignElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], alignment: Alignment.Middle }); - const command = new AlignElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); assertAllBounds( new Map([ @@ -247,8 +258,18 @@ describe('AlignElementsCommand', () => { }); describe('ResizeElementsCommand', () => { - it('should make same width as last', () => { + let handler: ResizeElementsActionHandler; + const setModel = (newModel: SModelRoot): void => { + handler['selectionService'] = new MockSelectionService(newModel); + }; + + beforeEach(() => { actionDispatcher.dispatchedActions = []; + + handler = new ResizeElementsActionHandler(); + handler['actionDispatcher'] = actionDispatcher; + }); + it('should make same width as last', () => { const newModel = initModel([ { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, @@ -257,10 +278,10 @@ describe('ResizeElementsCommand', () => { const action = ResizeElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], dimension: ResizeDimension.Width, - reductionFunction: Reduce.last + reduceFunction: 'last' }); - const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); // resize is keeping the center, so the X moves by diff / 2 assertAllBoundsInChangeBounds( @@ -273,7 +294,6 @@ describe('ResizeElementsCommand', () => { }); it('should make same height as last', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, @@ -282,10 +302,10 @@ describe('ResizeElementsCommand', () => { const action = ResizeElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], dimension: ResizeDimension.Height, - reductionFunction: Reduce.last + reduceFunction: 'last' }); - const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); // resize is keeping the center, so the Y moves by diff / 2 assertAllBoundsInChangeBounds( @@ -298,7 +318,6 @@ describe('ResizeElementsCommand', () => { }); it('should make same width and height as last', () => { - actionDispatcher.dispatchedActions = []; const newModel = initModel([ { elementId: 'node1', newPosition: { x: 100, y: 100 }, newSize: { height: 10, width: 10 } }, { elementId: 'node2', newPosition: { x: 100, y: 200 }, newSize: { height: 20, width: 20 } }, @@ -307,10 +326,10 @@ describe('ResizeElementsCommand', () => { const action = ResizeElementsAction.create({ elementIds: ['node1', 'node2', 'node3'], dimension: ResizeDimension.Width_And_Height, - reductionFunction: Reduce.last + reduceFunction: 'last' }); - const command = new ResizeElementsCommand(action, actionDispatcher, selectionService); - command.execute(newContext(newModel)); + setModel(newModel); + handler.handle(action); // resize is keeping the center, so the Y moves by diff / 2 assertAllBoundsInChangeBounds( @@ -329,17 +348,6 @@ function initModel(elementAndBounds: ElementAndBounds[]): SModelRoot { return setBoundsCommand.execute(context) as SModelRoot; } -function newContext(root: SModelRoot): CommandExecutionContext { - return { - root: root, - modelFactory: graphFactory, - duration: 0, - modelChanged: undefined!, - logger: new ConsoleLogger(), - syncer: new AnimationFrameSyncer() - }; -} - function assertAllBounds(allBounds: Map): void { allBounds.forEach((bounds, nodeId) => assertBounds(nodeId, bounds)); } diff --git a/packages/client/src/features/layout/layout-commands.ts b/packages/client/src/features/layout/layout-elements-action.ts similarity index 57% rename from packages/client/src/features/layout/layout-commands.ts rename to packages/client/src/features/layout/layout-elements-action.ts index c9731ee..e0553b8 100644 --- a/packages/client/src/features/layout/layout-commands.ts +++ b/packages/client/src/features/layout/layout-elements-action.ts @@ -13,12 +13,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, ChangeBoundsOperation, ElementAndBounds, hasArrayProp, hasNumberProp, SetBoundsAction } from '@eclipse-glsp/protocol'; +import { + Action, + ChangeBoundsOperation, + ElementAndBounds, + hasArrayProp, + hasNumberProp, + hasStringProp, + SetBoundsAction, + Writable +} from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { Command, CommandExecutionContext, CommandReturn, ElementMove, IActionDispatcher, MoveAction, SModelElement } from 'sprotty'; +import { ElementMove, IActionDispatcher, IActionHandler, ICommand, MoveAction, SModelElement } from 'sprotty'; import { TYPES } from '../../base/types'; -import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove } from '../../utils/layout-utils'; -import { BoundsAwareModelElement } from '../../utils/smodel-util'; +import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils'; +import { BoundsAwareModelElement, getElements } from '../../utils/smodel-util'; import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model'; import { IMovementRestrictor } from '../change-bounds/movement-restrictor'; import { SelectionService } from '../select/selection-service'; @@ -33,7 +42,14 @@ export enum ResizeDimension { Width_And_Height } -export namespace Reduce { +/** + * A function that computes a single number from a set of given numbers + * i.e. it reduces the given numbers to one single number. + * Mainly used by the {@link ResizeElementsCommand} to reduce the given dimensions to a target dimension value. + */ +export type ReduceFunction = (...values: number[]) => number; + +export namespace ReduceFunction { /** * Returns the minimal value of the given numbers. * @param values Numbers to be evaluated. @@ -79,13 +95,18 @@ export namespace Reduce { } /** - * Returns the reduce function that corresponds to the given {@link ReduceFunctionKind}. + * Returns the reduce function that corresponds to the given {@link ReduceFunctionType}. * @param type The reduce function kind. * @returns The corresponding reduce function. */ + export function get(type: ReduceFunctionType): ReduceFunction { + return ReduceFunction[type]; + } } -type ReduceFn = (...values: number[]) => number; +/** Union type of all {@link ReduceFunction} keys. */ +export type ReduceFunctionType = Exclude; + export interface ResizeElementsAction extends Action { kind: typeof ResizeElementsAction.KIND; @@ -93,14 +114,16 @@ export interface ResizeElementsAction extends Action { * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized. */ elementIds: string[]; + /** * Resize dimension. The default is {@link ResizeDimension.Width}. */ dimension: ResizeDimension; + /** - * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions. + * Type of the {@link ReduceFunction} that should be used to reduce the dimension to a target dimension value */ - reductionFunction: ReduceFn; + reduceFunction: ReduceFunctionType; } export namespace ResizeElementsAction { @@ -111,14 +134,14 @@ export namespace ResizeElementsAction { Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds') && hasNumberProp(object, 'dimension') && - 'reductionFunction' in object + hasStringProp(object, 'reduceFunction') ); } export function create(options: { elementIds?: string[]; dimension?: ResizeDimension; - reductionFunction: ReduceFn; + reduceFunction: ReduceFunctionType; }): ResizeElementsAction { return { kind: KIND, @@ -129,99 +152,24 @@ export namespace ResizeElementsAction { } } -// eslint-disable-next-line no-shadow -export enum Alignment { - Left, - Center, - Right, - Top, - Middle, - Bottom -} - -export namespace Select { - export function all(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { - return elements; - } - - export function first(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { - return [elements[0]]; - } - - export function last(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { - return [elements[elements.length - 1]]; - } -} - -type SelectFn = (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[]; -export interface AlignElementsAction extends Action { - kind: typeof AlignElementsAction.KIND; +@injectable() +export abstract class LayoutElementsActionHandler implements IActionHandler { + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; - /** - * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. - */ - elementIds: string[]; - /** - * Alignment direction. The default is {@link Alignment.Left} - */ - alignment: Alignment; - /** - * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions. - * The default value is {@link Select.all}. - */ - selectionFunction: SelectFn; -} + @inject(TYPES.SelectionService) + protected selectionService: SelectionService; -export namespace AlignElementsAction { - export const KIND = 'alignElements'; + @inject(TYPES.IMovementRestrictor) + @optional() + protected movementRestrictor?: IMovementRestrictor; - export function is(object: any): object is AlignElementsAction { - return ( - Action.hasKind(object, KIND) && - hasArrayProp(object, 'elementIds') && - hasNumberProp(object, 'alignment') && - 'selectionFunction' in object - ); - } + abstract handle(action: Action): void | Action | ICommand; - export function create( - options: { elementIds?: string[]; alignment?: Alignment; selectionFunction?: SelectFn } = {} - ): AlignElementsAction { - return { - kind: KIND, - elementIds: [], - alignment: Alignment.Left, - selectionFunction: Select.all, - ...options - }; - } -} -@injectable() -abstract class LayoutElementsCommand extends Command { - constructor( - @inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction, - @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher, - @inject(TYPES.SelectionService) protected selectionService: SelectionService, - @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor - ) { - super(); - } - - getActionElements(context: CommandExecutionContext): BoundsAwareModelElement[] { - const model = context.root; - const elementIDs = this.action.elementIds; - if (elementIDs.length === 0) { - // collect the selected elements from the selection service (selection order is kept by service) - this.selectionService.getSelectedElementIDs().forEach(elementID => elementIDs.push(elementID)); - } - const boundsAwareElements: BoundsAwareModelElement[] = []; - elementIDs.forEach(id => { - const element = model.index.getById(id); - if (element && this.isActionElement(element)) { - boundsAwareElements.push(element); - } - }); - return boundsAwareElements; + getSelectedElements(selection: { elementIds: string[] }): BoundsAwareModelElement[] { + const index = this.selectionService.getModelRoot().index; + const selectedElements = selection.elementIds.length > 0 ? selection.elementIds : this.selectionService.getSelectedElementIDs(); + return getElements(index, selectedElements, this.isActionElement); } protected abstract isActionElement(element: SModelElement): element is BoundsAwareModelElement; @@ -236,79 +184,60 @@ abstract class LayoutElementsCommand extends Command { } @injectable() -export class ResizeElementsCommand extends LayoutElementsCommand { - static readonly KIND = ResizeElementsAction.KIND; - - constructor( - @inject(TYPES.Action) protected override action: ResizeElementsAction, - @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, - @inject(TYPES.SelectionService) protected override selectionService: SelectionService, - @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor - ) { - super(action, actionDispatcher, selectionService, movementRestrictor); - } - - protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { - return isResizable(element); - } - - execute(context: CommandExecutionContext): CommandReturn { - const elements = this.getActionElements(context); +export class ResizeElementsActionHandler extends LayoutElementsActionHandler { + handle(action: ResizeElementsAction): void { + const elements = this.getSelectedElements(action); if (elements.length > 1) { - switch (this.action.dimension) { + const reduceFn = ReduceFunction.get(action.reduceFunction); + switch (action.dimension) { case ResizeDimension.Width: - this.resizeWidth(elements); - break; + return this.resizeWidth(elements, reduceFn); case ResizeDimension.Height: - this.resizeHeight(elements); - break; + return this.resizeHeight(elements, reduceFn); case ResizeDimension.Width_And_Height: - this.resizeWidthAndHeight(elements); - break; + return this.resizeWidthAndHeight(elements, reduceFn); } } - return context.root; } - resizeWidth(elements: BoundsAwareModelElement[]): void { - const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); + resizeWidth(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void { + const targetWidth = reduceFn(...elements.map(element => element.bounds.width)); this.dispatchResizeActions(elements, (element, bounds) => { - // resize around center + // resize around center (horizontal) const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width); - bounds.newPosition.x = element.bounds.x - halfDiffWidth; + bounds.newPosition!.x = element.bounds.x - halfDiffWidth; bounds.newSize.width = targetWidth; }); } - resizeHeight(elements: BoundsAwareModelElement[]): void { - const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); + resizeHeight(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void { + const targetHeight = reduceFn(...elements.map(element => element.bounds.height)); this.dispatchResizeActions(elements, (element, bounds) => { - // resize around middle + // resize around middle (vertical) const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height); - bounds.newPosition.y = element.bounds.y - halfDiffHeight; + bounds.newPosition!.y = element.bounds.y - halfDiffHeight; bounds.newSize.height = targetHeight; }); } - resizeWidthAndHeight(elements: BoundsAwareModelElement[]): void { - const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width)); - const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height)); + resizeWidthAndHeight(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void { + const targetWidth = reduceFn(...elements.map(element => element.bounds.width)); + const targetHeight = reduceFn(...elements.map(element => element.bounds.height)); this.dispatchResizeActions(elements, (element, bounds) => { - // resize around center and middle + // resize around center and middle (horizontal & vertical) const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width); const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height); - bounds.newPosition.x = element.bounds.x - halfDiffWidth; - bounds.newPosition.y = element.bounds.y - halfDiffHeight; - bounds.newSize.width = targetWidth; - bounds.newSize.height = targetHeight; + bounds.newPosition = { x: element.bounds.x - halfDiffWidth, y: element.bounds.y - halfDiffHeight }; + bounds.newSize = { width: targetWidth, height: targetHeight }; }); } dispatchResizeActions( elements: BoundsAwareModelElement[], - change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void + change: (element: BoundsAwareModelElement, bounds: Writable) => void ): void { const elementAndBounds: ElementAndBounds[] = []; // client- and server-side resize + elements.forEach(element => { const elementChange = this.createElementAndBounds(element, change); if (elementChange) { @@ -321,9 +250,9 @@ export class ResizeElementsCommand extends LayoutElementsCommand { createElementAndBounds( element: BoundsAwareModelElement, - change: (_element: BoundsAwareModelElement, _bounds: WriteableElementAndBounds) => void - ): WriteableElementAndBounds | undefined { - const bounds: WriteableElementAndBounds = { + change: (_element: BoundsAwareModelElement, _bounds: Writable) => void + ): Writable | undefined { + const bounds: ElementAndBounds = { elementId: element.id, newPosition: { x: element.bounds.x, @@ -337,107 +266,173 @@ export class ResizeElementsCommand extends LayoutElementsCommand { change(element, bounds); return toValidElementAndBounds(element, bounds, this.movementRestrictor); } + protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + return isResizable(element); + } +} + +// eslint-disable-next-line no-shadow +export enum Alignment { + Left, + Center, + Right, + Top, + Middle, + Bottom +} - undo(context: CommandExecutionContext): CommandReturn { - // we dispatch another action which can be undone, so no explicit implementation necessary - return context.root; +/** + * A function that retrieves a specific (sub)-selection of elements from a given set of elements. + * Mainly used by the {@link AlignElementsActionHandler}. + */ +export type SelectFunction = (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[]; +export namespace SelectFunction { + /** + * Select all elements from the given set of elements. + * @param elements The set of elements. + * @returns All elements. + */ + export function all(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { + return elements; } - redo(context: CommandExecutionContext): CommandReturn { - // we dispatch another action which can be redone, so no explicit implementation necessary - return context.root; + /** + * Select the first element from a given set of elements. + * @param elements The elements. + * @returns An array containing the first element of the given elements. + */ + export function first(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { + return [elements[0]]; + } + + /** + * Select the last element from a given set of elements. + * @param elements The elements. + * @returns An array containing the last element of the given elements. + */ + export function last(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] { + return [elements[elements.length - 1]]; + } + + /** + * Returns the select function that corresponds to the given {@link SelectFunctionType}. + * @param type The select function type. + * @returns The corresponding select function. + */ + export function get(kind: SelectFunctionType): SelectFunction { + return SelectFunction[kind]; } } -@injectable() -export class AlignElementsCommand extends LayoutElementsCommand { - static readonly KIND = AlignElementsAction.KIND; +/** Union type of all {@link SelectFunction} keys. */ +export type SelectFunctionType = Exclude; - constructor( - @inject(TYPES.Action) protected override action: AlignElementsAction, - @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher, - @inject(TYPES.SelectionService) protected override selectionService: SelectionService, - @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor - ) { - super(action, actionDispatcher, selectionService, movementRestrictor); +export interface AlignElementsAction extends Action { + kind: typeof AlignElementsAction.KIND; + + /** + * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned. + */ + elementIds: string[]; + /** + * Alignment direction. The default is {@link Alignment.Left} + */ + alignment: Alignment; + /** + * Function to selected elements that are considered during alignment calculation. + * The default value is {@link Select.all}. + */ + selectFunction: SelectFunctionType; +} + +export namespace AlignElementsAction { + export const KIND = 'alignElements'; + + export function is(object: any): object is AlignElementsAction { + return ( + Action.hasKind(object, KIND) && + hasArrayProp(object, 'elementIds') && + hasNumberProp(object, 'alignment') && + hasStringProp(object, 'selectFunction') + ); } - protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { - return isBoundsAwareMoveable(element); + export function create( + options: { elementIds?: string[]; alignment?: Alignment; selectionFunction?: SelectFunctionType } = {} + ): AlignElementsAction { + return { + kind: KIND, + elementIds: [], + alignment: Alignment.Left, + selectFunction: 'all', + ...options + }; } +} - execute(context: CommandExecutionContext): CommandReturn { - const elements = this.getActionElements(context); +@injectable() +export class AlignElementsActionHandler extends LayoutElementsActionHandler { + handle(action: AlignElementsAction): void { + const elements = this.getSelectedElements(action); + const selectFn = SelectFunction.get(action.selectFunction); + const calculatedElements = selectFn(elements); if (elements.length > 1) { - switch (this.action.alignment) { + switch (action.alignment) { case Alignment.Left: - this.alignLeft(elements); - break; + return this.alignLeft(calculatedElements); case Alignment.Center: - this.alignCenter(elements); - break; + return this.alignCenter(calculatedElements); case Alignment.Right: - this.alignRight(elements); - break; + return this.alignRight(calculatedElements); case Alignment.Top: - this.alignTop(elements); - break; + return this.alignTop(calculatedElements); case Alignment.Middle: - this.alignMiddle(elements); - break; + return this.alignMiddle(calculatedElements); case Alignment.Bottom: - this.alignBottom(elements); - break; + return this.alignBottom(calculatedElements); } } - return context.root; } alignLeft(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); + const minX = elements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); this.dispatchAlignActions(elements, (_, move) => (move.toPosition.x = minX)); } alignCenter(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); - const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); + const minX = elements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b)); + const maxX = elements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); const diffX = maxX - minX; const centerX = minX + 0.5 * diffX; this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = centerX - 0.5 * element.bounds.width)); } alignRight(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); + const maxX = elements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b)); this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = maxX - element.bounds.width)); } alignTop(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); + const minY = elements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); this.dispatchAlignActions(elements, (_, move) => (move.toPosition.y = minY)); } alignMiddle(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); - const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); + const minY = elements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b)); + const maxY = elements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); const diffY = maxY - minY; const middleY = minY + 0.5 * diffY; this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = middleY - 0.5 * element.bounds.height)); } alignBottom(elements: BoundsAwareModelElement[]): void { - const calculationElements = this.action.selectionFunction(elements); - const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); + const maxY = elements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b)); this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = maxY - element.bounds.height)); } dispatchAlignActions( elements: BoundsAwareModelElement[], - change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void + change: (element: BoundsAwareModelElement, move: Writable) => void ): void { const moves: ElementMove[] = []; // client-side move const elementAndBounds: ElementAndBounds[] = []; // server-side move @@ -455,9 +450,9 @@ export class AlignElementsCommand extends LayoutElementsCommand { createElementMove( element: BoundsAwareModelElement, - change: (_element: BoundsAwareModelElement, _move: WriteableElementMove) => void - ): WriteableElementMove | undefined { - const move: WriteableElementMove = { + change: (_element: BoundsAwareModelElement, _move: Writable) => void + ): Writable | undefined { + const move: ElementMove = { elementId: element.id, fromPosition: { x: element.bounds.x, @@ -485,14 +480,7 @@ export class AlignElementsCommand extends LayoutElementsCommand { } }; } - - undo(context: CommandExecutionContext): CommandReturn { - // we dispatch another action which can be undone, so no explicit implementation necessary - return context.root; - } - - redo(context: CommandExecutionContext): CommandReturn { - // we dispatch another action which can be redone, so no explicit implementation necessary - return context.root; + protected isActionElement(element: SModelElement): element is BoundsAwareModelElement { + return isBoundsAwareMoveable(element); } } diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 92d6652..3a56c4b 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -33,7 +33,7 @@ export class NavigationTargetResolver { async resolve(navigationTarget: NavigationTarget): Promise { const contextService = await this.editorContextService(); - const sourceUri = await contextService.getSourceUri(); + const sourceUri = contextService.sourceUri; return this.resolveWithSourceUri(sourceUri, navigationTarget); } diff --git a/packages/client/src/features/select/selection-service.spec.ts b/packages/client/src/features/select/selection-service.spec.ts index b0417b3..0a346dd 100644 --- a/packages/client/src/features/select/selection-service.spec.ts +++ b/packages/client/src/features/select/selection-service.spec.ts @@ -347,7 +347,7 @@ describe('SelectionService', () => { expect(selectionService.hasSelectedElements()).to.equal(expectedSelection.length > 0); expect(selectionService.getSelectedElementIDs()).to.have.lengthOf(expectedSelection.length); if (expectedSelection.length > 0) { - expect(selectionService.getSelectedElementIDs()).to.have.all.keys(...expectedSelection); + expect(selectionService.getSelectedElementIDs()).to.have.ordered.members(expectedSelection); } } diff --git a/packages/client/src/features/select/selection-service.ts b/packages/client/src/features/select/selection-service.ts index 99da278..3e214de 100644 --- a/packages/client/src/features/select/selection-service.ts +++ b/packages/client/src/features/select/selection-service.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action, distinctAdd, remove, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; +import { Action, distinctAdd, pluck, remove, SelectAction, SelectAllAction } from '@eclipse-glsp/protocol'; import { inject, injectable, multiInject, optional } from 'inversify'; import { Command, @@ -25,12 +25,11 @@ import { SelectAllCommand as SprottySelectAllCommand, SelectCommand as SprottySelectCommand, SModelElement, - SModelRoot, - SParentElement + SModelRoot } from 'sprotty'; import { SModelRootListener } from '../../base/model/update-model-command'; import { TYPES } from '../../base/types'; -import { getElements } from '../../utils/smodel-util'; +import { getElements, getMatchingElements } from '../../utils/smodel-util'; import { IFeedbackActionDispatcher } from '../tool-feedback/feedback-action-dispatcher'; import { SelectFeedbackAction } from './select-feedback-action'; @@ -135,8 +134,8 @@ export class SelectionService implements SModelRootListener { * QUERY METHODS */ - getSelectedElementIDs(): Set { - return this.selectedElementIDs; + getSelectedElementIDs(): string[] { + return [...this.selectedElementIDs]; } hasSelectedElements(): boolean { @@ -152,6 +151,11 @@ export class SelectionService implements SModelRootListener { } } +/** + * Handles a {@link SelectAction} and propagates the new selection to the {@link SelectionService}. + * Other tools might be selection-sensitive which means {@link SelectAction}s must be processed as fast as possible. + * Handling the action with a command ensures that the action is processed before the next render tick. + */ @injectable() export class SelectCommand extends Command { static readonly KIND = SprottySelectCommand.KIND; @@ -168,36 +172,30 @@ export class SelectCommand extends Command { execute(context: CommandExecutionContext): SModelRoot { const model = context.root; - this.action.selectedElementsIDs.forEach(id => { - const element = model.index.getById(id); - if (element instanceof SChildElement && isSelectable(element)) { - this.selected.push(element); - } - }); - this.action.deselectedElementsIDs.forEach(id => { - const element = model.index.getById(id); - if (element instanceof SChildElement && isSelectable(element)) { - this.deselected.push(element); - } - }); - return this.redo(context); + const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element); + const selectedElements = getElements(model.index, this.action.selectedElementsIDs, selectionGuard); + const deselectedElements = getElements(model.index, this.action.deselectedElementsIDs, selectionGuard); + + this.selectionService.updateSelection(model, pluck(selectedElements, 'id'), pluck(deselectedElements, 'id')); + return model; } + // Basically no-op since client-side undo is not supported in GLSP. undo(context: CommandExecutionContext): SModelRoot { - const select = this.deselected.map(element => element.id); - const deselect = this.selected.map(element => element.id); - this.selectionService.updateSelection(context.root, select, deselect); return context.root; } + // Basically no-op since client-side redo is not supported in GLSP. redo(context: CommandExecutionContext): SModelRoot { - const select = this.selected.map(element => element.id); - const deselect = this.deselected.map(element => element.id); - this.selectionService.updateSelection(context.root, select, deselect); return context.root; } } +/** + * Handles a {@link SelectAllAction} and propagates the new selection to the {@link SelectionService}. + * Other tools might be selection-sensitive which means {@link SelectionAllAction}s must be processed as fast as possible. + * Handling the action with a command ensures that the action is processed before the next render tick. + */ @injectable() export class SelectAllCommand extends Command { static readonly KIND = SprottySelectAllCommand.KIND; @@ -211,38 +209,27 @@ export class SelectAllCommand extends Command { } execute(context: CommandExecutionContext): SModelRoot { - return this.redo(context); + const model = context.root; + const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element); + + const selectables = getMatchingElements(model.index, selectionGuard); + const selectableIds = pluck(selectables, 'id'); + if (this.action.select) { + this.selectionService.updateSelection(model, selectableIds, []); + } else { + this.selectionService.updateSelection(model, [], selectableIds); + } + + return model; } + // Basically no-op since client-side undo is not supported in GLSP. undo(context: CommandExecutionContext): SModelRoot { - const index = context.root.index; - for (const previousState of this.previousSelection) { - const element = index.getById(previousState[0]); - if (element !== undefined && isSelectable(element)) { - element.selected = previousState[1]; - } - } return context.root; } + // Basically no-op since client-side redo is not supported in GLSP. redo(context: CommandExecutionContext): SModelRoot { - const selectables: string[] = []; - this.selectAll(context.root, this.action.select, selectables); - if (this.action.select) { - this.selectionService.updateSelection(context.root, selectables, []); - } else { - this.selectionService.updateSelection(context.root, [], selectables); - } return context.root; } - - protected selectAll(element: SParentElement, newState: boolean, selected: string[]): void { - if (isSelectable(element)) { - this.previousSelection.set(element.id, element.selected); - selected.push(element.id); - } - for (const child of element.children) { - this.selectAll(child, newState, selected); - } - } } diff --git a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts index f23f3ca..53a36b4 100644 --- a/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/change-bounds-tool-feedback.ts @@ -224,7 +224,7 @@ export class FeedbackMoveMouseListener extends MouseListener { protected validateMove(startPostion: Point, toPosition: Point, element: SModelElement, isFinished: boolean): Point { let newPosition = toPosition; if (this.tool.movementRestrictor) { - const valid = this.tool.movementRestrictor.validate(toPosition, element); + const valid = this.tool.movementRestrictor.validate(element, toPosition); let action; if (!valid) { action = createMovementRestrictionFeedback(element, this.tool.movementRestrictor); diff --git a/packages/client/src/features/validation/di.config.ts b/packages/client/src/features/validation/di.config.ts index 5eed0eb..99b94df 100644 --- a/packages/client/src/features/validation/di.config.ts +++ b/packages/client/src/features/validation/di.config.ts @@ -13,30 +13,33 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { SetMarkersAction } from '@eclipse-glsp/protocol'; import { ContainerModule } from 'inversify'; -import { configureCommand } from 'sprotty'; +import { configureActionHandler, configureCommand } from 'sprotty'; import { TYPES } from '../../base/types'; import { LeftToRightTopToBottomComparator, MarkerNavigator, MarkerNavigatorContextMenuItemProvider, MarkerNavigatorKeyListener, - NavigateToMarkerCommand, + NavigateToMarkerAction, + NavigateToMarkerActionHandler, SModelElementComparator } from './marker-navigator'; -import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersCommand, ValidationFeedbackEmitter } from './validate'; +import { ApplyMarkersCommand, DeleteMarkersCommand, SetMarkersActionHandler, ValidationFeedbackEmitter } from './validate'; export const validationModule = new ContainerModule((bind, _unbind, isBound) => { - configureCommand({ bind, isBound }, SetMarkersCommand); - configureCommand({ bind, isBound }, ApplyMarkersCommand); - configureCommand({ bind, isBound }, DeleteMarkersCommand); + const context = { bind, isBound }; + configureActionHandler(context, SetMarkersAction.KIND, SetMarkersActionHandler); + configureCommand(context, ApplyMarkersCommand); + configureCommand(context, DeleteMarkersCommand); bind(ValidationFeedbackEmitter).toSelf().inSingletonScope(); }); export const markerNavigatorModule = new ContainerModule((bind, _unbind, isBound) => { bind(SModelElementComparator).to(LeftToRightTopToBottomComparator).inSingletonScope(); bind(MarkerNavigator).toSelf().inSingletonScope(); - configureCommand({ bind, isBound }, NavigateToMarkerCommand); + configureActionHandler({ bind, isBound }, NavigateToMarkerAction.KIND, NavigateToMarkerActionHandler); }); /** diff --git a/packages/client/src/features/validation/marker-navigator.ts b/packages/client/src/features/validation/marker-navigator.ts index 38caacc..2a302c1 100644 --- a/packages/client/src/features/validation/marker-navigator.ts +++ b/packages/client/src/features/validation/marker-navigator.ts @@ -16,12 +16,10 @@ import { Action, CenterAction, hasArrayProp, hasStringProp, Point, SelectAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { + ActionDispatcher, BoundsAware, - CenterCommand, - Command, - CommandExecutionContext, - CommandReturn, findParentByFeature, + IActionHandler, IContextMenuItemProvider, isBoundsAware, isSelectable, @@ -36,8 +34,8 @@ import { import { matchesKeystroke } from 'sprotty/lib/utils/keyboard'; import { TYPES } from '../../base/types'; import { collectIssueMarkers, MarkerPredicates } from '../../utils/marker'; -import { isSelectableAndBoundsAware } from '../../utils/smodel-util'; -import { SelectCommand, SelectionService } from '../select/selection-service'; +import { getElements, isSelectableAndBoundsAware } from '../../utils/smodel-util'; +import { SelectionService } from '../select/selection-service'; export interface NavigateToMarkerAction extends Action { kind: typeof NavigateToMarkerAction.KIND; @@ -106,7 +104,7 @@ export class MarkerNavigator { protected markerComparator: SModelElementComparator; next( - root: SModelRoot, + root: Readonly, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL ): SIssueMarker | undefined { @@ -118,7 +116,7 @@ export class MarkerNavigator { } previous( - root: SModelRoot, + root: Readonly, current?: SModelElement & BoundsAware, predicate: (marker: SIssueMarker) => boolean = MarkerPredicates.ALL ): SIssueMarker | undefined { @@ -129,7 +127,7 @@ export class MarkerNavigator { return markers[this.getPreviousIndex(current, markers) % markers.length]; } - protected getMarkers(root: SModelRoot, predicate: (marker: SIssueMarker) => boolean): SIssueMarker[] { + protected getMarkers(root: Readonly, predicate: (marker: SIssueMarker) => boolean): SIssueMarker[] { const markers = collectIssueMarkers(root); return markers.filter(predicate).sort(this.markerComparator.compare); } @@ -154,9 +152,7 @@ export class MarkerNavigator { } @injectable() -export class NavigateToMarkerCommand extends Command { - static KIND = NavigateToMarkerAction.KIND; - +export class NavigateToMarkerActionHandler implements IActionHandler { @inject(SModelElementComparator) protected markerComparator: SModelElementComparator; @@ -166,73 +162,40 @@ export class NavigateToMarkerCommand extends Command { @inject(TYPES.SelectionService) protected selectionService: SelectionService; - protected selectCommand: SelectCommand; - protected centerCommand: CenterCommand; - - constructor(@inject(TYPES.Action) protected action: NavigateToMarkerAction) { - super(); - } + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: ActionDispatcher; - execute(context: CommandExecutionContext): CommandReturn { - const root = context.root; - const selected = this.getSelectedElements(root); - const target = this.getTarget(selected, root); - if (target === undefined) { - return root; - } + handle(action: NavigateToMarkerAction): void { + const selected = this.getSelectedElements(action); + const target = this.getTarget(action, selected); - const selectableTarget = findParentByFeature(target, isSelectable); + const selectableTarget = target ? findParentByFeature(target, isSelectable) : undefined; if (selectableTarget) { - const deselect = selected.map(e => e.id).filter(id => id !== selectableTarget.id); - this.selectCommand = new SelectCommand( - SelectAction.create({ selectedElementsIDs: [selectableTarget.id], deselectedElementsIDs: deselect }), - this.selectionService - ); - this.centerCommand = new CenterCommand(CenterAction.create([selectableTarget.id])); - this.centerCommand.execute(context); - return this.selectCommand.execute(context); + const deselectedElementsIDs = selected.map(e => e.id).filter(id => id !== selectableTarget.id); + this.actionDispatcher.dispatch(SelectAction.create({ selectedElementsIDs: [selectableTarget.id], deselectedElementsIDs })); + this.actionDispatcher.dispatch(CenterAction.create([selectableTarget.id])); } - return root; } - protected getSelectedElements(root: SModelRoot): (SModelElement & Selectable)[] { - let selectedIds = []; - if (this.action.selectedElementIds !== undefined && this.action.selectedElementIds.length > 0) { - selectedIds = this.action.selectedElementIds; - } else { - return this.selectionService.getSelectedElements(); + protected getSelectedElements(action: NavigateToMarkerAction): (SModelElement & Selectable)[] { + if (action.selectedElementIds && action.selectedElementIds.length > 0) { + return getElements(this.selectionService.getModelRoot().index, action.selectedElementIds, isSelectable); } - return selectedIds.map(id => root.index.getById(id)).filter(element => element !== undefined && isSelectable(element)) as Array< - SModelElement & Selectable - >; + return this.selectionService.getSelectedElements(); } - protected getTarget(selected: SModelElement[], root: SModelRoot): SIssueMarker | undefined { - const selectedBoundsAware = selected.filter(isBoundsAware).sort(this.markerComparator.compare); - const currentTopmost = selectedBoundsAware.length > 0 ? selectedBoundsAware[0] : undefined; - if (this.action.direction === 'previous') { - return this.markerNavigator.previous(root, currentTopmost, marker => this.matchesSeverities(marker)); + protected getTarget(action: NavigateToMarkerAction, selected: SModelElement[]): SIssueMarker | undefined { + const root = this.selectionService.getModelRoot(); + const target = selected.sort(this.markerComparator.compare).find(isBoundsAware); + if (action.direction === 'previous') { + return this.markerNavigator.previous(root, target, marker => this.matchesSeverities(action, marker)); } else { - return this.markerNavigator.next(root, currentTopmost, marker => this.matchesSeverities(marker)); + return this.markerNavigator.next(root, target, marker => this.matchesSeverities(action, marker)); } } - protected matchesSeverities(marker: SIssueMarker): boolean { - return marker.issues.find(issue => this.action.severities.includes(issue.severity)) !== undefined; - } - - undo(context: CommandExecutionContext): CommandReturn { - if (this.selectCommand) { - context.root = this.selectCommand.undo(context); - } - return this.centerCommand ? this.centerCommand.undo(context) : context.root; - } - - redo(context: CommandExecutionContext): CommandReturn { - if (this.selectCommand) { - context.root = this.selectCommand.redo(context); - } - return this.centerCommand ? this.centerCommand.redo(context) : context.root; + protected matchesSeverities(action: NavigateToMarkerAction, marker: SIssueMarker): boolean { + return marker.issues.find(issue => action.severities.includes(issue.severity)) !== undefined; } } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 2167644..75ae01d 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -15,17 +15,7 @@ ********************************************************************************/ import { Action, DeleteMarkersAction, hasArrayProp, Marker, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; -import { - Command, - CommandExecutionContext, - CommandReturn, - IActionDispatcher, - SIssue, - SIssueMarker, - SModelElement, - SModelRoot, - SParentElement -} from 'sprotty'; +import { CommandExecutionContext, CommandReturn, IActionDispatcher, IActionHandler, ICommand, SIssueMarker, SParentElement } from 'sprotty'; import { EditorContextService } from '../../base/editor-context-service'; import { TYPES } from '../../base/types'; import { removeCssClasses } from '../../utils/smodel-util'; @@ -94,43 +84,29 @@ export abstract class ExternalMarkerManager { abstract setMarkers(markers: Marker[], sourceUri?: string): void; } -/** - * Command for handling `SetMarkersAction` - */ @injectable() -export class SetMarkersCommand extends Command { - @inject(ValidationFeedbackEmitter) protected validationFeedbackEmitter: ValidationFeedbackEmitter; - @inject(ExternalMarkerManager) @optional() protected externalMarkerManager?: ExternalMarkerManager; - @inject(EditorContextService) protected editorContextService: EditorContextService; +export class SetMarkersActionHandler implements IActionHandler { + @inject(ValidationFeedbackEmitter) + protected validationFeedbackEmitter: ValidationFeedbackEmitter; - static readonly KIND = SetMarkersAction.KIND; + @inject(ExternalMarkerManager) + @optional() + protected externalMarkerManager?: ExternalMarkerManager; - constructor(@inject(TYPES.Action) public action: SetMarkersAction) { - super(); - } + @inject(EditorContextService) + protected editorContextService: EditorContextService; - async execute(context: CommandExecutionContext): Promise { - const markers: Marker[] = this.action.markers; - const uri = await this.editorContextService.getSourceUri(); - if (this.externalMarkerManager) { - this.externalMarkerManager.setMarkers(markers, uri); - } + handle(action: SetMarkersAction): void | Action | ICommand { + const markers: Marker[] = action.markers; + const uri = this.editorContextService.sourceUri; + this.externalMarkerManager?.setMarkers(markers, uri); const applyMarkersAction = ApplyMarkersAction.create(markers); this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); - return context.root; - } - - undo(context: CommandExecutionContext): CommandReturn { - return context.root; - } - - redo(context: CommandExecutionContext): CommandReturn { - return this.execute(context); } } /** - * Action for applying makers to a model + * Action for applying makers to the graphical model. */ export interface ApplyMarkersAction extends Action { kind: typeof ApplyMarkersAction.KIND; @@ -153,38 +129,29 @@ export namespace ApplyMarkersAction { } /** - * Command for handling `ApplyMarkersAction` + * Handles {@link ApplyMarkersAction}s by creating the corresponding {@link SIssueMarker}s and + * adding them to the graphical model. */ @injectable() export class ApplyMarkersCommand extends FeedbackCommand { static KIND = ApplyMarkersAction.KIND; - override readonly priority = 0; constructor(@inject(TYPES.Action) protected action: ApplyMarkersAction) { super(); } execute(context: CommandExecutionContext): CommandReturn { - const markers: Marker[] = this.action.markers; - for (const marker of markers) { - const modelElement: SModelElement | undefined = context.root.index.getById(marker.elementId); + this.action.markers.forEach(marker => { + const modelElement = context.root.index.getById(marker.elementId); if (modelElement instanceof SParentElement) { - const issueMarker: SIssueMarker = getOrCreateSIssueMarker(modelElement); - const issue: SIssue = createSIssue(marker); + const issueMarker = getOrCreateSIssueMarker(modelElement); + const issue = createSIssue(marker); issueMarker.issues.push(issue); addMaxSeverityCSSClassToIssueParent(modelElement, issueMarker); } - } - return context.root; - } - - override undo(context: CommandExecutionContext): CommandReturn { + }); return context.root; } - - override redo(context: CommandExecutionContext): CommandReturn { - return this.execute(context); - } } function addMaxSeverityCSSClassToIssueParent(modelElement: SParentElement, issueMarker: SIssueMarker): void { @@ -206,7 +173,7 @@ function removeCSSClassFromIssueParent(modelElement: SParentElement, issueMarker * Command for handling `DeleteMarkersAction` */ @injectable() -export class DeleteMarkersCommand extends Command { +export class DeleteMarkersCommand extends FeedbackCommand { static KIND = DeleteMarkersAction.KIND; constructor(@inject(TYPES.Action) protected action: DeleteMarkersAction) { @@ -214,15 +181,14 @@ export class DeleteMarkersCommand extends Command { } execute(context: CommandExecutionContext): CommandReturn { - const markers: Marker[] = this.action.markers; - for (const marker of markers) { - const modelElement: SModelElement | undefined = context.root.index.getById(marker.elementId); + this.action.markers.forEach(marker => { + const modelElement = context.root.index.getById(marker.elementId); if (modelElement instanceof SParentElement) { - const issueMarker: SIssueMarker | undefined = getSIssueMarker(modelElement); - if (issueMarker !== undefined) { + const issueMarker = getSIssueMarker(modelElement); + if (issueMarker) { removeCSSClassFromIssueParent(modelElement, issueMarker); for (let index = 0; index < issueMarker.issues.length; ++index) { - const issue: SIssue = issueMarker.issues[index]; + const issue = issueMarker.issues[index]; if (issue.message === marker.description) { issueMarker.issues.splice(index--, 1); } @@ -234,15 +200,8 @@ export class DeleteMarkersCommand extends Command { } } } - } - return context.root; - } + }); - undo(context: CommandExecutionContext): CommandReturn { return context.root; } - - redo(context: CommandExecutionContext): CommandReturn { - return this.execute(context); - } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index b8aaf9e..6ddd3fc 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -21,7 +21,7 @@ import glspDecorationModule from './features/decoration/di.config'; import glspEditLabelModule from './features/edit-label/di.config'; import modelHintsModule from './features/hints/di.config'; import glspHoverModule from './features/hover/di.config'; -import layoutCommandsModule from './features/layout/di.config'; +import layoutModule from './features/layout/di.config'; import modelSourceWatcherModule from './features/model-source-watcher/di.config'; import glspMouseToolModule from './features/mouse-tool/di.config'; import { navigationModule } from './features/navigation/di.config'; @@ -65,7 +65,7 @@ export * from './features/edit-label/edit-label-validator'; export * from './features/hints/model'; export * from './features/hints/type-hints'; export * from './features/hover/hover'; -export * from './features/layout/layout-commands'; +export * from './features/layout/layout-elements-action'; export * from './features/model-source-watcher/model-source-changed-action-handler'; export * from './features/mouse-tool/mouse-tool'; export * from './features/navigation/navigation-action-handler'; @@ -113,7 +113,7 @@ export { copyPasteContextMenuModule, glspSelectModule, glspMouseToolModule, - layoutCommandsModule, + layoutModule, glspEditLabelModule, glspHoverModule, toolsModule, diff --git a/packages/client/src/model-source/glsp-diagram-server.ts b/packages/client/src/model-source/glsp-diagram-server.ts index d2b3d74..185a0b9 100644 --- a/packages/client/src/model-source/glsp-diagram-server.ts +++ b/packages/client/src/model-source/glsp-diagram-server.ts @@ -90,7 +90,7 @@ export class GLSPDiagramServer extends DiagramServerProxy implements SourceUriAw return !isReceivedFromServer(action); } - public getSourceURI(): string { + public get sourceURI(): string { return this._sourceUri; } } diff --git a/packages/client/src/utils/layout-utils.ts b/packages/client/src/utils/layout-utils.ts index f83af08..77615a8 100644 --- a/packages/client/src/utils/layout-utils.ts +++ b/packages/client/src/utils/layout-utils.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Dimension, ElementAndBounds, Point } from '@eclipse-glsp/protocol'; +import { Dimension, ElementAndBounds, Point, Writable } from '@eclipse-glsp/protocol'; import { BoundsAware, ElementMove, ModelLayoutOptions, SModelElement } from 'sprotty'; import { IMovementRestrictor } from '../features/change-bounds/movement-restrictor'; @@ -45,18 +45,18 @@ export function isValidSize(element: SModelElement & BoundsAware, size: Dimensio return size.width >= minWidth(element) && size.height >= minHeight(element); } -export function isValidMove(element: SModelElement & BoundsAware, newPosition: Point, movementRestrictor?: IMovementRestrictor): boolean { +export function isValidMove(element: SModelElement & BoundsAware, newPosition?: Point, movementRestrictor?: IMovementRestrictor): boolean { if (movementRestrictor) { - return movementRestrictor.validate(newPosition, element); + return movementRestrictor.validate(element, newPosition); } return true; } export function toValidElementMove( element: SModelElement & BoundsAware, - move: WriteableElementMove, + move: ElementMove, movementRestrictor?: IMovementRestrictor -): WriteableElementMove | undefined { +): ElementMove | undefined { if (!isValidMove(element, move.toPosition, movementRestrictor)) { return; } @@ -65,9 +65,9 @@ export function toValidElementMove( export function toValidElementAndBounds( element: SModelElement & BoundsAware, - bounds: WriteableElementAndBounds, + bounds: Writable, movementRestrictor?: IMovementRestrictor -): WriteableElementAndBounds | undefined { +): ElementAndBounds | undefined { if (!isValidMove(element, bounds.newPosition, movementRestrictor)) { return; } @@ -81,23 +81,3 @@ export function toValidElementAndBounds( } return bounds; } - -export interface WriteablePoint extends Point { - x: number; - y: number; -} - -export interface WriteableElementMove extends ElementMove { - fromPosition?: WriteablePoint; - toPosition: WriteablePoint; -} - -export interface WriteableDimension extends Dimension { - width: number; - height: number; -} - -export interface WriteableElementAndBounds extends ElementAndBounds { - newPosition: WriteablePoint; - newSize: WriteableDimension; -} diff --git a/packages/protocol/src/utils/array-util.ts b/packages/protocol/src/utils/array-util.ts index 1e21d78..cf36d62 100644 --- a/packages/protocol/src/utils/array-util.ts +++ b/packages/protocol/src/utils/array-util.ts @@ -13,6 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ + +import { Constructor, Primitive } from './type-util'; + /** * A union type for for objects that can either be a single element or and array of elements. */ @@ -23,12 +26,13 @@ export type MaybeArray = T | T[]; * @param array the array. * @returns the element at index 0. */ -export function first(array: T[]): number; +export function first(array: T[]): T; + /** * Returns the first n elements of the given array. - * @param array the array. - * @param n the number of elements that should be returned - * @returns the first n elements of the array + * @param array The array. + * @param n The number of elements that should be returned + * @returns The first n elements of the array */ export function first(array: T[], n: number): T[]; export function first(array: T[], n?: number): T[] | T { @@ -38,6 +42,37 @@ export function first(array: T[], n?: number): T[] | T { return array[0]; } +/** + * Returns the last element of the given array. + * @param array The array. + * @returns The last element in the array. + */ +export function last(array: T[]): T; + +/** + * Returns the last n elements of the given array. + * @param array The array. + * @param n The number of elements that should be returned + * @returns The last n elements of the array + */ +export function last(array: T[], n: number): T[]; +export function last(array: T[], n?: number): T[] | T { + if (n) { + return array.filter((_, index) => array.length - index <= n); + } + return array[array.length - 1]; +} + +/** + * Plucks (i.e. extracts) the property value that corresponds to the given key from all objects of the array. + * @param array The array which should be plucked. + * @param key The key of the property that should be extracted. + * @returns A new array containing the plugged property for each element of the array. + */ +export function pluck(array: T[], key: K): Array { + return array.map(element => element[key]); +} + /** * Removes the given values from the given array (if present). * @param array The array to execute the remove operation on. @@ -76,10 +111,6 @@ export function distinctAdd(array: T[], ...values: T[]): void { } }); } -interface Constructor { - new (...args: any[]): T; -} -type PrimitiveType = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'; /** * A typeguard function to check wether a given object is an array of a specific type `T`. As it checks the type of each individual @@ -113,7 +144,7 @@ export function isArrayOfClass(object: any, constructor: Constructor, supp * @param supportEmpty A flag to determine wether empty arrays should pass the typeguard check. * @returns A type predicate indicating wether the given object has passed the type guard check. */ -export function isArrayOfPrimitive(object: any, primitiveType: PrimitiveType, supportEmpty = false): object is T[] { +export function isArrayOfPrimitive(object: any, primitiveType: Primitive, supportEmpty = false): object is T[] { return isArrayMatching(object, element => typeof element === primitiveType, supportEmpty); } diff --git a/packages/protocol/src/utils/type-util.ts b/packages/protocol/src/utils/type-util.ts index a2df90d..4a1257c 100644 --- a/packages/protocol/src/utils/type-util.ts +++ b/packages/protocol/src/utils/type-util.ts @@ -33,11 +33,41 @@ export namespace AnyObject { } } +/** + * Utility type to capture all primitive types. + */ +export type Primitive = string | number | boolean | bigint | symbol | undefined | null; + +/** + * Utility type to describe objects that have a constructor function i.e. classes. + */ +export interface Constructor { + new (...args: any[]): T; +} + +/** + * Utility type to declare a given type `T` as writable. Essentially this removes + * all readonly modifiers of the type`s properties. Please use with care and only in instances + * where you know that overwriting a readonly property is safe and doesn't cause any unintended side effects. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type Writable = { -readonly [P in keyof T]: Writable }; + /** * Utility type to describe typeguard functions. */ export type TypeGuard = (element: any, ...args: any[]) => element is T; +/** + * Utility function that create a typeguard function for a given class constructor. + * Essentially this wraps an instance of check as typeguard function. + * @param constructor The constructor fo the class for which the typeguard should be created. + * @returns The typeguard for this class. + */ +export function toTypeGuard(constructor: Constructor): TypeGuard { + return (element: any): element is G => element instanceof constructor; +} + /** * Validates whether the given object as a property of type `string` with the given key. * @param object The object that should be validated From e8744346b00c2729896be47b1d0b888b61858d61 Mon Sep 17 00:00:00 2001 From: Lukas <42733123+ivy-lli@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:35:57 +0200 Subject: [PATCH 097/566] Issue-613 Fix snap routing handles (#177) Also snap the routing handle before send it to the server. With this change it will be possible to draw a straight line out of a polyline edge. --- .../tool-feedback/edge-edit-tool-feedback.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts index 700a1fb..e60235d 100644 --- a/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts +++ b/packages/client/src/features/tool-feedback/edge-edit-tool-feedback.ts @@ -265,20 +265,20 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { } const positionUpdate = this.pointPositionUpdater.updatePosition(target, { x: event.pageX, y: event.pageY }, !event.altKey); if (positionUpdate) { - const moveActions = this.handleMoveOnClient(target, positionUpdate); + const moveActions = this.handleMoveOnClient(target, positionUpdate, !event.altKey); result.push(...moveActions); } return result; } - protected handleMoveOnClient(target: SModelElement, positionUpdate: Point): Action[] { + protected handleMoveOnClient(target: SModelElement, positionUpdate: Point, isSnap: boolean): Action[] { const handleMoves: ElementMove[] = []; target.root.index .all() .filter(element => isSelected(element)) .forEach(element => { if (isRoutingHandle(element)) { - const elementMove = this.toElementMove(element, positionUpdate); + const elementMove = this.toElementMove(element, positionUpdate, isSnap); if (elementMove) { handleMoves.push(elementMove); } @@ -290,21 +290,29 @@ export class FeedbackEdgeRouteMovingMouseListener extends MouseListener { return []; } - protected toElementMove(element: SRoutingHandle, positionDelta: Point): ElementMove | undefined { + protected toElementMove(element: SRoutingHandle, positionDelta: Point, isSnap: boolean): ElementMove | undefined { const point = this.getHandlePosition(element); if (point !== undefined) { + const snappedPoint = this.getSnappedHandlePosition(element, point, isSnap); return { elementId: element.id, fromPosition: point, toPosition: { - x: point.x + positionDelta.x, - y: point.y + positionDelta.y + x: snappedPoint.x + positionDelta.x, + y: snappedPoint.y + positionDelta.y } }; } return undefined; } + protected getSnappedHandlePosition(element: SRoutingHandle, point: Point, isSnap: boolean): Point { + if (this.snapper && isSnap) { + return this.snapper.snap(point, element); + } + return point; + } + protected getHandlePosition(handle: SRoutingHandle): Point | undefined { if (this.edgeRouterRegistry) { const parent = handle.parent; From 85deb812df98ce2790441fce46179ec330a70f8d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 21 Apr 2022 12:40:06 +0200 Subject: [PATCH 098/566] #624 Support file-url ins GModelStorage (#14) Fixes eclipse-glsp/glsp/issues/624 --- packages/server-node/src/base-impl/gmodel-storage.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/server-node/src/base-impl/gmodel-storage.ts b/packages/server-node/src/base-impl/gmodel-storage.ts index 04f30b3..7f4ad52 100644 --- a/packages/server-node/src/base-impl/gmodel-storage.ts +++ b/packages/server-node/src/base-impl/gmodel-storage.ts @@ -18,6 +18,7 @@ import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp import { writeFileSync } from 'fs'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; +import { fileURLToPath } from 'url'; import { GModelSerializer } from '../features/model/gmodel-serializer'; import { SourceModelStorage } from '../features/model/source-model-storage'; import { getOrThrow, GLSPServerError } from '../utils/glsp-server-error'; @@ -51,9 +52,10 @@ export class GModelStorage implements SourceModelStorage { this.modelState.root = root; } - protected loadFromFile(url: string): GModelRoot { + protected loadFromFile(sourceUri: string): GModelRoot { try { - const fileContent = this.readFile(url); + const path = this.isFileUrl(sourceUri) ? fileURLToPath(sourceUri) : sourceUri; + const fileContent = this.readFile(path); if (!fileContent) { return EMPTY_ROOT; } @@ -62,10 +64,14 @@ export class GModelStorage implements SourceModelStorage { } return this.modelSerializer.createRoot(fileContent); } catch (error) { - throw new GLSPServerError(`Could not load model from file: ${url}`, error); + throw new GLSPServerError(`Could not load model from file: ${sourceUri}`, error); } } + protected isFileUrl(sourceUri: string): boolean { + return sourceUri.startsWith('file://'); + } + protected readFile(url: string): unknown { try { const data = fs.readFileSync(url, { encoding: 'utf8' }); From 1cd607ef4ee7ac5b7b73136fc5a5e688792a43e4 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Fri, 22 Apr 2022 10:30:37 +0200 Subject: [PATCH 099/566] #620 Introduce explicit binding methods for diagram module (#15) Introduce the concept of `BindingTargets` which enables us to define bindings in the diagram and server modules in a more explicit way using dedicated submethods. (Similar to the pattern we currently use in the Java GLSP Server). Using `BindingTargets` allows us to move the specification of the target binding into a submethod without limiting the power of inversify. Adopters can still chose whether they want to bind a service identifier to constructor, self, constant value or another service. In addition, this change fixes a couple of issues in the test setup regarding error handling. Fixes eclipse-glsp/glsp/issues/629 --- .../src/workflow-diagram-module.ts | 64 +++-- .../src/actions/action-dispatcher.spec.ts | 3 +- .../src/base-impl/gmodel-diagram-module.ts | 29 +- .../server-node/src/di/binding-target.spec.ts | 142 ++++++++++ packages/server-node/src/di/binding-target.ts | 165 +++++++++++ packages/server-node/src/di/diagram-module.ts | 261 +++++++++++++----- packages/server-node/src/di/glsp-module.ts | 4 +- packages/server-node/src/di/multi-binding.ts | 19 +- .../server-node/src/di/multi-bindings.spec.ts | 6 +- packages/server-node/src/di/server-module.ts | 72 ++++- ...apply-label-edit-operation-handler.spec.ts | 5 +- .../server-node/src/launch/cli-parser.spec.ts | 6 +- .../src/launch/socket-cli-parser.spec.ts | 6 +- .../src/protocol/glsp-server.spec.ts | 11 +- .../src/test/integration-test.spec.ts | 22 +- 15 files changed, 656 insertions(+), 159 deletions(-) create mode 100644 packages/server-node/src/di/binding-target.spec.ts create mode 100644 packages/server-node/src/di/binding-target.ts diff --git a/examples/workflow-server/src/workflow-diagram-module.ts b/examples/workflow-server/src/workflow-diagram-module.ts index ab44e4f..82e94b0 100644 --- a/examples/workflow-server/src/workflow-diagram-module.ts +++ b/examples/workflow-server/src/workflow-diagram-module.ts @@ -14,25 +14,23 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { - ClassMultiBinding, CommandPaletteActionProvider, ContextMenuItemProvider, - DefaultToolPaletteItemProvider, DiagramConfiguration, GLSPServer, GModelDiagramModule, InstanceMultiBinding, - JsonRpcGLSPServer, LabelEditValidator, ModelValidator, + MultiBinding, NavigationTargetProvider, NavigationTargetResolver, OperationHandlerConstructor, PopupModelFactory, - ServerModule, - ToolPaletteItemProvider + ServerModule } from '@eclipse-glsp/server-node'; -import { injectable, interfaces } from 'inversify'; +import { BindingTarget } from '@eclipse-glsp/server-node/lib/di/binding-target'; +import { injectable } from 'inversify'; import { CreateAutomatedTaskHandler } from './handler/create-automated-task-handler'; import { CreateCategoryHandler } from './handler/create-category-handler'; import { CreateDecisionNodeHandler } from './handler/create-decision-node-handler'; @@ -56,11 +54,8 @@ import { WorkflowPopupFactory } from './workflow-popup-factory'; @injectable() export class WorkflowServerModule extends ServerModule { - override configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { - super.configure(bind, unbind, isBound, rebind); - bind(WorkflowGLSPServer).toSelf().inSingletonScope(); - rebind(GLSPServer).toService(WorkflowGLSPServer); - rebind(JsonRpcGLSPServer).toService(WorkflowGLSPServer); + protected override bindGLSPServer(): BindingTarget { + return WorkflowGLSPServer; } } @@ -70,23 +65,6 @@ export class WorkflowDiagramModule extends GModelDiagramModule { return 'workflow-diagram'; } - protected override configure( - bind: interfaces.Bind, - unbind: interfaces.Unbind, - isBound: interfaces.IsBound, - rebind: interfaces.Rebind - ): void { - super.configure(bind, unbind, isBound, rebind); - bind(DiagramConfiguration).to(WorkflowDiagramConfiguration).inSingletonScope(); - bind(NavigationTargetResolver).to(WorkflowNavigationTargetResolver).inSingletonScope(); - bind(ContextMenuItemProvider).to(WorkflowContextMenuItemProvider).inSingletonScope(); - bind(CommandPaletteActionProvider).to(WorkflowCommandPaletteActionProvider).inSingletonScope(); - bind(LabelEditValidator).to(WorkflowLabelEditValidator).inSingletonScope(); - bind(PopupModelFactory).to(WorkflowPopupFactory).inSingletonScope(); - bind(ModelValidator).to(WorkflowModelValidator).inSingletonScope(); - bind(ToolPaletteItemProvider).to(DefaultToolPaletteItemProvider).inSingletonScope(); - } - protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(CreateAutomatedTaskHandler); @@ -100,7 +78,35 @@ export class WorkflowDiagramModule extends GModelDiagramModule { binding.add(CreateCategoryHandler); } - protected override configureNavigationTargetProviders(binding: ClassMultiBinding): void { + protected bindDiagramConfiguration(): BindingTarget { + return WorkflowDiagramConfiguration; + } + + protected override bindNavigationTargetResolver(): BindingTarget | undefined { + return WorkflowNavigationTargetResolver; + } + + protected override bindContextMenuItemProvider(): BindingTarget | undefined { + return WorkflowContextMenuItemProvider; + } + + protected override bindCommandPaletteActionProvider(): BindingTarget | undefined { + return WorkflowCommandPaletteActionProvider; + } + + protected override bindLabelEditValidator(): BindingTarget | undefined { + return WorkflowLabelEditValidator; + } + + protected override bindPopupModelFactory(): BindingTarget | undefined { + return WorkflowPopupFactory; + } + + protected override bindModelValidator(): BindingTarget | undefined { + return WorkflowModelValidator; + } + + protected override configureNavigationTargetProviders(binding: MultiBinding): void { super.configureNavigationTargetProviders(binding); binding.add(NextNodeNavigationTargetProvider); binding.add(PreviousNodeNavigationTargetProvider); diff --git a/packages/server-node/src/actions/action-dispatcher.spec.ts b/packages/server-node/src/actions/action-dispatcher.spec.ts index bf1e5bc..40ef79a 100644 --- a/packages/server-node/src/actions/action-dispatcher.spec.ts +++ b/packages/server-node/src/actions/action-dispatcher.spec.ts @@ -24,6 +24,7 @@ import { Logger } from '../utils/logger'; import { DefaultActionDispatcher } from './action-dispatcher'; import { ActionHandler } from './action-handler'; import { ActionHandlerRegistry } from './action-handler-registry'; +import assert = require('assert'); function waitSync(timeInMillis: number): void { const start = Date.now(); @@ -61,7 +62,7 @@ describe('test DefaultActionDispatcher', () => { describe('test with one-way actions (no response actions)', () => { it('dispatch- unhandled action', async () => { - expect(actionDispatcher.dispatch({ kind: 'unhandled' })).to.throw; + assert.rejects(actionDispatcher.dispatch({ kind: 'unhandled' })); }); it('dispatch - one action', async () => { diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/base-impl/gmodel-diagram-module.ts index 4c82667..ddaede2 100644 --- a/packages/server-node/src/base-impl/gmodel-diagram-module.ts +++ b/packages/server-node/src/base-impl/gmodel-diagram-module.ts @@ -17,7 +17,7 @@ import { injectable, interfaces } from 'inversify'; import { ActionHandlerConstructor } from '../actions/action-handler'; import { SaveModelActionHandler } from '../actions/save-model-action-handler'; import { ChangeBoundsOperationHandler } from '../base-impl/change-bounds-operation-handler'; -import { CommandStack, DefaultCommandStack } from '../command/command-stack'; +import { applyBindingTarget, BindingTarget } from '../di/binding-target'; import { DiagramModule } from '../di/diagram-module'; import { InstanceMultiBinding } from '../di/multi-binding'; import { RequestClipboardDataActionHandler } from '../features/clipboard/request-clipboard-data-action-handler'; @@ -63,16 +63,25 @@ export abstract class GModelDiagramModule extends DiagramModule { rebind: interfaces.Rebind ): void { super.configure(bind, unbind, isBound, rebind); + const context = this.context; + applyBindingTarget(context, GModelIndex, this.bindGModelIndex()).inSingletonScope(); + } - bind(SourceModelStorage).to(GModelStorage); - bind(CommandStack).to(DefaultCommandStack).inSingletonScope(); + protected override bindSourceModelStorage(): BindingTarget { + return GModelStorage; + } - // bind GModelState - bind(GModelState).toSelf().inSingletonScope(); - bind(ModelState).toService(GModelState); + protected override bindGModelFactory(): BindingTarget { + return GModelFactoryNullImpl; + } - bind(GModelFactory).to(GModelFactoryNullImpl).inSingletonScope(); - bind(GModelIndex).toSelf().inSingletonScope(); + protected override bindModelState(): BindingTarget { + applyBindingTarget(this.context, GModelState, this.bindGModelState()).inSingletonScope(); + return { service: GModelState }; + } + + protected bindGModelState(): BindingTarget { + return GModelState; } protected override configureActionHandlers(binding: InstanceMultiBinding): void { @@ -82,6 +91,10 @@ export abstract class GModelDiagramModule extends DiagramModule { binding.add(RequestClipboardDataActionHandler); } + protected bindGModelIndex(): BindingTarget { + return GModelIndex; + } + protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(ApplyLabelEditOperationHandler); diff --git a/packages/server-node/src/di/binding-target.spec.ts b/packages/server-node/src/di/binding-target.spec.ts new file mode 100644 index 0000000..52afe53 --- /dev/null +++ b/packages/server-node/src/di/binding-target.spec.ts @@ -0,0 +1,142 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { expect } from 'chai'; +import { Container, ContainerModule, interfaces } from 'inversify'; +import * as sinon from 'sinon'; +import { applyBindingTarget } from './binding-target'; +// Simple no op classes to construct inversify binding syntaxes. +class Target {} + +class SubTarget extends Target {} + +describe('BindingTarget', () => { + describe('bindTarget()', () => { + // Setup nested spies for the fluent inversify binding API + let context: { + bind: sinon.SinonStub<[serviceIdentifier: interfaces.ServiceIdentifier], interfaces.BindingToSyntax>; + isBound: sinon.SinonStub<[serviceIdentifier: interfaces.ServiceIdentifier], boolean>; + }; + let toSyntax: sinon.SinonStubbedInstance>; + let whenOnSyntax: sinon.SinonStubbedInstance>; + + const setupStubs = (): { + context: { + bind: sinon.SinonStub<[serviceIdentifier: interfaces.ServiceIdentifier], interfaces.BindingToSyntax>; + isBound: sinon.SinonStub<[serviceIdentifier: interfaces.ServiceIdentifier], boolean>; + }; + toSyntax: sinon.SinonStubbedInstance>; + inWhenOnSyntax: sinon.SinonStubbedInstance>; + whenOnSyntax: sinon.SinonStubbedInstance>; + } => { + const container = new Container(); + const bind = sinon.stub<[serviceIdentifier: interfaces.ServiceIdentifier], interfaces.BindingToSyntax>(); + const isBound = sinon.stub<[serviceIdentifier: interfaces.ServiceIdentifier], boolean>(); + + let toSyntax: sinon.SinonStubbedInstance> = {} as any; + let inWhenOnSyntax: sinon.SinonStubbedInstance> = {} as any; + container.load( + new ContainerModule(_bind => { + const toStub = _bind('StubMe'); + inWhenOnSyntax = sinon.stub(toStub.to(Target)); + whenOnSyntax = sinon.stub(toStub.toConstantValue('')); + toSyntax = sinon.stub(toStub); + + bind.returns(toSyntax); + toSyntax.to.returns(inWhenOnSyntax); + toSyntax.toSelf.returns(inWhenOnSyntax); + toSyntax.toDynamicValue.returns(inWhenOnSyntax); + toSyntax.toService.returns(); + }) + ); + + return { + context: { bind, isBound }, + toSyntax, + inWhenOnSyntax, + whenOnSyntax + }; + }; + + beforeEach(() => { + const stubs = setupStubs(); + context = stubs.context; + toSyntax = stubs.toSyntax; + whenOnSyntax = stubs.whenOnSyntax; + }); + + describe('Bind to constructor', () => { + it('Should bind the service identifier `to` the given target with no scope', () => { + applyBindingTarget(context, Target, SubTarget); + expect(toSyntax.to.calledOnceWith(SubTarget)).to.be.true; + }); + + it('Should bind the service identifier `toSelf` with no scope', () => { + applyBindingTarget(context, Target, Target); + expect(toSyntax.toSelf.calledOnce).to.be.true; + }); + }); + + describe('Bind to service', () => { + it('Should bind the service identifier `service` using the given target service with no scope', () => { + context.isBound.returns(true); + applyBindingTarget(context, Target, { service: SubTarget }); + expect(toSyntax.toService.calledOnceWith(SubTarget)).to.be.true; + }); + it('Should throw an error because the given target service is not bound', () => { + context.isBound.returns(false); + expect(() => applyBindingTarget(context, Target, { service: SubTarget })).to.throw(); + }); + it('The return syntax should be no op and invocation of a syntax function should throw an error', () => { + context.isBound.returns(true); + const syntax = applyBindingTarget(context, Target, { service: SubTarget }); + expect(() => { + syntax.inSingletonScope(); + }).to.throw( + `${Target.toString()} has been bound to 'service'.` + + "Using 'in','when' or 'on' bindings after" + + "a 'toService' binding is not possible." + ); + }); + }); + + describe('Bind to constant value', () => { + it('Should bind the service identifier `toConstantValue` using the given target with no scope', () => { + const subTarget = new SubTarget(); + applyBindingTarget(context, Target, { constantValue: subTarget }); + expect(toSyntax.toConstantValue.calledOnceWith(subTarget)).to.be.true; + }); + it("The return syntax's in functions should be no op and invocation should log a warning", () => { + const spy = sinon.spy(console, 'warn'); + const subTarget = new SubTarget(); + const syntax = applyBindingTarget(context, Target, { constantValue: subTarget }); + syntax.inSingletonScope(); + expect( + spy.calledWith( + `${Target.toString()} has been bound to 'constantValue'. Binding in Singleton scope has no effect.` + + 'Constant value bindings are effectively Singleton bindings.' + ) + ).to.be.true; + }); + }); + + describe('Bind to dynamic value', () => { + it('Should bind the service identifier `toDynamicValue` using the given factory function with no scope', () => { + applyBindingTarget(context, Target, { dynamicValue: context => new SubTarget() }); + expect(toSyntax.toDynamicValue.calledOnce); + }); + }); + }); +}); diff --git a/packages/server-node/src/di/binding-target.ts b/packages/server-node/src/di/binding-target.ts new file mode 100644 index 0000000..b2f30b4 --- /dev/null +++ b/packages/server-node/src/di/binding-target.ts @@ -0,0 +1,165 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { AnyObject, Constructor } from '@eclipse-glsp/protocol'; +import { interfaces } from 'inversify'; +/** + * Collection of utility types and functions to enable flexible service binding with dedicated + * binding methods in the GLSP DI modules + */ + +/** + * Binds the given service identifier to the given {@link BindingTarget}. If the service identifier is + * a {@link Constructor} and the target is the same constructor the service identifier will be bound to itself. + * @param bind The inversify bind function (typically provided from a GLSP DI module) + * @param serviceIdentifier The service identifier that should be bound. + * @param target The binding target. + * @returns The corresponding {@link interfaces.BindingInWhenOnSyntax}. + */ +export function applyBindingTarget( + context: { bind: interfaces.Bind; isBound: interfaces.IsBound }, + serviceIdentifier: interfaces.ServiceIdentifier, + target: BindingTarget +): interfaces.BindingInWhenOnSyntax { + if (isConstructor(target)) { + // If service identifier and target are the same constructor => self binding + return serviceIdentifier === target // + ? context.bind(serviceIdentifier).toSelf() + : context.bind(serviceIdentifier).to(target); + } else if (ServiceTarget.is(target)) { + if (!context.isBound(target.service)) { + throw new Error(`The target service ${target.service.toString()} is not bound!. Cannot apply target binding`); + } + context.bind(serviceIdentifier).toService(target.service); + return NoOPSyntax.serviceSyntax(serviceIdentifier); + } else if (ConstantValueTarget.is(target)) { + const whenOnSyntax = context.bind(serviceIdentifier).toConstantValue(target.constantValue); + return NoOPSyntax.constantValueSyntax(serviceIdentifier, whenOnSyntax); + } else { + return context.bind(serviceIdentifier).toDynamicValue(_context => target.dynamicValue(_context)); + } +} + +export function applyOptionalBindingTarget( + context: { bind: interfaces.Bind; isBound: interfaces.IsBound }, + serviceIdentifier: interfaces.ServiceIdentifier, + target?: BindingTarget +): interfaces.BindingInWhenOnSyntax | undefined { + if (target) { + return applyBindingTarget(context, serviceIdentifier, target); + } + return undefined; +} + +/** + * The different types of binding targets that can be returned by a dedicated binding method. + */ +export type BindingTarget = Constructor | DynamicValueTarget | ConstantValueTarget | ServiceTarget; + +/** + * Binding target for service identifiers that should be bound `toConstantValue`. + */ +export interface ConstantValueTarget { + constantValue: T; +} + +export namespace ConstantValueTarget { + export function is(object: any): object is ConstantValueTarget { + return AnyObject.is(object) && 'constantValue' in object; + } +} + +/** + * Binding target for service identifiers that should be bound `toService`. + */ +export interface ServiceTarget { + service: interfaces.ServiceIdentifier; +} + +export namespace ServiceTarget { + export function is(object: any): object is ServiceTarget { + return AnyObject.is(object) && 'service' in object; + } +} + +/** + * Binding target for service identifiers that should be bound `toDynamicValue`. + */ +export interface DynamicValueTarget { + dynamicValue(context: interfaces.Context): T; +} + +/** + * No-op binding syntax definitions for `constantValue` and `toService` bindings. + * Using this no-op syntaxes allows the {@link applyBindingTarget} function to return a {@interfaces.BindingInWhenOnSyntax} + * independently of the actual {@link BindingTarget}. + */ +namespace NoOPSyntax { + export function constantValueSyntax( + serviceIdentifier: interfaces.ServiceIdentifier, + syntax: interfaces.BindingWhenOnSyntax + ): interfaces.BindingInWhenOnSyntax { + const noOpReturn = (scope: string): interfaces.BindingWhenOnSyntax => { + console.warn( + `${serviceIdentifier.toString()} has been bound to 'constantValue'. Binding in ${scope} scope has no effect.` + + 'Constant value bindings are effectively Singleton bindings.' + ); + return syntax; + }; + return { + ...syntax, + inSingletonScope: () => noOpReturn('Singleton'), + inRequestScope: () => noOpReturn('Request'), + inTransientScope: () => noOpReturn('Transient') + }; + } + + export function serviceSyntax(serviceIdentifier: interfaces.ServiceIdentifier): interfaces.BindingInWhenOnSyntax { + const noOpReturn = (): interfaces.BindingInWhenOnSyntax => { + const errorMsg = + `${serviceIdentifier.toString()} has been bound to 'service'.` + + "Using 'in','when' or 'on' bindings after" + + "a 'toService' binding is not possible."; + const error = new Error(errorMsg); + error.name = 'NoOpInvocation'; + throw error; + }; + return { + onActivation: noOpReturn, + when: noOpReturn, + whenAnyAncestorIs: noOpReturn, + whenAnyAncestorMatches: noOpReturn, + whenAnyAncestorNamed: noOpReturn, + whenAnyAncestorTagged: noOpReturn, + whenInjectedInto: noOpReturn, + whenNoAncestorIs: noOpReturn, + whenNoAncestorMatches: noOpReturn, + whenNoAncestorNamed: noOpReturn, + whenNoAncestorTagged: noOpReturn, + whenParentNamed: noOpReturn, + whenParentTagged: noOpReturn, + whenTargetIsDefault: noOpReturn, + whenTargetNamed: noOpReturn, + whenTargetTagged: noOpReturn, + inRequestScope: noOpReturn, + inSingletonScope: noOpReturn, + inTransientScope: noOpReturn + }; + } +} + +export function isConstructor(object: any): object is Constructor { + return typeof object === 'function' && !!object.prototype && !!(object.prototype as any).constructor; +} diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index 39d372f..949447c 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -13,8 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -/* eslint-disable no-restricted-imports */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { CenterAction, DeleteMarkersAction, @@ -28,6 +26,7 @@ import { SelectAllAction, ServerMessageAction, ServerStatusAction, + SetBoundsAction, SetClipboardDataAction, SetContextActions, SetDirtyStateAction, @@ -39,6 +38,7 @@ import { SetPopupModelAction, SetResolvedNavigationTargetAction, SetTypeHintsAction, + SetViewportAction, TriggerEdgeCreationAction, TriggerNodeCreationAction, UpdateModelAction @@ -48,35 +48,48 @@ import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dis import { ActionHandlerConstructor, ActionHandlerFactory } from '../actions/action-handler'; import { ActionHandlerRegistry, ActionHandlerRegistryInitializer } from '../actions/action-handler-registry'; import { ClientActionHandler } from '../actions/client-action-handler'; +import { DiagramConfiguration } from '../diagram/diagram-configuration'; import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler'; +import { CommandPaletteActionProvider } from '../features/contextactions/command-palette-action-provider'; import { ContextActionsProvider } from '../features/contextactions/context-actions-provider'; import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry'; +import { ContextMenuItemProvider } from '../features/contextactions/context-menu-item-provider'; import { RequestContextActionsHandler } from '../features/contextactions/request-context-actions-handler'; +import { DefaultToolPaletteItemProvider, ToolPaletteItemProvider } from '../features/contextactions/tool-palette-item-provider'; import { ContextEditValidator } from '../features/directediting/context-edit-validator'; import { ContextEditValidatorRegistry, DefaultContextEditValidatorRegistry } from '../features/directediting/context-edit-validator-registry'; +import { LabelEditValidator } from '../features/directediting/label-edit-validator'; import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler'; +import { LayoutEngine } from '../features/layout/layout-engine'; +import { GModelFactory } from '../features/model/gmodel-factory'; import { DefaultGModelSerializer, GModelSerializer } from '../features/model/gmodel-serializer'; +import { ModelState } from '../features/model/model-state'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; import { RequestModelActionHandler } from '../features/model/request-model-action-handler'; +import { SourceModelStorage } from '../features/model/source-model-storage'; import { NavigationTargetProvider } from '../features/navigation/navigation-target-provider'; import { DefaultNavigationTargetProviderRegistry, NavigationTargetProviderRegistry } from '../features/navigation/navigation-target-provider-registry'; +import { NavigationTargetResolver } from '../features/navigation/navigation-target-resolver'; import { RequestNavigationTargetsActionHandler } from '../features/navigation/request-navigation-targets-action-handler'; import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler'; +import { PopupModelFactory } from '../features/popup/popup-model-factory'; import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler'; +import { ModelValidator } from '../features/validation/model-validator'; import { RequestMarkersHandler } from '../features/validation/request-markers-handler'; import { CompoundOperationHandler } from '../operations/compound-operation-handler'; import { OperationActionHandler } from '../operations/operation-action-handler'; import { OperationHandlerConstructor, OperationHandlerFactory } from '../operations/operation-handler'; import { OperationHandlerRegistry, OperationHandlerRegistryInitializer } from '../operations/operation-handler-registry'; import { ClientSessionInitializer } from '../session/client-session-initializer'; +import { applyBindingTarget, applyOptionalBindingTarget, BindingTarget } from './binding-target'; import { GLSPModule } from './glsp-module'; -import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; +import { InstanceMultiBinding, MultiBinding } from './multi-binding'; import { ClientActionKinds, ClientId, @@ -91,96 +104,113 @@ import { * The diagram module is the central configuration artifact for configuring a client session specific injector. For each * session that is initialized by a {@link GLSPServer} a new client session injector is created. The diagram module * provides the base bindings necessary to provide diagram implementation (i.e. diagram language). In addition to the - * diagram specific bindings, session specific bindings like the {@link GModelState} are configured + * diagram specific bindings, session specific bindings like the {@link ModelState} are configured * * Client session injectors are child injectors of a server injector and therefore inherit the bindings from * {@link ServerModule}. * - * The following bindings are provided by this base configuration: - * - {@link ClientId} to default + * The following bindings are provided: + * - {@link DiagramType} + * - {@link ClientId} + * - {@link DiagramConfiguration} * - {@link GModelSerializer} + * - {@link ModelState} + * - {@link SourceModelStorage} + * - {@link GModelFactory} + * - {@link ModelSubmissionHandler} + * - {@link ModelValidator} as optional binding + * - {@link LabelEditValidator} as optional binding + * - {@link ToolPaletteItemProvider}as optional binding + * - {@link CommandPaletteActionProvider}as optional binding + * - {@link ContextMenuItemProvider} as optional binding + * - {@link ContextActionsProviders} as {@link ClassMultiBinding} (empty) + * - {@link ContextActionsProviderRegistry} * - {@link ActionDispatcher} + * - {@link ClientActionKinds} as {@link InstanceMultiBinding} + * - {@link ActionHandler} as {@link InstanceMultiBinding} + * - {@link ActionHandlerFactory} * - {@link ActionHandlerRegistry} + * - {@link OperationHandler} as {@link InstanceMultiBinding} + * - {@link OperationHandlerFactory} * - {@link OperationHandlerRegistry} - * - {@link ModelSubmissionHandler} + * - {@link Operations} + * - {@link NavigationTargetResolver} as optional binding + * {@link NavigationTargetProvider} as {@link ClassMultiBinding} (empty) * - {@link NavigationTargetProviderRegistry} - * - {@link ContextActionsProviderRegistry} * - {@link ContextEditValidatorRegistry} - * - {@link OperationHandler} as {@link InstanceMultiBinding} - * - {@link ActionHandler} as {@link InstanceMultiBinding} - * - {@link ClientSessionInitializer} as {@link ClassMultiBinding} - * - {@link ClientActionKinds} as {@link InstanceMultiBinding} - * - {@link NavigationTargetProviders} as {@link ClassMultiBinding} (empty) - * - {@link ContextActionsProviders} as {@link ClassMultiBinding} (empty) * - {@link ContextEditValidators} as {@link ClassMultiBinding} (empty) - * - * The following bindings are required in either a subclass or an additional module: - * - {@link DiagramType} via the subclasses diagramType property - * - {@link DiagramConfiguration} - * - {@link SourceModelStorage} - * - {@link GModelState} - * - {@link GModelFactory} - * - {@link CommandStack} - * - * The following bindings can be optionally added via a module: - * - {@link ModelValidator} - * - {@link LabelEditValidator} - * - {@link CommandPaletteActionProvider} - * - {@link ContextMenuItemProvider} - * - {@link PopupModelFactory} - * - {@link ToolPaletteItemProvider} - * - {@link NavigationTargetResolver} + * - {@link ClientSessionInitializer} as {@link ClassMultiBinding} + * - {@link PopupModelFactory} as optional binding + * - {@link LayoutEngine} as optional binding */ export abstract class DiagramModule extends GLSPModule { static readonly FALLBACK_CLIENT_ID = 'FallbackClientId'; abstract readonly diagramType: string; protected configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { - bind(DiagramType).toConstantValue(this.diagramType); - bind(ClientId).toConstantValue(DiagramModule.FALLBACK_CLIENT_ID); - bind(GModelSerializer).to(DefaultGModelSerializer).inSingletonScope(); - bind(ActionDispatcher).to(DefaultActionDispatcher).inSingletonScope(); - bind(ActionHandlerRegistry).toSelf().inSingletonScope(); - bind(OperationHandlerRegistry).toSelf().inSingletonScope(); - bind(ModelSubmissionHandler).toSelf().inSingletonScope(); - bind(NavigationTargetProviderRegistry).to(DefaultNavigationTargetProviderRegistry).inSingletonScope(); - bind(ContextActionsProviderRegistry).toSelf().inSingletonScope(); - bind(ContextEditValidatorRegistry).to(DefaultContextEditValidatorRegistry).inSingletonScope(); - - // factory bindings - bind(ActionHandlerFactory).toDynamicValue(ctx => constructor => ctx.container.resolve(constructor)); - bind(OperationHandlerFactory).toDynamicValue(ctx => constructor => ctx.container.resolve(constructor)); - - // bind Kinds - bind(Operations).toDynamicValue(context => - context.container - .get(OperationHandlerConstructor) - .map(constructor => new constructor().operationType) - ); + const context = { bind, isBound }; + // Configurations + applyBindingTarget(context, DiagramType, this.bindDiagramType()); + applyBindingTarget(context, ClientId, this.bindClientId()); + applyBindingTarget(context, DiagramConfiguration, this.bindDiagramConfiguration()).inSingletonScope(); - // multi-bindings - this.configureMultiBinding(new ClassMultiBinding(ClientSessionInitializer), binding => - this.configureClientSessionInitializers(binding) + // Model-related bindings + applyBindingTarget(context, GModelSerializer, this.bindGModelSerializer()).inSingletonScope(); + applyBindingTarget(context, ModelState, this.bindModelState()); + applyBindingTarget(context, SourceModelStorage, this.bindSourceModelStorage()).inSingletonScope(); + applyBindingTarget(context, GModelFactory, this.bindGModelFactory()); + applyBindingTarget(context, ModelSubmissionHandler, this.bindModelSubmissionHandler()).inSingletonScope(); + + // Model Validation + applyOptionalBindingTarget(context, ModelValidator, this.bindModelValidator()); + applyOptionalBindingTarget(context, LabelEditValidator, this.bindLabelEditValidator()); + + // Context action providers + applyOptionalBindingTarget(context, ToolPaletteItemProvider, this.bindToolPaletteItemProvider()); + applyOptionalBindingTarget(context, CommandPaletteActionProvider, this.bindCommandPaletteActionProvider()); + applyOptionalBindingTarget(context, ContextMenuItemProvider, this.bindContextMenuItemProvider()); + this.configureMultiBinding(new MultiBinding(ContextActionsProviders), binding => + this.configureContextActionProviders(binding) ); + applyBindingTarget(context, ContextActionsProviderRegistry, this.bindContextActionsProviderRegistry()).inSingletonScope(); + + // Action & operation related bindings + applyBindingTarget(context, ActionDispatcher, this.bindActionDispatcher()).inSingletonScope(); + this.configureMultiBinding(new InstanceMultiBinding(ClientActionKinds), binding => this.configureClientActions(binding)); this.configureMultiBinding(new InstanceMultiBinding(ActionHandlerConstructor), binding => this.configureActionHandlers(binding) ); + applyBindingTarget(context, ActionHandlerFactory, this.bindActionHandlerFactory()); + applyBindingTarget(context, ActionHandlerRegistry, this.bindActionHandlerRegistry()).inSingletonScope(); this.configureMultiBinding(new InstanceMultiBinding(OperationHandlerConstructor), binding => this.configureOperationHandlers(binding) ); - this.configureMultiBinding(new InstanceMultiBinding(ClientActionKinds), binding => this.configureClientActions(binding)); - this.configureMultiBinding(new ClassMultiBinding(NavigationTargetProviders), binding => + applyBindingTarget(context, OperationHandlerRegistry, this.bindOperationHandlerRegistry()).inSingletonScope(); + applyBindingTarget(context, OperationHandlerFactory, this.bindOperationHandlerFactory()); + applyBindingTarget(context, Operations, this.bindOperations()).inSingletonScope(); + + // Navigation + applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver()); + this.configureMultiBinding(new MultiBinding(NavigationTargetProviders), binding => this.configureNavigationTargetProviders(binding) ); - this.configureMultiBinding(new ClassMultiBinding(ContextActionsProviders), binding => - this.configureContextActionProviders(binding) - ); - this.configureMultiBinding(new ClassMultiBinding(ContextEditValidators), binding => + applyBindingTarget(context, NavigationTargetProviderRegistry, this.bindNavigationTargetProviderRegistry()).inSingletonScope(); + + // Context edit + applyBindingTarget(context, ContextEditValidatorRegistry, this.bindContextEditValidatorRegistry()).inSingletonScope(); + this.configureMultiBinding(new MultiBinding(ContextEditValidators), binding => this.configureContextEditValidators(binding) ); + + // Misc + this.configureMultiBinding(new MultiBinding(ClientSessionInitializer), binding => + this.configureClientSessionInitializers(binding) + ); + applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory()); + applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine?.()); } - configureClientSessionInitializers(binding: ClassMultiBinding): void { + configureClientSessionInitializers(binding: MultiBinding): void { binding.add(ActionHandlerRegistryInitializer); binding.add(OperationHandlerRegistryInitializer); } @@ -198,19 +228,72 @@ export abstract class DiagramModule extends GLSPModule { binding.add(ResolveNavigationTargetsActionHandler); } + protected bindDiagramType(): BindingTarget { + return { constantValue: this.diagramType }; + } + + protected bindClientId(): BindingTarget { + return { constantValue: DiagramModule.FALLBACK_CLIENT_ID }; + } + + protected bindGModelSerializer(): BindingTarget { + return DefaultGModelSerializer; + } + + protected bindActionDispatcher(): BindingTarget { + return DefaultActionDispatcher; + } + + protected bindActionHandlerRegistry(): BindingTarget { + return ActionHandlerRegistry; + } + + protected bindOperationHandlerRegistry(): BindingTarget { + return OperationHandlerRegistry; + } + + protected bindModelSubmissionHandler(): BindingTarget { + return ModelSubmissionHandler; + } + + protected bindNavigationTargetProviderRegistry(): BindingTarget { + return DefaultNavigationTargetProviderRegistry; + } + + protected bindContextEditValidatorRegistry(): BindingTarget { + return DefaultContextEditValidatorRegistry; + } + + protected bindActionHandlerFactory(): BindingTarget { + return { dynamicValue: ctx => constructor => ctx.container.resolve(constructor) }; + } + + protected bindOperationHandlerFactory(): BindingTarget { + return { dynamicValue: ctx => constructor => ctx.container.resolve(constructor) }; + } + + protected bindOperations(): BindingTarget { + return { + dynamicValue: context => + context.container + .get(OperationHandlerConstructor) + .map(constructor => new constructor().operationType) + }; + } + protected configureOperationHandlers(binding: InstanceMultiBinding): void { binding.add(CompoundOperationHandler); } - protected configureContextActionProviders(binding: ClassMultiBinding): void { + protected configureContextActionProviders(binding: MultiBinding): void { // empty as default } - protected configureContextEditValidators(binding: ClassMultiBinding): void { + protected configureContextEditValidators(binding: MultiBinding): void { // empty as default } - protected configureNavigationTargetProviders(binding: ClassMultiBinding): void { + protected configureNavigationTargetProviders(binding: MultiBinding): void { // empty as default } @@ -226,7 +309,7 @@ export abstract class DiagramModule extends GLSPModule { binding.add(SelectAction.KIND); binding.add(SelectAllAction.KIND); binding.add(ServerMessageAction.KIND); - // binding.add(SetBoundsAction.KIND); TODO: Add missing action to protocol + binding.add(SetBoundsAction.KIND); binding.add(SetClipboardDataAction.KIND); binding.add(SetContextActions.KIND); binding.add(SetDirtyStateAction.KIND); @@ -238,10 +321,54 @@ export abstract class DiagramModule extends GLSPModule { binding.add(SetPopupModelAction.KIND); binding.add(SetResolvedNavigationTargetAction.KIND); binding.add(SetTypeHintsAction.KIND); - // binding.add(SetViewportAction.KIND); TODO: Add missing action to protocol + binding.add(SetViewportAction.KIND); binding.add(ServerStatusAction.KIND); binding.add(TriggerNodeCreationAction.KIND); binding.add(TriggerEdgeCreationAction.KIND); binding.add(UpdateModelAction.KIND); } + + protected bindContextActionsProviderRegistry(): BindingTarget { + return ContextActionsProviderRegistry; + } + + // Required abstract bindings + + protected abstract bindSourceModelStorage(): BindingTarget; + + protected abstract bindModelState(): BindingTarget; + + protected abstract bindDiagramConfiguration(): BindingTarget; + + protected abstract bindGModelFactory(): BindingTarget; + + // Optional bindings + + protected bindModelValidator(): BindingTarget | undefined { + return undefined; + } + protected bindLabelEditValidator(): BindingTarget | undefined { + return undefined; + } + + protected bindToolPaletteItemProvider(): BindingTarget | undefined { + return DefaultToolPaletteItemProvider; + } + protected bindCommandPaletteActionProvider(): BindingTarget | undefined { + return undefined; + } + protected bindContextMenuItemProvider(): BindingTarget | undefined { + return undefined; + } + + protected bindNavigationTargetResolver(): BindingTarget | undefined { + return undefined; + } + + protected bindPopupModelFactory(): BindingTarget | undefined { + return undefined; + } + protected bindLayoutEngine(): BindingTarget | undefined { + return undefined; + } } diff --git a/packages/server-node/src/di/glsp-module.ts b/packages/server-node/src/di/glsp-module.ts index f11a849..e1cc26d 100644 --- a/packages/server-node/src/di/glsp-module.ts +++ b/packages/server-node/src/di/glsp-module.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ContainerModule, injectable, interfaces } from 'inversify'; -import { MultiBinding } from './multi-binding'; +import { AbstractMultiBinding } from './multi-binding'; /** * A wrapper interface to get access to the binding related functions @@ -59,7 +59,7 @@ export abstract class GLSPModule extends ContainerModule { * @param binding The multi binding configuration object * @param configurator The consumer that should be used to configure the given {@link MultiBinding} */ - protected configureMultiBinding(binding: MultiBinding, configurator: (binding: MultiBinding) => void): void { + protected configureMultiBinding(binding: AbstractMultiBinding, configurator: (binding: AbstractMultiBinding) => void): void { configurator(binding); binding.applyBindings(this.context); } diff --git a/packages/server-node/src/di/multi-binding.ts b/packages/server-node/src/di/multi-binding.ts index 8c25a5b..58969de 100644 --- a/packages/server-node/src/di/multi-binding.ts +++ b/packages/server-node/src/di/multi-binding.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { distinctAdd, flatPush, MaybeArray, remove } from '@eclipse-glsp/protocol'; -import { interfaces } from 'inversify'; +import { applyBindingTarget, BindingTarget } from './binding-target'; import { ModuleContext } from './glsp-module'; /** @@ -22,9 +22,9 @@ import { ModuleContext } from './glsp-module'; * Instead of directly binding to the service identifier a new multi binding object can be created. This object * should then be passed to a overridable configuration-function (i.e. configure(binding:V)=>void). This gives subclasses of the * `GLSPModule` the chance to manipulate the binding configuration using the provided manipulation methods (e.g. add,remove, rebind). - * Once the configuration is finished the binding can be finalized using the {@link MultiBinding.applyBindings} method. + * Once the configuration is finished the binding can be finalized using the {@link AbstractMultiBinding.applyBindings} method. */ -export abstract class MultiBinding { +export abstract class AbstractMultiBinding { protected bindings: T[] = []; constructor(readonly identifier: string | symbol) {} @@ -66,24 +66,23 @@ export abstract class MultiBinding { } /** - * Implementation of {@link MultiBinding} for multi-injected values that should be bound to classes using the - * `bind(serviceIdentifier).to(MyCustomBinding) syntax. + * Implementation of {@link AbstractMultiBinding} for multi-injected values that using an arbitrary {@link BindingTarget}. * - * @typeparam T the base type of the classes. + * @typeparam T the base type of {@link BindingTarget}. */ -export class ClassMultiBinding extends MultiBinding> { +export class MultiBinding extends AbstractMultiBinding> { applyBindings(context: ModuleContext): void { - this.bindings.forEach(binding => context.bind(this.identifier).to(binding)); + this.bindings.forEach(binding => applyBindingTarget(context, this.identifier, binding)); } } /** - * Implementation of {@link MultiBinding} for multi-injected values that should be bound to concrete instance using the + * Implementation of {@link AbstractMultiBinding} for multi-injected values that should be bound to concrete instance using the * `bind(serviceIdentifier).toConstantValue(MyCustomBinding) syntax. * * * @typeparam T the type of the instances */ -export class InstanceMultiBinding extends MultiBinding { +export class InstanceMultiBinding extends AbstractMultiBinding { applyBindings(context: ModuleContext): void { context.bind(this.identifier).toConstantValue(this.bindings); } diff --git a/packages/server-node/src/di/multi-bindings.spec.ts b/packages/server-node/src/di/multi-bindings.spec.ts index d823ddd..c416ce1 100644 --- a/packages/server-node/src/di/multi-bindings.spec.ts +++ b/packages/server-node/src/di/multi-bindings.spec.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; -import { ClassMultiBinding, InstanceMultiBinding } from './multi-binding'; +import { InstanceMultiBinding, MultiBinding } from './multi-binding'; @injectable() class TestClass {} @@ -31,7 +31,7 @@ class TestClass3 extends TestClass {} describe('test implementations of MultiBinding', () => { describe('test basic functionaly (ClassMultiBinding) ', () => { - const binding = new ClassMultiBinding('TestClass'); + const binding = new MultiBinding('TestClass'); it('add - new binding', () => { binding.add(TestClass1); expect(binding.contains(TestClass1)).true; @@ -97,7 +97,7 @@ describe('test implementations of MultiBinding', () => { const testContainer = new Container(); const testModule = new ContainerModule((bind, unbind, isBound, rebind) => { const context = { bind, unbind, isBound, rebind }; - const binding = new ClassMultiBinding('TestClass'); + const binding = new MultiBinding('TestClass'); binding.addAll([TestClass1, TestClass2, TestClass3]); binding.applyBindings(context); }); diff --git a/packages/server-node/src/di/server-module.ts b/packages/server-node/src/di/server-module.ts index fd8e0fb..fd9b995 100644 --- a/packages/server-node/src/di/server-module.ts +++ b/packages/server-node/src/di/server-module.ts @@ -20,8 +20,10 @@ import { DefaultGLSPServer, GLSPServer, JsonRpcGLSPServer } from '../protocol/gl import { GLSPServerListener } from '../protocol/glsp-server-listener'; import { ClientSessionFactory, DefaultClientSessionFactory } from '../session/client-session-factory'; import { ClientSessionManager, DefaultClientSessionManager } from '../session/client-session-manager'; +import { applyBindingTarget, BindingTarget } from './binding-target'; import { DiagramModule } from './diagram-module'; import { GLSPModule } from './glsp-module'; +import { MultiBinding } from './multi-binding'; import { DiagramModules, InjectionContainer } from './service-identifiers'; /** @@ -68,18 +70,62 @@ export class ServerModule extends GLSPModule { } configure(bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind): void { - bind>(DiagramModules).toConstantValue(this.diagramModules); - bind(DefaultGLSPServer).toSelf().inSingletonScope(); - bind(GLSPServer).toService(DefaultGLSPServer); - bind(JsonRpcGLSPServer).toService(DefaultGLSPServer); - bind(ClientSessionFactory).to(DefaultClientSessionFactory).inSingletonScope(); - bind(DefaultClientSessionManager).toSelf().inSingletonScope(); - bind(ClientSessionManager).toService(DefaultClientSessionManager); - bind(GLSPServerListener).toService(DefaultClientSessionManager); - bind(GlobalActionProvider).to(DefaultGlobalActionProvider).inSingletonScope(); - bind(InjectionContainer).toDynamicValue(context => context.container); - bind(DefaultGLSPClientProxy).toSelf().inSingletonScope(); - bind(GLSPClientProxy).toService(DefaultGLSPClientProxy); - bind(JsonRpcGLSPClientProxy).toService(DefaultGLSPClientProxy); + const context = this.context; + applyBindingTarget(context, DiagramModules, this.bindDiagramModules()); + + applyBindingTarget(context, GLSPServer, this.bindGLSPServer()).inSingletonScope(); + applyBindingTarget(context, JsonRpcGLSPServer, this.bindJsonRpcGLSPServer()); + applyBindingTarget(context, ClientSessionFactory, this.bindClientSessionFactory()).inSingletonScope(); + applyBindingTarget(context, ClientSessionManager, this.bindClientSessionManager()).inSingletonScope(); + this.configureMultiBinding(new MultiBinding(GLSPServerListener), binding => + this.configureGLSPServerListeners(binding) + ); + + applyBindingTarget(context, GlobalActionProvider, this.bindGlobalActionProvider()).inSingletonScope(); + + applyBindingTarget(context, InjectionContainer, this.bindInjectionContainer()); + + applyBindingTarget(context, GLSPClientProxy, this.bindGLSPClientProxy()).inSingletonScope(); + applyBindingTarget(context, JsonRpcGLSPClientProxy, this.bindJsonRpcGLSPClientProxy()); + } + + protected bindDiagramModules(): BindingTarget> { + return { constantValue: this.diagramModules }; + } + + protected bindGLSPServer(): BindingTarget { + return DefaultGLSPServer; + } + + protected bindJsonRpcGLSPServer(): BindingTarget { + return { service: GLSPServer }; + } + + protected bindClientSessionFactory(): BindingTarget { + return DefaultClientSessionFactory; + } + + protected bindClientSessionManager(): BindingTarget { + return DefaultClientSessionManager; + } + + protected bindGlobalActionProvider(): BindingTarget { + return DefaultGlobalActionProvider; + } + + protected bindInjectionContainer(): BindingTarget { + return { dynamicValue: context => context.container }; + } + + protected bindGLSPClientProxy(): BindingTarget { + return DefaultGLSPClientProxy; + } + + protected bindJsonRpcGLSPClientProxy(): BindingTarget { + return { service: GLSPClientProxy }; + } + + protected configureGLSPServerListeners(binding: MultiBinding): void { + binding.add({ service: ClientSessionManager }); } } diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts index 1275510..5e77ed7 100644 --- a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts +++ b/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts @@ -20,6 +20,7 @@ import * as sinon from 'sinon'; import { GModelState } from '../../base-impl/gmodel-state'; import { GModelIndex } from '../model/gmodel-index'; import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; +import assert = require('assert'); describe('Test ApplyLabelEditOperationHandler', () => { const label = new GLabel(); @@ -30,7 +31,9 @@ describe('Test ApplyLabelEditOperationHandler', () => { Object.defineProperty(applyLabelEditOperationHandler, 'modelState', { value: modelState }); it('text is changed after ApplyLabelEditOperation', async () => { - expect(applyLabelEditOperationHandler.execute(ApplyLabelEditOperation.create({ labelId: 'myId', text: 'test' }))).to.not.throw; + assert.doesNotThrow(() => + applyLabelEditOperationHandler.execute(ApplyLabelEditOperation.create({ labelId: 'myId', text: 'test' })) + ); expect(label.text).to.be.equal('test'); }); }); diff --git a/packages/server-node/src/launch/cli-parser.spec.ts b/packages/server-node/src/launch/cli-parser.spec.ts index a72bdc1..3cdd8e3 100644 --- a/packages/server-node/src/launch/cli-parser.spec.ts +++ b/packages/server-node/src/launch/cli-parser.spec.ts @@ -13,10 +13,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import * as path from 'path'; import { LogLevel } from '../utils/logger'; import { createCliParser, defaultLaunchOptions } from './cli-parser'; -import { expect } from 'chai'; describe('test createCliParser', () => { const parser = createCliParser(); @@ -30,7 +30,7 @@ describe('test createCliParser', () => { }); it('parse - invalid log dir', () => { - expect(() => parser.parse([...argv, '--logDir', 'invalid.Path'])).to.throw; + expect(() => parser.parse([...argv, '--logDir', 'invalid.Path'])).to.throw(); }); it('parse - valid log dir', () => { @@ -40,7 +40,7 @@ describe('test createCliParser', () => { }); it('parse - invalid logLevel', () => { - expect(() => parser.parse([...argv, '--logLevel', 'someRandomLevel'])).to.throw; + expect(() => parser.parse([...argv, '--logLevel', 'someRandomLevel'])).to.throw(); }); it('parse - valid logLevel', () => { diff --git a/packages/server-node/src/launch/socket-cli-parser.spec.ts b/packages/server-node/src/launch/socket-cli-parser.spec.ts index 96a02bc..c8a79af 100644 --- a/packages/server-node/src/launch/socket-cli-parser.spec.ts +++ b/packages/server-node/src/launch/socket-cli-parser.spec.ts @@ -13,9 +13,9 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { expect } from 'chai'; import { LogLevel } from '../utils/logger'; import { createSocketCliParser, defaultSocketLaunchOptions, SocketLaunchOptions } from './socket-cli-parser'; -import { expect } from 'chai'; describe('test createCliParser', () => { const parser = createSocketCliParser(); @@ -29,11 +29,11 @@ describe('test createCliParser', () => { }); it('parse - invalid port (below lower range)', () => { - expect(() => parser.parse([...argv, '--port', '-1'])).to.throw; + expect(() => parser.parse([...argv, '--port', '-1'])).to.throw(); }); it('parse - invalid port (below upper range)', () => { - expect(() => parser.parse([...argv, '--port', '65536'])).to.throw; + expect(() => parser.parse([...argv, '--port', '65536'])).to.throw(); }); it('parse - valid port', () => { diff --git a/packages/server-node/src/protocol/glsp-server.spec.ts b/packages/server-node/src/protocol/glsp-server.spec.ts index c9524bf..e206f50 100644 --- a/packages/server-node/src/protocol/glsp-server.spec.ts +++ b/packages/server-node/src/protocol/glsp-server.spec.ts @@ -24,6 +24,7 @@ import { Logger } from '../utils/logger'; import { GLSPClientProxy, JsonRpcGLSPClientProxy } from './glsp-client-proxy'; import { DefaultGLSPServer } from './glsp-server'; import { GLSPServerListener } from './glsp-server-listener'; +import assert = require('assert'); describe('test DefaultGLSPServer', () => { const container = new Container(); @@ -66,9 +67,9 @@ describe('test DefaultGLSPServer', () => { }); it('Test calls before server initialization (should throw errors)', async () => { - expect(async () => glspServer.initializeClientSession({ clientSessionId: 'id', diagramType: 'type' })).to.throw; - expect(async () => glspServer.disposeClientSession({ clientSessionId: 'id' })).to.throw; - expect(async () => glspServer.process({ clientId: 'id', action: { kind: 'action' } })).to.throw; + assert.rejects(() => glspServer.initializeClientSession({ clientSessionId: 'id', diagramType: 'type' })); + assert.rejects(() => glspServer.disposeClientSession({ clientSessionId: 'id' })); + assert.throws(() => glspServer.process({ clientId: 'id', action: { kind: 'action' } })); }); it('addListener - add existing listener', () => { @@ -98,7 +99,7 @@ describe('test DefaultGLSPServer', () => { it('initialize - with wrong protocol version', async () => { const initializeParameters: InitializeParameters = { applicationId, protocolVersion: 'abc' }; - await expect(async () => glspServer.initialize(initializeParameters)).to.throw; + assert.rejects(glspServer.initialize(initializeParameters)); }); it('initialize - with correct parameters', async () => { @@ -121,7 +122,7 @@ describe('test DefaultGLSPServer', () => { it('initialize - subsequent call with other parameters', async () => { const initializeParameters = { applicationId: 'someOtherApp', protocolVersion: 'AnotherProtocolVersion' }; - await expect(async () => glspServer.initialize(initializeParameters)).to.throw; + await assert.rejects(() => glspServer.initialize(initializeParameters)); }); it('initialize client session', async () => { diff --git a/packages/server-node/src/test/integration-test.spec.ts b/packages/server-node/src/test/integration-test.spec.ts index e27fb63..5d2fdc3 100644 --- a/packages/server-node/src/test/integration-test.spec.ts +++ b/packages/server-node/src/test/integration-test.spec.ts @@ -24,10 +24,11 @@ import { RequestModelAction } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; -import { Container, ContainerModule, injectable, interfaces } from 'inversify'; +import { Container, ContainerModule, injectable } from 'inversify'; import * as path from 'path'; import * as sinon from 'sinon'; import { GModelDiagramModule } from '../base-impl/gmodel-diagram-module'; +import { BindingTarget } from '../di/binding-target'; import { InstanceMultiBinding } from '../di/multi-binding'; import { ServerModule } from '../di/server-module'; import { InjectionContainer } from '../di/service-identifiers'; @@ -48,21 +49,14 @@ const clientId = 'session1'; const sourceUri = path.resolve(__dirname, 'minimal.json'); class TestDiagramModule extends GModelDiagramModule { - diagramType = diagramType; - - protected override configure( - bind: interfaces.Bind, - unbind: interfaces.Unbind, - isBound: interfaces.IsBound, - rebind: interfaces.Rebind - ): void { - super.configure(bind, unbind, isBound, rebind); - bind(DiagramConfiguration).toConstantValue( - new (class extends mock.StubDiagramConfiguration { + protected bindDiagramConfiguration(): BindingTarget { + return { + constantValue: new (class extends mock.StubDiagramConfiguration { override typeMapping = getDefaultMapping(); })() - ); + }; } + diagramType = diagramType; override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); @@ -117,7 +111,7 @@ describe('Integration tests for a glsp server created by SocketServerLauncher', } }) }) - ).to.throw; + ).to.throw(); }); it('initialize - should return valid initializeResult', async () => { From 8e82be645a5ebdc7627997c44e35affe52dc55fc Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Apr 2022 08:38:25 +0200 Subject: [PATCH 100/566] General refactoring and cleanup (#16) - Ensure consistent usage of `ModelState` Previously the default implementations used and inconsistent mix of injecting `GModelState` (the custom model state for the direct Gmodel lib) and `ModelState` (the default class). This introduced and indirect dependency to the `GModelDiagramModule`. Since the `GModelState` actually did only contain generally reusable code it has been removed and merged with `ModelState`. All default implementations now consistently inject the `ModelState`. - Move all services (action & operations handlers etc.) that contain implementation specific code for the direct gmodel usecase (graphical model === source model) into a dedicated `gmodel-lib` subdirectory. - Extract a reusable `AbstractJsonModelStorage` base implementation from the `GModelStorage` that can be reused for any model storage that loads JSON models from files. - Introduce a generic abstract base implementation for `CreateNodeOperationHandlers`. - Ensure that the `OperationHandler` correctly sets the reason when dispatching a new `SetDirtyStateAction` - Fix issue in `ChangeBoundsOperationHandler`. Only set the `prefWidth, prefHeight` layout options if the changed object has a `layout` property. - Cleanup index imports --- .../handler/create-activity-node-handler.ts | 6 +- .../handler/create-automated-task-handler.ts | 4 +- .../src/handler/create-category-handler.ts | 6 +- .../handler/create-decision-node-handler.ts | 4 +- .../src/handler/create-edge-handler.ts | 6 +- .../create-fork-or-join-node-handler.ts | 4 +- .../src/handler/create-manual-task-handler.ts | 4 +- .../src/handler/create-merge-node-handler.ts | 4 +- .../src/handler/create-task-handler.ts | 8 +- .../handler/create-weighted-edge-handler.ts | 6 +- .../create-workflow-node-operation-handler.ts | 10 +- .../workflow-label-edit-validator.ts | 6 +- .../src/marker/workflow-model-validator.ts | 6 +- .../workflow-navigation-target-resolver.ts | 6 +- ...-or-previous-navigation-target-provider.ts | 6 +- ...ocumentation-navigation-target-provider.ts | 8 +- ...orkflow-command-palette-action-provider.ts | 6 +- .../workflow-context-menu-item-provider.ts | 6 +- .../server-node/src/base-impl/gmodel-state.ts | 31 ----- .../src/base-impl/gmodel-storage.ts | 92 -------------- .../server-node/src/di/binding-target.spec.ts | 2 +- packages/server-node/src/di/binding-target.ts | 20 ++- packages/server-node/src/di/diagram-module.ts | 11 +- .../request-clipboard-data-action-handler.ts | 6 +- .../command-palette-action-provider.ts | 6 +- .../context-edit-validator-registry.spec.ts | 12 +- .../context-edit-validator-registry.ts | 4 +- .../directediting/context-edit-validator.ts | 4 +- .../request-edit-validation-handler.spec.ts | 4 +- .../model/abstract-json-model-storage.ts | 114 ++++++++++++++++++ .../src/features/model/gmodel-factory.ts | 6 +- .../src/features/model/gmodel-index.ts | 27 +++++ .../src/features/model/model-state.ts | 12 ++ .../model}/save-model-action-handler.ts | 12 +- .../validation/request-markers-handler.ts | 4 +- packages/server-node/src/gmodel-lib/README.md | 7 ++ ...apply-label-edit-operation-handler.spec.ts | 7 +- .../apply-label-edit-operation-handler.ts | 10 +- .../change-bounds-operation-handler.ts | 17 +-- .../computed-bounds-action-handler.ts | 27 ++--- .../delete-operation-handler.ts | 16 +-- .../gmodel-create-edge-operation-handler.ts | 58 +++++++++ .../gmodel-create-node-operation-handler.ts | 47 ++++++++ .../gmodel-diagram-module.ts | 46 ++----- .../src/gmodel-lib/gmodel-storage.ts | 53 ++++++++ .../src/{base-impl => gmodel-lib}/index.ts | 8 +- .../paste-operation-handler.ts | 6 +- packages/server-node/src/index.ts | 9 +- .../operations/create-operation-handler.ts | 103 ++++++---------- .../src/operations/cut-operation-handler.ts | 2 +- .../operations/operation-action-handler.ts | 6 +- .../src/operations/operation-handler.ts | 6 +- .../reconnect-edge-operation-handler.ts | 10 +- .../src/test/integration-test.spec.ts | 9 +- packages/server-node/src/test/mock-util.ts | 13 +- 55 files changed, 547 insertions(+), 386 deletions(-) delete mode 100644 packages/server-node/src/base-impl/gmodel-state.ts delete mode 100644 packages/server-node/src/base-impl/gmodel-storage.ts create mode 100644 packages/server-node/src/features/model/abstract-json-model-storage.ts rename packages/server-node/src/{actions => features/model}/save-model-action-handler.ts (84%) create mode 100644 packages/server-node/src/gmodel-lib/README.md rename packages/server-node/src/{features/directediting => gmodel-lib}/apply-label-edit-operation-handler.spec.ts (91%) rename packages/server-node/src/{features/directediting => gmodel-lib}/apply-label-edit-operation-handler.ts (84%) rename packages/server-node/src/{base-impl => gmodel-lib}/change-bounds-operation-handler.ts (83%) rename packages/server-node/src/{base-impl => gmodel-lib}/computed-bounds-action-handler.ts (74%) rename packages/server-node/src/{base-impl => gmodel-lib}/delete-operation-handler.ts (88%) create mode 100644 packages/server-node/src/gmodel-lib/gmodel-create-edge-operation-handler.ts create mode 100644 packages/server-node/src/gmodel-lib/gmodel-create-node-operation-handler.ts rename packages/server-node/src/{base-impl => gmodel-lib}/gmodel-diagram-module.ts (69%) create mode 100644 packages/server-node/src/gmodel-lib/gmodel-storage.ts rename packages/server-node/src/{base-impl => gmodel-lib}/index.ts (72%) rename packages/server-node/src/{operations => gmodel-lib}/paste-operation-handler.ts (97%) diff --git a/examples/workflow-server/src/handler/create-activity-node-handler.ts b/examples/workflow-server/src/handler/create-activity-node-handler.ts index 8fdd979..b664b7a 100644 --- a/examples/workflow-server/src/handler/create-activity-node-handler.ts +++ b/examples/workflow-server/src/handler/create-activity-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, GModelState, GNode, Point } from '@eclipse-glsp/server-node'; +import { CreateNodeOperation, GNode, ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNode, ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -21,11 +21,11 @@ import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-opera @injectable() export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperationHandler { - createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { return this.builder(relativeLocation, this.modelState).build(); } - protected builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { return ActivityNode.builder() .position(point ?? Point.ORIGIN) .type(this.elementTypeIds[0]) diff --git a/examples/workflow-server/src/handler/create-automated-task-handler.ts b/examples/workflow-server/src/handler/create-automated-task-handler.ts index 7175c5e..14b7b20 100644 --- a/examples/workflow-server/src/handler/create-automated-task-handler.ts +++ b/examples/workflow-server/src/handler/create-automated-task-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateAutomatedTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.AUTOMATED_TASK]; label = 'Automated Task'; - protected override builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + protected override builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { return super.builder(point, modelState).addCssClass('automated'); } } diff --git a/examples/workflow-server/src/handler/create-category-handler.ts b/examples/workflow-server/src/handler/create-category-handler.ts index ec63b54..2d1e830 100644 --- a/examples/workflow-server/src/handler/create-category-handler.ts +++ b/examples/workflow-server/src/handler/create-category-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, ArgsUtil, GModelState, GNode, Point } from '@eclipse-glsp/server-node'; +import { ArgsUtil, CreateNodeOperation, GNode, ModelState, Point } from '@eclipse-glsp/server-node'; import { Category, CategoryNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @@ -22,11 +22,11 @@ export class CreateCategoryHandler extends CreateWorkflowNodeOperationHandler { elementTypeIds = [ModelTypes.CATEGORY]; label = 'Category'; - createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { return this.builder(relativeLocation, this.modelState).build(); } - protected builder(point: Point | undefined, modelState: GModelState): CategoryNodeBuilder { + protected builder(point: Point | undefined, modelState: ModelState): CategoryNodeBuilder { return Category.builder() .type(this.elementTypeIds[0]) .position(point ?? Point.ORIGIN) diff --git a/examples/workflow-server/src/handler/create-decision-node-handler.ts b/examples/workflow-server/src/handler/create-decision-node-handler.ts index 63b12a4..2625e26 100644 --- a/examples/workflow-server/src/handler/create-decision-node-handler.ts +++ b/examples/workflow-server/src/handler/create-decision-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateDecisionNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.DECISION_NODE]; label = 'Decision Node'; - protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('decision'); } } diff --git a/examples/workflow-server/src/handler/create-edge-handler.ts b/examples/workflow-server/src/handler/create-edge-handler.ts index d3b07e7..6f28522 100644 --- a/examples/workflow-server/src/handler/create-edge-handler.ts +++ b/examples/workflow-server/src/handler/create-edge-handler.ts @@ -13,13 +13,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CreateEdgeOperationHandler, DefaultTypes, GEdge, GEdgeBuilder, GModelElement, GModelState } from '@eclipse-glsp/server-node'; +import { DefaultTypes, GEdge, GEdgeBuilder, GModelCreateEdgeOperationHandler, GModelElement } from '@eclipse-glsp/server-node'; -export class CreateEdgeHandler extends CreateEdgeOperationHandler { +export class CreateEdgeHandler extends GModelCreateEdgeOperationHandler { label = 'Edge'; elementTypeIds = [DefaultTypes.EDGE]; - createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + createEdge(source: GModelElement, target: GModelElement): GEdge | undefined { return new GEdgeBuilder(GEdge).sourceId(source.id).targetId(target.id).build(); } } diff --git a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts index 535fd21..30cac2e 100644 --- a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts +++ b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { CreateActivityNodeHandler } from './create-activity-node-handler'; @injectable() export abstract class CreateForkOrJoinNodeHandler extends CreateActivityNodeHandler { - protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('forkOrJoin').size(10, 50); } } diff --git a/examples/workflow-server/src/handler/create-manual-task-handler.ts b/examples/workflow-server/src/handler/create-manual-task-handler.ts index 0cce665..1b8af5e 100644 --- a/examples/workflow-server/src/handler/create-manual-task-handler.ts +++ b/examples/workflow-server/src/handler/create-manual-task-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateManualTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.MANUAL_TASK]; label = 'Manual Task'; - protected override builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + protected override builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { return super.builder(point, modelState).addCssClass('manual'); } } diff --git a/examples/workflow-server/src/handler/create-merge-node-handler.ts b/examples/workflow-server/src/handler/create-merge-node-handler.ts index c88ffae..4aaf8f7 100644 --- a/examples/workflow-server/src/handler/create-merge-node-handler.ts +++ b/examples/workflow-server/src/handler/create-merge-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState, Point } from '@eclipse-glsp/server-node'; +import { ModelState, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateMergeNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.MERGE_NODE]; label = 'Merge Node'; - protected override builder(point: Point | undefined, modelState: GModelState): ActivityNodeBuilder { + protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { return super.builder(point, modelState).addCssClass('merge'); } } diff --git a/examples/workflow-server/src/handler/create-task-handler.ts b/examples/workflow-server/src/handler/create-task-handler.ts index f4c23d0..5873fa6 100644 --- a/examples/workflow-server/src/handler/create-task-handler.ts +++ b/examples/workflow-server/src/handler/create-task-handler.ts @@ -13,8 +13,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, Point } from '@eclipse-glsp/protocol'; -import { GModelState, GNode } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/protocol'; +import { CreateNodeOperation, GNode, ModelState } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNode, TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -22,11 +22,11 @@ import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-opera @injectable() export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandler { - createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined { + createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { return this.builder(relativeLocation, this.modelState).build(); } - protected builder(point: Point | undefined, modelState: GModelState): TaskNodeBuilder { + protected builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { return TaskNode.builder() .position(point ?? Point.ORIGIN) .addCssClass('task') diff --git a/examples/workflow-server/src/handler/create-weighted-edge-handler.ts b/examples/workflow-server/src/handler/create-weighted-edge-handler.ts index 41ecebc..cacea0f 100644 --- a/examples/workflow-server/src/handler/create-weighted-edge-handler.ts +++ b/examples/workflow-server/src/handler/create-weighted-edge-handler.ts @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CreateEdgeOperationHandler, GEdge, GModelElement, GModelState } from '@eclipse-glsp/server-node'; +import { GEdge, GModelCreateEdgeOperationHandler, GModelElement } from '@eclipse-glsp/server-node'; import { WeightedEdge } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; -export class CreateWeightedEdgeHandler extends CreateEdgeOperationHandler { +export class CreateWeightedEdgeHandler extends GModelCreateEdgeOperationHandler { elementTypeIds = [ModelTypes.WEIGHTED_EDGE]; label = 'Weighted edge'; - createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + createEdge(source: GModelElement, target: GModelElement): GEdge | undefined { return WeightedEdge.builder().sourceId(source.id).targetId(target.id).probability('medium').addCssClass('medium').build(); } } diff --git a/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts index 0d1feea..d245c8c 100644 --- a/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts +++ b/examples/workflow-server/src/handler/create-workflow-node-operation-handler.ts @@ -15,10 +15,10 @@ ********************************************************************************/ import { CreateNodeOperation, - CreateNodeOperationHandler, GCompartment, + GModelCreateNodeOperationHandler, GModelElement, - GModelState, + ModelState, Point } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; @@ -27,9 +27,9 @@ import { ModelTypes } from '../util/model-types'; import { GridSnapper } from './grid-snapper'; @injectable() -export abstract class CreateWorkflowNodeOperationHandler extends CreateNodeOperationHandler { - @inject(GModelState) - protected override modelState: GModelState; +export abstract class CreateWorkflowNodeOperationHandler extends GModelCreateNodeOperationHandler { + @inject(ModelState) + protected override modelState: ModelState; override getLocation(operation: CreateNodeOperation): Point | undefined { return GridSnapper.snap(operation.location); diff --git a/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts b/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts index 3c9f6d1..0fa482a 100644 --- a/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts +++ b/examples/workflow-server/src/labeledit/workflow-label-edit-validator.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, GModelState, LabelEditValidator, ValidationStatus } from '@eclipse-glsp/server-node'; +import { GModelElement, LabelEditValidator, ModelState, ValidationStatus } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; import { TaskNode } from '../graph-extension'; @injectable() export class WorkflowLabelEditValidator implements LabelEditValidator { - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; validate(label: string, element: GModelElement): ValidationStatus { if (label.length < 1) { diff --git a/examples/workflow-server/src/marker/workflow-model-validator.ts b/examples/workflow-server/src/marker/workflow-model-validator.ts index ba0703a..f4c5fb7 100644 --- a/examples/workflow-server/src/marker/workflow-model-validator.ts +++ b/examples/workflow-server/src/marker/workflow-model-validator.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, Marker, ModelValidator, GModelState, MarkerKind, GCompartment, GLabel } from '@eclipse-glsp/server-node'; +import { GCompartment, GLabel, GModelElement, Marker, MarkerKind, ModelState, ModelValidator } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; import { ActivityNode, TaskNode } from '../graph-extension'; @injectable() export class WorkflowModelValidator implements ModelValidator { - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; validate(elements: GModelElement[]): Marker[] { const markers: Marker[] = []; diff --git a/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts b/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts index 3ebb0de..7aa4474 100644 --- a/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts +++ b/examples/workflow-server/src/model/workflow-navigation-target-resolver.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { NavigationTarget } from '@eclipse-glsp/protocol'; -import { GModelState } from '@eclipse-glsp/server-node'; +import { ModelState } from '@eclipse-glsp/server-node'; import { NavigationTargetResolution } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolution'; import { NavigationTargetResolver } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-resolver'; import { inject, injectable } from 'inversify'; @@ -22,8 +22,8 @@ import { TaskNode } from '../graph-extension'; @injectable() export class WorkflowNavigationTargetResolver extends NavigationTargetResolver { - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; async resolve(navigationTarget: NavigationTarget): Promise { if (navigationTarget.args && navigationTarget.args['name']) { diff --git a/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts b/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts index 1958633..0eb8e23 100644 --- a/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts +++ b/examples/workflow-server/src/provider/abstract-next-or-previous-navigation-target-provider.ts @@ -14,17 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Args, EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; +import { GEdge, ModelState } from '@eclipse-glsp/server-node'; import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; import { inject, injectable } from 'inversify'; -import { GEdge, GModelState } from '@eclipse-glsp/server-node'; import { TaskNode } from '../graph-extension'; @injectable() export abstract class AbstractNextOrPreviousNavigationTargetProvider implements NavigationTargetProvider { abstract targetTypeId: string; - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; getTargets(editorContext: EditorContext): NavigationTarget[] { const sourceUri = this.modelState.sourceUri; diff --git a/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts b/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts index 8445225..9e9aee0 100644 --- a/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts +++ b/examples/workflow-server/src/provider/node-documentation-navigation-target-provider.ts @@ -14,18 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Args, EditorContext, NavigationTarget } from '@eclipse-glsp/protocol'; -import { GModelState } from '@eclipse-glsp/server-node'; +import { ModelState } from '@eclipse-glsp/server-node'; +import { JsonOpenerOptions } from '@eclipse-glsp/server-node/lib/features/navigation/json-opener-options'; import { NavigationTargetProvider } from '@eclipse-glsp/server-node/lib/features/navigation/navigation-target-provider'; import { inject, injectable } from 'inversify'; import { TaskNode } from '../graph-extension'; -import { JsonOpenerOptions } from '@eclipse-glsp/server-node/lib/features/navigation/json-opener-options'; @injectable() export class NodeDocumentationNavigationTargetProvider implements NavigationTargetProvider { targetTypeId = 'documentation'; - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; getTargets(editorContext: EditorContext): NavigationTarget[] { if (editorContext.selectedElementIds.length === 1) { diff --git a/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts index 770186c..45cf373 100644 --- a/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts +++ b/examples/workflow-server/src/provider/workflow-command-palette-action-provider.ts @@ -21,9 +21,9 @@ import { DefaultTypes, DeleteElementOperation, GModelElement, - GModelState, GNode, LabeledAction, + ModelState, Point } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; @@ -32,8 +32,8 @@ import { ModelTypes } from '../util/model-types'; @injectable() export class WorkflowCommandPaletteActionProvider extends CommandPaletteActionProvider { - @inject(GModelState) - protected override modelState: GModelState; + @inject(ModelState) + protected override modelState: ModelState; getPaletteActions(selectedElementIds: string[], selectedElements: GModelElement[], position: Point, args?: Args): LabeledAction[] { const actions: LabeledAction[] = []; diff --git a/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts index cbd8995..3588467 100644 --- a/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts +++ b/examples/workflow-server/src/provider/workflow-context-menu-item-provider.ts @@ -13,15 +13,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Args, ContextMenuItemProvider, CreateNodeOperation, GModelState, MenuItem, Point } from '@eclipse-glsp/server-node'; +import { Args, ContextMenuItemProvider, CreateNodeOperation, MenuItem, ModelState, Point } from '@eclipse-glsp/server-node'; import { inject, injectable } from 'inversify'; import { GridSnapper } from '../handler/grid-snapper'; import { ModelTypes } from '../util/model-types'; @injectable() export class WorkflowContextMenuItemProvider extends ContextMenuItemProvider { - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; getItems(selectedElementIds: string[], position: Point, args?: Args): MenuItem[] { if (this.modelState.isReadonly || selectedElementIds.length !== 0) { diff --git a/packages/server-node/src/base-impl/gmodel-state.ts b/packages/server-node/src/base-impl/gmodel-state.ts deleted file mode 100644 index a019a4f..0000000 --- a/packages/server-node/src/base-impl/gmodel-state.ts +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { inject, injectable } from 'inversify'; -import { ClientId } from '../di/service-identifiers'; -import { DefaultModelState } from '../features/model/model-state'; - -@injectable() -export class GModelState extends DefaultModelState { - @inject(ClientId) - readonly clientId: string; - - static readonly SOURCE_URI = 'sourceUri'; - - get sourceUri(): string | undefined { - return this.get(GModelState.SOURCE_URI); - } -} diff --git a/packages/server-node/src/base-impl/gmodel-storage.ts b/packages/server-node/src/base-impl/gmodel-storage.ts deleted file mode 100644 index 7f4ad52..0000000 --- a/packages/server-node/src/base-impl/gmodel-storage.ts +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022 STMicroelectronics and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { GGraph, GModelElementSchema, GModelRoot } from '@eclipse-glsp/graph'; -import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; -import { writeFileSync } from 'fs'; -import * as fs from 'fs-extra'; -import { inject, injectable } from 'inversify'; -import { fileURLToPath } from 'url'; -import { GModelSerializer } from '../features/model/gmodel-serializer'; -import { SourceModelStorage } from '../features/model/source-model-storage'; -import { getOrThrow, GLSPServerError } from '../utils/glsp-server-error'; -import { Logger } from '../utils/logger'; -import { GModelState } from './gmodel-state'; - -export const EMPTY_ROOT = GGraph.builder().id('empty').build(); - -/** - * A {@link SourceModelStorage} that reads and writes the graph model directly from / into - * a JSON file and uses it as source model. - */ -@injectable() -export class GModelStorage implements SourceModelStorage { - @inject(Logger) - protected logger: Logger; - - @inject(GModelSerializer) - protected modelSerializer: GModelSerializer; - - @inject(GModelState) - protected modelState: GModelState; - - loadSourceModel(action: RequestModelAction): MaybePromise { - const sourceUri = getOrThrow( - this.modelState.sourceUri, - `Invalid RequestModelAction! Missing argument with key '${GModelState.SOURCE_URI}'` - ); - const root = this.loadFromFile(sourceUri); - root.revision = -1; - this.modelState.root = root; - } - - protected loadFromFile(sourceUri: string): GModelRoot { - try { - const path = this.isFileUrl(sourceUri) ? fileURLToPath(sourceUri) : sourceUri; - const fileContent = this.readFile(path); - if (!fileContent) { - return EMPTY_ROOT; - } - if (!GModelElementSchema.is(fileContent)) { - throw new Error('The loaded root object is not of type SModelRootSchema'); - } - return this.modelSerializer.createRoot(fileContent); - } catch (error) { - throw new GLSPServerError(`Could not load model from file: ${sourceUri}`, error); - } - } - - protected isFileUrl(sourceUri: string): boolean { - return sourceUri.startsWith('file://'); - } - - protected readFile(url: string): unknown { - try { - const data = fs.readFileSync(url, { encoding: 'utf8' }); - return JSON.parse(data); - } catch (error) { - throw new GLSPServerError(`Could not read & parse file contents of '${url}' as json`, error); - } - } - - saveSourceModel(action: SaveModelAction): MaybePromise { - try { - const data = this.modelSerializer.createSchema(this.modelState.root); - writeFileSync(this.modelState.sourceUri!, JSON.stringify(data, undefined, 2)); - } catch (error) { - throw new GLSPServerError(`Could not load model from file: ${this.modelState.sourceUri}`, error); - } - } -} diff --git a/packages/server-node/src/di/binding-target.spec.ts b/packages/server-node/src/di/binding-target.spec.ts index 52afe53..0e0033a 100644 --- a/packages/server-node/src/di/binding-target.spec.ts +++ b/packages/server-node/src/di/binding-target.spec.ts @@ -103,7 +103,7 @@ describe('BindingTarget', () => { context.isBound.returns(true); const syntax = applyBindingTarget(context, Target, { service: SubTarget }); expect(() => { - syntax.inSingletonScope(); + syntax.inRequestScope(); }).to.throw( `${Target.toString()} has been bound to 'service'.` + "Using 'in','when' or 'on' bindings after" + diff --git a/packages/server-node/src/di/binding-target.ts b/packages/server-node/src/di/binding-target.ts index b2f30b4..39c5227 100644 --- a/packages/server-node/src/di/binding-target.ts +++ b/packages/server-node/src/di/binding-target.ts @@ -43,7 +43,7 @@ export function applyBindingTarget( throw new Error(`The target service ${target.service.toString()} is not bound!. Cannot apply target binding`); } context.bind(serviceIdentifier).toService(target.service); - return NoOPSyntax.serviceSyntax(serviceIdentifier); + return NoOPSyntax.serviceSyntax(serviceIdentifier, target); } else if (ConstantValueTarget.is(target)) { const whenOnSyntax = context.bind(serviceIdentifier).toConstantValue(target.constantValue); return NoOPSyntax.constantValueSyntax(serviceIdentifier, whenOnSyntax); @@ -126,7 +126,10 @@ namespace NoOPSyntax { }; } - export function serviceSyntax(serviceIdentifier: interfaces.ServiceIdentifier): interfaces.BindingInWhenOnSyntax { + export function serviceSyntax( + serviceIdentifier: interfaces.ServiceIdentifier, + target: BindingTarget + ): interfaces.BindingInWhenOnSyntax { const noOpReturn = (): interfaces.BindingInWhenOnSyntax => { const errorMsg = `${serviceIdentifier.toString()} has been bound to 'service'.` + @@ -136,7 +139,7 @@ namespace NoOPSyntax { error.name = 'NoOpInvocation'; throw error; }; - return { + const syntax = { onActivation: noOpReturn, when: noOpReturn, whenAnyAncestorIs: noOpReturn, @@ -154,9 +157,18 @@ namespace NoOPSyntax { whenTargetNamed: noOpReturn, whenTargetTagged: noOpReturn, inRequestScope: noOpReturn, - inSingletonScope: noOpReturn, + inSingletonScope: () => { + if (ServiceTarget.is(target)) { + // toService bindings are essentially singleTons. + // We don't throw an error in this case. + return syntax; + } + return noOpReturn(); + }, inTransientScope: noOpReturn }; + + return syntax; } } diff --git a/packages/server-node/src/di/diagram-module.ts b/packages/server-node/src/di/diagram-module.ts index 949447c..fa43104 100644 --- a/packages/server-node/src/di/diagram-module.ts +++ b/packages/server-node/src/di/diagram-module.ts @@ -65,10 +65,12 @@ import { LabelEditValidator } from '../features/directediting/label-edit-validat import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler'; import { LayoutEngine } from '../features/layout/layout-engine'; import { GModelFactory } from '../features/model/gmodel-factory'; +import { GModelIndex } from '../features/model/gmodel-index'; import { DefaultGModelSerializer, GModelSerializer } from '../features/model/gmodel-serializer'; import { ModelState } from '../features/model/model-state'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; import { RequestModelActionHandler } from '../features/model/request-model-action-handler'; +import { SaveModelActionHandler } from '../features/model/save-model-action-handler'; import { SourceModelStorage } from '../features/model/source-model-storage'; import { NavigationTargetProvider } from '../features/navigation/navigation-target-provider'; import { @@ -115,6 +117,7 @@ import { * - {@link DiagramConfiguration} * - {@link GModelSerializer} * - {@link ModelState} + * - {@link GModelIndex} * - {@link SourceModelStorage} * - {@link GModelFactory} * - {@link ModelSubmissionHandler} @@ -156,7 +159,8 @@ export abstract class DiagramModule extends GLSPModule { // Model-related bindings applyBindingTarget(context, GModelSerializer, this.bindGModelSerializer()).inSingletonScope(); - applyBindingTarget(context, ModelState, this.bindModelState()); + applyBindingTarget(context, ModelState, this.bindModelState()).inSingletonScope(); + applyBindingTarget(context, GModelIndex, this.bindGModelIndex()).inSingletonScope(); applyBindingTarget(context, SourceModelStorage, this.bindSourceModelStorage()).inSingletonScope(); applyBindingTarget(context, GModelFactory, this.bindGModelFactory()); applyBindingTarget(context, ModelSubmissionHandler, this.bindModelSubmissionHandler()).inSingletonScope(); @@ -226,6 +230,7 @@ export abstract class DiagramModule extends GLSPModule { binding.add(RequestEditValidationHandler); binding.add(RequestNavigationTargetsActionHandler); binding.add(ResolveNavigationTargetsActionHandler); + binding.add(SaveModelActionHandler); } protected bindDiagramType(): BindingTarget { @@ -240,6 +245,10 @@ export abstract class DiagramModule extends GLSPModule { return DefaultGModelSerializer; } + protected bindGModelIndex(): BindingTarget { + return GModelIndex; + } + protected bindActionDispatcher(): BindingTarget { return DefaultActionDispatcher; } diff --git a/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts index 2ad863c..eb7e074 100644 --- a/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts +++ b/packages/server-node/src/features/clipboard/request-clipboard-data-action-handler.ts @@ -23,15 +23,15 @@ import { } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; -import { GModelState } from '../../base-impl/gmodel-state'; import { GModelSerializer } from '../model/gmodel-serializer'; +import { ModelState } from '../model/model-state'; @injectable() export class RequestClipboardDataActionHandler implements ActionHandler { actionKinds = [RequestClipboardDataAction.KIND]; - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; @inject(GModelSerializer) protected modelSerializer: GModelSerializer; diff --git a/packages/server-node/src/features/contextactions/command-palette-action-provider.ts b/packages/server-node/src/features/contextactions/command-palette-action-provider.ts index 2848d03..a9653c0 100644 --- a/packages/server-node/src/features/contextactions/command-palette-action-provider.ts +++ b/packages/server-node/src/features/contextactions/command-palette-action-provider.ts @@ -16,7 +16,7 @@ import { GModelElement } from '@eclipse-glsp/graph'; import { Args, EditorContext, LabeledAction, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { GModelState } from '../../base-impl/gmodel-state'; +import { ModelState } from '../model/model-state'; import { ContextActionsProvider } from './context-actions-provider'; /** @@ -24,8 +24,8 @@ import { ContextActionsProvider } from './context-actions-provider'; */ @injectable() export abstract class CommandPaletteActionProvider implements ContextActionsProvider { - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; readonly TEXT = 'text'; readonly INDEX = 'index'; diff --git a/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts b/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts index b075ce9..24d09b9 100644 --- a/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts +++ b/packages/server-node/src/features/directediting/context-edit-validator-registry.spec.ts @@ -13,29 +13,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelState } from '../../base-impl/gmodel-state'; +import { expect } from 'chai'; import { TestContextEditValidator, TestLabelEditValidator } from '../../test/mock-util'; +import { DefaultModelState } from '../model/model-state'; import { ContextEditValidator } from './context-edit-validator'; import { DefaultContextEditValidatorRegistry } from './context-edit-validator-registry'; -import { expect } from 'chai'; describe('Test DefaultContextEditValidatorRegistry', () => { it('check if default registry is empty', () => { const contextEditValidators: ContextEditValidator[] = []; - const contextEditValidatorRegistry = new DefaultContextEditValidatorRegistry(new GModelState(), contextEditValidators); + const contextEditValidatorRegistry = new DefaultContextEditValidatorRegistry(new DefaultModelState(), contextEditValidators); expect(contextEditValidatorRegistry.keys()).to.have.length(0); }); it('register TestContextEditValidator via ContextEditValidators list', () => { const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; - const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry(new GModelState(), contextEditValidators); + const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry(new DefaultModelState(), contextEditValidators); expect(contextEditValidatorsRegistry.keys()).to.have.length(1); }); it('register TestLabelEditValidator via LabelEditValidator', () => { const contextEditValidators: ContextEditValidator[] = []; const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( - new GModelState(), + new DefaultModelState(), contextEditValidators, new TestLabelEditValidator() ); @@ -45,7 +45,7 @@ describe('Test DefaultContextEditValidatorRegistry', () => { it('register via ContextEditValidators list and LabelEditValidator', () => { const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( - new GModelState(), + new DefaultModelState(), contextEditValidators, new TestLabelEditValidator() ); diff --git a/packages/server-node/src/features/directediting/context-edit-validator-registry.ts b/packages/server-node/src/features/directediting/context-edit-validator-registry.ts index f517107..21a064f 100644 --- a/packages/server-node/src/features/directediting/context-edit-validator-registry.ts +++ b/packages/server-node/src/features/directediting/context-edit-validator-registry.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable, multiInject, optional } from 'inversify'; -import { GModelState } from '../../base-impl/gmodel-state'; import { ContextEditValidators } from '../../di/service-identifiers'; import { Registry } from '../../utils/registry'; +import { ModelState } from '../model/model-state'; import { ContextEditValidator, ValidateLabelEditAdapter } from './context-edit-validator'; import { LabelEditValidator } from './label-edit-validator'; @@ -30,7 +30,7 @@ export interface ContextEditValidatorRegistry extends Registry implements ContextEditValidatorRegistry { constructor( - @inject(GModelState) modelState: GModelState, + @inject(ModelState) modelState: ModelState, @multiInject(ContextEditValidators) @optional() contextEditValidators: ContextEditValidator[] = [], @inject(LabelEditValidator) @optional() labelEditValidator?: LabelEditValidator ) { diff --git a/packages/server-node/src/features/directediting/context-edit-validator.ts b/packages/server-node/src/features/directediting/context-edit-validator.ts index 38323be..13eefa5 100644 --- a/packages/server-node/src/features/directediting/context-edit-validator.ts +++ b/packages/server-node/src/features/directediting/context-edit-validator.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { GModelElement } from '@eclipse-glsp/graph/lib/gmodel-element'; import { RequestEditValidationAction, ValidationStatus } from '@eclipse-glsp/protocol'; -import { GModelState } from '../../base-impl/gmodel-state'; +import { ModelState } from '../model/model-state'; import { LabelEditValidator } from './label-edit-validator'; export const ContextEditValidator = Symbol('ContextEditValidator'); @@ -37,7 +37,7 @@ export interface ContextEditValidator { export class ValidateLabelEditAdapter implements ContextEditValidator { labelEditValidator: LabelEditValidator; - constructor(readonly modelState: GModelState, labelEditValidator: LabelEditValidator) { + constructor(readonly modelState: ModelState, labelEditValidator: LabelEditValidator) { this.labelEditValidator = labelEditValidator; } diff --git a/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts index 3d583db..c87ca60 100644 --- a/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts +++ b/packages/server-node/src/features/directediting/request-edit-validation-handler.spec.ts @@ -17,9 +17,9 @@ import { GNode } from '@eclipse-glsp/graph'; import { RequestEditValidationAction, SetEditValidationResultAction, ValidationStatus } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import * as sinon from 'sinon'; -import { GModelState } from '../../base-impl/gmodel-state'; import { TestContextEditValidator, TestLabelEditValidator } from '../../test/mock-util'; import { GModelIndex } from '../model/gmodel-index'; +import { DefaultModelState } from '../model/model-state'; import { ContextEditValidator } from './context-edit-validator'; import { DefaultContextEditValidatorRegistry } from './context-edit-validator-registry'; import { LabelEditValidator } from './label-edit-validator'; @@ -27,7 +27,7 @@ import { RequestEditValidationHandler } from './request-edit-validation-handler' describe('Test RequestEditValidationHandler', () => { const contextEditValidators: ContextEditValidator[] = [new TestContextEditValidator()]; - const modelState = new GModelState(); + const modelState = new DefaultModelState(); Object.defineProperty(modelState, 'index', { value: new GModelIndex() }); sinon.stub(modelState.index, 'get').callsFake(() => new GNode()); const contextEditValidatorsRegistry = new DefaultContextEditValidatorRegistry( diff --git a/packages/server-node/src/features/model/abstract-json-model-storage.ts b/packages/server-node/src/features/model/abstract-json-model-storage.ts new file mode 100644 index 0000000..5316835 --- /dev/null +++ b/packages/server-node/src/features/model/abstract-json-model-storage.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { MaybePromise, RequestModelAction, SaveModelAction, TypeGuard } from '@eclipse-glsp/protocol'; +import * as fs from 'fs-extra'; +import { inject, injectable } from 'inversify'; +import { fileURLToPath } from 'url'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { ModelState, SOURCE_URI_ARG } from './model-state'; +import { SourceModelStorage } from './source-model-storage'; + +/** + * An abstract implementation of {@link SourceModelStorage} that provides utility methods for loading and saving JSON source models + * from/into a file. + */ +@injectable() +export abstract class AbstractJsonModelStorage implements SourceModelStorage { + @inject(ModelState) + protected modelState: ModelState; + + abstract loadSourceModel(action: RequestModelAction): MaybePromise; + + abstract saveSourceModel(action: SaveModelAction): MaybePromise; + + protected getSourceUri(action: RequestModelAction): string { + const sourceUri = action.options?.[SOURCE_URI_ARG]; + if (typeof sourceUri !== 'string') { + throw new GLSPServerError(`Invalid RequestModelAction! Missing argument with key '${SOURCE_URI_ARG}'`); + } + return sourceUri; + } + + protected loadFromFile(sourceUri: string): unknown; + protected loadFromFile(sourceUri: string, guard: TypeGuard): T; + protected loadFromFile(sourceUri: string, guard?: TypeGuard): T | unknown { + try { + const path = this.toPath(sourceUri); + let fileContent = this.readFile(path); + if (!fileContent) { + fileContent = this.createModelForEmptyFile(path); + if (!fileContent) { + throw new GLSPServerError(`Could not load the source model. The file '${path}' is empty!.`); + } + } + if (guard && !guard(fileContent)) { + throw new Error('The loaded root object is not of the expected type!'); + } + return fileContent; + } catch (error) { + throw new GLSPServerError(`Could not load model from file: ${sourceUri}`, error); + } + } + + /** + * Can be overwritten to customize the behavior if the given file path points to an empty file. + * The default implementation returns undefined, concrete subclasses can customize this behavior and + * return new source model object instead. + * @param path The path of the empty file. + * @returns The new model or `undefined` + */ + protected createModelForEmptyFile(path: string): unknown | undefined { + return undefined; + } + + protected readFile(path: string): unknown | undefined { + try { + const data = fs.readFileSync(path, { encoding: 'utf8' }); + if (!data || data.length === 0) { + return undefined; + } + return this.toJson(data); + } catch (error) { + throw new GLSPServerError(`Could not read & parse file contents of '${path}' as json`, error); + } + } + + protected toJson(fileContent: string): unknown { + return JSON.parse(fileContent); + } + + protected toPath(sourceUri: string): string { + return sourceUri.startsWith('file://') ? fileURLToPath(sourceUri) : sourceUri; + } + + protected getFileUri(action: SaveModelAction): string { + const uri = action.fileUri ?? this.modelState.get(SOURCE_URI_ARG); + if (!uri) { + throw new GLSPServerError('Could not derive fileUri for saving the current source model'); + } + return uri; + } + + protected writeFile(fileUri: string, model: unknown): void { + const path = this.toPath(fileUri); + const content = this.toString(model); + fs.writeFileSync(path, content); + } + + protected toString(model: unknown): string { + return JSON.stringify(model, undefined, 2); + } +} diff --git a/packages/server-node/src/features/model/gmodel-factory.ts b/packages/server-node/src/features/model/gmodel-factory.ts index 27ed235..3d16ded 100644 --- a/packages/server-node/src/features/model/gmodel-factory.ts +++ b/packages/server-node/src/features/model/gmodel-factory.ts @@ -20,11 +20,11 @@ export const GModelFactory = Symbol('GModelFactory'); /** * A graph model factory produces a graph model from the model state; typically its contained source model. * - * The responsibility of a {@link GModelFactory} implementation is to define how a {@link GModelState} is to be + * The responsibility of a {@link GModelFactory} implementation is to define how a {@link ModelState} is to be * translated into a {@link GModelRoot} that is sent to the client for rendering. Before a {@link GModelFactory} * is invoked, the {@link SourceModelStorage} has already been executed for loading the source model into the - * {@link GModelState}. The {@link GModelFactory} then produces the {@link GModelRoot} from the source model in the - * {@link GModelState}. Implementations of {@link GModelFactory} are usually specific to the type of source model, as + * {@link ModelState}. The {@link GModelFactory} then produces the {@link GModelRoot} from the source model in the + * {@link ModelState}. Implementations of {@link GModelFactory} are usually specific to the type of source model, as * they need to understand the source model in order to translate it into a graph model. * * The graph model factory is invoked after initial load of the source model and after each operation that is applied diff --git a/packages/server-node/src/features/model/gmodel-index.ts b/packages/server-node/src/features/model/gmodel-index.ts index c9adfea..7f08705 100644 --- a/packages/server-node/src/features/model/gmodel-index.ts +++ b/packages/server-node/src/features/model/gmodel-index.ts @@ -14,6 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { GEdge, GModelElement, GModelElementConstructor, GModelRoot, GNode } from '@eclipse-glsp/graph'; +import { TypeGuard } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { getOrThrow, GLSPServerError } from '../../utils/glsp-server-error'; @@ -165,4 +166,30 @@ export class GModelIndex { getAllEdges(): GEdge[] { return this.getAllByClass(GEdge); } + + /** + * Returns the first element matching the given {@link TypeGuard} starting element with the + * given id and walking up the parent hierarchy. + * + * @param elementId The element to start the search from + * @param guard The typeguard which the element should match + * @return The first matching element or `undefined`. + */ + findParentElement(elementId: string, guard: TypeGuard): T | undefined { + const element = this.get(elementId); + return element ? this._findElement(element.parent, guard) : undefined; + } + + protected _findElement(element: GModelElement, guard: TypeGuard): T | undefined { + if (!element) { + return undefined; + } + if (guard(element)) { + return element; + } + if (element instanceof GModelRoot) { + return undefined; + } + return this._findElement(element.parent, guard); + } } diff --git a/packages/server-node/src/features/model/model-state.ts b/packages/server-node/src/features/model/model-state.ts index ee9f810..acd5181 100644 --- a/packages/server-node/src/features/model/model-state.ts +++ b/packages/server-node/src/features/model/model-state.ts @@ -16,6 +16,7 @@ import { GModelRoot } from '@eclipse-glsp/graph'; import { EditMode } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; +import { ClientId } from '../../di/service-identifiers'; import { GModelIndex } from './gmodel-index'; import { GModelSerializer } from './gmodel-serializer'; @@ -29,10 +30,14 @@ export interface ModelState { root: GModelRoot; editMode: string; isDirty: boolean; + sourceUri?: string; + clientId: string; readonly isReadonly: boolean; readonly index: GModelIndex; } +export const SOURCE_URI_ARG = 'sourceUri'; + @injectable() export class DefaultModelState implements ModelState { @inject(GModelIndex) @@ -41,6 +46,9 @@ export class DefaultModelState implements ModelState { @inject(GModelSerializer) protected serializer: GModelSerializer; + @inject(ClientId) + readonly clientId: string; + protected properties = new Map(); protected _root: GModelRoot; @@ -65,6 +73,10 @@ export class DefaultModelState implements ModelState { return guard(result) ? result : undefined; } + get sourceUri(): string | undefined { + return this.get(SOURCE_URI_ARG); + } + clear(key: string): void { this.properties.delete(key); } diff --git a/packages/server-node/src/actions/save-model-action-handler.ts b/packages/server-node/src/features/model/save-model-action-handler.ts similarity index 84% rename from packages/server-node/src/actions/save-model-action-handler.ts rename to packages/server-node/src/features/model/save-model-action-handler.ts index cd4fa77..ffccba9 100644 --- a/packages/server-node/src/actions/save-model-action-handler.ts +++ b/packages/server-node/src/features/model/save-model-action-handler.ts @@ -15,17 +15,17 @@ ********************************************************************************/ import { Action, MaybePromise, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { GModelState } from '../base-impl/gmodel-state'; -import { SourceModelStorage } from '../features/model/source-model-storage'; -import { GLSPServerError } from '../utils/glsp-server-error'; -import { ActionHandler } from './action-handler'; +import { ActionHandler } from '../../actions/action-handler'; +import { GLSPServerError } from '../../utils/glsp-server-error'; +import { ModelState } from './model-state'; +import { SourceModelStorage } from './source-model-storage'; @injectable() export class SaveModelActionHandler implements ActionHandler { actionKinds = [SaveModelAction.KIND]; - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; @inject(SourceModelStorage) protected sourceModelStorage: SourceModelStorage; diff --git a/packages/server-node/src/features/validation/request-markers-handler.ts b/packages/server-node/src/features/validation/request-markers-handler.ts index 84f7656..3568523 100644 --- a/packages/server-node/src/features/validation/request-markers-handler.ts +++ b/packages/server-node/src/features/validation/request-markers-handler.ts @@ -17,8 +17,8 @@ import { GGraph } from '@eclipse-glsp/graph'; import { Action, Marker, RequestMarkersAction, SetMarkersAction } from '@eclipse-glsp/protocol'; import { inject, injectable, optional } from 'inversify'; import { ActionHandler } from '../../actions/action-handler'; -import { GModelState } from '../../base-impl/gmodel-state'; import { GLSPServerError } from '../../utils/glsp-server-error'; +import { ModelState } from '../model/model-state'; import { ModelValidator } from './model-validator'; @injectable() @@ -26,7 +26,7 @@ export class RequestMarkersHandler implements ActionHandler { actionKinds = [RequestMarkersAction.KIND]; @inject(ModelValidator) @optional() validator: ModelValidator; - @inject(GModelState) modelState: GModelState; + @inject(ModelState) modelState: ModelState; async execute(action: RequestMarkersAction, ...args: unknown[]): Promise { let elementIDs = action.elementsIDs; diff --git a/packages/server-node/src/gmodel-lib/README.md b/packages/server-node/src/gmodel-lib/README.md new file mode 100644 index 0000000..4822dd3 --- /dev/null +++ b/packages/server-node/src/gmodel-lib/README.md @@ -0,0 +1,7 @@ +# Eclipse GLSP - Node Server + +## Direct GModel Library + +A collection of reusable base handlers and services to implement GLSP diagram languages directly use the graphical model as source model. +This means the source model is a serializable representation of the graphical model. All operation and action handlers are operating +directly on the graphical model. diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts b/packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.spec.ts similarity index 91% rename from packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts rename to packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.spec.ts index 5e77ed7..27738af 100644 --- a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.spec.ts +++ b/packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.spec.ts @@ -17,14 +17,15 @@ import { GLabel } from '@eclipse-glsp/graph'; import { ApplyLabelEditOperation } from '@eclipse-glsp/protocol'; import { expect } from 'chai'; import * as sinon from 'sinon'; -import { GModelState } from '../../base-impl/gmodel-state'; -import { GModelIndex } from '../model/gmodel-index'; +import { GModelIndex } from '../features/model/gmodel-index'; +import { DefaultModelState } from '../features/model/model-state'; import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; + import assert = require('assert'); describe('Test ApplyLabelEditOperationHandler', () => { const label = new GLabel(); - const modelState = new GModelState(); + const modelState = new DefaultModelState(); Object.defineProperty(modelState, 'index', { value: new GModelIndex() }); sinon.stub(modelState.index, 'findByClass').returns(label); const applyLabelEditOperationHandler = new ApplyLabelEditOperationHandler(); diff --git a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts b/packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.ts similarity index 84% rename from packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts rename to packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.ts index 88c4e39..9a69c3a 100644 --- a/packages/server-node/src/features/directediting/apply-label-edit-operation-handler.ts +++ b/packages/server-node/src/gmodel-lib/apply-label-edit-operation-handler.ts @@ -16,16 +16,16 @@ import { GLabel } from '@eclipse-glsp/graph'; import { ApplyLabelEditOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { GLSPServerError } from '../../utils/glsp-server-error'; -import { GModelState } from '../../base-impl/gmodel-state'; -import { OperationHandler } from '../../operations/operation-handler'; +import { ModelState } from '../features/model/model-state'; +import { OperationHandler } from '../operations/operation-handler'; +import { GLSPServerError } from '../utils/glsp-server-error'; @injectable() export class ApplyLabelEditOperationHandler implements OperationHandler { readonly operationType = ApplyLabelEditOperation.KIND; - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; execute(operation: ApplyLabelEditOperation): void { const element = this.modelState.index.findByClass(operation.labelId, GLabel); diff --git a/packages/server-node/src/base-impl/change-bounds-operation-handler.ts b/packages/server-node/src/gmodel-lib/change-bounds-operation-handler.ts similarity index 83% rename from packages/server-node/src/base-impl/change-bounds-operation-handler.ts rename to packages/server-node/src/gmodel-lib/change-bounds-operation-handler.ts index 914e9e3..d22774b 100644 --- a/packages/server-node/src/base-impl/change-bounds-operation-handler.ts +++ b/packages/server-node/src/gmodel-lib/change-bounds-operation-handler.ts @@ -16,15 +16,15 @@ import { GModelRoot, GNode } from '@eclipse-glsp/graph'; import { ChangeBoundsOperation, Dimension, Point } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; +import { ModelState } from '../features/model/model-state'; import { OperationHandler } from '../operations/operation-handler'; -import { GModelState } from './gmodel-state'; @injectable() export class ChangeBoundsOperationHandler implements OperationHandler { operationType = ChangeBoundsOperation.KIND; - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; execute(operation: ChangeBoundsOperation): void { for (const element of operation.newBounds) { @@ -44,11 +44,14 @@ export class ChangeBoundsOperationHandler implements OperationHandler { if (newPosition) { positionToSet = parent instanceof GModelRoot ? newPosition : { x: Math.max(0, newPosition.x), y: Math.max(0, newPosition.y) }; } - if (!nodeToUpdate.layoutOptions) { - nodeToUpdate.layoutOptions = {}; + if (nodeToUpdate.layout) { + if (!nodeToUpdate.layoutOptions) { + nodeToUpdate.layoutOptions = {}; + } + nodeToUpdate.layoutOptions['prefWidth'] = newSize.width; + nodeToUpdate.layoutOptions['prefHeight'] = newSize.height; } - nodeToUpdate.layoutOptions['prefWidth'] = newSize.width; - nodeToUpdate.layoutOptions['prefHeight'] = newSize.height; + nodeToUpdate.size = newSize; nodeToUpdate.position = positionToSet; } diff --git a/packages/server-node/src/base-impl/computed-bounds-action-handler.ts b/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts similarity index 74% rename from packages/server-node/src/base-impl/computed-bounds-action-handler.ts rename to packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts index 4ae979a..71ada93 100644 --- a/packages/server-node/src/base-impl/computed-bounds-action-handler.ts +++ b/packages/server-node/src/gmodel-lib/computed-bounds-action-handler.ts @@ -17,36 +17,31 @@ import { GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph'; import { Action, ComputedBoundsAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; -import { GModelIndex } from '../features/model/gmodel-index'; +import { ModelState } from '../features/model/model-state'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; -import { GModelState } from './gmodel-state'; @injectable() export class ComputedBoundsActionHandler implements ActionHandler { @inject(ModelSubmissionHandler) protected submissionHandler: ModelSubmissionHandler; - @inject(GModelState) - protected modelState: GModelState; - - @inject(GModelIndex) - protected index: GModelIndex; + @inject(ModelState) + protected modelState: ModelState; actionKinds = [ComputedBoundsAction.KIND]; - execute(action: Action): Action[] { - if (ComputedBoundsAction.is(action)) { - const model = this.modelState.root; - if (action.revision === model.revision) { - this.applyBounds(model, action); - return this.submissionHandler.submitModelDirectly(); - } + execute(action: ComputedBoundsAction): Action[] { + const model = this.modelState.root; + if (action.revision === model.revision) { + this.applyBounds(model, action); + return this.submissionHandler.submitModelDirectly(); } + return []; } protected applyBounds(root: GModelRoot, action: ComputedBoundsAction): void { action.bounds.forEach(b => { - const element = this.index.get(b.elementId); + const element = this.modelState.index.get(b.elementId); if (isGBoundsAware(element)) { element.position = b.newPosition ?? element.position; element.size = b.newSize; @@ -54,7 +49,7 @@ export class ComputedBoundsActionHandler implements ActionHandler { }); action.alignments?.forEach(a => { - const element = this.index.get(a.elementId); + const element = this.modelState.index.get(a.elementId); if (isGAlignable(element)) { element.alignment = a.newAlignment; } diff --git a/packages/server-node/src/base-impl/delete-operation-handler.ts b/packages/server-node/src/gmodel-lib/delete-operation-handler.ts similarity index 88% rename from packages/server-node/src/base-impl/delete-operation-handler.ts rename to packages/server-node/src/gmodel-lib/delete-operation-handler.ts index d2a5a88..99d8d6a 100644 --- a/packages/server-node/src/base-impl/delete-operation-handler.ts +++ b/packages/server-node/src/gmodel-lib/delete-operation-handler.ts @@ -17,18 +17,18 @@ import { GEdge, GModelElement, GNode } from '@eclipse-glsp/graph'; import { DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { GModelIndex } from '../features/model/gmodel-index'; +import { ModelState } from '../features/model/model-state'; import { OperationHandler } from '../operations/operation-handler'; import { Logger } from '../utils/logger'; -import { GModelState } from './gmodel-state'; @injectable() -export class DeleteOperationHandler implements OperationHandler { +export class GModelDeleteOperationHandler implements OperationHandler { @inject(Logger) protected logger: Logger; protected allDependantsIds: Set; - @inject(GModelState) protected readonly modelState: GModelState; + @inject(ModelState) protected readonly modelState: ModelState; get operationType(): string { return DeleteElementOperation.KIND; @@ -66,7 +66,7 @@ export class DeleteOperationHandler implements OperationHandler { } const dependents = new Set(); - this.collectDependents(dependents, nodeToDelete, this.modelState); + this.collectDependents(dependents, nodeToDelete); dependents.forEach(dependant => { const index = this.modelState.root.children.findIndex(element => element === dependant); @@ -79,22 +79,22 @@ export class DeleteOperationHandler implements OperationHandler { return true; } - protected collectDependents(dependents: Set, nodeToDelete: GModelElement, modelState: GModelState): void { + protected collectDependents(dependents: Set, nodeToDelete: GModelElement): void { if (dependents.has(nodeToDelete)) { return; } if (nodeToDelete.children.length > 0) { - nodeToDelete.children.forEach(child => this.collectDependents(dependents, child, modelState)); + nodeToDelete.children.forEach(child => this.collectDependents(dependents, child)); } if (nodeToDelete instanceof GNode) { const index = this.modelState.index; index.getIncomingEdges(nodeToDelete).forEach(incoming => { - this.collectDependents(dependents, incoming, modelState); + this.collectDependents(dependents, incoming); }); index.getOutgoingEdges(nodeToDelete).forEach(outgoing => { - this.collectDependents(dependents, outgoing, modelState); + this.collectDependents(dependents, outgoing); }); } diff --git a/packages/server-node/src/gmodel-lib/gmodel-create-edge-operation-handler.ts b/packages/server-node/src/gmodel-lib/gmodel-create-edge-operation-handler.ts new file mode 100644 index 0000000..123f5dd --- /dev/null +++ b/packages/server-node/src/gmodel-lib/gmodel-create-edge-operation-handler.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GEdge, GModelElement, GPort } from '@eclipse-glsp/graph'; +import { GNode } from '@eclipse-glsp/graph/lib/gnode'; +import { CreateEdgeOperation } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ModelState } from '../features/model/model-state'; +import { CreateOperationHandler } from '../operations/create-operation-handler'; + +/** + * An abstract base implementation of {@link CreateEdgeOperation} handlers for diagram implementations + * where the graphical model is also directly used as source model. + * (i.e. all operation handlers directly modify the graphical model). + */ +@injectable() +export abstract class GModelCreateEdgeOperationHandler extends CreateOperationHandler { + @inject(ModelState) + protected modelState: ModelState; + + get operationType(): string { + return CreateEdgeOperation.KIND; + } + + execute(operation: CreateEdgeOperation): void { + const index = this.modelState.index; + + const source = index.find(operation.sourceElementId, element => element instanceof GNode || element instanceof GPort); + const target = index.find(operation.targetElementId, element => element instanceof GNode || element instanceof GPort); + + if (!source || !target) { + throw new Error( + `Invalid source or target for source ID ${operation.sourceElementId} and target ID ${operation.targetElementId}` + ); + } + + const connection = this.createEdge(source, target); + if (connection) { + const currentModel = this.modelState.root; + currentModel.children.push(connection); + } + } + + abstract createEdge(source: GModelElement, target: GModelElement): GEdge | undefined; +} diff --git a/packages/server-node/src/gmodel-lib/gmodel-create-node-operation-handler.ts b/packages/server-node/src/gmodel-lib/gmodel-create-node-operation-handler.ts new file mode 100644 index 0000000..a07ba2c --- /dev/null +++ b/packages/server-node/src/gmodel-lib/gmodel-create-node-operation-handler.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2022 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { GNode } from '@eclipse-glsp/graph'; +import { CreateNodeOperation, Point, SelectAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { ActionDispatcher } from '../actions/action-dispatcher'; +import { CreateNodeOperationHandler } from '../operations/create-operation-handler'; + +/** + * An abstract base implementation of {@link CreateNodeOperationHandler} for diagram implementations + * where the graphical model is also directly used as source model. + * (i.e. all operation handlers directly modify the graphical model). + */ +@injectable() +export abstract class GModelCreateNodeOperationHandler extends CreateNodeOperationHandler { + @inject(ActionDispatcher) + protected actionDispatcher: ActionDispatcher; + + abstract override elementTypeIds: string[]; + + execute(operation: CreateNodeOperation): void { + const container = this.getContainer(operation) ?? this.modelState.root; + const relativeLocation = this.getRelativeLocation(operation); + const element = this.createNode(operation, relativeLocation); + if (element) { + container.children.push(element); + element.parent = container; + this.actionDispatcher.dispatchAfterNextUpdate(SelectAction.create({ selectedElementsIDs: [element.id] })); + } + } + + abstract createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined; +} diff --git a/packages/server-node/src/base-impl/gmodel-diagram-module.ts b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts similarity index 69% rename from packages/server-node/src/base-impl/gmodel-diagram-module.ts rename to packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts index ddaede2..beaa372 100644 --- a/packages/server-node/src/base-impl/gmodel-diagram-module.ts +++ b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts @@ -13,28 +13,26 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, interfaces } from 'inversify'; +import { injectable } from 'inversify'; import { ActionHandlerConstructor } from '../actions/action-handler'; -import { SaveModelActionHandler } from '../actions/save-model-action-handler'; -import { ChangeBoundsOperationHandler } from '../base-impl/change-bounds-operation-handler'; -import { applyBindingTarget, BindingTarget } from '../di/binding-target'; +import { BindingTarget } from '../di/binding-target'; import { DiagramModule } from '../di/diagram-module'; import { InstanceMultiBinding } from '../di/multi-binding'; import { RequestClipboardDataActionHandler } from '../features/clipboard/request-clipboard-data-action-handler'; -import { ApplyLabelEditOperationHandler } from '../features/directediting/apply-label-edit-operation-handler'; import { LayoutOperationHandler } from '../features/layout/layout-operation-handler'; import { GModelFactory, GModelFactoryNullImpl } from '../features/model/gmodel-factory'; -import { GModelIndex } from '../features/model/gmodel-index'; -import { ModelState } from '../features/model/model-state'; +import { DefaultModelState, ModelState } from '../features/model/model-state'; +import { SaveModelActionHandler } from '../features/model/save-model-action-handler'; import { SourceModelStorage } from '../features/model/source-model-storage'; import { CutOperationHandler } from '../operations/cut-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; -import { PasteOperationHandler } from '../operations/paste-operation-handler'; import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; +import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; +import { ChangeBoundsOperationHandler } from './change-bounds-operation-handler'; import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; -import { DeleteOperationHandler } from './delete-operation-handler'; -import { GModelState } from './gmodel-state'; +import { GModelDeleteOperationHandler } from './delete-operation-handler'; import { GModelStorage } from './gmodel-storage'; +import { PasteOperationHandler } from './paste-operation-handler'; /** * Extension of the {@link DiagramModule} to provide GModel integration. @@ -44,7 +42,7 @@ import { GModelStorage } from './gmodel-storage'; * Additionally binds: * - {@link SourceModelStorage} to {@link GModelLoader} * - {@link CommandStack} to {@link DefaultCommandStack} - * - {@link ModelState} to {@link GModelState} + * - {@link ModelState} to {@link ModelState} * - {@link GModelFactory} to {@link GModelFactoryNullImpl} * - {@link GModelIndex} to self * - {@link ComputedBoundsActionHandler} to {@link ActionHandler} @@ -56,17 +54,6 @@ import { GModelStorage } from './gmodel-storage'; */ @injectable() export abstract class GModelDiagramModule extends DiagramModule { - protected override configure( - bind: interfaces.Bind, - unbind: interfaces.Unbind, - isBound: interfaces.IsBound, - rebind: interfaces.Rebind - ): void { - super.configure(bind, unbind, isBound, rebind); - const context = this.context; - applyBindingTarget(context, GModelIndex, this.bindGModelIndex()).inSingletonScope(); - } - protected override bindSourceModelStorage(): BindingTarget { return GModelStorage; } @@ -75,13 +62,8 @@ export abstract class GModelDiagramModule extends DiagramModule { return GModelFactoryNullImpl; } - protected override bindModelState(): BindingTarget { - applyBindingTarget(this.context, GModelState, this.bindGModelState()).inSingletonScope(); - return { service: GModelState }; - } - - protected bindGModelState(): BindingTarget { - return GModelState; + protected bindModelState(): BindingTarget { + return DefaultModelState; } protected override configureActionHandlers(binding: InstanceMultiBinding): void { @@ -91,16 +73,12 @@ export abstract class GModelDiagramModule extends DiagramModule { binding.add(RequestClipboardDataActionHandler); } - protected bindGModelIndex(): BindingTarget { - return GModelIndex; - } - protected override configureOperationHandlers(binding: InstanceMultiBinding): void { super.configureOperationHandlers(binding); binding.add(ApplyLabelEditOperationHandler); binding.add(ChangeBoundsOperationHandler); binding.add(CutOperationHandler); - binding.add(DeleteOperationHandler); + binding.add(GModelDeleteOperationHandler); binding.add(PasteOperationHandler); binding.add(ReconnectEdgeOperationHandler); binding.add(LayoutOperationHandler); diff --git a/packages/server-node/src/gmodel-lib/gmodel-storage.ts b/packages/server-node/src/gmodel-lib/gmodel-storage.ts new file mode 100644 index 0000000..3ff8402 --- /dev/null +++ b/packages/server-node/src/gmodel-lib/gmodel-storage.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { GGraph, GModelElementSchema } from '@eclipse-glsp/graph'; +import { MaybePromise, RequestModelAction, SaveModelAction } from '@eclipse-glsp/protocol'; +import { inject, injectable } from 'inversify'; +import { AbstractJsonModelStorage } from '../features/model/abstract-json-model-storage'; +import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { ModelState } from '../features/model/model-state'; +import { Logger } from '../utils/logger'; + +export const EMPTY_ROOT = GGraph.builder().id('empty').build(); + +/** + * A {@link SourceModelStorage} that reads and writes the graph model directly from / into + * a JSON file and uses it as source model. + */ +@injectable() +export class GModelStorage extends AbstractJsonModelStorage { + @inject(Logger) + protected logger: Logger; + + @inject(GModelSerializer) + protected modelSerializer: GModelSerializer; + + @inject(ModelState) + protected override modelState: ModelState; + + loadSourceModel(action: RequestModelAction): MaybePromise { + const sourceUri = this.getSourceUri(action); + const rootSchema = this.loadFromFile(sourceUri, GModelElementSchema.is); + const root = this.modelSerializer.createRoot(rootSchema); + this.modelState.root = root; + } + + saveSourceModel(action: SaveModelAction): MaybePromise { + const fileUri = this.getFileUri(action); + const schema = this.modelSerializer.createSchema(this.modelState.root); + this.writeFile(fileUri, schema); + } +} diff --git a/packages/server-node/src/base-impl/index.ts b/packages/server-node/src/gmodel-lib/index.ts similarity index 72% rename from packages/server-node/src/base-impl/index.ts rename to packages/server-node/src/gmodel-lib/index.ts index eaafa01..7427de4 100644 --- a/packages/server-node/src/base-impl/index.ts +++ b/packages/server-node/src/gmodel-lib/index.ts @@ -14,6 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +export * from './apply-label-edit-operation-handler'; +export * from './change-bounds-operation-handler'; +export * from './computed-bounds-action-handler'; +export * from './delete-operation-handler'; +export * from './gmodel-create-edge-operation-handler'; +export * from './gmodel-create-node-operation-handler'; export * from './gmodel-diagram-module'; -export * from './gmodel-state'; export * from './gmodel-storage'; +export * from './paste-operation-handler'; diff --git a/packages/server-node/src/operations/paste-operation-handler.ts b/packages/server-node/src/gmodel-lib/paste-operation-handler.ts similarity index 97% rename from packages/server-node/src/operations/paste-operation-handler.ts rename to packages/server-node/src/gmodel-lib/paste-operation-handler.ts index 5f29d26..e68e54d 100644 --- a/packages/server-node/src/operations/paste-operation-handler.ts +++ b/packages/server-node/src/gmodel-lib/paste-operation-handler.ts @@ -17,8 +17,8 @@ import { GBoundsAware, GModelElement, isGBoundsAware } from '@eclipse-glsp/graph import { PasteOperation, Point, SModelElementSchema } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import * as uuid from 'uuid'; -import { GModelState } from '../base-impl/gmodel-state'; import { GModelSerializer } from '../features/model/gmodel-serializer'; +import { ModelState } from '../features/model/model-state'; import { OperationHandler } from '../operations/operation-handler'; @injectable() @@ -27,8 +27,8 @@ export class PasteOperationHandler implements OperationHandler { protected readonly DEFAULT_OFFSET = 20; - @inject(GModelState) - protected modelState: GModelState; + @inject(ModelState) + protected modelState: ModelState; @inject(GModelSerializer) protected modelSerializer: GModelSerializer; diff --git a/packages/server-node/src/index.ts b/packages/server-node/src/index.ts index 64b9d7c..cb36e6b 100644 --- a/packages/server-node/src/index.ts +++ b/packages/server-node/src/index.ts @@ -21,10 +21,6 @@ export * from './actions/action-handler'; export * from './actions/action-handler-registry'; export * from './actions/client-action-handler'; export * from './actions/global-action-provider'; -export * from './base-impl'; -export * from './base-impl/change-bounds-operation-handler'; -export * from './base-impl/computed-bounds-action-handler'; -export * from './base-impl/delete-operation-handler'; export * from './command/command'; export * from './command/command-stack'; export * from './di/app-module'; @@ -43,13 +39,13 @@ export * from './features/contextactions/context-actions-provider-registry'; export * from './features/contextactions/context-menu-item-provider'; export * from './features/contextactions/request-context-actions-handler'; export * from './features/contextactions/tool-palette-item-provider'; -export * from './features/directediting/apply-label-edit-operation-handler'; export * from './features/directediting/context-edit-validator'; export * from './features/directediting/context-edit-validator-registry'; export * from './features/directediting/label-edit-validator'; export * from './features/directediting/request-edit-validation-handler'; export * from './features/layout/layout-engine'; export * from './features/layout/layout-operation-handler'; +export * from './features/model/abstract-json-model-storage'; export * from './features/model/gmodel-factory'; export * from './features/model/gmodel-index'; export * from './features/model/gmodel-serializer'; @@ -68,6 +64,7 @@ export * from './features/popup/popup-model-factory'; export * from './features/popup/request-popup-model-action-handler'; export * from './features/validation/model-validator'; export * from './features/validation/request-markers-handler'; +export * from './gmodel-lib'; export * from './launch/cli-parser'; export * from './launch/glsp-server-launcher'; export * from './launch/socket-cli-parser'; @@ -75,10 +72,8 @@ export * from './launch/socket-server-launcher'; export * from './operations/compound-operation-handler'; export * from './operations/create-operation-handler'; export * from './operations/cut-operation-handler'; -export * from './operations/operation-action-handler'; export * from './operations/operation-handler'; export * from './operations/operation-handler-registry'; -export * from './operations/paste-operation-handler'; export * from './protocol/glsp-client-proxy'; export * from './protocol/glsp-server'; export * from './protocol/glsp-server-listener'; diff --git a/packages/server-node/src/operations/create-operation-handler.ts b/packages/server-node/src/operations/create-operation-handler.ts index ee98729..4f5a27e 100644 --- a/packages/server-node/src/operations/create-operation-handler.ts +++ b/packages/server-node/src/operations/create-operation-handler.ts @@ -13,27 +13,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GBoundsAware, GEdge, GGraph, GModelElement, GModelRoot, GNode, GPort, isGBoundsAware } from '@eclipse-glsp/graph'; +import { GBoundsAware, GGraph, GModelElement, GModelRoot, isGBoundsAware } from '@eclipse-glsp/graph'; import { - Args, CreateEdgeOperation, CreateNodeOperation, CreateOperation, Operation, Point, - SelectAction, TriggerEdgeCreationAction, TriggerNodeCreationAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { ActionDispatcher } from '../actions/action-dispatcher'; -import { GModelState } from '../base-impl/gmodel-state'; +import { ModelState } from '../features/model/model-state'; import { OperationHandler } from './operation-handler'; /** * A special {@link OperationHandler} that is responsible for the handling of {@link CreateOperation}s. Depending on its * operation type the triggered actions are {@link TriggerNodeCreationAction} or {@link TriggerEdgeCreationAction}s. */ +@injectable() export abstract class CreateOperationHandler implements OperationHandler { abstract operationType: string; abstract readonly label: string; @@ -60,33 +58,25 @@ export abstract class CreateOperationHandler implements OperationHandler { } } +/** + * Abstract base implementation for {@link CreateOperationHandler} that + * create an element that is represented with a {@link GNode} in the graphical model. + */ @injectable() export abstract class CreateNodeOperationHandler extends CreateOperationHandler { - @inject(GModelState) - protected modelState: GModelState; - - @inject(ActionDispatcher) - protected actionDispatcher: ActionDispatcher; + @inject(ModelState) + protected modelState: ModelState; get operationType(): string { return CreateNodeOperation.KIND; } - abstract override elementTypeIds: string[]; - - execute(operation: CreateNodeOperation): void { - const container = this.getContainer(operation) ?? this.modelState.root; - - const absoluteLocation = this.getLocation(operation); - const relativeLocation = this.getRelativeLocation(absoluteLocation, container); - const element = this.createNode(relativeLocation, operation.args); - if (element) { - container.children.push(element); - element.parent = container; - this.actionDispatcher.dispatchAfterNextUpdate(SelectAction.create({ selectedElementsIDs: [element.id] })); - } - } - + /** + * Retrieve the graphical model element that should contain the newly created {@link GNode}. + * If `undefined` is returned the {@link GNode} should be added directly to the diagram root. + * @param operation The currently handled operation. + * @returns The container {@link GModeElement} or `undefined`. + */ getContainer(operation: CreateNodeOperation): GModelElement | undefined { const index = this.modelState.index; return operation.containerId ? index.get(operation.containerId) : undefined; @@ -96,52 +86,29 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler return operation.location; } - getRelativeLocation(absoluteLocation: Point | undefined, container: GModelElement | undefined): Point | undefined { - if (absoluteLocation && container) { - const allowNegativeCoordinates = container instanceof GGraph; - if (isGBoundsAware(container)) { - const relativePosition = absoluteToRelative(absoluteLocation, container); - const relativeLocation = allowNegativeCoordinates - ? relativePosition - : { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) }; - return relativeLocation; - } + /** + * Retrieves the diagram absolute location and the target container from the given {@link CreateNodeOperation} + * and converts the absolute location to coordinates relative to the given container. + * Relative coordinates can only be retrieved if the given container element is part of + * a hierarchy of {@link GBoundsAware} elements. This means each (recursive) parent element need to + * implement {@link GBoundsAware}. If that is not the case this method returns `undefined`. + * @param absoluteLocation The diagram absolute position. + * @param container The container element. + * @returns The relative position or `undefined`. + */ + getRelativeLocation(operation: CreateNodeOperation): Point | undefined { + const container = this.getContainer(operation) ?? this.modelState.root; + const absoluteLocation = this.getLocation(operation) ?? Point.ORIGIN; + const allowNegativeCoordinates = container instanceof GGraph; + if (isGBoundsAware(container)) { + const relativePosition = absoluteToRelative(absoluteLocation, container); + const relativeLocation = allowNegativeCoordinates + ? relativePosition + : { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) }; + return relativeLocation; } return undefined; } - - abstract createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode | undefined; -} - -@injectable() -export abstract class CreateEdgeOperationHandler extends CreateOperationHandler { - @inject(GModelState) - protected modelState: GModelState; - - get operationType(): string { - return CreateEdgeOperation.KIND; - } - - execute(operation: CreateEdgeOperation): void { - const index = this.modelState.index; - - const source = index.find(operation.sourceElementId, element => element instanceof GNode || element instanceof GPort); - const target = index.find(operation.targetElementId, element => element instanceof GNode || element instanceof GPort); - - if (!source || !target) { - throw new Error( - `Invalid source or target for source ID ${operation.sourceElementId} and target ID ${operation.targetElementId}` - ); - } - - const connection = this.createEdge(source, target, this.modelState); - if (connection) { - const currentModel = this.modelState.root; - (currentModel as GModelElement).children.push(connection); - } - } - - abstract createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined; } /** diff --git a/packages/server-node/src/operations/cut-operation-handler.ts b/packages/server-node/src/operations/cut-operation-handler.ts index c3d47af..2a703eb 100644 --- a/packages/server-node/src/operations/cut-operation-handler.ts +++ b/packages/server-node/src/operations/cut-operation-handler.ts @@ -16,7 +16,7 @@ import { CutOperation, DeleteElementOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionDispatcher } from '../actions/action-dispatcher'; -import { OperationHandler } from '../operations/operation-handler'; +import { OperationHandler } from './operation-handler'; @injectable() export class CutOperationHandler implements OperationHandler { diff --git a/packages/server-node/src/operations/operation-action-handler.ts b/packages/server-node/src/operations/operation-action-handler.ts index 7dd5316..4eb30aa 100644 --- a/packages/server-node/src/operations/operation-action-handler.ts +++ b/packages/server-node/src/operations/operation-action-handler.ts @@ -16,8 +16,8 @@ import { Action, CreateOperation, MaybePromise, Operation, ServerMessageAction } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; import { ActionHandler } from '../actions/action-handler'; -import { GModelState } from '../base-impl/gmodel-state'; import { Operations } from '../di/service-identifiers'; +import { ModelState } from '../features/model/model-state'; import { ModelSubmissionHandler } from '../features/model/model-submission-handler'; import { OperationHandler } from './operation-handler'; import { OperationHandlerRegistry } from './operation-handler-registry'; @@ -26,7 +26,7 @@ import { OperationHandlerRegistry } from './operation-handler-registry'; export class OperationActionHandler implements ActionHandler { @inject(OperationHandlerRegistry) protected operationHandlerRegistry: OperationHandlerRegistry; @inject(ModelSubmissionHandler) protected modelSubmissionHandler: ModelSubmissionHandler; - @inject(GModelState) protected modelState: GModelState; + @inject(ModelState) protected modelState: ModelState; constructor(@inject(Operations) readonly actionKinds: string[]) {} @@ -53,7 +53,7 @@ export class OperationActionHandler implements ActionHandler { this.modelState.index.indexRoot(this.modelState.root); this.modelState.isDirty = true; // TODO: this.modelState.execute(command) - return this.modelSubmissionHandler.submitModel(); // TODO: Add SetDirtyStateAction.Reason.Operation + return this.modelSubmissionHandler.submitModel('operation'); } handles(action: Action): boolean { diff --git a/packages/server-node/src/operations/operation-handler.ts b/packages/server-node/src/operations/operation-handler.ts index 9ba2513..ce072e8 100644 --- a/packages/server-node/src/operations/operation-handler.ts +++ b/packages/server-node/src/operations/operation-handler.ts @@ -21,10 +21,10 @@ export const OperationHandler = Symbol('OperationHandler'); /** * An operation handler can execute {@link Operation}s of a certain type (subclass). * The operation handler processes the operation in the {@link OperationHandler.execute()} method. The result - * of the execution is an update of the `GModelState` state. + * of the execution is an update of the `ModelState` state. * This update is reversible (undo) and can be reapplied (redo). For basic diagram languages these updates are typically - * applied directly on the `GModelState` using EMF `Command`s and the - * `GModelState.execute()` method. For more complex diagram languages the + * applied directly on the `ModelState` using EMF `Command`s and the + * `ModelState.execute()` method. For more complex diagram languages the * GModel state might be updated indirectly and the operation handler manipulates a custom model representation. * * The `OperationActionHandler` is responsible for retrieving all available (valid) operation handlers for an diff --git a/packages/server-node/src/operations/reconnect-edge-operation-handler.ts b/packages/server-node/src/operations/reconnect-edge-operation-handler.ts index bc9293a..2831bc2 100644 --- a/packages/server-node/src/operations/reconnect-edge-operation-handler.ts +++ b/packages/server-node/src/operations/reconnect-edge-operation-handler.ts @@ -13,19 +13,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { GEdge, GNode, GPort } from '@eclipse-glsp/graph'; import { ReconnectEdgeOperation } from '@eclipse-glsp/protocol'; import { inject, injectable } from 'inversify'; -import { GLSPServerError } from '../utils/glsp-server-error'; +import { ModelState } from '../features/model/model-state'; import { OperationHandler } from '../operations/operation-handler'; -import { GModelState } from '../base-impl/gmodel-state'; -import { GEdge, GNode, GPort } from '@eclipse-glsp/graph'; +import { GLSPServerError } from '../utils/glsp-server-error'; @injectable() export class ReconnectEdgeOperationHandler implements OperationHandler { operationType = ReconnectEdgeOperation.KIND; - @inject(GModelState) - protected readonly modelState: GModelState; + @inject(ModelState) + protected readonly modelState: ModelState; execute(operation: ReconnectEdgeOperation): void { if (!operation.edgeElementId || !operation.sourceElementId || !operation.targetElementId) { diff --git a/packages/server-node/src/test/integration-test.spec.ts b/packages/server-node/src/test/integration-test.spec.ts index 5d2fdc3..e81af99 100644 --- a/packages/server-node/src/test/integration-test.spec.ts +++ b/packages/server-node/src/test/integration-test.spec.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { getDefaultMapping, GNode } from '@eclipse-glsp/graph'; import { - Args, CompoundOperation, CreateNodeOperation, InitializeResult, @@ -27,15 +26,15 @@ import { expect } from 'chai'; import { Container, ContainerModule, injectable } from 'inversify'; import * as path from 'path'; import * as sinon from 'sinon'; -import { GModelDiagramModule } from '../base-impl/gmodel-diagram-module'; import { BindingTarget } from '../di/binding-target'; import { InstanceMultiBinding } from '../di/multi-binding'; import { ServerModule } from '../di/server-module'; import { InjectionContainer } from '../di/service-identifiers'; import { DiagramConfiguration } from '../diagram/diagram-configuration'; +import { GModelCreateNodeOperationHandler } from '../gmodel-lib'; +import { GModelDiagramModule } from '../gmodel-lib/gmodel-diagram-module'; import { SocketServerLauncher } from '../launch/socket-server-launcher'; import { CompoundOperationHandler } from '../operations/compound-operation-handler'; -import { CreateNodeOperationHandler } from '../operations/create-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; import { OperationHandlerRegistry } from '../operations/operation-handler-registry'; import { GLSPClientProxy, JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; @@ -65,11 +64,11 @@ class TestDiagramModule extends GModelDiagramModule { } @injectable() -class CreateANodeOperationHandler extends CreateNodeOperationHandler { +class CreateANodeOperationHandler extends GModelCreateNodeOperationHandler { label = 'ANode'; elementTypeIds = ['ANode']; - createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode { + createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { return new GNode(); } } diff --git a/packages/server-node/src/test/mock-util.ts b/packages/server-node/src/test/mock-util.ts index 65599dc..a3f2b77 100644 --- a/packages/server-node/src/test/mock-util.ts +++ b/packages/server-node/src/test/mock-util.ts @@ -20,6 +20,7 @@ import { Action, ActionMessage, Args, + CreateNodeOperation, EdgeTypeHint, InitializeClientSessionParameters, MaybeArray, @@ -32,11 +33,11 @@ import { Container } from 'inversify'; import { MessageConnection } from 'vscode-jsonrpc'; import { ActionDispatcher } from '../actions/action-dispatcher'; import { ActionHandler, ActionHandlerFactory } from '../actions/action-handler'; -import { GModelState } from '../base-impl'; import { DiagramConfiguration, ServerLayoutKind } from '../diagram/diagram-configuration'; import { ContextEditValidator } from '../features/directediting/context-edit-validator'; import { LabelEditValidator } from '../features/directediting/label-edit-validator'; -import { CreateEdgeOperationHandler, CreateNodeOperationHandler } from '../operations/create-operation-handler'; +import { GModelCreateNodeOperationHandler } from '../gmodel-lib'; +import { GModelCreateEdgeOperationHandler } from '../gmodel-lib/gmodel-create-edge-operation-handler'; import { JsonRpcGLSPClientProxy } from '../protocol/glsp-client-proxy'; import { GLSPServer } from '../protocol/glsp-server'; import { GLSPServerListener } from '../protocol/glsp-server-listener'; @@ -77,7 +78,7 @@ export class StubActionHandler implements ActionHandler { } } -export class StubCreateNodeOperationHandler extends CreateNodeOperationHandler { +export class StubCreateNodeOperationHandler extends GModelCreateNodeOperationHandler { elementTypeIds: string[]; constructor(readonly label: string) { @@ -85,12 +86,12 @@ export class StubCreateNodeOperationHandler extends CreateNodeOperationHandler { this.elementTypeIds = [label]; } - createNode(relativeLocation: Point | undefined, args: Args | undefined): GNode { + createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { return new GNode(); } } -export class StubCreateEdgeOperationHandler extends CreateEdgeOperationHandler { +export class StubCreateEdgeOperationHandler extends GModelCreateEdgeOperationHandler { elementTypeIds: string[]; constructor(readonly label: string) { @@ -98,7 +99,7 @@ export class StubCreateEdgeOperationHandler extends CreateEdgeOperationHandler { this.elementTypeIds = [label]; } - createEdge(source: GModelElement, target: GModelElement, modelState: GModelState): GEdge | undefined { + createEdge(source: GModelElement, target: GModelElement): GEdge | undefined { return undefined; } } From 260dac07b5bcf116242866dc3639811a03c5ffec Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Apr 2022 13:14:56 +0200 Subject: [PATCH 101/566] Improve default configuration for foreign objects (#178) Disable `selectFeature` and `moveFeature` for the default foreign object configuration. The select feature doesn`t make sense for `ForeignObjectElements` as each DOMElement rendered from the objects `code` property might provide it's own selection behavior. In addition this ensure that the resize feature also works for nodes that contain `ForeignObjectElements`. Typically `ForeignObjectElement`s are children of a GNode so we also disable the move feature as nested child elements should not be movable. Also: Add missing `Button` definition to default types and clean up imports of `model.ts` Contributed on behalf of STMicroelectronics --- packages/client/src/lib/model.ts | 2 +- packages/client/src/views/base-view-module.ts | 6 +++++- packages/protocol/src/model/default-types.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/client/src/lib/model.ts b/packages/client/src/lib/model.ts index b40f148..486f702 100644 --- a/packages/client/src/lib/model.ts +++ b/packages/client/src/lib/model.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { SModelElementSchema } from '@eclipse-glsp/protocol'; -import { exportFeature, SGraph, SModelElement, viewportFeature } from 'sprotty/lib'; +import { exportFeature, SGraph, SModelElement, viewportFeature } from 'sprotty'; import { Containable, containerFeature } from '../features/hints/model'; import { Saveable, saveFeature } from '../features/save/model'; diff --git a/packages/client/src/views/base-view-module.ts b/packages/client/src/views/base-view-module.ts index 5f656e1..06d610c 100644 --- a/packages/client/src/views/base-view-module.ts +++ b/packages/client/src/views/base-view-module.ts @@ -26,6 +26,7 @@ import { ForeignObjectView, HtmlRoot, HtmlRootView, + moveFeature, PreRenderedElement, PreRenderedView, RectangularNode, @@ -34,6 +35,7 @@ import { SCompartment, SCompartmentView, SEdge, + selectFeature, SGraphView, ShapedPreRenderedElement, SIssueMarker, @@ -66,7 +68,9 @@ export function configureDefaultModelElements(context: ContainerContext): void { configureModelElement(context, DefaultTypes.HTML, HtmlRoot, HtmlRootView); // generic elements - configureModelElement(context, DefaultTypes.FOREIGN_OBJECT, ForeignObjectElement, ForeignObjectView); + configureModelElement(context, DefaultTypes.FOREIGN_OBJECT, ForeignObjectElement, ForeignObjectView, { + disable: [selectFeature, moveFeature] + }); configureModelElement(context, DefaultTypes.PRE_RENDERED, PreRenderedElement, PreRenderedView); configureModelElement(context, DefaultTypes.SHAPE_PRE_RENDERED, ShapedPreRenderedElement, PreRenderedView); diff --git a/packages/protocol/src/model/default-types.ts b/packages/protocol/src/model/default-types.ts index ce60376..964dfd6 100644 --- a/packages/protocol/src/model/default-types.ts +++ b/packages/protocol/src/model/default-types.ts @@ -41,6 +41,7 @@ export namespace DefaultTypes { export const LABEL = 'label'; // UI elements + export const BUTTON = 'button'; export const BUTTON_EXPAND = 'button:expand'; export const ISSUE_MARKER = 'marker'; From 504312ef541edf1bdad756e9b5d0b2c2c5fe31cf Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Apr 2022 15:10:22 +0200 Subject: [PATCH 102/566] Add `GShapedPrerenderedElement` to graph model (#17) Add missing `GShapedPrerenderedElement` model element to the graph model. This can be used to construct special shaped prerendered elements like `ForeignObjectElements`. Also: Cleanup default type mappings Requires: https://github.com/eclipse-glsp/glsp-client/pull/178 Contributed on behalf of STMicroelectronics --- packages/graph/src/default-types.ts | 10 +-- packages/graph/src/gpre-rendered-element.ts | 2 +- .../src/gpre-shape-prerendered-element.ts | 61 +++++++++++++++++++ packages/graph/src/index.ts | 1 + 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 packages/graph/src/gpre-shape-prerendered-element.ts diff --git a/packages/graph/src/default-types.ts b/packages/graph/src/default-types.ts index 6fd0482..eb2b2ca 100644 --- a/packages/graph/src/default-types.ts +++ b/packages/graph/src/default-types.ts @@ -24,6 +24,7 @@ import { GModelElementConstructor, GModelRoot } from './gmodel-element'; import { GNode } from './gnode'; import { GPort } from './gport'; import { GPreRenderedElement } from './gpre-rendered-element'; +import { GShapePreRenderedElement } from './gpre-shape-prerendered-element'; export function getDefaultMapping(): Map { const mapping = new Map(); @@ -33,16 +34,11 @@ export function getDefaultMapping(): Map { mapping.set(types.PORT, GPort); mapping.set(types.LABEL, GLabel); mapping.set(types.COMPARTMENT, GCompartment); - mapping.set(types.COMPARTMENT_HEADER, GCompartment); - // TODO: mapping.set(types.BUTTON, GButton); - mapping.set(types.BUTTON_EXPAND, GButton); + mapping.set(types.BUTTON, GButton); mapping.set(types.ISSUE_MARKER, GIssueMarker); - mapping.set(types.NODE_CIRCLE, GNode); - mapping.set(types.NODE_RECTANGLE, GNode); - mapping.set(types.NODE_DIAMOND, GNode); - mapping.set(types.HTML, GModelRoot); mapping.set(types.PRE_RENDERED, GPreRenderedElement); + mapping.set(types.FOREIGN_OBJECT, GShapePreRenderedElement); return mapping; } diff --git a/packages/graph/src/gpre-rendered-element.ts b/packages/graph/src/gpre-rendered-element.ts index 32bc2a2..ed6b9cc 100644 --- a/packages/graph/src/gpre-rendered-element.ts +++ b/packages/graph/src/gpre-rendered-element.ts @@ -14,8 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { GModelElement, GModelElementBuilder } from './gmodel-element'; import { DefaultTypes } from '@eclipse-glsp/protocol'; +import { GModelElement, GModelElementBuilder } from './gmodel-element'; /** * Pre-rendered elements contain HTML or SVG code to be transferred to the DOM. This can be useful to diff --git a/packages/graph/src/gpre-shape-prerendered-element.ts b/packages/graph/src/gpre-shape-prerendered-element.ts new file mode 100644 index 0000000..f53e70e --- /dev/null +++ b/packages/graph/src/gpre-shape-prerendered-element.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2022 STMicroelectronics and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Dimension, Point } from '@eclipse-glsp/protocol'; +import { GAlignable } from './galignable'; +import { GBoundsAware, GBoundsAwareBuilder } from './gbound-aware'; +import { GModelElementBuilder } from './gmodel-element'; +import { GPreRenderedElement } from './gpre-rendered-element'; + +/** + * Pre-rendered elements contain HTML or SVG code to be transferred to the DOM. This can be useful to + * render complex figures or to compute the view on the server instead of the client code. + * Pre rendered elements are often used for the popup model that is created by a `PopupModelFactory`. + * A popup model is rendered when hovering over a element and for many common use cases e.g rendering a tooltip + * this model can be computed entirely on the server side. + */ +export class GShapePreRenderedElement extends GPreRenderedElement implements GBoundsAware, GAlignable { + static override builder(): GShapePreRenderedElementBuilder { + return new GShapePreRenderedElementBuilder(GShapePreRenderedElement); + } + + [GBoundsAware] = true; + [GAlignable] = true; + position: Point; + size: Dimension; + alignment: Point; +} + +export class GShapePreRenderedElementBuilder< + G extends GShapePreRenderedElement = GShapePreRenderedElement +> extends GModelElementBuilder { + code(code: string): this { + this.proxy.code = code; + return this; + } + + position(x: number, y: number): this; + position(position: Point): this; + position(pointOrX: Point | number, y?: number): this { + return GBoundsAwareBuilder.position(this, pointOrX, y); + } + + size(width: number, height: number): this; + size(size: Dimension): this; + size(sizeOrWidth: Dimension | number, height?: number): this { + return GBoundsAwareBuilder.size(this, sizeOrWidth, height); + } +} diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index c4f127d..65f9331 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -30,4 +30,5 @@ export * from './gmodel-util'; export * from './gnode'; export * from './gport'; export * from './gpre-rendered-element'; +export * from './gpre-shape-prerendered-element'; export * from './gshape-element'; From 1db80b4726978a62b3c42e8011cee5dbce847903 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 27 Apr 2022 14:25:30 +0200 Subject: [PATCH 103/566] #633 Make `EditorContextService.sourceUri` async again (#179) Partly revert the changes made to the `EditorContextService` in https://github.com/eclipse-glsp/glsp-client/pull/176 and make the `sourceUri` retrieval async again Fixes eclipse-glsp/glsp/issues/633 Contributed on behalf of STMicroelectronics --- .../client/src/base/editor-context-service.ts | 16 +++++++--------- .../navigation/navigation-target-resolver.ts | 2 +- .../client/src/features/validation/validate.ts | 6 +++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/client/src/base/editor-context-service.ts b/packages/client/src/base/editor-context-service.ts index d151489..94ed8df 100644 --- a/packages/client/src/base/editor-context-service.ts +++ b/packages/client/src/base/editor-context-service.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Action, Args, distinctAdd, EditMode, EditorContext, remove, SetEditModeAction } from '@eclipse-glsp/protocol'; -import { inject, injectable, multiInject, optional, postConstruct } from 'inversify'; +import { inject, injectable, multiInject, optional } from 'inversify'; import { IActionHandler, ModelSource, MousePositionTracker, SModelElement, SModelRoot } from 'sprotty'; import { SelectionService } from '../features/select/selection-service'; import { isSourceUriAware } from './source-uri-aware'; @@ -52,12 +52,6 @@ export class EditorContextService implements IActionHandler { protected modelSourceProvider: () => Promise; protected _editMode: string; - protected modelSource: ModelSource | undefined; - - @postConstruct() - async initialize(): Promise { - this.modelSource = await this.modelSourceProvider(); - } register(editModeListener: EditModeListener): void { distinctAdd(this.editModeListeners, editModeListener); @@ -95,8 +89,12 @@ export class EditorContextService implements IActionHandler { this.editModeListeners.forEach(listener => listener.editModeChanged(oldValue, this.editMode)); } - get sourceUri(): string | undefined { - return this.modelSource && isSourceUriAware(this.modelSource) ? this.modelSource.sourceURI : undefined; + async getSourceUri(): Promise { + const modelSource = await this.modelSourceProvider(); + if (isSourceUriAware(modelSource)) { + return modelSource.sourceURI; + } + return undefined; } get editMode(): string { diff --git a/packages/client/src/features/navigation/navigation-target-resolver.ts b/packages/client/src/features/navigation/navigation-target-resolver.ts index 3a56c4b..92d6652 100644 --- a/packages/client/src/features/navigation/navigation-target-resolver.ts +++ b/packages/client/src/features/navigation/navigation-target-resolver.ts @@ -33,7 +33,7 @@ export class NavigationTargetResolver { async resolve(navigationTarget: NavigationTarget): Promise { const contextService = await this.editorContextService(); - const sourceUri = contextService.sourceUri; + const sourceUri = await contextService.getSourceUri(); return this.resolveWithSourceUri(sourceUri, navigationTarget); } diff --git a/packages/client/src/features/validation/validate.ts b/packages/client/src/features/validation/validate.ts index 75ae01d..7df695f 100644 --- a/packages/client/src/features/validation/validate.ts +++ b/packages/client/src/features/validation/validate.ts @@ -98,7 +98,11 @@ export class SetMarkersActionHandler implements IActionHandler { handle(action: SetMarkersAction): void | Action | ICommand { const markers: Marker[] = action.markers; - const uri = this.editorContextService.sourceUri; + this.setMarkers(markers); + } + + async setMarkers(markers: Marker[]): Promise { + const uri = await this.editorContextService.getSourceUri(); this.externalMarkerManager?.setMarkers(markers, uri); const applyMarkersAction = ApplyMarkersAction.create(markers); this.validationFeedbackEmitter.registerValidationFeedbackAction(applyMarkersAction); From bc6ad68387c75051e40b8ef12eb0d2f30e388702 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 28 Apr 2022 09:48:41 +0200 Subject: [PATCH 104/566] #632: Add missing index exports (#18) Add missing exports to index.ts files and move `reconnect-edge-operation-handler` into gmodel-lib feature Fixes https://github.com/eclipse-glsp/glsp/issues/632 Contributed on behalf of STMicroelectronics --- packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts | 2 +- packages/server-node/src/gmodel-lib/index.ts | 1 + .../reconnect-edge-operation-handler.ts | 0 packages/server-node/src/index.ts | 5 +++++ 4 files changed, 7 insertions(+), 1 deletion(-) rename packages/server-node/src/{operations => gmodel-lib}/reconnect-edge-operation-handler.ts (100%) diff --git a/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts index beaa372..54a4e41 100644 --- a/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts +++ b/packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts @@ -26,13 +26,13 @@ import { SaveModelActionHandler } from '../features/model/save-model-action-hand import { SourceModelStorage } from '../features/model/source-model-storage'; import { CutOperationHandler } from '../operations/cut-operation-handler'; import { OperationHandlerConstructor } from '../operations/operation-handler'; -import { ReconnectEdgeOperationHandler } from '../operations/reconnect-edge-operation-handler'; import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler'; import { ChangeBoundsOperationHandler } from './change-bounds-operation-handler'; import { ComputedBoundsActionHandler } from './computed-bounds-action-handler'; import { GModelDeleteOperationHandler } from './delete-operation-handler'; import { GModelStorage } from './gmodel-storage'; import { PasteOperationHandler } from './paste-operation-handler'; +import { ReconnectEdgeOperationHandler } from './reconnect-edge-operation-handler'; /** * Extension of the {@link DiagramModule} to provide GModel integration. diff --git a/packages/server-node/src/gmodel-lib/index.ts b/packages/server-node/src/gmodel-lib/index.ts index 7427de4..6870911 100644 --- a/packages/server-node/src/gmodel-lib/index.ts +++ b/packages/server-node/src/gmodel-lib/index.ts @@ -23,3 +23,4 @@ export * from './gmodel-create-node-operation-handler'; export * from './gmodel-diagram-module'; export * from './gmodel-storage'; export * from './paste-operation-handler'; +export * from './reconnect-edge-operation-handler'; diff --git a/packages/server-node/src/operations/reconnect-edge-operation-handler.ts b/packages/server-node/src/gmodel-lib/reconnect-edge-operation-handler.ts similarity index 100% rename from packages/server-node/src/operations/reconnect-edge-operation-handler.ts rename to packages/server-node/src/gmodel-lib/reconnect-edge-operation-handler.ts diff --git a/packages/server-node/src/index.ts b/packages/server-node/src/index.ts index cb36e6b..890be86 100644 --- a/packages/server-node/src/index.ts +++ b/packages/server-node/src/index.ts @@ -24,6 +24,7 @@ export * from './actions/global-action-provider'; export * from './command/command'; export * from './command/command-stack'; export * from './di/app-module'; +export * from './di/binding-target'; export * from './di/client-session-module'; export * from './di/diagram-module'; export * from './di/glsp-module'; @@ -52,6 +53,7 @@ export * from './features/model/gmodel-serializer'; export * from './features/model/model-state'; export * from './features/model/model-submission-handler'; export * from './features/model/request-model-action-handler'; +export * from './features/model/save-model-action-handler'; export * from './features/model/source-model-storage'; export * from './features/navigation/json-opener-options'; export * from './features/navigation/navigation-target-provider'; @@ -72,6 +74,7 @@ export * from './launch/socket-server-launcher'; export * from './operations/compound-operation-handler'; export * from './operations/create-operation-handler'; export * from './operations/cut-operation-handler'; +export * from './operations/operation-action-handler'; export * from './operations/operation-handler'; export * from './operations/operation-handler-registry'; export * from './protocol/glsp-client-proxy'; @@ -79,9 +82,11 @@ export * from './protocol/glsp-server'; export * from './protocol/glsp-server-listener'; export * from './session/client-session'; export * from './session/client-session-factory'; +export * from './session/client-session-initializer'; export * from './session/client-session-listener'; export * from './session/client-session-manager'; export * from './utils/args-util'; +export * from './utils/client-options-util'; export * from './utils/disposable'; export * from './utils/glsp-server-error'; export * from './utils/logger'; From 1b0beb9ed3bb6b292534ee2fd718a102fc33df34 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Wed, 4 May 2022 19:15:21 +0200 Subject: [PATCH 105/566] GH-631 Improve to service bindings in diagram module (#19) Update behavior of `applyBindingsTarget` for binding `ServiceTargets`. Add an optional `autoBind` flag to `ServiceTarget` which is enabled by default. If the target service identifier is not bound yet and this flag is enabled we check whether the target service can be bound to itself in singleton scope. If so we do that, otherwise we throw an error. This covers the most common case for our `ServiceBindings`: Subclassing a default implementation and then bind the service identifier to this implementation. (e.g. bind(MyModelState).toSelf().inSingletonScope(); bind(ModelState).toService(MyModelState)) For more complex binding configurations its still possible to access the binding context directly (this.context) and apply the bindings this way. Closes https://github.com/eclipse-glsp/glsp/issues/631 Also: Fix unused model state parameter in workflow builder methods. --- .../src/handler/create-activity-node-handler.ts | 6 +++--- .../src/handler/create-automated-task-handler.ts | 6 +++--- .../src/handler/create-category-handler.ts | 6 +++--- .../src/handler/create-decision-node-handler.ts | 6 +++--- .../handler/create-fork-or-join-node-handler.ts | 6 +++--- .../src/handler/create-manual-task-handler.ts | 6 +++--- .../src/handler/create-merge-node-handler.ts | 6 +++--- .../src/handler/create-task-handler.ts | 6 +++--- .../server-node/src/di/binding-target.spec.ts | 7 ++++++- packages/server-node/src/di/binding-target.ts | 15 +++++++++++++-- 10 files changed, 43 insertions(+), 27 deletions(-) diff --git a/examples/workflow-server/src/handler/create-activity-node-handler.ts b/examples/workflow-server/src/handler/create-activity-node-handler.ts index b664b7a..b3231d3 100644 --- a/examples/workflow-server/src/handler/create-activity-node-handler.ts +++ b/examples/workflow-server/src/handler/create-activity-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { CreateNodeOperation, GNode, ModelState, Point } from '@eclipse-glsp/server-node'; +import { CreateNodeOperation, GNode, Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNode, ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -22,10 +22,10 @@ import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-opera @injectable() export abstract class CreateActivityNodeHandler extends CreateWorkflowNodeOperationHandler { createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { - return this.builder(relativeLocation, this.modelState).build(); + return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { + protected builder(point: Point | undefined): ActivityNodeBuilder { return ActivityNode.builder() .position(point ?? Point.ORIGIN) .type(this.elementTypeIds[0]) diff --git a/examples/workflow-server/src/handler/create-automated-task-handler.ts b/examples/workflow-server/src/handler/create-automated-task-handler.ts index 14b7b20..c2211cd 100644 --- a/examples/workflow-server/src/handler/create-automated-task-handler.ts +++ b/examples/workflow-server/src/handler/create-automated-task-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelState, Point } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateAutomatedTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.AUTOMATED_TASK]; label = 'Automated Task'; - protected override builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { - return super.builder(point, modelState).addCssClass('automated'); + protected override builder(point: Point | undefined): TaskNodeBuilder { + return super.builder(point).addCssClass('automated'); } } diff --git a/examples/workflow-server/src/handler/create-category-handler.ts b/examples/workflow-server/src/handler/create-category-handler.ts index 2d1e830..b538897 100644 --- a/examples/workflow-server/src/handler/create-category-handler.ts +++ b/examples/workflow-server/src/handler/create-category-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ArgsUtil, CreateNodeOperation, GNode, ModelState, Point } from '@eclipse-glsp/server-node'; +import { ArgsUtil, CreateNodeOperation, GNode, Point } from '@eclipse-glsp/server-node'; import { Category, CategoryNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-operation-handler'; @@ -23,10 +23,10 @@ export class CreateCategoryHandler extends CreateWorkflowNodeOperationHandler { label = 'Category'; createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { - return this.builder(relativeLocation, this.modelState).build(); + return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined, modelState: ModelState): CategoryNodeBuilder { + protected builder(point: Point | undefined): CategoryNodeBuilder { return Category.builder() .type(this.elementTypeIds[0]) .position(point ?? Point.ORIGIN) diff --git a/examples/workflow-server/src/handler/create-decision-node-handler.ts b/examples/workflow-server/src/handler/create-decision-node-handler.ts index 2625e26..03724aa 100644 --- a/examples/workflow-server/src/handler/create-decision-node-handler.ts +++ b/examples/workflow-server/src/handler/create-decision-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelState, Point } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateDecisionNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.DECISION_NODE]; label = 'Decision Node'; - protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { - return super.builder(point, modelState).addCssClass('decision'); + protected override builder(point?: Point): ActivityNodeBuilder { + return super.builder(point).addCssClass('decision'); } } diff --git a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts index 30cac2e..df52a99 100644 --- a/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts +++ b/examples/workflow-server/src/handler/create-fork-or-join-node-handler.ts @@ -13,14 +13,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelState, Point } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { CreateActivityNodeHandler } from './create-activity-node-handler'; @injectable() export abstract class CreateForkOrJoinNodeHandler extends CreateActivityNodeHandler { - protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { - return super.builder(point, modelState).addCssClass('forkOrJoin').size(10, 50); + protected override builder(point: Point | undefined): ActivityNodeBuilder { + return super.builder(point).addCssClass('forkOrJoin').size(10, 50); } } diff --git a/examples/workflow-server/src/handler/create-manual-task-handler.ts b/examples/workflow-server/src/handler/create-manual-task-handler.ts index 1b8af5e..40c4bc1 100644 --- a/examples/workflow-server/src/handler/create-manual-task-handler.ts +++ b/examples/workflow-server/src/handler/create-manual-task-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelState, Point } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateManualTaskHandler extends CreateTaskHandler { elementTypeIds = [ModelTypes.MANUAL_TASK]; label = 'Manual Task'; - protected override builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { - return super.builder(point, modelState).addCssClass('manual'); + protected override builder(point: Point | undefined): TaskNodeBuilder { + return super.builder(point).addCssClass('manual'); } } diff --git a/examples/workflow-server/src/handler/create-merge-node-handler.ts b/examples/workflow-server/src/handler/create-merge-node-handler.ts index 4aaf8f7..8c9dbe6 100644 --- a/examples/workflow-server/src/handler/create-merge-node-handler.ts +++ b/examples/workflow-server/src/handler/create-merge-node-handler.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ModelState, Point } from '@eclipse-glsp/server-node'; +import { Point } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { ActivityNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -24,7 +24,7 @@ export class CreateMergeNodeHandler extends CreateActivityNodeHandler { elementTypeIds = [ModelTypes.MERGE_NODE]; label = 'Merge Node'; - protected override builder(point: Point | undefined, modelState: ModelState): ActivityNodeBuilder { - return super.builder(point, modelState).addCssClass('merge'); + protected override builder(point: Point | undefined): ActivityNodeBuilder { + return super.builder(point).addCssClass('merge'); } } diff --git a/examples/workflow-server/src/handler/create-task-handler.ts b/examples/workflow-server/src/handler/create-task-handler.ts index 5873fa6..8bfc117 100644 --- a/examples/workflow-server/src/handler/create-task-handler.ts +++ b/examples/workflow-server/src/handler/create-task-handler.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { Point } from '@eclipse-glsp/protocol'; -import { CreateNodeOperation, GNode, ModelState } from '@eclipse-glsp/server-node'; +import { CreateNodeOperation, GNode } from '@eclipse-glsp/server-node'; import { injectable } from 'inversify'; import { TaskNode, TaskNodeBuilder } from '../graph-extension'; import { ModelTypes } from '../util/model-types'; @@ -23,10 +23,10 @@ import { CreateWorkflowNodeOperationHandler } from './create-workflow-node-opera @injectable() export abstract class CreateTaskHandler extends CreateWorkflowNodeOperationHandler { createNode(operation: CreateNodeOperation, relativeLocation?: Point): GNode | undefined { - return this.builder(relativeLocation, this.modelState).build(); + return this.builder(relativeLocation).build(); } - protected builder(point: Point | undefined, modelState: ModelState): TaskNodeBuilder { + protected builder(point: Point | undefined): TaskNodeBuilder { return TaskNode.builder() .position(point ?? Point.ORIGIN) .addCssClass('task') diff --git a/packages/server-node/src/di/binding-target.spec.ts b/packages/server-node/src/di/binding-target.spec.ts index 0e0033a..146979d 100644 --- a/packages/server-node/src/di/binding-target.spec.ts +++ b/packages/server-node/src/di/binding-target.spec.ts @@ -97,7 +97,12 @@ describe('BindingTarget', () => { }); it('Should throw an error because the given target service is not bound', () => { context.isBound.returns(false); - expect(() => applyBindingTarget(context, Target, { service: SubTarget })).to.throw(); + expect(() => applyBindingTarget(context, Target, { service: SubTarget, autoBind: false })).to.throw(); + }); + it('Should bind the unbound target service to itself before applying the toService binding', () => { + context.isBound.returns(false); + applyBindingTarget(context, Target, { service: SubTarget }); + expect(context.bind.calledWith(Target)).to.be.true; }); it('The return syntax should be no op and invocation of a syntax function should throw an error', () => { context.isBound.returns(true); diff --git a/packages/server-node/src/di/binding-target.ts b/packages/server-node/src/di/binding-target.ts index 39c5227..89222a3 100644 --- a/packages/server-node/src/di/binding-target.ts +++ b/packages/server-node/src/di/binding-target.ts @@ -40,7 +40,12 @@ export function applyBindingTarget( : context.bind(serviceIdentifier).to(target); } else if (ServiceTarget.is(target)) { if (!context.isBound(target.service)) { - throw new Error(`The target service ${target.service.toString()} is not bound!. Cannot apply target binding`); + const autoBind = target.autoBind === undefined || target.autoBind === true; + if (autoBind && isConstructor(target.service)) { + context.bind(target.service).toSelf().inSingletonScope(); + } else { + throw new Error(`The target service ${target.service.toString()} is not bound!. Cannot apply target binding`); + } } context.bind(serviceIdentifier).toService(target.service); return NoOPSyntax.serviceSyntax(serviceIdentifier, target); @@ -86,6 +91,12 @@ export namespace ConstantValueTarget { */ export interface ServiceTarget { service: interfaces.ServiceIdentifier; + /** + * Boolean flag to configure how to handle unbound service identifiers. + * If `undefined` or `true` the {@link applyBindingTarget} function will attempt unbound constructor service identifiers + * to `self().inSingletonScope()`. + */ + autoBind?: boolean; } export namespace ServiceTarget { @@ -103,7 +114,7 @@ export interface DynamicValueTarget { /** * No-op binding syntax definitions for `constantValue` and `toService` bindings. - * Using this no-op syntaxes allows the {@link applyBindingTarget} function to return a {@interfaces.BindingInWhenOnSyntax} + * Using this no-op syntaxes allows the {@link applyBindingTarget} function to return a {@link interfaces.BindingInWhenOnSyntax} * independently of the actual {@link BindingTarget}. */ namespace NoOPSyntax { From 06d822c6d6e8bbf440307824b5ba73f177bcb8a3 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Thu, 19 May 2022 08:53:28 -0700 Subject: [PATCH 106/566] GH-652 Configurable gridsnapper (#181) Make grid size of the `GridSnapper` configurable via constructor and setter. Fixes https://github.com/eclipse-glsp/glsp/issues/652 --- .../client/src/features/change-bounds/snap.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/client/src/features/change-bounds/snap.ts b/packages/client/src/features/change-bounds/snap.ts index 47dca86..8658c9e 100644 --- a/packages/client/src/features/change-bounds/snap.ts +++ b/packages/client/src/features/change-bounds/snap.ts @@ -17,20 +17,24 @@ import { Point, Writable } from '@eclipse-glsp/protocol'; import { injectable } from 'inversify'; import { findParentByFeature, ISnapper, isViewport, SModelElement } from 'sprotty'; +/** + * A {@link ISnapper} implementation that snaps all elements onto a fixed gride size. + * The default grid size is 10x10 pixel. + * To configure a custom grid size bind the `TYPES.ISnapper` service identifier + * to constant value, e.g: + * + * ```ts + * bind(TYPES.ISnapper).toConstantValue(new GridSnapper({x:25 ,y:25 })); + * ``` + */ @injectable() export class GridSnapper implements ISnapper { - get gridX(): number { - return 10; - } - - get gridY(): number { - return 10; - } + constructor(public grid: { x: number; y: number } = { x: 10, y: 10 }) {} snap(position: Point, element: SModelElement): Point { return { - x: Math.round(position.x / this.gridX) * this.gridX, - y: Math.round(position.y / this.gridY) * this.gridY + x: Math.round(position.x / this.grid.x) * this.grid.x, + y: Math.round(position.y / this.grid.y) * this.grid.y }; } } From d928c6b99daccca574bbec1444c2b4dfabf9bc04 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 23 May 2022 03:36:26 -0700 Subject: [PATCH 107/566] GH-639 Align ModelState API with Java GLSP Server API (#20) * GH-639 Align ModelState API with Java GLSP Server API Introduce `updateRoot` method and make root setter protected - Update changelog Fixes https://github.com/eclipse-glsp/glsp/issues/639 * Address review comments --- .../server-node/src/features/model/model-state.ts | 11 ++++++++--- packages/server-node/src/gmodel-lib/gmodel-storage.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/server-node/src/features/model/model-state.ts b/packages/server-node/src/features/model/model-state.ts index acd5181..aef4b01 100644 --- a/packages/server-node/src/features/model/model-state.ts +++ b/packages/server-node/src/features/model/model-state.ts @@ -27,7 +27,8 @@ export interface ModelState { setAll(properties: Record): void; get